diff options
Diffstat (limited to 'odb/odb')
213 files changed, 91611 insertions, 0 deletions
diff --git a/odb/odb/.gitignore b/odb/odb/.gitignore new file mode 100644 index 0000000..4fd410e --- /dev/null +++ b/odb/odb/.gitignore @@ -0,0 +1,2 @@ +/odb +/options.?xx diff --git a/odb/odb/buildfile b/odb/odb/buildfile new file mode 100644 index 0000000..34a6329 --- /dev/null +++ b/odb/odb/buildfile @@ -0,0 +1,161 @@ +# file : odb/buildfile +# license : GNU GPL v3; see accompanying LICENSE file + +define plugin: libs + +plugin{*}: +{ + bin.lib.prefix = # No lib prefix. + backlink = true # Backlink in forwarded configs (next to exe). +} + +# For now we use the .so extension everywhere except Windows (see +# plugin_path() in odb.cxx for details). +# +if ($cxx.target.class != 'windows') + plugin{*}: extension = so + +# By default install the plugin next to the driver. +# +# On Windows this is the only sane option where we want the plugin (odb.dll) +# to go into bin/ since failed that we won't be able to find libraries we +# depend on. +# +# On other platforms another option is to install into the GCC's plugin +# directory. This way the same driver can be used with multiple GCC versions +# and is something that distributions packagers sometimes want to do. +# +# So at some point we should also make it configurable, including support for +# installing into GCC's plugin directory. +# +# NOTE: see ODB_GCC_PLUGIN_DIR when adding this support. +# +plugin{*}: install = bin/ + +import libs = libcutl%lib{cutl} +import libs += libstudxml%lib{studxml} + +./: exe{odb} plugin{odb} + +# We need to make driver depend on plugin but not link it so that when, for +# example, driver is imported, plugin is updated as well. +# +# We, however, don't want to install via the driver since the same driver +# build could be used with multiple plugin builds (e.g., for different GCC +# versions, which is something distribution packagers sometimes want to do). +# @@ For this we will have to wait for operation-specific values support. +# +exe{odb}: cxx{odb} +exe{odb}: libus{odb}: bin.whole = false +exe{odb}: plugin{odb}: include = adhoc + +# Target metadata, see also --build2-metadata in odb.cxx. +# +# While ODB itself doesn't use any environment variables, it uses GCC +# underneath which does (see "Environment Variables Affecting GCC"). +# +exe{odb}: +{ + export.metadata = 1 odb + odb.name = [string] odb + odb.version = [string] $version + odb.checksum = [string] $version + odb.environment = [strings] CPATH CPLUS_INCLUDE_PATH GCC_EXEC_PREFIX COMPILER_PATH +} + +plugin{odb}: libus{odb} + +switch $cxx.target.system +{ + # On Windows we have to link the import stub. + # + case 'mingw32' + plugin{odb}: cxx.libs += $plugin_dir/cc1plus.exe.a + + # On Mac OS we have to allow undefined symbols. + # + case 'darwin' + plugin{odb}: cxx.loptions += -undefined dynamic_lookup +} + +libus{odb}: {hxx ixx txx cxx}{** -odb -options -pregenerated/**} $libs + +# Build options. +# +# Note: escape backslashes in gxx_name. +# +cxx.poptions += "-I$plugin_dir/include" +cxx.poptions += "-DODB_GXX_NAME=\"$regex.replace($gxx_name, '\\', '\\\\')\"" +cxx.poptions += -DODB_BUILD2 # @@ TMP while supporting other build systems. + +## Consumption build ($develop == false). +# + +# Use pregenerated versions in the consumption build. +# +libus{odb}: pregenerated/{hxx ixx cxx}{**}: include = (!$develop) + +if! $develop + cxx.poptions =+ "-I($src_base/pregenerated)" # Note: must come first. + +# Distribute pregenerated versions only in the consumption build. +# +pregenerated/{hxx ixx cxx}{*}: dist = (!$develop) + +# +## + +## Development build ($develop == true). +# + +libus{odb}: {hxx ixx cxx}{options}: include = $develop + +if $develop + import! [metadata] cli = cli%exe{cli} + +# In the development build distribute regenerated {hxx ixx cxx}{options}, +# remapping their locations to the paths of the pregenerated versions (which +# are only distributed in the consumption build; see above). This way we make +# sure that the distributed files are always up-to-date. +# +<{hxx ixx cxx}{options}>: cli{options} $cli +{ + dist = ($develop ? pregenerated/odb/ : false) + + # Symlink the generated code in src for convenience of development. + # + backlink = true +} +% +if $develop +{{ + options = --include-with-brackets --include-prefix odb --guard-prefix ODB \ + --generate-file-scanner --generate-specifier --generate-modifier \ + --generate-description --suppress-undocumented \ + --cxx-prologue '#include <odb/option-parsers.hxx>' + + $cli $options -o $out_base $path($<[0]) + + # If the result differs from the pregenerated version, copy it over. + # + if diff $src_base/pregenerated/odb/options.hxx $path($>[0]) >- && \ + diff $src_base/pregenerated/odb/options.ixx $path($>[1]) >- && \ + diff $src_base/pregenerated/odb/options.cxx $path($>[2]) >- + exit + end + + cp $path($>[0]) $src_base/pregenerated/odb/options.hxx + cp $path($>[1]) $src_base/pregenerated/odb/options.ixx + cp $path($>[2]) $src_base/pregenerated/odb/options.cxx +}} + +# +## + +# Pass the copyright notice extracted from the LICENSE file. +# +obj{odb}: cxx.poptions += -DODB_COPYRIGHT=\"$copyright\" + +# Don't install any of the plugin's headers. +# +{hxx ixx txx}{*}: install = false diff --git a/odb/odb/common-query.cxx b/odb/odb/common-query.cxx new file mode 100644 index 0000000..517c92c --- /dev/null +++ b/odb/odb/common-query.cxx @@ -0,0 +1,1413 @@ +// file : odb/common-query.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <sstream> + +#include <odb/common-query.hxx> + +using namespace std; + +// query_utils +// + +string query_utils:: +depth_suffix (size_t d) +{ + if (d != 0) + { + ostringstream os; + os << d; + return '_' + os.str (); + } + + return string (); +} + +// Collect nested (composite) types as generated by query_columns. +// +struct query_nested_types: object_columns_base, virtual context +{ + query_nested_types (bool ptr): ptr_ (ptr), in_ptr_ (false), depth_ (0) {} + + virtual void + traverse_object (semantics::class_& c) + { + // We don't want to traverse bases. + // + names (c); + } + + virtual void + traverse_composite (semantics::data_member* m, semantics::class_& c) + { + if (m != 0) + { + string name (prefix_ + public_name (*m)); + name += in_ptr_ ? "_column_class" : "_class"; + name += query_utils::depth_suffix (depth_); + name += '_'; + types.push_back (name); + + depth_++; + string p (prefix_); + prefix_ = name + "::"; + object_columns_base::traverse_composite (m, c); + prefix_ = p; + depth_--; + } + else + object_columns_base::traverse_composite (m, c); // Base + } + + virtual void + traverse_pointer (semantics::data_member& m, semantics::class_& c) + { + // The same logic as in query_columns. + // + if (inverse (m, key_prefix_)) + return; + + bool poly_ref (m.count ("polymorphic-ref")); + + if (composite_wrapper (utype (*id_member (c)))) + { + if (ptr_ || poly_ref) + object_columns_base::traverse_pointer (m, c); + else + { + in_ptr_ = true; + object_columns_base::traverse_pointer (m, c); + in_ptr_ = false; + } + } + } + +public: + strings types; + +protected: + bool ptr_; + bool in_ptr_; // True while we are "inside" an object pointer. + string prefix_; + size_t depth_; +}; + +void query_utils:: +inst_query_columns (bool decl, + bool ptr, + string const& type, + string const& alias, + semantics::class_& c) +{ + inst_header (decl); + os << (ptr ? "pointer_" : "") << "query_columns<" << endl + << " " << type << "," << endl + << " id_" << db << "," << endl + << " " << alias << " >;" + << endl; + + // If we are generating extern declarations, we also have to generate + // them for all the nested (composite) structs. That's what VC++ needs. + // + if (decl) + { + query_nested_types t (ptr); + t.traverse (c); + + for (strings::iterator i (t.types.begin ()); i != t.types.end (); ++i) + { + inst_header (decl, true); // Omit export, GCC doesn't like it. + os << (ptr ? "pointer_" : "") << "query_columns<" << endl + << " " << type << "," << endl + << " id_" << db << "," << endl + << " " << alias << " >::" << *i << ";" + << endl; + } + } +} + +// query_tags +// + +void query_tags:: +traverse (semantics::class_& c) +{ + if (object (c) || composite (c)) + { + object_columns_base::traverse (c); + } + else if (view (c)) + { + if (c.get<size_t> ("object-count") != 0) + { + view_objects& objs (c.get<view_objects> ("objects")); + + for (view_objects::const_iterator i (objs.begin ()); + i < objs.end (); + ++i) + { + if (i->kind != view_object::object) + continue; // Skip tables. + + if (i->alias.empty ()) + continue; + + generate (i->alias); + } + } + } + // Otherwise it is a transient base (of a composite value). + + if (nl_) + os << endl; +} + +void query_tags:: +traverse_object (semantics::class_& c) +{ + names (c); // We don't want to traverse bases. +} + +void query_tags:: +traverse_composite (semantics::data_member* m, semantics::class_& c) +{ + // Base type. + // + if (m == 0) + { + object_columns_base::traverse_composite (m, c); + return; + } + + // Don't generate an empty struct if we don't have any pointers. + // + if (!has_a (c, test_pointer)) + return; + + if (nl_) + os << endl; + + os << "struct " << public_name (*m) << "_tag" << + query_utils::depth_suffix (depth_) + << "{"; + + depth_++; + object_columns_base::traverse_composite (m, c); + depth_--; + + os << "};"; + + nl_ = false; +} + +void query_tags:: +traverse_pointer (semantics::data_member& m, semantics::class_&) +{ + // Ignore polymorphic id references. + // + if (m.count ("polymorphic-ref")) + return; + + generate (public_name (m)); +} + +void query_tags:: +generate (string const& name) +{ + os << "struct " << name << "_tag;"; + nl_ = true; +} + +// query_alias_traits +// + +query_alias_traits:: +query_alias_traits (semantics::class_& c, bool decl) + : decl_ (decl), depth_ (0) +{ + scope_ = "access::"; + scope_ += (object (c) ? "object_traits_impl" : "view_traits_impl"); + scope_ += "< " + class_fq_name (c) + ", id_" + db.string () + " >"; +} + +void query_alias_traits:: +traverse_object (semantics::class_& c) +{ + // We don't want to traverse bases. + // + names (c); +} + +void query_alias_traits:: +traverse_composite (semantics::data_member* m, semantics::class_& c) +{ + // Base type. + // + if (m == 0) + { + object_columns_base::traverse_composite (m, c); + return; + } + + string old_scope (scope_); + scope_ += "::" + public_name (*m) + "_tag" + + query_utils::depth_suffix (depth_); + + depth_++; + object_columns_base::traverse_composite (m, c); + depth_--; + + scope_ = old_scope; +} + +void query_alias_traits:: +traverse_pointer (semantics::data_member& m, semantics::class_& c) +{ + // Ignore polymorphic id references. + // + if (m.count ("polymorphic-ref")) + return; + + if (decl_) + generate_decl (public_name (m), c); + else + generate_def (m, c); +} + +void query_alias_traits:: +generate_decl (string const& tag, semantics::class_& c) +{ + semantics::class_* poly_root (polymorphic (c)); + bool poly_derived (poly_root != 0 && poly_root != &c); + semantics::class_* poly_base (poly_derived ? &polymorphic_base (c) : 0); + + if (poly_derived) + generate_decl (tag, *poly_base); + + string const& fq_name (class_fq_name (c)); + + os << "template <>" << endl + << "struct " << exp << "alias_traits<" << endl + << " " << fq_name << "," << endl + << " id_" << db << "," << endl + << " " << scope_ << "::" << tag << "_tag>" + << "{"; + + if (poly_derived) + os << "typedef alias_traits<" << endl + << " " << class_fq_name (*poly_base) << "," << endl + << " id_" << db << "," << endl + << " " << scope_ << "::" << tag << "_tag>" << endl + << "base_traits;" + << endl; + + // For dynamic multi-database support also generate common traits + // alias. Note that the tag type is the same since they all are + // derived from object_traits. + // + if (db != database::common && multi_dynamic) + os << "typedef alias_traits<" << endl + << " " << fq_name << "," << endl + << " id_common," << endl + << " " << scope_ << "::" << tag << "_tag>" << endl + << "common_traits;" + << endl; + + generate_decl_body (); // Table name, etc. + + os << "};"; +} + +void query_alias_traits:: +generate_decl_body () +{ +} + +void query_alias_traits:: +generate_def (semantics::data_member&, semantics::class_&) +{ +} + +void query_alias_traits:: +generate_def (string const&, semantics::class_&, string const&) +{ +} + +// query_columns_base +// + +query_columns_base:: +query_columns_base (semantics::class_& c, bool decl, bool inst) + : decl_ (decl), inst_ (inst), depth_ (0) +{ + string const& n (class_fq_name (c)); + + if (decl) + scope_ = "access::object_traits_impl< " + n + ", id_" + + db.string () + " >"; + else + scope_ = "query_columns_base< " + n + ", id_" + db.string () + " >"; +} + +void query_columns_base:: +traverse_object (semantics::class_& c) +{ + // We don't want to traverse bases. + // + names (c); +} + +void query_columns_base:: +traverse_composite (semantics::data_member* m, semantics::class_& c) +{ + // Base type. + // + if (m == 0) + { + object_columns_base::traverse_composite (m, c); + return; + } + + // Don't generate an empty struct if we don't have any pointers. + // + if (!has_a (c, test_pointer)) + return; + + string name (public_name (*m)); + string dsuffix (query_utils::depth_suffix (depth_)); + + if (decl_) + { + os << "// " << name << endl + << "//" << endl + << "struct " << name << "_base" << dsuffix << '_' + << "{"; + + string old_scope (scope_); + scope_ += "::" + name + "_tag" + dsuffix; + + depth_++; + object_columns_base::traverse_composite (m, c); + depth_--; + + scope_ = old_scope; + + os << "};"; + } + else + { + string old_scope (scope_); + scope_ += "::" + name + "_base" + dsuffix + '_'; + + depth_++; + object_columns_base::traverse_composite (m, c); + depth_--; + + scope_ = old_scope; + } +} + +void query_columns_base:: +traverse_pointer (semantics::data_member& m, semantics::class_& c) +{ + // Ignore polymorphic id references. + // + if (m.count ("polymorphic-ref")) + return; + + string name (public_name (m)); + string const& fq_name (class_fq_name (c)); + bool inv (inverse (m, key_prefix_)); + + if (decl_) + { + os << "// " << name << endl + << "//" << endl; + + os << "typedef" << endl + << "odb::alias_traits<" << endl + << " " << fq_name << "," << endl + << " id_" << db << "," << endl + << " " << scope_ << "::" << name << "_tag>" << endl + << name << "_alias_;" + << endl; + + if (inv) + { + os << "typedef" << endl + << "odb::query_pointer<" << endl + << " odb::pointer_query_columns<" << endl + << " " << fq_name << "," << endl + << " id_" << db << "," << endl + << " " << name << "_alias_ > >" << endl + << name << "_type_ ;" + << endl + << "static " << const_ << name << "_type_ " << name << ";" + << endl; + } + } + else if (inst_) + { + generate_inst (m, c); + } + else + { + // Generate explicit template instantiation directive for the + // pointed-to pointer_query_columns. + // + if (multi_dynamic) + generate_inst (m, c); + + if (inv) + os << const_ << scope_ << "::" << name << "_type_" << endl + << scope_ << "::" << name << ";" + << endl; + } +} + +void query_columns_base:: +generate_inst (semantics::data_member& m, semantics::class_& c) +{ + string name (public_name (m)); + string const& fq_name (class_fq_name (c)); + + string alias (scope_ + "::" + name + "_alias_"); + + // Instantiate base [pointer_]query_columns. + // + { + instance<query_columns_base_insts> b (true, inst_, alias, true); + traversal::inherits i (*b); + inherits (c, i); + } + + // If the pointed-to class has no pointers of its own then + // pointer_query_columns just derives from query_columns and + // that's what we need to instantiate. + // + inst_query_columns (inst_, + has_a (c, test_pointer | include_base), + fq_name, + alias, + c); +} + +// query_columns +// + +query_columns:: +query_columns (bool decl, bool ptr, semantics::class_& c) + : decl_ (decl), ptr_ (ptr), poly_ref_ (false), in_ptr_ (false), + fq_name_ (class_fq_name (c)), + resue_abstract_ (abstract (c) && !polymorphic (c)), + depth_ (0) +{ +} + +void query_columns:: +traverse_object (semantics::class_& c) +{ + // We don't want to traverse bases. + // + names (c); +} + +void query_columns:: +traverse_composite (semantics::data_member* m, semantics::class_& c) +{ + // Base type. + // + if (m == 0) + { + object_columns_base::traverse_composite (m, c); + return; + } + + // Use _class_ instead of _type_ to avoid potential clashes between + // the class and member names. + // + string name (public_name (*m)); + string suffix (in_ptr_ ? "_column_class" : "_class"); + + // Add depth to the nested composite to avoid potential name conflicts + // in situations like these: + // + // struct inner { ... }; + // struct outer { inner value; }; + // struct object { outer value; } + // + string dsuffix (query_utils::depth_suffix (depth_)); + suffix += dsuffix; + suffix += '_'; + + depth_++; + + if (decl_) + { + os << "// " << name << endl + << "//" << endl + << "struct "; + + // For some bizarre reason VC++ needs the export directive for + // a type nested in an (exported) template. This appears not + // to cause any problems for GCC. + // + // We only generate the export directive if we are also + // explicitly instantiating the query_columns templates. + // + if (multi_dynamic && !resue_abstract_) + os << exp; + + os << name << suffix; + + // Derive from the base in query_columns_base. It contains columns + // data for the pointer members. + // + if (!ptr_ && !poly_ref_ && has_a (c, test_pointer)) + os << ": " << name << "_base" << dsuffix << '_'; + + os << "{"; + + if (!const_.empty ()) + os << name << suffix << " ()" // Need user-defined default c-tor for + << "{" // initialization of static const. + << "}"; + + object_columns_base::traverse_composite (m, c); + + os << "};"; + + if (!in_ptr_) + os << "static " << const_ << name << suffix << " " << name << ";" + << endl; + } + else + { + // Handle nested members first. + // + string old_scope (scope_); + scope_ += "::" + name + suffix; + + object_columns_base::traverse_composite (m, c); + + scope_ = old_scope; + + // Composite member. Note that here we don't use suffix for the in- + // pointer case because the actual pointer column type derives from + // the composite column type (dual interface; see traverse_pointer() + // below). + // + string tmpl (ptr_ ? "pointer_query_columns" : "query_columns"); + tmpl += "< " + fq_name_ + ", id_" + db.string () + ", A >" + scope_; + + os << "template <typename A>" << endl + << const_ << "typename " << tmpl << "::" << name << + (in_ptr_ ? string ("_type_") : suffix) << endl + << tmpl << "::" << name << ";" + << endl; + } + + depth_--; +} + +void query_columns:: +column_ctor (string const&, string const&, string const&) +{ +} + +void query_columns:: +column_common (semantics::data_member& m, + string const& type, + string const&, + string const& suffix) +{ + string name (public_name (m)); + + if (decl_) + { + os << "// " << name << endl + << "//" << endl; + + os << "typedef odb::query_column< " << type << " > " << name << + suffix << ";" + << endl; + } + else + { + // Note that here we don't use suffix. + // + string tmpl (ptr_ ? "pointer_query_columns" : "query_columns"); + tmpl += "< " + fq_name_ + ", id_" + db.string () + ", A >" + scope_; + + os << "template <typename A>" << endl + << const_ << "typename " << tmpl << "::" << name << "_type_" << endl + << tmpl << "::" << name << ";" + << endl; + } +} + +bool query_columns:: +traverse_column (semantics::data_member& m, string const& column, bool) +{ + semantics::names* hint; + semantics::type* t (&utype (m, hint)); + + // Unwrap it if it is a wrapper. + // + if (semantics::type* wt = wrapper (*t, hint)) + t = &utype (*wt, hint); + + column_common (m, t->fq_name (hint), column); + + if (decl_) + { + string name (public_name (m)); + + os << "static " << const_ << name << "_type_ " << name << ";" + << endl; + } + + return true; +} + +void query_columns:: +traverse_pointer (semantics::data_member& m, semantics::class_& c) +{ + // If this is for the pointer_query_columns and the member is not + // inverse, then create the normal member corresponding to the id + // column. This will allow the user to check it for NULL or to + // compare ids. In case this is for query_columns, then for the + // inverse member everything has been generated in query_columns_base. + // + if (inverse (m, key_prefix_)) + return; + + // If we ignore polymorphic references, then a view that uses a custom + // join condition based on id will use the id column from the base + // table. But the base table hasn't been joined yet. To resolve this + // we will generate the id member that points to our column. + // + poly_ref_ = m.count ("polymorphic-ref"); + + string name (public_name (m)); + + data_member_path& id (*id_member (c)); + semantics::names* hint; + semantics::type& t (utype (id, hint)); + + if (composite_wrapper (t)) + { + // Composite id. + // + + // For pointer_query_columns and poly refs generate normal composite + // mapping. + // + if (ptr_ || poly_ref_) + object_columns_base::traverse_pointer (m, c); + else + { + // If this is a non-inverse relationship, then make the column have + // a dual interface: that of an object pointer and of an id column. + // The latter allows the user to, for example, use the is_null() + // test in a natural way. For inverse relationships there is no + // column and so the column interface is not available. + // + in_ptr_ = true; + object_columns_base::traverse_pointer (m, c); + in_ptr_ = false; + + if (decl_) + { + os << "typedef" << endl + << "odb::query_pointer<" << endl + << " odb::pointer_query_columns<" << endl + << " " << class_fq_name (c) << "," << endl + << " id_" << db << "," << endl + << " " << name << "_alias_ > >" << endl + << name << "_pointer_type_;" + << endl; + + os << "struct " << name << "_type_: " << + name << "_pointer_type_, " << + name << "_column_class" << query_utils::depth_suffix (depth_) << '_' + << "{"; + + if (!const_.empty ()) + os << name << "_type_ ()" // Need user-defined default c-tor for + << "{" // initialization of static const. + << "}"; + + os << "};"; + + os << "static " << const_ << name << "_type_ " << name << ";" + << endl; + } + } + } + else + { + // Simple id. + // + string type (t.fq_name (hint)); + string col (column_name (m, key_prefix_, default_name_, column_prefix_)); + + // For pointer_query_columns and poly refs generate normal column mapping. + // + if (ptr_ || poly_ref_) + column_common (m, type, col); + else + { + // If this is a non-inverse relationship, then make the column have + // a dual interface: that of an object pointer and of an id column. + // The latter allows the user to, for example, use the is_null() + // test in a natural way. For inverse relationships there is no + // column and so the column interface is not available. + // + column_common (m, type, col, "_column_type_"); + + if (decl_) + { + os << "typedef" << endl + << "odb::query_pointer<" << endl + << " odb::pointer_query_columns<" << endl + << " " << class_fq_name (c) << "," << endl + << " id_" << db << "," << endl + << " " << name << "_alias_ > >" << endl + << name << "_pointer_type_;" + << endl; + + os << "struct " << name << "_type_: " << + name << "_pointer_type_, " << name << "_column_type_" + << "{"; + + column_ctor (type, name + "_type_", name + "_column_type_"); + + os << "};"; + } + } + + if (decl_) + os << "static " << const_ << name << "_type_ " << name << ";" + << endl; + } + + poly_ref_ = false; +} + +// query_columns_bases +// + +void query_columns_bases:: +traverse (type& c) +{ + // Ignore transient bases. Not used for views. + // + if (!object (c)) + return; + + if (first_) + { + os << ":" << endl + << " "; + first_ = false; + } + else + os << "," << endl + << " "; + + os << (ptr_ ? "pointer_query_columns" : "query_columns") << "< " << + class_fq_name (c) << ", id_" << db << ", "; + + // If our base is polymorphic, then it has its own table/alias. + // + if (polymorphic (c)) + os << "typename A::base_traits"; + else + os << "A"; + + os << " >"; +} + +// query_columns_base_aliases +// + +void query_columns_base_aliases:: +traverse (type& c) +{ + // Ignore transient bases. Not used for views. + // + if (!object (c)) + return; + + string const& name (class_name (c)); + + os << "// " << name << endl + << "//" << endl + << "typedef " << (ptr_ ? "pointer_query_columns" : "query_columns") << + "< " << class_fq_name (c) << ", id_" << db << ", "; + + if (polymorphic (c)) + os << "typename A::base_traits"; + else + os << "A"; + + os << " > " << name << ";" + << endl; +} + +// query_columns_base_insts +// + +query_columns_base_insts:: +query_columns_base_insts (bool test_ptr, + bool decl, + string const& alias, + bool poly) + : test_ptr_ (test_ptr), decl_ (decl), alias_ (alias), poly_ (poly) +{ + *this >> inherits_ >> *this; +} + +query_columns_base_insts:: +query_columns_base_insts (query_columns_base_insts const& x) + : context (), // @@ -Wextra + test_ptr_ (x.test_ptr_), + decl_ (x.decl_), + alias_ (x.alias_), + poly_ (x.poly_) +{ + *this >> inherits_ >> *this; +} + +void query_columns_base_insts:: +traverse (type& c) +{ + if (!object (c)) + return; + + bool poly (polymorphic (c)); + if (poly && (poly != poly_)) + return; + + bool ptr (has_a (c, test_pointer | include_base)); + + string old_alias; + if (poly) + { + old_alias = alias_; + alias_ += "::base_traits"; + } + + // Instantiate bases recursively. + // + inherits (c, inherits_); + + inst_query_columns (decl_, + test_ptr_ && ptr, + class_fq_name (c), + alias_, + c); + + if (!test_ptr_ && ptr) + inst_query_columns (decl_, true, class_fq_name (c), alias_, c); + + if (poly) + alias_ = old_alias; +} + +// query_columns_type +// + +void query_columns_type:: +traverse (type& c) +{ + string const& type (class_fq_name (c)); + + if (ptr_) + { + os << "template <typename A>" << endl + << "struct pointer_query_columns< " << type << ", id_" << db << ", A >"; + + // If we don't have pointers (in the whole hierarchy), then + // pointer_query_columns and query_columns are the same. + // + if (!has_a (c, test_pointer | include_base)) + { + os << ":" << endl + << " query_columns< " << type << ", id_" << db << ", A >" + << "{" + << "};"; + } + else + { + { + instance<query_columns_bases> b (ptr_); + traversal::inherits i (*b); + inherits (c, i); + } + + os << "{"; + + { + instance<query_columns_base_aliases> b (ptr_); + traversal::inherits i (*b); + inherits (c, i); + } + + { + bool true_ (true); + instance<query_columns> t (true_, ptr_, c); //@@ forwarding + t->traverse (c); + } + + os << "};"; + + generate_impl (c); + } + } + else if (decl_) + { + bool has_ptr (has_a (c, test_pointer | exclude_base)); + + if (has_ptr) + { + // Generate aliases. + // + { + bool true_ (true); //@@ (im)perfect forwarding + instance<query_alias_traits> t (c, true_); + t->traverse (c); + } + + // This class contains everything for inverse pointers and + // aliases for non-inverse ones. It doesn't depend on the + // table alias (A) template argument. + // + os << "template <>" << endl + << "struct " << exp << "query_columns_base< " << type << ", " << + "id_" << db << " >" + << "{"; + + bool true_ (true); //@@ (im)perfect forwarding. + bool false_ (false); + instance<query_columns_base> t (c, true_, false_); + t->traverse (c); + + os << "};"; + } + + os << "template <typename A>" << endl + << "struct query_columns< " << type << ", id_" << db << ", A >"; + + if (has_ptr) + os << ":" << endl + << " query_columns_base< " << type << ", id_" << db << " >"; + + { + instance<query_columns_bases> b (ptr_, !has_ptr); + traversal::inherits i (*b); + inherits (c, i); + } + + os << "{"; + + { + instance<query_columns_base_aliases> b (ptr_); + traversal::inherits i (*b); + inherits (c, i); + } + + { + instance<query_columns> t (decl_, ptr_, c); + t->traverse (c); + } + + os << "};"; + + generate_impl (c); + } + else if (inst_) + { + // If we have the extern symbol, generate extern template declarations. + // + if (!ext.empty ()) + { + bool has_ptr (has_a (c, test_pointer | exclude_base)); + bool reuse_abst (abstract (c) && !polymorphic (c)); + + if (has_ptr || !reuse_abst) + { + os << "#ifdef " << ext << endl + << endl; + + if (has_ptr) + { + bool true_ (true); //@@ (im)perfect forwarding. + bool false_ (false); + + instance<query_columns_base> t (c, false_, true_); + t->traverse (c); + } + + // Don't generate it for reuse-abstract classes. + // + if (!reuse_abst) + generate_inst (c); + + os << "#endif // " << ext << endl + << endl; + } + } + } + else + { + bool has_ptr (has_a (c, test_pointer | exclude_base)); + + // Generate alias_traits specializations. While the class + // is generated even if our base has a pointer, there is + // not source code if we don't have pointers ourselves. + // + if (has_ptr) + { + bool false_ (false); //@@ (im)perfect forwarding + instance<query_alias_traits> t (c, false_); + t->traverse (c); + } + + // query_columns_base + // + if (has_ptr) + { + bool false_ (false); //@@ (im)perfect forwarding. + instance<query_columns_base> t (c, false_, false_); + t->traverse (c); + } + + // Explicit template instantiations. Don't generate it for reuse- + // abstract classes. + // + if (multi_dynamic && (!abstract (c) || polymorphic (c))) + generate_inst (c); + } +} + +void query_columns_type:: +generate_impl (type& c) +{ + string guard; + + // Exclude definitions (they will be explicitly instantiated once in + // the source file) unless we have the extern symbol. In this case + // the extern template declaration will make sure we don't get + // instantiations in multiple places and we will avoid the VC++ + // warning C4661 (no definition provided). + // + if (multi_dynamic && ext.empty ()) + { + guard = make_guard ("ODB_" + db.string () + "_QUERY_COLUMNS_DEF"); + + os << "#ifdef " << guard << endl + << endl; + } + + { + bool false_ (false); + instance<query_columns> t (false_, ptr_, c); + t->traverse (c); + } + + if (!guard.empty ()) + os << "#endif // " << guard << endl + << endl; +} + +void query_columns_type:: +generate_inst (type& c) +{ + string const& type (class_fq_name (c)); + + // Explicit template instantiations. Here is what we need to + // instantiate + // + // 1. Reuse inheritance bases all the way to the ultimate base. + // Unlike poly inheritance, reuse inheritance uses the table + // alias of the derived type. Note that bases can have object + // pointers of their own but their types have already been + // instantiated by step 3 below performed for the base object. + // + // 2. Object pointers. Note that while object pointers cannot have + // their own pointers, they can have reuse inheritance bases. + // + // 3. The query_columns class for the table itself. + // + // We also need to repeat these steps for pointer_query_columns + // since it is used by views. + // + string alias ("access::object_traits_impl< " + type + ", id_" + + db.string () + " >"); + + // 1 + // + { + instance<query_columns_base_insts> b (false, inst_, alias, false); + traversal::inherits i (*b); + inherits (c, i); + } + + // 2: Handled by query_columns_base (which is where we generate + // all the aliases for object pointers). + // + + // 3 + // + inst_query_columns (inst_, false, type, alias, c); + + if (has_a (c, test_pointer | exclude_base)) + inst_query_columns (inst_, true, type, alias, c); +} + +// view_query_columns_type +// + +void view_query_columns_type:: +traverse (type& c) +{ + if (decl_) + generate_decl (c); + else + generate_def (c); +} + +void view_query_columns_type:: +generate_decl (type& c) +{ + string const& type (class_fq_name (c)); + size_t obj_count (c.get<size_t> ("object-count")); + view_objects& objs (c.get<view_objects> ("objects")); + + // Generate alias_traits specializations. + // + { + bool true_ (true); //@@ (im)perfect forwarding + instance<query_alias_traits> at (c, true_); + + for (view_objects::const_iterator i (objs.begin ()); i < objs.end (); ++i) + { + if (i->kind != view_object::object) + continue; // Skip tables. + + if (i->alias.empty ()) + continue; + + semantics::class_& o (*i->obj); + qname const& t (table_name (o)); + + // Check that the alias is not the same as the table name + // (if this is a polymorphic object, then the alias is just + // a prefix). + // + if (polymorphic (o) || t.qualified () || i->alias != t.uname ()) + at->generate_decl (i->alias, o); + } + } + + // If we have the extern symbol, generate extern template declarations. + // Do it before query_columns since the inheritance will trigger + // instantiation and we won't be able to change visibility (GCC). + // + if (obj_count != 0 && multi_dynamic && !ext.empty ()) + { + os << "#ifdef " << ext << endl + << endl; + + generate_inst (c); + + os << "#endif // " << ext << endl + << endl; + } + + // query_columns + // + os << "struct " << exp << "access::view_traits_impl< " << type << ", " << + "id_" << db << " >::query_columns"; + + if (obj_count > 1) + { + os << "{"; + + for (view_objects::const_iterator i (objs.begin ()); i < objs.end (); ++i) + { + if (i->kind != view_object::object) + continue; // Skip tables. + + bool alias (!i->alias.empty ()); + semantics::class_& o (*i->obj); + string const& oname (alias ? i->alias : class_name (o)); + string const& otype (class_fq_name (o)); + qname const& table (table_name (o)); + + os << "// " << oname << endl + << "//" << endl + << "typedef" << endl + << "odb::pointer_query_columns<" << endl + << " " << otype << "," << endl + << " id_" << db << "," << endl; + + if (alias && (polymorphic (o) || + table.qualified () || + i->alias != table.uname ())) + { + os << " odb::alias_traits< " << otype << "," << endl + << " id_" << db << "," << endl + << " access::view_traits_impl< " << type << ", id_" << db << + " >::" << i->alias << "_tag> >" << endl; + } + else + os << " odb::access::object_traits_impl< " << otype << ", id_" << + db << " > >" << endl; + + os << oname << ";" + << endl; + } + + os << "};"; + } + else + { + // For a single object view we generate a shortcut without + // an intermediate typedef. + // + view_object const* vo (0); + for (view_objects::const_iterator i (objs.begin ()); + vo == 0 && i < objs.end (); + ++i) + { + if (i->kind == view_object::object) + vo = &*i; + } + + bool alias (!vo->alias.empty ()); + semantics::class_& o (*vo->obj); + string const& otype (class_fq_name (o)); + qname const& table (table_name (o)); + + os << ":" << endl + << " odb::pointer_query_columns<" << endl + << " " << otype << "," << endl + << " id_" << db << "," << endl; + + if (alias && (polymorphic (o) || + table.qualified () || + vo->alias != table.uname ())) + { + os << " odb::alias_traits<" << endl + << " " << otype << "," << endl + << " id_" << db << "," << endl + << " access::view_traits_impl< " << type << ", id_" << + db << " >::" << vo->alias << "_tag> >"; + } + else + os << " odb::access::object_traits_impl< " << otype << + ", id_" << db << " > >"; + + os << "{" + << "};"; + } +} + +void view_query_columns_type:: +generate_def (type& c) +{ + view_objects& objs (c.get<view_objects> ("objects")); + + // Generate alias_traits specializations. + // + { + bool false_ (false); //@@ (im)perfect forwarding + instance<query_alias_traits> at (c, false_); + + for (view_objects::const_iterator i (objs.begin ()); + i < objs.end (); + ++i) + { + if (i->kind != view_object::object) + continue; // Skip tables. + + if (i->alias.empty ()) + continue; + + semantics::class_& o (*i->obj); + qname const& t (table_name (o)); + + // Check that the alias is not the same as the table name + // (if this is a polymorphic object, then the alias is just + // a prefix). + // + if (polymorphic (o) || t.qualified () || i->alias != t.uname ()) + { + at->generate_def (i->alias, o, i->alias); + } + } + } + + if (multi_dynamic) + generate_inst (c); +} + +void view_query_columns_type:: +generate_inst (type& c) +{ + string const& type (class_fq_name (c)); + view_objects& objs (c.get<view_objects> ("objects")); + + string traits ("access::view_traits_impl< " + type + ", id_" + + db.string () + " >"); + + // Instantiate [pointer_]query_columns. + // + for (view_objects::const_iterator i (objs.begin ()); + i < objs.end (); + ++i) + { + if (i->kind != view_object::object) + continue; // Skip tables. + + if (i->alias.empty ()) + continue; // Instantiated by object. + + semantics::class_& o (*i->obj); + qname const& t (table_name (o)); + + // Check that the alias is not the same as the table name + // (if this is a polymorphic object, then the alias is just + // a prefix). + // + if (polymorphic (o) || t.qualified () || i->alias != t.uname ()) + { + string const& otype (class_fq_name (o)); + string alias ("odb::alias_traits<\n" + " " + otype + ",\n" + " id_" + db.string () + ",\n" + " " + traits + "::" + i->alias + "_tag>"); + + // Instantiate base [pointer_]query_columns. + // + { + instance<query_columns_base_insts> b (true, decl_, alias, true); + traversal::inherits i (*b); + inherits (o, i); + } + + // If the pointed-to class has no pointers of its own then + // pointer_query_columns just derives from query_columns and + // that's what we need to instantiate. + // + inst_query_columns (decl_, + has_a (o, test_pointer | include_base), + otype, + alias, + o); + } + } +} diff --git a/odb/odb/common-query.hxx b/odb/odb/common-query.hxx new file mode 100644 index 0000000..e90dd69 --- /dev/null +++ b/odb/odb/common-query.hxx @@ -0,0 +1,279 @@ +// file : odb/common-query.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_COMMON_QUERY_HXX +#define ODB_COMMON_QUERY_HXX + +#include <odb/common.hxx> +#include <odb/context.hxx> + +// +// Query-related generators. +// + +struct query_utils: virtual context +{ + void + inst_query_columns (bool decl, // Extern declaration or instanatiation. + bool ptr, // pointer_query_columns or query_columns + string const& type, // Object fq-type. + string const& alias, // Table alias. + semantics::class_&); // Traverse for nested structs. + + static string + depth_suffix (size_t); +}; + +// Generate query tags for pointers in this object. +// +struct query_tags: object_columns_base, virtual context +{ + typedef query_tags base; + + query_tags (): nl_ (false), depth_ (0) {} + + virtual void + traverse (semantics::class_&); + + virtual void + traverse_object (semantics::class_&); + + virtual void + traverse_composite (semantics::data_member*, semantics::class_&); + + virtual void + traverse_pointer (semantics::data_member&, semantics::class_&); + + virtual void + generate (string const& name); + +private: + bool nl_; + size_t depth_; +}; + +// Generate alias_traits specializations for pointers in this objects. +// +struct query_alias_traits: object_columns_base, virtual context +{ + typedef query_alias_traits base; + + query_alias_traits (semantics::class_&, bool decl); + + virtual void + traverse_object (semantics::class_&); + + virtual void + traverse_composite (semantics::data_member*, semantics::class_&); + + virtual void + traverse_pointer (semantics::data_member&, semantics::class_&); + + virtual void + generate_decl (string const& tag, semantics::class_&); + + virtual void + generate_decl_body (); + + virtual void + generate_def (semantics::data_member&, semantics::class_&); + + virtual void + generate_def (string const& tag, semantics::class_&, string const& alias); + +protected: + bool decl_; + string scope_; + size_t depth_; +}; + +// Generate query columns in the query_columns_base class. +// +struct query_columns_base: object_columns_base, query_utils +{ + typedef query_columns_base base; + + // If inst is true, then we generate extern template declarations + // in the header. + // + query_columns_base (semantics::class_&, bool decl, bool inst); + + virtual void + traverse_object (semantics::class_&); + + virtual void + traverse_composite (semantics::data_member*, semantics::class_&); + + virtual void + traverse_pointer (semantics::data_member&, semantics::class_&); + + virtual void + generate_inst (semantics::data_member&, semantics::class_&); + +protected: + bool decl_; + bool inst_; + string const_; // Const prefix or empty. + string scope_; + size_t depth_; +}; + +// Generate query columns in the query_columns or pointer_query_columns +// class. +// +struct query_columns: object_columns_base, virtual context +{ + typedef query_columns base; + + query_columns (bool decl, bool ptr, semantics::class_&); + + virtual void + column_ctor (string const& type, string const& name, string const& base); + + virtual void + traverse_object (semantics::class_&); + + virtual void + traverse_composite (semantics::data_member*, semantics::class_&); + + virtual void + column_common (semantics::data_member&, + string const& type, + string const& column, + string const& suffix = "_type_"); + + virtual bool + traverse_column (semantics::data_member&, string const&, bool); + + virtual void + traverse_pointer (semantics::data_member&, semantics::class_&); + +protected: + bool decl_; + bool ptr_; + bool poly_ref_; + string const_; // Const prefix or empty. + bool in_ptr_; // True while we are "inside" an object pointer. + string fq_name_; + bool resue_abstract_; // Object is reuse-abstract. + string scope_; + size_t depth_; +}; + +// Generate the list of base classes for the query_columns or +// pointer_query_columns class. +// +struct query_columns_bases: traversal::class_, virtual context +{ + typedef query_columns_bases base; + + query_columns_bases (bool ptr, bool first = true) + : ptr_ (ptr), first_ (first) {} + + virtual void + traverse (type&); + +private: + bool ptr_; + bool first_; +}; + +// Generate the base class aliases (typedefs). These can be used to +// resolve member ambiguities. +// +struct query_columns_base_aliases: traversal::class_, virtual context +{ + typedef query_columns_base_aliases base; + + query_columns_base_aliases (bool ptr): ptr_ (ptr) {} + + virtual void + traverse (type&); + +private: + bool ptr_; +}; + +// Generate explicit instantiations of base classes. +// +struct query_columns_base_insts: traversal::class_, query_utils +{ + typedef query_columns_base_insts base; + + query_columns_base_insts (bool test_ptr, + bool decl, + string const& alias, + bool poly); // Traverse polymorphic bases. + query_columns_base_insts (query_columns_base_insts const&); + + virtual void + traverse (type&); + +private: + bool test_ptr_; + bool decl_; + string alias_; + bool poly_; + traversal::inherits inherits_; +}; + +// Generate the query_columns_base/query_columns or pointer_query_columns +// classes for objects. +// +struct query_columns_type: traversal::class_, query_utils +{ + typedef query_columns_type base; + + // Depending on the ptr argument, generate query_columns or + // pointer_query_columns specialization. The latter is used + // for object pointers where we don't support nested pointers. + // + // If decl is false then generate definitions (only needed for + // query_columns so ptr should be false). + // + // If inst if true, then generate extern template declarations + // in the header (ptr and decl should be false). + // + query_columns_type (bool ptr, bool decl, bool inst) + : ptr_ (ptr), decl_ (decl), inst_ (inst) {} + + virtual void + traverse (type&); + + virtual void + generate_impl (type&); + + virtual void + generate_inst (type&); + +public: + bool ptr_; + bool decl_; + bool inst_; +}; + +// Generate the query_columns class for views. +// +struct view_query_columns_type: traversal::class_, query_utils +{ + typedef view_query_columns_type base; + + view_query_columns_type (bool decl): decl_ (decl) {} + + virtual void + traverse (type&); + + void + generate_decl (type&); + + void + generate_def (type&); + + void + generate_inst (type&); + +public: + bool decl_; +}; + +#endif // ODB_COMMON_QUERY_HXX diff --git a/odb/odb/common.cxx b/odb/odb/common.cxx new file mode 100644 index 0000000..63e49ad --- /dev/null +++ b/odb/odb/common.cxx @@ -0,0 +1,584 @@ +// file : odb/common.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <odb/common.hxx> + +using namespace std; + +// +// object_members_base +// + +void object_members_base:: +traverse_simple (semantics::data_member&) +{ +} + +void object_members_base:: +traverse_pointer (semantics::data_member& m, semantics::class_& c) +{ + if (!view_member (m)) // Not really "as if" pointed-to id member. + traverse_member (m, utype (*id_member (c))); +} + +void object_members_base:: +traverse_composite (semantics::data_member*, semantics::class_& c) +{ + inherits (c); + names (c); +} + +void object_members_base:: +traverse_composite_wrapper (semantics::data_member* m, + semantics::class_& c, + semantics::type*) +{ + traverse_composite (m, c); +} + +void object_members_base:: +traverse_container (semantics::data_member&, semantics::type&) +{ +} + +void object_members_base:: +traverse_object (semantics::class_& c) +{ + inherits (c); + names (c); +} + +void object_members_base:: +traverse_view (semantics::class_& c) +{ + // A view has no bases. + // + names (c); +} + +void object_members_base:: +traverse (semantics::class_& c) +{ + class_kind_type k (class_kind (c)); + + if (k == class_other) + { + // Ignore transient bases. + // + return; + } + else if (k == class_composite) + { + if (member_scope_.empty ()) + member_scope_.push_back (class_inheritance_chain ()); + + member_scope_.back ().push_back (&c); + + traverse_composite_wrapper (0, c, 0); + + member_scope_.back ().pop_back (); + + if (member_scope_.back ().empty ()) + member_scope_.pop_back (); + + return; + } + + if (top_level_) + top_level_ = false; + else + { + // Unless requested otherwise, don't go into bases if we are a derived + // type in a polymorphic hierarchy. + // + if (!traverse_poly_base_ && polymorphic (c)) + return; + } + + if (context::top_object == 0) + context::top_object = &c; + + semantics::class_* prev (context::cur_object); + context::cur_object = &c; + + if (member_scope_.empty ()) + member_scope_.push_back (class_inheritance_chain ()); + + member_scope_.back ().push_back (&c); + + if (build_table_prefix_) + { + // Don't reset the table prefix if we are traversing a base. + // + bool tb (false); + + if (table_prefix_.level == 0) + { + table_prefix_ = table_prefix (c); + tb = true; + } + + if (k == class_object) + traverse_object (c); + else + traverse_view (c); + + if (tb) + table_prefix_ = table_prefix (); + } + else + { + if (k == class_object) + traverse_object (c); + else + traverse_view (c); + } + + member_scope_.back ().pop_back (); + + if (member_scope_.back ().empty ()) + member_scope_.pop_back (); + + context::cur_object = prev; + + if (prev == 0) + context::top_object = 0; +} + +void object_members_base:: +traverse_member (semantics::data_member& m, semantics::type& t) +{ + if (semantics::class_* comp = context::composite_wrapper (t)) + { + member_scope_.push_back (class_inheritance_chain ()); + member_scope_.back ().push_back (comp); + + table_prefix old_table_prefix; + string old_flat_prefix, old_member_prefix; + + if (build_flat_prefix_) + { + old_flat_prefix = flat_prefix_; + flat_prefix_ += public_name (m); + flat_prefix_ += '_'; + } + + if (build_member_prefix_) + { + old_member_prefix = member_prefix_; + member_prefix_ += m.name (); + member_prefix_ += '.'; + } + + if (build_table_prefix_) + { + old_table_prefix = table_prefix_; + table_prefix_.append (m); + } + + traverse_composite_wrapper (&m, *comp, (wrapper (t) ? &t : 0)); + + if (build_table_prefix_) + table_prefix_ = old_table_prefix; + + if (build_flat_prefix_) + flat_prefix_ = old_flat_prefix; + + if (build_member_prefix_) + member_prefix_ = old_member_prefix; + + member_scope_.pop_back (); + } + else + traverse_simple (m); +} + +bool object_members_base:: +section_test (data_member_path const& mp) +{ + // By default ignore members from the wrong section. + // + return section_ == 0 || *section_ == section (mp); +} + +void object_members_base::member:: +traverse (semantics::data_member& m) +{ + if (transient (m)) + return; + + om_.member_path_.push_back (&m); + + // Ignore members from the wrong section. + // + if (om_.section_test (om_.member_path_)) + { + semantics::type& t (utype (m)); + + if (semantics::type* c = context::container (m)) + om_.traverse_container (m, *c); + else if (semantics::class_* c = object_pointer (t)) + om_.traverse_pointer (m, *c); + else + om_.traverse_member (m, t); + } + + om_.member_path_.pop_back (); +} + +// +// object_columns_base +// + +void object_columns_base:: +flush () +{ +} + +bool object_columns_base:: +traverse_column (semantics::data_member&, string const&, bool) +{ + return false; +} + +void object_columns_base:: +traverse_pointer (semantics::data_member& m, semantics::class_& c) +{ + if (!view_member (m)) // Not really "as if" pointed-to id column. + traverse_member (m, utype (*id_member (c))); +} + +void object_columns_base:: +traverse_points_to (semantics::data_member& m, semantics::class_&) +{ + traverse_member (m, utype (m)); +} + +void object_columns_base:: +traverse_composite (semantics::data_member*, semantics::class_& c) +{ + inherits (c); + names (c); +} + +void object_columns_base:: +traverse_object (semantics::class_& c) +{ + inherits (c); + names (c); +} + +void object_columns_base:: +traverse_view (semantics::class_& c) +{ + // A view has no bases. + // + names (c); +} + +void object_columns_base:: +traverse_pre (semantics::nameable&) +{ +} + +void object_columns_base:: +traverse_post (semantics::nameable&) +{ +} + +void object_columns_base:: +traverse (semantics::data_member& m) +{ + traverse_pre (m); + + semantics::type& t (utype (m)); + semantics::class_* c (object_pointer (t)); + semantics::type* rt (c == 0 ? &t : &utype (*id_member (*c))); + + root_ = &m; + + // It would seem natural to add m to member_path_ so that we don't + // have these two cases. However, for member path to work correctly + // with readonly() we also have to have corresponding member_scope, + // which is a whole different level of complexity. + // + root_id_ = member_path_.empty () + ? context::id (m) + : context::id (member_path_) != 0; + root_op_ = (c != 0); + root_null_ = context::null (m); + + + if (root_op_) + traverse_pointer (m, *c); + else + traverse_member (m, *rt); + + if (!first_ && composite_wrapper (*rt)) + flush (); + + root_ = 0; + + traverse_post (m); +} + +void object_columns_base:: +traverse (semantics::data_member& m, + semantics::type& t, + std::string const& kp, + std::string const& dn, + semantics::class_* to) +{ + traverse_pre (m); + + semantics::class_* oto (context::top_object); + + if (to != 0) + context::top_object = to; + + semantics::class_* c (object_pointer (t)); + semantics::type* rt (c == 0 ? &t : &utype (*id_member (*c))); + + root_ = &m; + root_id_ = (kp == "id"); + root_op_ = (c != 0); + root_null_ = context::null (m, kp); + + key_prefix_ = kp; + default_name_ = dn; + + if (root_op_) + traverse_pointer (m, *c); + else + traverse_member (m, *rt); + + key_prefix_.clear (); + default_name_.clear (); + + if (!first_ && composite_wrapper (*rt)) + flush (); + + root_ = 0; + context::top_object = oto; + + traverse_post (m); +} + +void object_columns_base:: +traverse (semantics::class_& c) +{ + class_kind_type k (class_kind (c)); + + // Ignore transient bases. + // + if (k == class_other) + return; + + bool f (top_level_); + + if (top_level_) + { + traverse_pre (c); + top_level_ = false; + } + else + { + // Unless requested otherwise, don't go into bases if we are a derived + // type in a polymorphic hierarchy. + // + if (!traverse_poly_base_ && polymorphic (c)) + return; + } + + semantics::class_* prev (0); + if (k == class_object || k == class_view) + { + if (context::top_object == 0) + context::top_object = &c; + + prev = context::cur_object; + context::cur_object = &c; + } + + if (member_scope_.empty ()) + member_scope_.push_back (class_inheritance_chain ()); + + member_scope_.back ().push_back (&c); + + if (k == class_object) + traverse_object (c); + else if (k == class_view) + traverse_view (c); + else if (k == class_composite) + traverse_composite (0, c); + + member_scope_.back ().pop_back (); + + if (member_scope_.back ().empty ()) + member_scope_.pop_back (); + + if (k == class_object || k == class_view) + { + context::cur_object = prev; + + if (prev == 0) + context::top_object = 0; + } + + if (f) + { + if (!first_) + flush (); + + traverse_post (c); + } +} + +void object_columns_base:: +traverse_member (semantics::data_member& m, semantics::type& t) +{ + if (semantics::class_* comp = composite_wrapper (t)) + { + member_scope_.push_back (class_inheritance_chain ()); + member_scope_.back ().push_back (comp); + + column_prefix old_prefix (column_prefix_); + column_prefix_.append (m, key_prefix_, default_name_); + + // Save and clear the key prefix and default name. + // + string old_kp, old_dn; + old_kp.swap (key_prefix_); + old_dn.swap (default_name_); + + traverse_composite (&m, *comp); + + old_kp.swap (key_prefix_); + old_dn.swap (default_name_); + + column_prefix_ = old_prefix; + member_scope_.pop_back (); + } + else + { + string name (column_name (m, key_prefix_, default_name_, column_prefix_)); + + if (traverse_column (m, name, first_)) + { + if (first_) + first_ = false; + } + } +} + +bool object_columns_base:: +section_test (data_member_path const& mp) +{ + // By default ignore members from the wrong section. + // + return section_ == 0 || *section_ == section (mp); +} + +void object_columns_base::member:: +traverse (semantics::data_member& m) +{ + if (transient (m)) + return; + + // Container gets its own table, so nothing to do here. + // + if (container (m)) + return; + + oc_.member_path_.push_back (&m); + + if (oc_.section_test (oc_.member_path_)) + { + using semantics::class_; + semantics::type& t (utype (m)); + + if (class_* c = object_pointer (t)) + oc_.traverse_pointer (m, *c); + else if (class_* c = points_to (m)) + oc_.traverse_points_to (m, *c); + else + oc_.traverse_member (m, t); + } + + oc_.member_path_.pop_back (); +} + +// +// object_columns_list +// + +void object_columns_list:: +traverse_pointer (semantics::data_member& m, semantics::class_& c) +{ + // Ignore inverse object pointers. + // + if (!ignore_inverse_ || !inverse (m, key_prefix_)) + object_columns_base::traverse_pointer (m, c); +} + +bool object_columns_list:: +traverse_column (semantics::data_member& m, std::string const& name, bool) +{ + columns_.push_back (column (name, column_type (), m)); + return true; +} + +// +// typedefs +// + +void typedefs:: +traverse (semantics::typedefs& t) +{ + if (check (t)) + traversal::typedefs::traverse (t); +} + +bool typedefs:: +check (semantics::typedefs& t) +{ + // This typedef must be for a class template instantiation. + // + using semantics::class_instantiation; + class_instantiation* ci (dynamic_cast<class_instantiation*> (&t.type ())); + + if (ci == 0) + return false; + + // It must be an object, view, or composite value. + // + if (class_kind (*ci) == class_other) + return false; + + // This typedef name should be the one that was used in the pragma. + // + using semantics::names; + tree type (ci->get<tree> ("tree-node")); + + names* hint; + if (ci->count ("tree-hint")) + hint = ci->get<names*> ("tree-hint"); + else + { + hint = unit.find_hint (type); + ci->set ("tree-hint", hint); // Cache it. + } + + if (hint != &t) + return false; + + // And the definition may have to be in the file we are compiling. + // + if (!included_) + { + if (!options.at_once () && class_file (*ci) != unit.file ()) + return false; + } + + return true; +} diff --git a/odb/odb/common.hxx b/odb/odb/common.hxx new file mode 100644 index 0000000..149def7 --- /dev/null +++ b/odb/odb/common.hxx @@ -0,0 +1,468 @@ +// file : odb/common.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_COMMON_HXX +#define ODB_COMMON_HXX + +#include <string> +#include <vector> +#include <cstddef> // std::size_t +#include <cassert> + +#include <odb/context.hxx> +#include <odb/instance.hxx> + +// Traverse object members recursively by going into bases and +// composite members. +// +struct object_members_base: traversal::class_, virtual context +{ + virtual void + traverse_simple (semantics::data_member&); + + // Traverse object pointer. The second argument is the pointed-to + // class. When overriding this function, you will most likely want + // to call the base in order to traverse the pointer as a simple + // member (simple object id) or as composite (composite object id). + // + virtual void + traverse_pointer (semantics::data_member&, semantics::class_&); + + // If you override this function, you can call the base to traverse + // bases and members. The first argument is the data member and can + // be NULL if we are traversing the root type or a base. The second + // argument is the actual composite type, which is not necessarily + // the same as the member's type. + // + virtual void + traverse_composite (semantics::data_member*, semantics::class_&); + + // More general version of the above function that allows detection + // of wrapped composite value. By default this function calls + // traverse_composite (m, comp) ignoring the wrapper type. Note that + // this function is called for all composite value (wrapped or not). + // If it is not wrapped, the wrapper argument will be NULL. + // + virtual void + traverse_composite_wrapper (semantics::data_member*, + semantics::class_& comp, + semantics::type* wrapper); + + // The second argument is the actual container type in case the + // member type is a wrapper. + // + virtual void + traverse_container (semantics::data_member&, semantics::type&); + + // If you override this function, you can call the base to traverse + // bases and members. + // + virtual void + traverse_object (semantics::class_&); + + // If you override this function, you can call the base to traverse + // members. + // + virtual void + traverse_view (semantics::class_&); + + virtual bool + section_test (data_member_path const&); + +public: + object_members_base (bool traverse_poly_base = false, + object_section* section = 0) + : section_ (section), top_level_ (true), member_ (*this) + { + init (false, false, false, traverse_poly_base); + } + + object_members_base (bool build_flat_prefix, + bool build_table_prefix, + bool build_member_prefix, + bool traverse_poly_base = false, + object_section* section = 0) + : section_ (section), top_level_ (true), member_ (*this) + { + init (build_flat_prefix, + build_table_prefix, + build_member_prefix, + traverse_poly_base); + } + + object_members_base (object_members_base const& x) + : context (), //@@ -Wextra + section_ (x.section_), + top_level_ (true), + member_ (*this) + { + init (x.build_flat_prefix_, + x.build_table_prefix_, + x.build_member_prefix_, + x.traverse_poly_base_); + } + + virtual void + traverse (semantics::class_&); + +protected: + string flat_prefix_; + table_prefix table_prefix_; + string member_prefix_; + + data_member_path member_path_; + data_member_scope member_scope_; + + object_section* section_; + +protected: + semantics::data_member* + id () const + { + assert (!member_path_.empty ()); + return context::id (member_path_); + } + +private: + void + init (bool build_flat_prefix, + bool build_table_prefix, + bool build_member_prefix, + bool traverse_poly_base) + { + build_flat_prefix_ = build_flat_prefix; + build_table_prefix_ = build_table_prefix; + build_member_prefix_ = build_member_prefix; + traverse_poly_base_ = traverse_poly_base; + + *this >> names_ >> member_; + *this >> inherits_ >> *this; + } + +private: + virtual void + traverse_member (semantics::data_member&, semantics::type&); + + struct member: traversal::data_member + { + member (object_members_base& om): om_ (om) {} + + virtual void + traverse (semantics::data_member&); + + public: + object_members_base& om_; + }; + + bool build_flat_prefix_; + bool build_table_prefix_; + bool build_member_prefix_; + + bool traverse_poly_base_; + + bool top_level_; + + member member_; + traversal::names names_; + traversal::inherits inherits_; +}; + +// Traverse object columns recursively by going into composite members +// and bases. +// +struct object_columns_base: traversal::class_, virtual context +{ + // Returning false means that the column has been ignored and the + // first flag should not be changed. + // + virtual bool + traverse_column (semantics::data_member&, + string const& name, + bool first); + + // Traverse object pointer. The second argument is the pointed-to + // class. When overriding this function, you will most likely want + // to call the base in order to traverse the member as a column + // (simple object id) or columns (composite object id). + // + virtual void + traverse_pointer (semantics::data_member&, semantics::class_&); + + virtual void + traverse_points_to (semantics::data_member&, semantics::class_&); + + // If you override this function, you can call the base to traverse + // bases and members. The first argument is the data member and can + // be NULL if we are traversing the root type or a base. The second + // argument is the actual composite type, which is not necessarily + // the same as the member type. + // + virtual void + traverse_composite (semantics::data_member*, semantics::class_&); + + // If you override this function, you can call the base to traverse + // bases and members. + // + virtual void + traverse_object (semantics::class_&); + + // If you override this function, you can call the base to traverse + // members. + // + virtual void + traverse_view (semantics::class_&); + + // Called after the last column, provided at least one column hasn't + // been ignored. + // + virtual void + flush (); + + virtual bool + section_test (data_member_path const&); + + // Start/end traversal callbacks. + // + virtual void + traverse_pre (semantics::nameable&); + + virtual void + traverse_post (semantics::nameable&); + +public: + object_columns_base (bool first = true, + column_prefix const& cp = column_prefix (), + object_section* section = 0) + : column_prefix_ (cp), + section_ (section), + root_ (0), + traverse_poly_base_ (false), + first_ (first), + top_level_ (true), + member_ (*this) + { + init (); + } + + object_columns_base (bool first, + bool traverse_poly_base, + object_section* section = 0) + : section_ (section), + root_ (0), + traverse_poly_base_ (traverse_poly_base), + first_ (first), + top_level_ (true), + member_ (*this) + { + init (); + } + + object_columns_base (object_columns_base const& x) + : context (), //@@ -Wextra + column_prefix_ (x.column_prefix_), + section_ (x.section_), + root_ (0), + traverse_poly_base_ (x.traverse_poly_base_), + first_ (x.first_), + top_level_ (true), + member_ (*this) + { + init (); + } + + virtual void + traverse (semantics::class_&); + + // Traverse a data member with type, which can be a simple or composite + // value type, or an object pointer (with a simple or composite id). + // + virtual void + traverse (semantics::data_member&); + + virtual void + traverse (semantics::data_member& m, column_prefix const& cp) + { + column_prefix op (column_prefix_); + column_prefix_ = cp; + traverse (m); + column_prefix_ = op; + } + + virtual void + traverse (data_member_path& mp) + { + data_member_path op (member_path_); + member_path_ = mp; + traverse (*mp.back (), column_prefix (mp)); + member_path_ = op; + } + + // Should only be used for containers. + // + virtual void + traverse (semantics::data_member&, + semantics::type&, + string const& key_prefix, + string const& default_name, + semantics::class_* top_object = 0); // If not 0, switch top object. + +protected: + string key_prefix_; + string default_name_; + + column_prefix column_prefix_; + + data_member_path member_path_; + data_member_scope member_scope_; + + object_section* section_; + +protected: + semantics::data_member* + id () const + { + if (root_ != 0) + return root_id_ ? root_ : 0; // Cannot have ids below root. + else + { + assert (!member_path_.empty ()); + return context::id (member_path_); + } + } + + string + column_type () + { + if (member_path_.empty ()) + { + assert (root_ != 0); + return context::column_type (*root_, key_prefix_); + } + else + return context::column_type ( + member_path_, key_prefix_, root_ != 0 && (root_id_ || root_op_)); + } + + bool + null () const + { + return (root_ != 0 && root_null_) || context::null (member_path_); + } + +private: + semantics::data_member* root_; // Root member if traversing from a member. + bool root_id_; // True if traversing root as object id. + bool root_op_; // True if traversing root as object pointer. + bool root_null_; // True if root is null-able. + +private: + void + init () + { + *this >> names_ >> member_; + *this >> inherits_ >> *this; + } + +private: + virtual void + traverse_member (semantics::data_member&, semantics::type&); + + struct member: traversal::data_member, context + { + member (object_columns_base& oc): oc_ (oc) {} + + virtual void + traverse (semantics::data_member&); + + public: + object_columns_base& oc_; + }; + + bool traverse_poly_base_; + + bool first_; + bool top_level_; + + member member_; + traversal::names names_; + traversal::inherits inherits_; +}; + +struct object_columns_list: object_columns_base +{ + typedef object_columns_list base; + + object_columns_list (bool ignore_inverse = true) + : ignore_inverse_ (ignore_inverse) + { + } + + object_columns_list (column_prefix const& cp, bool ignore_inverse = true) + : object_columns_base (true, cp), ignore_inverse_ (ignore_inverse) + { + } + + struct column + { + column (std::string const& n, + std::string const& t, + semantics::data_member& m) + : name (n), type (t), member (&m) + { + } + + std::string name; + std::string type; + semantics::data_member* member; + }; + + typedef std::vector<column> columns; + typedef columns::const_iterator iterator; + + iterator + begin () const {return columns_.begin ();} + + iterator + end () const {return columns_.end ();} + + columns::size_type + size () const {return columns_.size ();} + + virtual void + traverse_pointer (semantics::data_member&, semantics::class_&); + + virtual bool + traverse_column (semantics::data_member&, string const&, bool); + +private: + bool ignore_inverse_; + columns columns_; +}; + +// Traverse objects, views, and composite values that are class template +// instantiations. +// +struct typedefs: traversal::typedefs, context +{ + typedefs (bool traverse_included) + : included_ (traverse_included) + { + } + + virtual void + traverse (semantics::typedefs&); + + // Returns true if we should traverse this typedef. + // + bool + check (semantics::typedefs&); + +private: + bool included_; +}; + +// Other common parts. +// +#include <odb/common-query.hxx> + +#endif // ODB_COMMON_HXX diff --git a/odb/odb/context.cxx b/odb/odb/context.cxx new file mode 100644 index 0000000..13fc1b3 --- /dev/null +++ b/odb/odb/context.cxx @@ -0,0 +1,3384 @@ +// file : odb/context.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <odb/gcc.hxx> + +#include <cctype> // std::toupper +#include <cassert> +#include <sstream> + +#include <odb/context.hxx> +#include <odb/common.hxx> +#include <odb/pragma.hxx> +#include <odb/cxx-lexer.hxx> +#include <odb/diagnostics.hxx> + +#include <odb/relational/mssql/context.hxx> +#include <odb/relational/mysql/context.hxx> +#include <odb/relational/oracle/context.hxx> +#include <odb/relational/pgsql/context.hxx> +#include <odb/relational/sqlite/context.hxx> + +using namespace std; + +static inline void +add_space (string& s) +{ + string::size_type n (s.size ()); + if (n != 0 && s[n - 1] != ' ') + s += ' '; +} + +// +// custom_cxx_type +// +string custom_cxx_type:: +translate (string const& val, const cxx_tokens& expr) +{ + // Similar to member_access::translate() and a few other places. + // + string r; + + cxx_tokens_lexer l; + l.start (expr); + + string tl; + for (cpp_ttype tt (l.next (tl)), ptt (CPP_EOF); tt != CPP_EOF;) + { + // Try to format the expression to resemble the style of the + // generated code. + // + switch (tt) + { + case CPP_NOT: + { + add_space (r); + r += '!'; + break; + } + case CPP_COMMA: + { + r += ", "; + break; + } + case CPP_OPEN_PAREN: + { + if (ptt == CPP_NAME || + ptt == CPP_KEYWORD) + add_space (r); + + r += '('; + break; + } + case CPP_CLOSE_PAREN: + { + r += ')'; + break; + } + case CPP_OPEN_SQUARE: + { + r += '['; + break; + } + case CPP_CLOSE_SQUARE: + { + r += ']'; + break; + } + case CPP_OPEN_BRACE: + { + add_space (r); + r += "{ "; + break; + } + case CPP_CLOSE_BRACE: + { + add_space (r); + r += '}'; + break; + } + case CPP_SEMICOLON: + { + r += ';'; + break; + } + case CPP_ELLIPSIS: + { + add_space (r); + r += "..."; + break; + } + case CPP_PLUS: + case CPP_MINUS: + { + bool unary (ptt != CPP_NAME && + ptt != CPP_SCOPE && + ptt != CPP_NUMBER && + ptt != CPP_STRING && + ptt != CPP_CLOSE_PAREN && + ptt != CPP_PLUS_PLUS && + ptt != CPP_MINUS_MINUS); + + if (!unary) + add_space (r); + + r += cxx_lexer::token_spelling[tt]; + + if (!unary) + r += ' '; + break; + } + case CPP_PLUS_PLUS: + case CPP_MINUS_MINUS: + { + if (ptt != CPP_NAME && + ptt != CPP_CLOSE_PAREN && + ptt != CPP_CLOSE_SQUARE) + add_space (r); + + r += cxx_lexer::token_spelling[tt]; + break; + } + case CPP_DEREF: + case CPP_DEREF_STAR: + case CPP_DOT: + case CPP_DOT_STAR: + { + r += cxx_lexer::token_spelling[tt]; + break; + } + case CPP_STRING: + { + if (ptt == CPP_NAME || + ptt == CPP_KEYWORD || + ptt == CPP_STRING || + ptt == CPP_NUMBER) + add_space (r); + + r += context::strlit (tl); + break; + } + case CPP_NUMBER: + { + if (ptt == CPP_NAME || + ptt == CPP_KEYWORD || + ptt == CPP_STRING || + ptt == CPP_NUMBER) + add_space (r); + + r += tl; + break; + } + case CPP_SCOPE: + { + // Add space except for a few common cases. + // + if (ptt != CPP_NAME && + ptt != CPP_OPEN_PAREN && + ptt != CPP_OPEN_SQUARE) + add_space (r); + + r += cxx_lexer::token_spelling[tt]; + break; + } + case CPP_NAME: + { + // Start of a name. + // + if (ptt == CPP_NAME || + ptt == CPP_KEYWORD || + ptt == CPP_STRING || + ptt == CPP_NUMBER) + add_space (r); + + r += tl; + break; + } + case CPP_QUERY: + { + if (ptt == CPP_OPEN_PAREN) + { + // Get the next token and see if it is ')'. + // + ptt = tt; + tt = l.next (tl); + + if (tt == CPP_CLOSE_PAREN) + r += val; + else + { + add_space (r); + r += "? "; + } + continue; // We have already gotten the next token. + } + } + // Fall through. + default: + { + // Handle CPP_KEYWORD here to avoid a warning (it is not + // part of the cpp_ttype enumeration). + // + if (tt == CPP_KEYWORD) + { + if (ptt == CPP_NAME || + ptt == CPP_KEYWORD || + ptt == CPP_STRING || + ptt == CPP_NUMBER) + add_space (r); + + r += tl; + } + else + { + // All the other operators. + // + add_space (r); + r += cxx_lexer::token_spelling[tt]; + r += ' '; + } + break; + } + } + + // + // Watch out for the continue statements above if you add any + // logic here. + // + + ptt = tt; + tt = l.next (tl); + } + + return r; +} + + +// +// view_object +// + +string view_object:: +name () const +{ + if (!alias.empty ()) + return alias; + + return kind == object ? context::class_name (*obj) : tbl_name.string (); +} + +// +// member_access +// + +bool member_access:: +placeholder () const +{ + for (cxx_tokens::const_iterator i (expr.begin ()), e (expr.end ()); i != e;) + { + if (i->type == CPP_OPEN_PAREN) + { + if (++i != e && i->type == CPP_QUERY) + { + if (++i != e && i->type == CPP_CLOSE_PAREN) + return true; + } + } + else + ++i; + } + + return false; +} + +string member_access:: +translate (string const& obj, string const& val, string const& db) const +{ + if (empty ()) + { + error (loc) << "non-empty " << kind << " expression required" << endl; + throw operation_failed (); + } + + // This code is similar to translate_expression() from relations/source.cxx. + // + string r; + + cxx_tokens_lexer l; + l.start (expr); + + string tl; + for (cpp_ttype tt (l.next (tl)), ptt (CPP_EOF); tt != CPP_EOF;) + { + // Try to format the expression to resemble the style of the + // generated code. + // + switch (tt) + { + case CPP_COMMA: + { + r += ", "; + break; + } + case CPP_OPEN_PAREN: + { + if (ptt == CPP_NAME || + ptt == CPP_KEYWORD) + add_space (r); + + r += '('; + break; + } + case CPP_CLOSE_PAREN: + { + r += ')'; + break; + } + case CPP_OPEN_SQUARE: + { + r += '['; + break; + } + case CPP_CLOSE_SQUARE: + { + r += ']'; + break; + } + case CPP_OPEN_BRACE: + { + add_space (r); + r += "{ "; + break; + } + case CPP_CLOSE_BRACE: + { + add_space (r); + r += '}'; + break; + } + case CPP_SEMICOLON: + { + r += ';'; + break; + } + case CPP_ELLIPSIS: + { + add_space (r); + r += "..."; + break; + } + case CPP_PLUS: + case CPP_MINUS: + { + bool unary (ptt != CPP_NAME && + ptt != CPP_SCOPE && + ptt != CPP_NUMBER && + ptt != CPP_STRING && + ptt != CPP_CLOSE_PAREN && + ptt != CPP_PLUS_PLUS && + ptt != CPP_MINUS_MINUS); + + if (!unary) + add_space (r); + + r += cxx_lexer::token_spelling[tt]; + + if (!unary) + r += ' '; + break; + } + case CPP_PLUS_PLUS: + case CPP_MINUS_MINUS: + { + if (ptt != CPP_NAME && + ptt != CPP_CLOSE_PAREN && + ptt != CPP_CLOSE_SQUARE) + add_space (r); + + r += cxx_lexer::token_spelling[tt]; + break; + } + case CPP_DEREF: + case CPP_DEREF_STAR: + case CPP_DOT: + case CPP_DOT_STAR: + { + r += cxx_lexer::token_spelling[tt]; + break; + } + case CPP_STRING: + { + if (ptt == CPP_NAME || + ptt == CPP_KEYWORD || + ptt == CPP_STRING || + ptt == CPP_NUMBER) + add_space (r); + + r += context::strlit (tl); + break; + } + case CPP_NUMBER: + { + if (ptt == CPP_NAME || + ptt == CPP_KEYWORD || + ptt == CPP_STRING || + ptt == CPP_NUMBER) + add_space (r); + + r += tl; + break; + } + case CPP_SCOPE: + { + // Add space except for a few common cases. + // + if (ptt != CPP_NAME && + ptt != CPP_OPEN_PAREN && + ptt != CPP_OPEN_SQUARE) + add_space (r); + + r += cxx_lexer::token_spelling[tt]; + break; + } + case CPP_NAME: + { + // Start of a name. + // + if (ptt == CPP_NAME || + ptt == CPP_KEYWORD || + ptt == CPP_STRING || + ptt == CPP_NUMBER) + add_space (r); + + r += tl; + break; + } + case CPP_NOT: + case CPP_QUERY: + { + if (ptt == CPP_OPEN_PAREN) + { + // Get the next token and see if it is ')'. + // + ptt = tt; + tt = l.next (tl); + + if (tt == CPP_CLOSE_PAREN) + { + if (ptt == CPP_NOT) + { + if (db.empty ()) + { + error (loc) << "database instance (!) not available in this " + << "context" << endl; + throw operation_failed (); + } + + r += db; + } + else + r += val; + } + else + { + add_space (r); + r += (ptt == CPP_NOT ? "!" : "? "); + } + continue; // We have already gotten the next token. + } + } + // Fall through. + default: + { + // Handle CPP_KEYWORD here to avoid a warning (it is not + // part of the cpp_ttype enumeration). + // + if (tt == CPP_KEYWORD) + { + if (ptt == CPP_NAME || + ptt == CPP_KEYWORD || + ptt == CPP_STRING || + ptt == CPP_NUMBER) + add_space (r); + + // Translate 'this'. + // + r += (tl == "this" ? obj : tl); + } + else + { + // All the other operators. + // + add_space (r); + r += cxx_lexer::token_spelling[tt]; + r += ' '; + } + break; + } + } + + // + // Watch out for the continue statements above if you add any + // logic here. + // + + ptt = tt; + tt = l.next (tl); + } + + return r; +} + +// Sections. +// +main_section_type main_section; + +bool main_section_type:: +compare (object_section const& s) const +{ + main_section_type const* ms (dynamic_cast<main_section_type const*> (&s)); + return ms != 0 && *this == *ms; +} + +bool user_section:: +compare (object_section const& s) const +{ + user_section const* us (dynamic_cast<user_section const*> (&s)); + return us != 0 && *this == *us; +} + +user_section* user_section:: +total_base () const +{ + if (base != 0) + { + semantics::class_* poly_root (context::polymorphic (*object)); + if (poly_root != 0 && poly_root != *object) + return base; + } + + return 0; +} + +size_t user_sections:: +count (unsigned short f) const +{ + size_t r (0); + + semantics::class_* poly_root (context::polymorphic (*object)); + bool poly_derived (poly_root != 0 && poly_root != object); + + if (poly_derived && (f & count_total) != 0) + r = context::polymorphic_base (*object).get<user_sections> ( + "user-sections").count (f); + + for (const_iterator i (begin ()); i != end (); ++i) + { + // Skip special sections unless we were explicitly asked to count them. + // + if (i->special == user_section::special_version && + (f & count_special_version) == 0) + continue; + + // Skip non-versioned sections if we are only interested in the + // versioned ones. + // + if ((f & count_versioned_only) != 0 && + !context::added (*i->member) && !context::deleted (*i->member)) + continue; + + bool ovd (i->base != 0 && poly_derived); + + if (i->load != user_section::load_eager) + { + if (i->load_empty ()) + { + if ((f & count_load_empty) != 0) + { + if (ovd) + { + if ((f & count_override) != 0) + { + r++; + continue; // Count each section only once. + } + } + else + { + if ((f & count_new) != 0 || (f & count_total) != 0) + { + r++; + continue; + } + } + } + } + else + { + if ((f & count_load) != 0) + { + if (ovd) + { + if ((f & count_override) != 0) + { + r++; + continue; + } + } + else + { + if ((f & count_new) != 0 || (f & count_total) != 0) + { + r++; + continue; + } + } + } + } + } + + if (i->update_empty ()) + { + if ((f & count_update_empty) != 0) + { + if (ovd) + { + if ((f & count_override) != 0) + { + r++; + continue; + } + } + else + { + if ((f & count_new) != 0 || (f & count_total) != 0) + { + r++; + continue; + } + } + } + } + else + { + if ((f & count_update) != 0) + { + if (ovd) + { + if ((f & count_override) != 0) + { + r++; + continue; + } + } + else + { + if ((f & count_new) != 0 || (f & count_total) != 0) + { + r++; + continue; + } + } + } + } + + if (i->optimistic ()) + { + if ((f & count_optimistic) != 0) + { + if (ovd) + { + if ((f & count_override) != 0) + { + r++; + continue; + } + } + else + { + if ((f & count_new) != 0 || (f & count_total) != 0) + { + r++; + continue; + } + } + } + } + } + + return r; +} + +// +// context +// + +namespace +{ + char const* keywords[] = + { + "NULL", + "and", + "asm", + "auto", + "bitand", + "bitor", + "bool", + "break", + "case", + "catch", + "char", + "class", + "compl", + "const", + "const_cast", + "continue", + "default", + "delete", + "do", + "double", + "dynamic_cast", + "else", + "end_eq", + "enum", + "explicit", + "export", + "extern", + "false", + "float", + "for", + "friend", + "goto", + "if", + "inline", + "int", + "long", + "mutable", + "namespace", + "new", + "not", + "not_eq", + "operator", + "or", + "or_eq", + "private", + "protected", + "public", + "register", + "reinterpret_cast", + "return", + "short", + "signed", + "sizeof", + "static", + "static_cast", + "struct", + "switch", + "template", + "this", + "throw", + "true", + "try", + "typedef", + "typeid", + "typename", + "union", + "unsigned", + "using", + "virtual", + "void", + "volatile", + "wchar_t", + "while", + "xor", + "xor_eq" + }; +} + +unique_ptr<context> +create_context (ostream& os, + semantics::unit& unit, + options const& ops, + features& f, + semantics::relational::model* m) +{ + unique_ptr<context> r; + + switch (ops.database ()[0]) + { + case database::common: + { + r.reset (new context (os, unit, ops, f)); + break; + } + case database::mssql: + { + r.reset (new relational::mssql::context (os, unit, ops, f, m)); + break; + } + case database::mysql: + { + r.reset (new relational::mysql::context (os, unit, ops, f, m)); + break; + } + case database::oracle: + { + r.reset (new relational::oracle::context (os, unit, ops, f, m)); + break; + } + case database::pgsql: + { + r.reset (new relational::pgsql::context (os, unit, ops, f, m)); + break; + } + case database::sqlite: + { + r.reset (new relational::sqlite::context (os, unit, ops, f, m)); + break; + } + } + + return r; +} + +context:: +~context () +{ + if (current_ == this) + current_ = 0; +} + +context:: +context (ostream& os_, + semantics::unit& u, + options_type const& ops, + 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), + features (f), + db (options.database ()[0]), + in_comment (data_->in_comment_), + exp (data_->exp_), + ext (data_->ext_), + keyword_set (data_->keyword_set_), + include_regex (data_->include_regex_), + accessor_regex (data_->accessor_regex_), + modifier_regex (data_->modifier_regex_), + embedded_schema ( + ops.generate_schema () && + ops.schema_format ()[db].count (schema_format::embedded)), + separate_schema ( + ops.generate_schema () && + ops.schema_format ()[db].count (schema_format::separate)), + multi_static (ops.multi_database () == multi_database::static_), + multi_dynamic (ops.multi_database () == multi_database::dynamic), + force_versioned (false), + top_object (data_->top_object_), + cur_object (data_->cur_object_) +{ + assert (current_ == 0); + current_ = this; + + // Write boolean values as true/false. + // + os.setf (ios_base::boolalpha); + + // Export control. + // + if (!ops.export_symbol ()[db].empty ()) + exp = ops.export_symbol ()[db] + " "; + + ext = ops.extern_symbol ()[db]; + + for (size_t i (0); i < sizeof (keywords) / sizeof (char*); ++i) + data_->keyword_set_.insert (keywords[i]); + + // SQL name regex. + // + if (ops.table_regex ().count (db) != 0) + { + strings const& s (ops.table_regex ()[db]); + data_->sql_name_regex_[sql_name_table].assign (s.begin (), s.end ()); + } + + if (ops.column_regex ().count (db) != 0) + { + strings const& s (ops.column_regex ()[db]); + data_->sql_name_regex_[sql_name_column].assign (s.begin (), s.end ()); + } + + if (ops.index_regex ().count (db) != 0) + { + strings const& s (ops.index_regex ()[db]); + data_->sql_name_regex_[sql_name_index].assign (s.begin (), s.end ()); + } + + if (ops.fkey_regex ().count (db) != 0) + { + strings const& s (ops.fkey_regex ()[db]); + data_->sql_name_regex_[sql_name_fkey].assign (s.begin (), s.end ()); + } + + if (ops.sequence_regex ().count (db) != 0) + { + strings const& s (ops.sequence_regex ()[db]); + data_->sql_name_regex_[sql_name_sequence].assign (s.begin (), s.end ()); + } + + if (ops.statement_regex ().count (db) != 0) + { + strings const& s (ops.statement_regex ()[db]); + data_->sql_name_regex_[sql_name_statement].assign (s.begin (), s.end ()); + } + + if (ops.sql_name_regex ().count (db) != 0) + { + strings const& s (ops.sql_name_regex ()[db]); + data_->sql_name_regex_[sql_name_all].assign (s.begin (), s.end ()); + } + + // Include regex. + // + for (strings::const_iterator i (ops.include_regex ().begin ()); + i != ops.include_regex ().end (); ++i) + data_->include_regex_.push_back (regexsub (*i)); + + // Common accessor/modifier naming variants. Try the user-supplied and + // more specific ones first. + // + for (strings::const_iterator i (ops.accessor_regex ().begin ()); + i != ops.accessor_regex ().end (); ++i) + data_->accessor_regex_.push_back (regexsub (*i)); + + data_->accessor_regex_.push_back (regexsub ("/(.+)/get_$1/")); // get_foo + data_->accessor_regex_.push_back (regexsub ("/(.+)/get\\u$1/")); // getFoo + data_->accessor_regex_.push_back (regexsub ("/(.+)/get$1/")); // getfoo + data_->accessor_regex_.push_back (regexsub ("/(.+)/$1/")); // foo + + for (strings::const_iterator i (ops.modifier_regex ().begin ()); + i != ops.modifier_regex ().end (); ++i) + data_->modifier_regex_.push_back (regexsub (*i)); + + data_->modifier_regex_.push_back (regexsub ("/(.+)/set_$1/")); // set_foo + data_->modifier_regex_.push_back (regexsub ("/(.+)/set\\u$1/")); // setFoo + data_->modifier_regex_.push_back (regexsub ("/(.+)/set$1/")); // setfoo + data_->modifier_regex_.push_back (regexsub ("/(.+)/$1/")); // foo +} + +context:: +context () + : data_ (current ().data_), + extra (current ().extra), + os (current ().os), + unit (current ().unit), + options (current ().options), + features (current ().features), + db (current ().db), + in_comment (current ().in_comment), + exp (current ().exp), + ext (current ().ext), + keyword_set (current ().keyword_set), + include_regex (current ().include_regex), + accessor_regex (current ().accessor_regex), + modifier_regex (current ().modifier_regex), + embedded_schema (current ().embedded_schema), + separate_schema (current ().separate_schema), + multi_static (current ().multi_static), + multi_dynamic (current ().multi_dynamic), + force_versioned (current ().force_versioned), + top_object (current ().top_object), + cur_object (current ().cur_object) +{ +} + +context* context::current_; + +semantics::data_member* context:: +id (data_member_path const& mp) +{ + semantics::data_member* idf (mp.front ()); + + if (!id (*idf)) + return 0; + + // This is for special ids, such as polymorphic-ref, which + // don't have "id-member" set (and we want to keep it that + // way since it is not really a full-fledged id). + // + if (idf->get<string> ("id").empty ()) // Not a nested id. + return idf; + + const data_member_path& id ( + *id_member ( + dynamic_cast<semantics::class_&> (idf->scope ()))); + + // Now we need to make sure id is a prefix of mp; + // + return mp.sub (id) ? idf : 0; +} + +semantics::data_member* context:: +object_pointer (data_member_path const& mp) +{ + for (data_member_path::const_reverse_iterator i (mp.rbegin ()); + i != mp.rend (); ++i) + { + if (object_pointer (utype (**i))) + return *i; + } + + return 0; +} + +bool context:: +readonly (data_member_path const& mp, data_member_scope const& ms) +{ + assert (mp.size () == ms.size ()); + + data_member_scope::const_reverse_iterator si (ms.rbegin ()); + + for (data_member_path::const_reverse_iterator pi (mp.rbegin ()); + pi != mp.rend (); + ++pi, ++si) + { + semantics::data_member& m (**pi); + + if (m.count ("readonly")) + return true; + + // Check if any of the classes in the inheritance chain for the + // class containing this member are readonly. + // + class_inheritance_chain const& ic (*si); + + assert (ic.back () == &m.scope ()); + + for (class_inheritance_chain::const_reverse_iterator ci (ic.rbegin ()); + ci != ic.rend (); + ++ci) + { + semantics::class_& c (**ci); + + if (c.count ("readonly")) + return true; + } + } + + return false; +} + +bool context:: +readonly (semantics::data_member& m) +{ + if (m.count ("readonly")) + return true; + + // Check if the whole class (object or composite value) is marked + // as readonly. + // + if (m.scope ().count ("readonly")) + return true; + + return false; +} + +bool context:: +null (data_member_path const& mp) const +{ + // Outer members can override the null-ability of the inner ones. So + // start from the most outer member. + // + for (data_member_path::const_iterator i (mp.begin ()); i != mp.end (); ++i) + { + if (null (**i)) + return true; + } + + return false; +} + +bool context:: +null (semantics::data_member& m) const +{ + semantics::names* hint; + semantics::type& t (utype (m, hint)); + + if (object_pointer (t)) + { + // By default pointers can be null. + // + if (m.count ("null")) + return true; + + if (!m.count ("not-null")) + { + if (t.count ("null")) + return true; + + if (!t.count ("not-null")) + return true; + } + + return false; + } + else + { + // Everything else by default is not null. + // + if (m.count ("null")) + return true; + + if (!m.count ("not-null")) + { + if (t.count ("null")) + return true; + + if (!t.count ("not-null")) + { + semantics::type* pt; + + // Check if this type is a wrapper. + // + if (t.get<bool> ("wrapper")) + { + // First see if it is null by default. + // + if (t.get<bool> ("wrapper-null-handler") && + t.get<bool> ("wrapper-null-default")) + return true; + + // Otherwise, check the wrapped type. + // + pt = t.get<semantics::type*> ("wrapper-type"); + hint = t.get<semantics::names*> ("wrapper-hint"); + pt = &utype (*pt, hint); + + if (pt->count ("null")) + return true; + + if (pt->count ("not-null")) + return false; + } + else + pt = &t; + } + } + + return false; + } +} + +bool context:: +null (semantics::data_member& m, string const& kp) const +{ + if (kp.empty ()) + return null (m); + + semantics::type& c (utype (m)); + semantics::type& t (utype (m, kp)); + + if (object_pointer (t)) + { + if (m.count (kp + "-null")) + return true; + + if (!m.count (kp + "-not-null")) + { + if (c.count (kp + "-null")) + return true; + + if (!c.count (kp + "-not-null")) + { + if (t.count ("null")) + return true; + + if (!t.count ("not-null")) + { + return true; + } + } + } + + return false; + } + else + { + if (m.count (kp + "-null")) + return true; + + if (!m.count (kp + "-not-null")) + { + if (c.count (kp + "-null")) + return true; + + if (!c.count (kp + "-not-null")) + { + if (t.count ("null")) + return true; + + if (!t.count ("not-null")) + { + semantics::type* pt; + + // Check if this type is a wrapper. + // + if (t.get<bool> ("wrapper")) + { + // First see if it is null by default. + // + if (t.get<bool> ("wrapper-null-handler") && + t.get<bool> ("wrapper-null-default")) + return true; + + // Otherwise, check the wrapped type. + // + pt = t.get<semantics::type*> ("wrapper-type"); + pt = &utype (*pt); + + if (pt->count ("null")) + return true; + + if (pt->count ("not-null")) + return false; + } + else + pt = &t; + } + } + } + + return false; + } +} + +size_t context:: +polymorphic_depth (semantics::class_& c) +{ + if (c.count ("polymorphic-depth")) + return c.get<size_t> ("polymorphic-depth"); + + // Calculate our hierarchy depth (number of classes). + // + using semantics::class_; + + class_* root (polymorphic (c)); + assert (root != 0); + + size_t r (1); // One for the root. + + for (class_* b (&c); b != root; b = &polymorphic_base (*b)) + ++r; + + c.set ("polymorphic-depth", r); + return r; +} + +context::class_kind_type context:: +class_kind (semantics::class_& c) +{ + if (object (c)) + return class_object; + else if (view (c)) + return class_view; + else if (composite (c)) + return class_composite; + else + return class_other; +} + +string context:: +class_name (semantics::class_& c) +{ + return c.is_a<semantics::class_instantiation> () + ? c.get<semantics::names*> ("tree-hint")->name () + : c.name (); +} + +string context:: +class_fq_name (semantics::class_& c) +{ + return c.is_a<semantics::class_instantiation> () + ? c.fq_name (c.get<semantics::names*> ("tree-hint")) + : c.fq_name (); +} + +semantics::scope& context:: +class_scope (semantics::class_& c) +{ + return c.is_a<semantics::class_instantiation> () + ? c.get<semantics::names*> ("tree-hint")->scope () + : c.scope (); +} + +semantics::path context:: +class_file (semantics::class_& c) +{ + // If we have an explicit definition location, use that. + // + if (c.count ("definition")) + return semantics::path (LOCATION_FILE (c.get<location_t> ("definition"))); + // + // Otherwise, if it is a template instantiation, use the location + // of the qualifier. + // + else if (c.is_a<semantics::class_instantiation> ()) + return semantics::path (LOCATION_FILE (c.get<location_t> ("location"))); + else + return c.file (); +} + +location_t context:: +class_location (semantics::class_& c) +{ + return c.count ("definition") + ? c.get<location_t> ("definition") + : class_real_location (c); +} + +location_t context:: +class_real_location (semantics::class_& c) +{ + return c.is_a<semantics::class_instantiation> () + ? c.get<location_t> ("location") + : real_source_location (TYPE_NAME (c.tree_node ())); +} + +string context:: +upcase (string const& s) +{ + string r; + string::size_type n (s.size ()); + + r.reserve (n); + + for (string::size_type i (0); i < n; ++i) + r.push_back (toupper (s[i])); + + return r; +} + +void context:: +diverge (streambuf* sb) +{ + data_->os_stack_.push (data_->os_.rdbuf ()); + data_->os_.rdbuf (sb); +} + +void context:: +restore () +{ + data_->os_.rdbuf (data_->os_stack_.top ()); + data_->os_stack_.pop (); +} + +semantics::type& context:: +utype (semantics::type& t) +{ + if (semantics::qualifier* q = dynamic_cast<semantics::qualifier*> (&t)) + return q->base_type (); + else + return t; +} + +semantics::type& context:: +utype (semantics::type& t, semantics::names*& hint) +{ + if (semantics::qualifier* q = dynamic_cast<semantics::qualifier*> (&t)) + { + hint = q->qualifies ().hint (); + return q->base_type (); + } + else + return t; +} + +semantics::type& context:: +utype (semantics::data_member& m, + semantics::names*& hint, + string const& kp, + const custom_cxx_type** translation) +{ + semantics::type* t (0); + + if (kp.empty ()) + { + t = &m.type (); + + if (semantics::qualifier* q = dynamic_cast<semantics::qualifier*> (t)) + { + hint = q->qualifies ().hint (); + t = &q->base_type (); + } + else + hint = m.belongs ().hint (); + } + else + { + if (m.count (kp + "-tree-type")) + t = indirect_type (m, kp, hint); + else + { + t = &utype (m); + + // "See through" wrappers. + // + if (semantics::type* wt = wrapper (*t)) + t = indirect_type (utype (*wt), kp, hint); + else + t = indirect_type (*t, kp, hint); + } + } + + // Do we need to map this type? + // + // @@ Need to cache the result on the member. + // + if (translation != 0) + *translation = 0; + + for (semantics::scope* s (&m.scope ());; s = &s->scope_ ()) + { + using semantics::namespace_; + + if (namespace_* ns = dynamic_cast<namespace_*> (s)) + { + if (ns->extension ()) + s = &ns->original (); + } + + if (s->count ("custom-cxx-type-map")) + { + typedef custom_cxx_type_map map; + + map& m (s->get<map> ("custom-cxx-type-map")); + map::const_iterator i (m.find (t)); + + if (i != m.end ()) + { + hint = i->second->as_hint; + t = i->second->as; + + if (translation != 0) + *translation = i->second; + + // Currently we only support one level of mapping, but I am + // sure someone will want multiple levels. + // + break; + } + } + + if (!s->named_p () || s->global_scope ()) + break; + } + + return *t; +} + +bool context:: +const_type (semantics::type& t) +{ + if (semantics::qualifier* q = dynamic_cast<semantics::qualifier*> (&t)) + return q->const_ (); + + return false; +} + +string context:: +type_ref_type (semantics::type& t, + semantics::names* hint, + bool mc, + string const& var, + bool decay) +{ + using semantics::array; + string r; + + // Note that trailing const syntax is used for a reason (consider + // t == const foo*). We may also have to decay then top-level array. + // + array* a; + if (decay && (a = dynamic_cast<array*> (&utype (t))) != 0) + { + semantics::type& bt (a->base_type ()); + hint = a->contains ().hint (); + + if (bt.is_a<array> ()) + { + // If we need to add/strip const or no name was used in the + // declaration, then create an array declaration (e.g., for + // char x[2][3] we will have char const (*x)[3]). + // + if (mc != const_type (t) || hint == 0) + return type_val_type (bt, 0, mc, "(*" + var + ")"); + } + + // Array base type is always cvr-unqualified. + // + if (mc) + r = bt.fq_name (hint) + " const"; + else + r = bt.fq_name (hint); + + r += '*'; + + if (!var.empty ()) + r += ' ' + var; + } + else + { + if (mc == const_type (t)) + r = t.fq_name (hint); + else if (mc) + r = t.fq_name (hint) + " const"; + else + { + semantics::type& ut (utype (t, hint)); + r = ut.fq_name (hint); + } + + r += '&'; + + if (!var.empty ()) + r += ' ' + var; + } + + return r; +} + +string context:: +type_val_type (semantics::type& t, + semantics::names* hint, + bool mc, + string const& var) +{ + using semantics::array; + string r; + + // Arrays are a complicated case. Firstly, we may need to add/strip const + // to/from the base type. Secondly, the array dimensions are written after + // the variable name. All this is further complicated by multiple dimensions. + // Thanks, Dennis! + // + if (array* a = dynamic_cast<array*> (&utype (t))) + { + semantics::type& bt (a->base_type ()); + + // If we don't need to add/strip const and a name was used in the + // declaration, then use that name. + // + if (mc == const_type (t) && hint != 0) + { + r = t.fq_name (hint); + + if (!var.empty ()) + r += ' ' + var; + } + else + { + // Otherwise, construct the array declaration. + // + string v (var); + v += '['; + ostringstream ostr; + ostr << a->size (); + v += ostr.str (); + + if (a->size () > 0xFFFFFFFF) + v += "ULL"; + else if (a->size () > 2147483647) + v += "U"; + + v += ']'; + + r = type_val_type (bt, a->contains ().hint (), mc, v); + } + } + else + { + if (mc == const_type (t)) + r = t.fq_name (hint); + else if (mc) + r = t.fq_name (hint) + " const"; + else + { + semantics::type& ut (utype (t, hint)); + r = ut.fq_name (hint); + } + + if (!var.empty ()) + r += ' ' + var; + } + + return r; +} + +void context:: +set_member (semantics::data_member& m, + const string& obj, + const string& val, + const string& db, + const string& type) +{ + member_access& ma (m.get<member_access> ("set")); + + // If this is a custom expression, output the location of where + // it came from. + // + if (!ma.synthesized) + os << "// From " << location_string (ma.loc, true) << endl; + + if (ma.placeholder ()) + { + // Cast the database to the concrete type this code is generated + // for. This way the user is free to use either the concrete or + // the common. + // + string d; + if (!db.empty ()) + d = "static_cast<" + context::db.string () + "::database&> (" + db + ")"; + + os << ma.translate (obj, val, d) << ";"; + } + else + { + // If this member is const and we have a synthesized direct access, + // then cast away constness. Otherwise, we assume that the user- + // provided expression handles this. + // + bool cast (!type.empty () && ma.direct () && const_member (m)); + if (cast) + os << "const_cast< " << type << "& > (" << endl; + + os << ma.translate (obj); + + if (cast) + os << ")"; + + os << " = " << val << ";"; + } +} + +void context:: +inc_member (semantics::data_member& m, + const string& obj, + const string& gobj, + const string& type) +{ + member_access& ma (m.get<member_access> ("set")); + + // If this is a custom expression, output the location of where + // it came from. + // + if (!ma.synthesized) + os << "// From " << location_string (ma.loc, true) << endl; + + if (ma.placeholder ()) + { + member_access& gma (m.get<member_access> ("get")); + + if (!gma.synthesized) + os << "// From " << location_string (gma.loc, true) << endl; + + os << ma.translate (obj, gma.translate (gobj) + " + 1") << ";"; + } + else + { + // If this member is const and we have a synthesized direct access, + // then cast away constness. Otherwise, we assume that the user- + // provided expression handles this. + // + os << "++"; + + bool cast (ma.direct () && const_member (m)); + if (cast) + os << "const_cast< " << type << "& > (" << endl; + + os << ma.translate (obj); + + if (cast) + os << ")"; + + os << ";"; + } +} + +void context:: +resolve_data_members (data_member_path& r, + semantics::class_& c, + const string& name, + const location& l, + cxx_string_lexer& lex) +{ + using semantics::class_; + using semantics::data_member; + + // The name was already verified to be syntactically correct so + // we don't need to do any extra error checking in this area. + // + lex.start (name); + + try + { + string tl; + cpp_ttype tt (lex.next (tl)); + + data_member& m (c.lookup<data_member> (tl, class_::include_hidden)); + + r.push_back (&m); + + if (container (m)) + return; + + // Resolve nested members if any. + // + for (tt = lex.next (tl); tt == CPP_DOT; tt = lex.next (tl)) + { + lex.next (tl); // Get CPP_NAME. + + data_member& om (*r.back ()); + + // Check that the outer member is composite and also unwrap it while + // at it. + // + class_* comp (composite_wrapper (utype (om))); + if (comp == 0) + { + error (l) << "data member '" << om.name () << "' is not composite" + << endl; + throw operation_failed (); + } + + data_member& nm ( + comp->lookup<data_member> (tl, class_::include_hidden)); + + r.push_back (&nm); + + if (container (nm)) + return; + } + } + catch (semantics::unresolved const& e) + { + if (e.type_mismatch) + error (l) << "name '" << e.name << "' does not refer to a data member" + << endl; + else + error (l) << "unable to resolve data member '" << e.name << endl; + + throw operation_failed (); + } + catch (semantics::ambiguous const& e) + { + error (l) << "data member name '" << e.first.name () << "' is ambiguous" + << endl; + + info (e.first.named ().location ()) << "could resolve to " << + "this data member" << endl; + + info (e.second.named ().location ()) << "or could resolve " << + "to this data member" << endl; + + throw operation_failed (); + } +} + +bool context:: +composite_ (semantics::class_& c) +{ + bool r (c.count ("value") && !c.count ("simple") && !c.count ("container")); + c.set ("composite-value", r); + return r; +} + +// context::table_prefix +// +context::table_prefix:: +table_prefix (semantics::class_& c) + : level (1) +{ + context& ctx (context::current ()); + + ns_schema = ctx.schema (class_scope (c)); + ns_prefix = ctx.table_name_prefix (class_scope (c)); + prefix = ctx.table_name (c, &derived); + prefix += "_"; +} + +void context::table_prefix:: +append (semantics::data_member& m) +{ + assert (level > 0); + + context& ctx (context::current ()); + + // If a custom table prefix was specified, then ignore the top-level + // table prefix (this corresponds to a container directly inside an + // object) but keep the schema unless the alternative schema is fully + // qualified. + // + if (m.count ("table")) + { + qname p, n (m.get<qname> ("table")); + + if (n.fully_qualified ()) + p = n.qualifier (); + else + { + if (n.qualified ()) + { + p = ns_schema; + p.append (n.qualifier ()); + } + else + p = prefix.qualifier (); + } + + if (level == 1) + { + p.append (ns_prefix); + derived = false; + } + else + p.append (prefix.uname ()); + + p += n.uname (); + prefix.swap (p); + } + // Otherwise use the member name and add an underscore unless it is + // already there. + // + else + { + string name (ctx.public_name_db (m)); + size_t n (name.size ()); + + prefix += name; + + if (n != 0 && name[n - 1] != '_') + prefix += "_"; + + derived = true; + } + + level++; +} + +qname context:: +schema (semantics::scope& s) const +{ + if (s.count ("qualified-schema")) + return s.get<qname> ("qualified-schema"); + + qname r; + + for (semantics::scope* ps (&s);; ps = &ps->scope_ ()) + { + using semantics::namespace_; + + namespace_* ns (dynamic_cast<namespace_*> (ps)); + + if (ns == 0) // Some other scope. + { + if (!ps->named_p ()) + break; + + continue; + } + + if (ns->extension ()) + ns = &ns->original (); + + bool sf (ns->count ("schema")); + bool tf (ns->count ("table")); + + if (tf) + { + qname n (ns->get<qname> ("table")); + tf = n.qualified (); + + // If we have both schema and qualified table prefix, see which + // takes precedence based on order. + // + + if (tf && sf) + { + if (ns->get<location_t> ("table-location") > + ns->get<location_t> ("schema-location")) + sf = false; + else + tf = false; + } + } + + if (sf || tf) + { + qname n ( + sf + ? ns->get<qname> ("schema") + : ns->get<qname> ("table").qualifier ()); + n.append (r); + n.swap (r); + } + + if (r.fully_qualified () || + ns->global_scope ()) // Note: namespaces always named. + break; + } + + // If we are still not fully qualified, add the schema that was + // specified on the command line. + // + if (!r.fully_qualified () && options.schema ().count (db) != 0) + { + qname n (options.schema ()[db]); + n.append (r); + n.swap (r); + } + + s.set ("qualified-schema", r); + return r; +} + +string context:: +table_name_prefix (semantics::scope& s) const +{ + if (s.count ("table-prefix")) + return s.get<string> ("table-prefix"); + + string r; + + for (semantics::scope* ps (&s);; ps = &ps->scope_ ()) + { + using semantics::namespace_; + + namespace_* ns (dynamic_cast<namespace_*> (ps)); + + if (ns == 0) // Some other scope. + { + if (!ps->named_p ()) + break; + + continue; + } + + if (ns->extension ()) + ns = &ns->original (); + + if (ns->count ("table")) + { + qname n (ns->get<qname> ("table")); + r = n.uname () + r; + } + + if (ns->global_scope ()) // Note: namespaces always named. + break; + } + + // Add the prefix that was specified on the command line. + // + if (options.table_prefix ().count (db) != 0) + r = options.table_prefix ()[db] + r; + + s.set ("table-prefix", r); + return r; +} + +qname context:: +table_name (semantics::class_& c, bool* pd) const +{ + if (c.count ("qualified-table")) + return c.get<qname> ("qualified-table"); + + qname r; + bool sf (c.count ("schema")); + bool derived; + + if (c.count ("table")) + { + r = c.get<qname> ("table"); + + if (sf) + { + // If we have both schema and qualified table, see which takes + // precedence based on order. If the table is unqualifed, then + // add the schema. + // + sf = !r.qualified () || + c.get<location_t> ("table-location") < + c.get<location_t> ("schema-location"); + } + + derived = false; + } + else + { + r = class_name (c); + derived = true; + } + + if (sf) + { + qname n (c.get<qname> ("schema")); + n.append (r.uname ()); + n.swap (r); + } + + // Unless we are fully qualified, add any schemas that were + // specified on the namespaces and/or with the command line + // option. + // + if (!r.fully_qualified ()) + { + qname n (schema (class_scope (c))); + n.append (r); + n.swap (r); + } + + // Add the table prefix if any. + // + r.uname () = table_name_prefix (class_scope (c)) + r.uname (); + + if (derived) + r.uname () = transform_name (r.uname (), sql_name_table); + + c.set ("qualified-table", r); + + if (pd != 0) + *pd = derived; + + return r; +} + +qname context:: +table_name (semantics::class_& obj, data_member_path const& mp) const +{ + table_prefix tp (obj); + + if (mp.size () == 1) + { + // Container directly in the object. + // + return table_name (*mp.back (), tp); + } + else + { + data_member_path::const_iterator i (mp.begin ()); + + // The last member is the container. + // + for (data_member_path::const_iterator e (mp.end () - 1); i != e; ++i) + tp.append (**i); + + return table_name (**i, tp); + } +} + +// The table prefix passed as the second argument must include the table +// prefix specified on namespaces and with the --table-prefix option. +// +qname context:: +table_name (semantics::data_member& m, table_prefix const& p) const +{ + assert (p.level > 0); + qname r; + string rn; + bool derived; // Any of the components in the table name is derived. + + // If a custom table name was specified, then ignore the top-level + // table prefix (this corresponds to a container directly inside an + // object). If the container table is unqualifed, then we use the + // object schema. If it is fully qualified, then we use that name. + // Finally, if it is qualified but not fully qualifed, then we + // append the object's namespace schema. + // + if (m.count ("table")) + { + qname n (m.get<qname> ("table")); + + if (n.fully_qualified ()) + r = n.qualifier (); + else + { + if (n.qualified ()) + { + r = p.ns_schema; + r.append (n.qualifier ()); + } + else + r = p.prefix.qualifier (); + } + + if (p.level == 1) + { + rn = p.ns_prefix; + derived = false; + } + else + { + rn = p.prefix.uname (); + derived = p.derived; + } + + rn += n.uname (); + } + else + { + r = p.prefix.qualifier (); + rn = p.prefix.uname () + public_name_db (m); + derived = true; + } + + if (derived) + r.append (transform_name (rn, sql_name_table)); + else + r.append (rn); + + return r; +} + +string context:: +table_options (semantics::class_& c) +{ + string r; + + // Accumulate options from class. + // + // @@ Should we also get them from bases? + // + // @@ Note for some databases (like SQLite), options are seperated with + // comma, not space. Likely the same issue in the column_options(). + // + if (c.count ("options")) + { + strings const& o (c.get<strings> ("options")); + + for (strings::const_iterator i (o.begin ()); i != o.end (); ++i) + { + if (i->empty ()) + r.clear (); + else + { + if (!r.empty ()) + r += ' '; + + r += *i; + } + } + } + + return r; +} + +string context:: +table_options (semantics::data_member& m, semantics::type& c) +{ + string r; + + // Accumulate options from container and member. + // + // @@ Currently there is no way to assign options to the container type. If + // we use the value specifier, then it ends up being treated as a value + // type. To support this we will probably need to invent the container + // specifier. + // + if (c.count ("options")) + { + strings const& o (c.get<strings> ("options")); + + for (strings::const_iterator i (o.begin ()); i != o.end (); ++i) + { + if (i->empty ()) + r.clear (); + else + { + if (!r.empty ()) + r += ' '; + + r += *i; + } + } + } + + if (m.count ("options")) + { + strings const& o (m.get<strings> ("options")); + + for (strings::const_iterator i (o.begin ()); i != o.end (); ++i) + { + if (i->empty ()) + r.clear (); + else + { + if (!r.empty ()) + r += ' '; + + r += *i; + } + } + } + + return r; +} + +// context::column_prefix +// +context::column_prefix:: +column_prefix (data_member_path const& mp, bool l) + : derived (false), underscore (false) +{ + if (mp.size () < (l ? 1 : 2)) + return; + + for (data_member_path::const_iterator i (mp.begin ()), + e (mp.end () - (l ? 0 : 1)); i != e; ++i) + append (**i); +} + +void context::column_prefix:: +append (semantics::data_member& m, string const& kp, string const& dn) +{ + bool d; + context& ctx (context::current ()); + + if (kp.empty ()) + prefix += ctx.column_name (m, d); + else + prefix += ctx.column_name (m, kp, dn, d); + + // If the user provided the column prefix, then use it verbatime. + // Otherwise, append the underscore, unless it is already there. + // + underscore = false; + if (d) + { + size_t n (prefix.size ()); + + if (n != 0 && prefix[n - 1] != '_') + { + prefix += '_'; + underscore = true; + } + } + + derived = derived || d; +} + +string context:: +column_name (semantics::data_member& m, bool& derived) const +{ + derived = !m.count ("column"); + return derived + ? public_name_db (m) + : m.get<table_column> ("column").column; +} + +string context:: +column_name (semantics::data_member& m, column_prefix const& cp) const +{ + bool d; + const string& cn (column_name (m, d)); + string n (cp.prefix); + + if (cn.empty () && cp.underscore) + n.resize (n.size () - 1); // Strip underscore that was auto added. + + n += cn; + + // If any component is derived, then run it through the SQL name regex. + // + if (d || cp.derived) + n = transform_name (n, sql_name_column); + + return n; +} + +string context:: +column_name (semantics::data_member& m, + string const& p, + string const& d, + bool& derived) const +{ + if (p.empty () && d.empty ()) + return column_name (m, derived); + + // A container column name can be specified for the member or for the + // container type. + // + string key (p + "-column"); + derived = false; + + if (m.count (key)) + return m.get<string> (key); + else + { + semantics::type& t (utype (m)); + + if (t.count (key)) + return t.get<string> (key); + } + + derived = true; + return d; +} + +string context:: +column_name (semantics::data_member& m, + string const& kp, + string const& dn, + column_prefix const& cp) const +{ + bool d; + const string& cn (column_name (m, kp, dn, d)); + string n (cp.prefix); + + if (cn.empty () && cp.underscore) + n.resize (n.size () - 1); // Strip underscore that was auto-added. + + n += cn; + + // If any component is derived, the run it through the SQL name regex. + // + if (d || cp.derived) + n = transform_name (n, sql_name_column); + + return n; +} + +string context:: +column_name (data_member_path const& mp) const +{ + return column_name (*mp.back (), column_prefix (mp)); +} + +string context:: +column_type (const data_member_path& mp, string const& kp, bool id) +{ + if (kp.empty ()) + { + // Return the id type if this member is or is a part of an object id + // or pointer to object. + // + return mp.back ()->get<string> ( + id || context::id (mp) || object_pointer (mp) + ? "column-id-type" + : "column-type"); + } + else + return indirect_value<string> (*mp.back (), kp + "-column-type"); +} + +string context:: +column_type (semantics::data_member& m, string const& kp) +{ + return kp.empty () + ? m.get<string> ("column-type") + : indirect_value<string> (m, kp + "-column-type"); +} + +string context:: +column_options (semantics::data_member& m) +{ + // Accumulate options from both type and member. + // + semantics::type* t (&utype (m)); + + if (semantics::class_* p = object_pointer (*t)) + t = &utype (*id_member (*p)); + + if (semantics::type* w = wrapper (*t)) + t = w; + + string r; + + if (t->count ("options")) + { + strings const& o (t->get<strings> ("options")); + + for (strings::const_iterator i (o.begin ()); i != o.end (); ++i) + { + if (i->empty ()) + r.clear (); + else + { + if (!r.empty ()) + r += ' '; + + r += *i; + } + } + } + + if (m.count ("options")) + { + strings const& o (m.get<strings> ("options")); + + for (strings::const_iterator i (o.begin ()); i != o.end (); ++i) + { + if (i->empty ()) + r.clear (); + else + { + if (!r.empty ()) + r += ' '; + + r += *i; + } + } + } + + return r; +} + +string context:: +column_options (semantics::data_member& m, string const& kp) +{ + if (kp.empty ()) + return column_options (m); + + string k (kp + "-options"); + + // Accumulate options from type, container, and member. + // + semantics::type& c (utype (m)); + semantics::type* t (&utype (m, kp)); + + if (semantics::class_* p = object_pointer (*t)) + t = &utype (*id_member (*p)); + + if (semantics::type* w = wrapper (*t)) + t = w; + + string r; + + if (t->count ("options")) + { + strings const& o (t->get<strings> ("options")); + + for (strings::const_iterator i (o.begin ()); i != o.end (); ++i) + { + if (i->empty ()) + r.clear (); + else + { + if (!r.empty ()) + r += ' '; + + r += *i; + } + } + } + + if (c.count (k)) + { + strings const& o (c.get<strings> (k)); + + for (strings::const_iterator i (o.begin ()); i != o.end (); ++i) + { + if (i->empty ()) + r.clear (); + else + { + if (!r.empty ()) + r += ' '; + + r += *i; + } + } + } + + if (m.count (k)) + { + strings const& o (m.get<strings> (k)); + + for (strings::const_iterator i (o.begin ()); i != o.end (); ++i) + { + if (i->empty ()) + r.clear (); + else + { + if (!r.empty ()) + r += ' '; + + r += *i; + } + } + } + + return r; +} + +context::type_map_type::const_iterator context::type_map_type:: +find (semantics::type& t, semantics::names* hint) +{ + const_iterator e (end ()), i (e); + + // First check the hinted name. This allows us to handle things like + // size_t which is nice to map to the same type irrespective of the + // actual type. Since this type can be an alias for the one we are + // interested in, go into nested hints. + // + for (; hint != 0 && i == e; hint = hint->hint ()) + i = base::find (t.fq_name (hint)); + + // If the hinted name didn't work, try the primary name (e.g., + // ::std::string) instead of a user typedef (e.g., my_string). + // + if (i == e) + i = base::find (t.fq_name ()); + + return i; +} + +string context:: +database_type_impl (semantics::type& t, + semantics::names* hint, + bool id, + bool* null) +{ + using semantics::enum_; + + // By default map an enum as its underlying type. + // + if (enum_* e = dynamic_cast<enum_*> (&t)) + return database_type_impl ( + e->underlying_type (), e->underlying_type_hint (), id, null); + + // Built-in type mapping. + // + type_map_type::const_iterator i (data_->type_map_.find (t, hint)); + if (i != data_->type_map_.end ()) + { + if (null != 0) + *null = i->second.null; + return id ? i->second.id_type : i->second.type; + } + + return string (); +} + +static string +public_name_impl (semantics::data_member& m) +{ + string s (m.name ()); + size_t n (s.size ()); + + // Do basic processing: remove trailing and leading underscores + // as well as the 'm_' prefix. + // + // @@ What if the resulting names conflict? + // + size_t b (0), e (n - 1); + + if (n > 2 && s[0] == 'm' && s[1] == '_') + b += 2; + + for (; b <= e && s[b] == '_'; b++) ; + for (; e >= b && s[e] == '_'; e--) ; + + return b > e ? s : string (s, b, e - b + 1); +} + +string context:: +public_name_db (semantics::data_member& m) const +{ + return public_name_impl (m); +} + +string context:: +compose_name (string const& prefix, string const& name) +{ + string r (prefix); + size_t n (r.size ()); + + // Add an underscore unless one is already in the prefix or + // the name is empty. Similarly, remove it if it is there but + // the name is empty. + // + if (n != 0) + { + if (r[n - 1] != '_') + { + if (!name.empty ()) + r += '_'; + } + else + { + if (name.empty ()) + r.resize (n - 1); + } + } + + r += name; + return r; +} + +string context:: +transform_name (string const& name, sql_name_type type) const +{ + string r; + + if (!data_->sql_name_regex_[type].empty () || + !data_->sql_name_regex_[sql_name_all].empty ()) + { + bool t (options.sql_name_regex_trace ()); + + if (t) + cerr << "name: '" << name << "'" << endl; + + bool found (false); + + // First try the type-specific transformations, if that didn't work, + // try common transformations. + // + for (unsigned short j (0); !found && j < 2; ++j) + { + regex_mapping const& rm = data_->sql_name_regex_[ + j == 0 ? type : sql_name_all]; + + for (regex_mapping::const_iterator i (rm.begin ()); i != rm.end (); ++i) + { + if (t) + cerr << "try: '" << i->regex () << "' : "; + + if (i->match (name)) + { + r = i->replace (name); + found = true; + + if (t) + cerr << "'" << r << "' : "; + } + + if (t) + cerr << (found ? '+' : '-') << endl; + + if (found) + break; + } + } + + if (!found) + r = name; + } + else + r = name; + + if (options.sql_name_case ().count (db) != 0) + { + switch (options.sql_name_case ()[db]) + { + case name_case::upper: + { + r = data_->sql_name_upper_.replace (r); + break; + } + case name_case::lower: + { + r = data_->sql_name_lower_.replace (r); + break; + } + } + } + + return r; +} + +string context:: +public_name (semantics::data_member& m, bool e) const +{ + return e ? escape (public_name_impl (m)) : public_name_impl (m); +} + +string context:: +flat_name (string const& fq) +{ + string r; + r.reserve (fq.size ()); + + for (string::size_type i (0), n (fq.size ()); i < n; ++i) + { + char c (fq[i]); + + if (c == ':') + { + if (!r.empty ()) + r += '_'; + ++i; // Skip the second ':'. + } + else + r += c; + } + + return r; +} + +string context:: +escape (string const& name) const +{ + typedef string::size_type size; + + string r; + size n (name.size ()); + + // In most common cases we will have that many characters. + // + r.reserve (n); + + for (size i (0); i < n; ++i) + { + char c (name[i]); + + if (i == 0) + { + if (!((c >= 'a' && c <= 'z') || + (c >= 'A' && c <= 'Z') || + c == '_')) + r = (c >= '0' && c <= '9') ? "cxx_" : "cxx"; + } + + if (!((c >= 'a' && c <= 'z') || + (c >= 'A' && c <= 'Z') || + (c >= '0' && c <= '9') || + c == '_')) + r += '_'; + else + r += c; + } + + if (r.empty ()) + r = "cxx"; + + // Custom reserved words. + // + /* + reserved_name_map_type::const_iterator i (reserved_name_map.find (r)); + + if (i != reserved_name_map.end ()) + { + if (!i->second.empty ()) + return i->second; + else + r += L'_'; + } + */ + + // Keywords + // + if (keyword_set.find (r) != keyword_set.end ()) + { + r += '_'; + + // Re-run custom words. + // + /* + i = reserved_name_map.find (r); + + if (i != reserved_name_map.end ()) + { + if (!i->second.empty ()) + return i->second; + else + r += L'_'; + } + */ + } + + return r; +} + +string context:: +make_guard (string const& s) const +{ + // Split words, e.g., "FooBar" to "Foo_Bar" and convert everything + // to upper case. + // + string r; + for (string::size_type i (0), n (s.size ()); i < n - 1; ++i) + { + char c1 (s[i]); + char c2 (s[i + 1]); + + r += toupper (c1); + + if (isalpha (c1) && isalpha (c2) && islower (c1) && isupper (c2)) + r += "_"; + } + r += toupper (s[s.size () - 1]); + + return escape (r); +} + +static string +charlit (unsigned int u) +{ + string r ("\\x"); + bool lead (true); + + for (short i (7); i >= 0; --i) + { + unsigned int x ((u >> (i * 4)) & 0x0F); + + if (lead) + { + if (x == 0) + continue; + + lead = false; + } + + r += static_cast<char> (x < 10 ? ('0' + x) : ('A' + x - 10)); + } + + return r; +} + +static string +strlit_ascii (string const& str) +{ + string r; + string::size_type n (str.size ()); + + // In most common cases we will have that many chars. + // + r.reserve (n + 2); + + r += '"'; + + bool escape (false); + + for (string::size_type i (0); i < n; ++i) + { + unsigned int u (static_cast<unsigned int> (str[i])); + + // [128 - ] - unrepresentable + // 127 - \x7F + // [32 - 126] - as is + // [0 - 31] - \X or \xXX + // + + if (u < 32 || u == 127) + { + switch (u) + { + case '\n': + { + r += "\\n"; + break; + } + case '\t': + { + r += "\\t"; + break; + } + case '\v': + { + r += "\\v"; + break; + } + case '\b': + { + r += "\\b"; + break; + } + case '\r': + { + r += "\\r"; + break; + } + case '\f': + { + r += "\\f"; + break; + } + case '\a': + { + r += "\\a"; + break; + } + default: + { + r += charlit (u); + escape = true; + break; + } + } + } + else if (u < 127) + { + if (escape) + { + // Close and open the string so there are no clashes. + // + r += '"'; + r += '"'; + + escape = false; + } + + switch (u) + { + case '"': + { + r += "\\\""; + break; + } + case '\\': + { + r += "\\\\"; + break; + } + default: + { + r += static_cast<char> (u); + break; + } + } + } + else + { + // @@ Unrepresentable character. + // + r += '?'; + } + } + + r += '"'; + + return r; +} + +string context:: +strlit (string const& str) +{ + return strlit_ascii (str); +} + +void context:: +inst_header (bool decl, bool omit_exp) +{ + if (decl && !ext.empty ()) + os << ext << " "; + + os << "template struct"; + + if (!omit_exp && !exp.empty ()) + { + // If we are generating an explicit instantiation directive rather + // than the extern template declaration, then omit the export symbol + // if we already have it in the header (i.e., extern symbol specified + // and defined). If we don't do that, then we get GCC warnings saying + // that the second set of visibility attributes is ignored. + // + if (!decl && !ext.empty ()) + os << endl + << "#ifndef " << ext << endl + << options.export_symbol ()[db] << endl + << "#endif" << endl; + else + os << " " << exp; + } + else + os << " "; +} + +namespace +{ + struct column_count_impl: object_members_base + { + column_count_impl (object_section* section = 0) + : object_members_base (false, section) + { + } + + virtual void + traverse_pointer (semantics::data_member& m, semantics::class_& c) + { + // Object pointers in views require special treatment. + // + if (view_member (m)) + { + using semantics::class_; + + column_count_type cc; + + if (class_* root = polymorphic (c)) + { + // For a polymorphic class we are going to load all the members + // from all the bases (i.e., equivalent to the first statement + // in the list of SELECT statements generated for the object). + // So our count should be the same as the first value in the + // generated column_counts array. + // + for (class_* b (&c);; b = &polymorphic_base (*b)) + { + column_count_type const& ccb (column_count (*b, section_)); + + cc.total += ccb.total - (b != root ? ccb.id : 0); + cc.separate_load += ccb.separate_load; + cc.soft += ccb.soft; + + if (b == root) + break; + } + } + else + cc = column_count (c, section_); + + c_.total += cc.total - cc.separate_load; + + if (added (member_path_) != 0 || deleted (member_path_) != 0) + c_.soft += cc.total; + else + c_.soft += cc.soft; + } + else + { + size_t t (c_.total); + + object_members_base::traverse_pointer (m, c); + + if (context::inverse (m)) + { + size_t n (c_.total - t); + + c_.inverse += n; + + if (separate_update (member_path_)) + c_.separate_update -= n; + } + } + } + + virtual void + traverse_simple (semantics::data_member& m) + { + c_.total++; + + bool ro (context::readonly (member_path_, member_scope_)); + + if (id ()) + c_.id++; + else if (ro) + c_.readonly++; + else if (context::version (m)) + c_.optimistic_managed++; + + // For now discriminator can only be a simple value. + // + if (discriminator (m)) + c_.discriminator++; + + { + unsigned long long av (added (member_path_)); + unsigned long long dv (deleted (member_path_)); + + // If the addition/deletion version is the same as the section's, + // then don't count. + // + if (user_section* s = dynamic_cast<user_section*> (section_)) + { + if (av == added (*s->member)) + av = 0; + + if (dv == deleted (*s->member)) + dv = 0; + } + + if (av != 0) + c_.added++; + + if (dv != 0) + c_.deleted++; + + if (av != 0 || dv != 0) + c_.soft++; + } + + if (separate_load (member_path_)) + c_.separate_load++; + + if (separate_update (member_path_) && !ro) + c_.separate_update++; + } + + context::column_count_type c_; + }; +} + +context::column_count_type context:: +column_count (semantics::class_& c, object_section* s) +{ + if (s == 0) + { + // Whole class. + // + if (!c.count ("column-count")) + { + column_count_impl t; + t.traverse (c); + c.set ("column-count", t.c_); + } + + return c.get<column_count_type> ("column-count"); + } + else + { + column_count_impl t (s); + t.traverse (c); + return t.c_; + } +} + +namespace +{ + struct has_a_impl: object_members_base + { + has_a_impl (unsigned short flags, object_section* s) + : object_members_base ((flags & context::include_base) != 0, s), + r_ (0), + flags_ (flags) + { + } + + size_t + result () const + { + return r_; + } + + virtual bool + section_test (data_member_path const& mp) + { + object_section& s (section (mp)); + + // Include eager loaded members into the main section if requested. + // + return section_ == 0 || + *section_ == s || + ((flags_ & include_eager_load) != 0 && + *section_ == main_section && + !s.separate_load ()); + } + + virtual void + traverse_pointer (semantics::data_member& m, semantics::class_&) + { + // Ignore polymorphic id references; they are represented as + // pointers but are normally handled in a special way. + // + if (m.count ("polymorphic-ref")) + return; + + // Ignore added/deleted members if so requested. + // + if (check_soft ()) + return; + + if (context::is_a (member_path_, member_scope_, flags_)) + r_++; + + // No need to go inside. + } + + virtual void + traverse_simple (semantics::data_member&) + { + // Ignore added/deleted members if so requested. + // + if (check_soft ()) + return; + + if (context::is_a (member_path_, member_scope_, flags_)) + r_++; + } + + virtual void + traverse_container (semantics::data_member& m, semantics::type&) + { + // Ignore added/deleted members if so requested. + // + if (check_soft ()) + return; + + // Ignore versioned containers if so requested. + // + if ((flags_ & exclude_versioned) != 0 && versioned (m)) + return; + + // We don't cross the container boundaries (separate table). + // + unsigned short f (flags_ & (context::test_container | + context::test_straight_container | + context::test_inverse_container | + context::test_readonly_container | + context::test_readwrite_container | + context::test_smart_container)); + + if (context::is_a (member_path_, + member_scope_, + f, + context::container_vt (m), + "value")) + r_++; + } + + virtual void + traverse_object (semantics::class_& c) + { + if ((flags_ & context::exclude_base) == 0) + inherits (c); + + names (c); + } + + private: + bool + check_soft () + { + if ((flags_ & exclude_added) != 0 || (flags_ & exclude_deleted) != 0) + { + unsigned long long av (added (member_path_)); + unsigned long long dv (deleted (member_path_)); + + // If the addition/deletion version is the same as the section's, + // then don't exclude. + // + if (user_section* s = dynamic_cast<user_section*> (section_)) + { + if (av == added (*s->member)) + av = 0; + + if (dv == deleted (*s->member)) + dv = 0; + } + + if ((av != 0 && (flags_ & exclude_added) != 0) || + (dv != 0 && (flags_ & exclude_deleted) != 0)) + return true; + } + + return false; + } + + private: + size_t r_; + unsigned short flags_; + }; +} + +bool context:: +is_a (data_member_path const& mp, + data_member_scope const& ms, + unsigned short f, + semantics::type& t, + string const& kp) +{ + bool r (false); + + semantics::data_member& m (*mp.back ()); + + if (f & test_pointer) + r = r || object_pointer (t); + + if (f & test_eager_pointer) + r = r || (object_pointer (t) && !lazy_pointer (t)); + + if (f & test_lazy_pointer) + r = r || (object_pointer (t) && lazy_pointer (t)); + + semantics::type* c; + if ((f & (test_container | + test_straight_container | + test_inverse_container | + test_readonly_container | + test_readwrite_container | + test_smart_container)) != 0 && + (c = container (m)) != 0) + { + if (f & test_container) + r = r || true; + + if (f & test_straight_container) + r = r || !inverse (m, kp); + + if (f & test_inverse_container) + r = r || inverse (m, kp); + + if (f & test_readonly_container) + r = r || readonly (mp, ms); + + if (f & test_readwrite_container) + r = r || (!inverse (m, kp) && !readonly (mp, ms)); + + if (f & test_smart_container) + r = r || (!inverse (m, kp) && !unordered (m) && container_smart (*c)); + } + + return r; +} + +size_t context:: +has_a (semantics::class_& c, unsigned short flags, object_section* s) +{ + has_a_impl impl (flags, s); + impl.dispatch (c); + return impl.result (); +} + +string context:: +process_include_path (string const& ip, bool prefix, char open) +{ + bool t (options.include_regex_trace ()); + string p (prefix ? options.include_prefix () : string ()); + + if (!p.empty () && p[p.size () - 1] != '/') + p.append ("/"); + + string path (p + ip), r; + + if (t) + cerr << "include: '" << path << "'" << endl; + + bool found (false); + + for (regex_mapping::const_iterator i (include_regex.begin ()); + i != include_regex.end (); ++i) + { + if (t) + cerr << "try: '" << i->regex () << "' : "; + + if (i->match (path)) + { + r = i->replace (path); + found = true; + + if (t) + cerr << "'" << r << "' : "; + } + + if (t) + cerr << (found ? '+' : '-') << endl; + + if (found) + break; + } + + if (!found) + r = path; + + // Add brackets or quotes unless the path already has them. + // + if (!r.empty () && r[0] != '"' && r[0] != '<') + { + bool b (open == '<' || (open == '\0' && options.include_with_brackets ())); + char op (b ? '<' : '"'), cl (b ? '>' : '"'); + r = op + r + cl; + } + + return r; +} diff --git a/odb/odb/context.hxx b/odb/odb/context.hxx new file mode 100644 index 0000000..ec4505b --- /dev/null +++ b/odb/odb/context.hxx @@ -0,0 +1,1941 @@ +// file : odb/context.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_CONTEXT_HXX +#define ODB_CONTEXT_HXX + +#include <odb/gcc-fwd.hxx> + +#include <map> +#include <set> +#include <list> +#include <stack> +#include <vector> +#include <string> +#include <memory> // std::unique_ptr +#include <ostream> +#include <cstddef> // std::size_t +#include <iostream> + +#include <libcutl/re.hxx> +#include <libcutl/shared-ptr.hxx> + +#include <odb/options.hxx> +#include <odb/features.hxx> +#include <odb/location.hxx> +#include <odb/cxx-token.hxx> +#include <odb/semantics.hxx> +#include <odb/semantics/relational/name.hxx> +#include <odb/semantics/relational/model.hxx> +#include <odb/traversal.hxx> + +using std::endl; +using std::cerr; + +// Regex. +// +using cutl::re::regex; +using cutl::re::regexsub; +typedef cutl::re::format regex_format; + +typedef std::vector<regexsub> regex_mapping; + +// Forward-declarations. +// +class cxx_string_lexer; + +// Generic exception thrown to indicate a failure when diagnostics +// has already been issued (to stderr). +// +class operation_failed {}; + +// Keep this enum synchronized with the one in libodb/odb/pointer-traits.hxx. +// +enum pointer_kind +{ + pk_raw, + pk_unique, + pk_shared, + pk_weak +}; + +// Keep this enum synchronized with the one in libodb/odb/container-traits.hxx. +// +enum container_kind +{ + ck_ordered, + ck_set, + ck_multiset, + ck_map, + ck_multimap +}; + +// The same as class_kind in libodb/odb/traits.hxx. +// +enum class_kind +{ + class_object, + class_view, + class_composite, + class_other +}; + +// Data member path. +// +// If it is a direct member of an object, then we will have just +// one member. However, if this is a member inside a composite +// value, then we will have a "path" constructed out of members +// that lead all the way from the object member to the innermost +// composite value member. +// +struct data_member_path: std::vector<semantics::data_member*> +{ + data_member_path () {} + + explicit + data_member_path (semantics::data_member& m) {push_back (&m);} + + // Return true if this is a sub-path of (or equal to) the + // specified path. + // + bool + sub (const data_member_path& p) const + { + size_t n (p.size ()); + + if (n > size ()) + return false; + + for (size_t i (0); i != n; ++i) + if ((*this)[i] != p[i]) + return false; + + return true; + } +}; + +// Class inheritance chain, from the most derived to base. +// +typedef std::vector<semantics::class_*> class_inheritance_chain; + +// A list of inheritance chains for a data member in an object. +// The first entry in this list would correspond to the object. +// All subsequent entries, if any, correspond to composite +// values. +// +typedef std::vector<class_inheritance_chain> data_member_scope; + +// +// Semantic graph context types. +// + +// Custom C++ type mapping. +// +struct custom_cxx_type +{ + custom_cxx_type (): type_node (0), as_node (0) {} + + std::string + translate_to (std::string const& v) const {return translate (v, to);} + + std::string + translate_from (std::string const& v) const {return translate (v, from);} + + tree type_node; + std::string type_name; + semantics::type* type; + semantics::names* type_hint; + + tree as_node; + std::string as_name; + semantics::type* as; + semantics::names* as_hint; + + cxx_tokens to; + bool to_move; // Single (?), so can move. + + cxx_tokens from; + bool from_move; // Single (?), so can move. + + location_t loc; + tree scope; // Scope for which this mapping is defined. + +private: + static std::string + translate (std::string const&, const cxx_tokens&); +}; + +typedef std::vector<custom_cxx_type> custom_cxx_types; +typedef std::map<semantics::type*, custom_cxx_type*> custom_cxx_type_map; + + +// Object or view pointer. +// +struct class_pointer +{ + std::string name; + tree scope; + location_t loc; +}; + +// +// +struct default_value +{ + enum kind_type + { + reset, // Default value reset. + null, + boolean, // Literal contains value (true or false). + integer, // Integer number. Literal contains sign. + floating, // Floating-point number. + string, // Literal contains value. + enumerator // Literal is the name, enum_value is the tree node. + }; + + kind_type kind; + std::string literal; + + union + { + tree enum_value; + unsigned long long int_value; + double float_value; + }; +}; + +// Database potentially-qualified and unqualifed names. +// +using semantics::relational::qname; +using semantics::relational::uname; + +// Object or table associated with the view. +// +struct view_object +{ + // Return a diagnostic name for this association. It is either the + // alias, unqualified object name, or string representation of the + // table name. + // + std::string + name () const; + + enum kind_type { object, table }; + enum join_type { left, right, full, inner, cross }; + + kind_type kind; + join_type join; + tree obj_node; // Tree node if kind is object. + std::string obj_name; // Name as specified in the pragma if kind is object. + qname tbl_name; // Table name if kind is table. + std::string alias; + tree scope; + location_t loc; + semantics::class_* obj; + semantics::data_member* ptr; // Corresponding object pointer, if any. + + cxx_tokens cond; // Join condition tokens. +}; + +typedef std::vector<view_object> view_objects; + +// The view_alias_map does not contain entries for tables. +// +typedef std::map<std::string, view_object*> view_alias_map; +typedef std::map<semantics::class_*, view_object*> view_object_map; + +// Collection of relationships via which the objects are joined. +// We need this information to figure out which alias/table +// names to use for columns corresponding to inverse object +// pointers inside objects that this view may be loading. +// +// The first object is the pointer (i.e., the one containing +// this data member) while the other is the pointee. In other +// words, first -> second. We always store the direct (i.e., +// non-inverse) side of the relationship. Note also that there +// could be multiple objects joined using the same relationship. +// +typedef +std::multimap<data_member_path, std::pair<view_object*, view_object*> > +view_relationship_map; + +// +// +struct view_query +{ + view_query (): distinct (false), for_update (false) {} + + enum kind_type + { + runtime, + complete_select, // SELECT query. + complete_execute, // Stored procedure call. + condition + }; + + kind_type kind; + std::string literal; + cxx_tokens expr; + tree scope; + location_t loc; + + // Result modifiers (only for condition). + // + bool distinct; // SELECT DISTINCT + bool for_update; // SELECT FOR UPDATE +}; + +// +// +struct table_column +{ + qname table; + std::string column; + bool expr; // True if column is an expression, and therefore should not + // be quoted. + + table_column () {} + + explicit + table_column (const std::string& c): column (c), expr (false) {} +}; + +// +// +struct column_expr_part +{ + enum kind_type + { + literal, + reference + }; + + kind_type kind; + std::string value; + qname table; // Table name/alias for references. + data_member_path member_path; // Path to member for references. + + // Scope and location of this pragma. Used to resolve the member name. + // + tree scope; + location_t loc; +}; + +struct column_expr: std::vector<column_expr_part> +{ + location_t loc; +}; + +// +// +struct member_access +{ + member_access (const location& l, const char* k, bool s) + : loc (l), kind (k), synthesized (s), by_value (false) {} + + // Return true of we have the (?) placeholder. + // + bool + placeholder () const; + + // Return true if this is a synthesized expression that goes + // directly for the member. + // + bool + direct () const + { + return synthesized && expr.size () == 3; // this.member + } + + bool + empty () const + { + return expr.empty (); + } + + // Issues diagnostics and throws operation_failed if expression is + // empty. + // + std::string + translate (std::string const& obj, + std::string const& val = std::string (), + std::string const& db = std::string ()) const; + + location loc; + const char* kind; // accessor/modifier; used for diagnostics. + bool synthesized; // If true, then this is a synthesized expression. + cxx_tokens expr; + bool by_value; // True if accessor returns by value. False doesn't + // necessarily mean that it is by reference. +}; + +// +// +struct model_version +{ + unsigned long long base; + unsigned long long current; + bool open; +}; + +// Sections. +// +struct object_section +{ + virtual bool + compare (object_section const&) const = 0; + + virtual bool + separate_load () const = 0; + + virtual bool + separate_update () const = 0; +}; + +inline bool +operator== (object_section const& x, object_section const& y) +{ + return x.compare (y); +} + +inline bool +operator!= (object_section const& x, object_section const& y) +{ + return !x.compare (y); +} + +// Main section. +// +struct main_section_type: object_section +{ + virtual bool + compare (object_section const& s) const; + + virtual bool + separate_load () const {return false;} + + virtual bool + separate_update () const {return false;} +}; + +inline bool +operator== (main_section_type const&, main_section_type const&) +{ + return true; // There is only one main section. +} + +extern main_section_type main_section; + +// User-defined section. +// +struct user_section: object_section +{ + enum load_type + { + load_eager, + load_lazy + }; + + enum update_type + { + update_always, + update_change, + update_manual + }; + + enum special_type + { + special_ordinary, + special_version // Fake section for optimistic version update. + }; + + user_section (semantics::data_member& m, + semantics::class_& o, + std::size_t i, + load_type l, + update_type u, + special_type s = special_ordinary) + : member (&m), object (&o), base (0), index (i), + load (l), update (u), special (s), + total (0), inverse (0), readonly (0), versioned (false), + containers (false), readwrite_containers (false), + versioned_containers (false), readwrite_versioned_containers (false) {} + + virtual bool + compare (object_section const& s) const; + + virtual bool + separate_load () const {return load != load_eager;} + + virtual bool + separate_update () const + { + // A separately-loaded section is always separately-updated since + // it might not be loaded when update is requested. + // + return separate_load () || update != update_always; + } + + bool + load_empty () const; + + bool + update_empty () const; + + bool + empty () const + { + return load_empty () && update_empty (); + } + + // A section is optimistic if the object that contains it is optimistic. + // For polymorphic hierarchies, only sections contained in the root are + // considered optimistic. + // + bool + optimistic () const; + + semantics::data_member* member; // Data member of this section. + semantics::class_* object; // Object containing this section. + user_section* base; // Base of this section. + std::size_t index; // Index of this sections. + + load_type load; + update_type update; + special_type special; + + // Column counts. + // + std::size_t total; + std::size_t inverse; + std::size_t readonly; + + bool versioned; + + bool containers; + bool readwrite_containers; + + bool versioned_containers; + bool readwrite_versioned_containers; + + // Total counts across overrides. + // + std::size_t + total_total () const + { + user_section* b (total_base ()); + return total + (b == 0 ? 0 : b->total_total ()); + } + + std::size_t + total_inverse () const + { + user_section* b (total_base ()); + return inverse + (b == 0 ? 0 : b->total_inverse ()); + } + + std::size_t + total_readonly () const + { + user_section* b (total_base ()); + return readonly + (b == 0 ? 0 : b->total_readonly ()); + } + + bool + total_containers () + { + user_section* b (total_base ()); + return containers || (b != 0 && b->total_containers ()); + } + + bool + total_readwrite_containers () + { + user_section* b (total_base ()); + return readwrite_containers || + (b != 0 && b->total_readwrite_containers ()); + } + +private: + user_section* + total_base () const; +}; + +inline bool +operator== (user_section const& x, user_section const& y) +{ + return x.member == y.member; +} + +// Using list for pointer for element stability (see user_section::base). +// +struct user_sections: std::list<user_section> +{ + // Count sections that have something to load. + // + static unsigned short const count_load = 0x01; + + // Count sections that are non-eager but have nothing to load. + // + static unsigned short const count_load_empty = 0x02; + + // Count sections that have something to update. + // + static unsigned short const count_update = 0x04; + + // Count sections that have nothing to update. + // + static unsigned short const count_update_empty = 0x08; + + // Count sections that are optimistic. + // + static unsigned short const count_optimistic = 0x10; + + // Modifiers: + // + + // Don't exclude fake optimistic version update section from the count. + // + static unsigned short const count_special_version = 0x20; + + // Only count versioned sections. + // + static unsigned short const count_versioned_only = 0x40; + + + // Count all sections, but excluding special. + // + static unsigned short const count_all = count_update | + count_update_empty; + + static unsigned short const count_new = 0x1000; + static unsigned short const count_override = 0x2000; + static unsigned short const count_total = 0x4000; + + std::size_t + count (unsigned short flags) const; + + user_sections (semantics::class_& o): object (&o) {}; + semantics::class_* object; +}; + +// Context. +// +class context +{ +public: + typedef std::size_t size_t; + typedef std::string string; + typedef std::vector<string> strings; + typedef std::ostream ostream; + + typedef ::options options_type; + + static string + upcase (string const&); + +public: + // Return cvr-unqualified base of the type, or type itself, if it is + // not qualified. + // + static semantics::type& + utype (semantics::type&); + + // The same as above, but also returns the name hint for the unqualified + // type. If the original type is already unqualified, then the hint + // argument is not modified. + // + static semantics::type& + utype (semantics::type&, semantics::names*& hint); + + // The same for a member's type but also do custom C++ type translation. + // + static semantics::type& + utype (semantics::data_member& m, const custom_cxx_type** translation = 0) + { + semantics::names* hint (0); + return utype (m, hint, string (), translation); + } + + static semantics::type& + utype (semantics::data_member& m, + string const& key_prefix, + const custom_cxx_type** translation = 0) + { + semantics::names* hint (0); + return utype (m, hint, key_prefix, translation); + } + + // In addition to the unqualified type, this version also returns the + // name hint for this type. If the member type is already unqualified, + // then the hint is from the belongs edge. Otherwise, it is from the + // qualifies edge. + // + static semantics::type& + utype (semantics::data_member&, + semantics::names*& hint, + string const& key_prefix = string (), + const custom_cxx_type** translation = 0); + + static semantics::type& + utype (const data_member_path& mp, const custom_cxx_type** translation = 0) + { + return utype (*mp.back (), translation); + } + + static semantics::type& + utype (const data_member_path& mp, + string const& key_prefix, + const custom_cxx_type** translation = 0) + { + return utype (*mp.back (), key_prefix, translation); + } + + static semantics::type& + utype (const data_member_path& mp, + semantics::names*& hint, + string const& key_prefix = string (), + const custom_cxx_type** translation = 0) + { + return utype (*mp.back (), hint, key_prefix, translation); + } + + // For arrays this function returns true if the (innermost) element + // type is const. + // + static bool + const_type (semantics::type&); + + static bool + const_member (semantics::data_member& m) {return const_type (m.type ());} + + // Form a reference type for a not mapped, actual member type. If + // make_const is true, then add top-level const qualifier, unless + // it is already there. If it is false, then strip it if it is + // already there. If var is not empty, then embed the variable + // name into the type (e.g., char (*v)[3]). If decay_array is + // false then don't decay the (top-level) array to a pointer. + // + static string + member_ref_type (semantics::data_member& m, + bool make_const, + string const& var = "", + bool decay_array = true) + { + return type_ref_type ( + m.type (), m.belongs ().hint (), make_const, var, decay_array); + } + + static string + type_ref_type (semantics::type&, + semantics::names* hint, + bool make_const, + string const& var = "", + bool decay_array = true); + + // Form a value type for a not mapped, actual member type. If make_const + // is true, then add top-level const qualifier, unless it is already + // there. If it is false, then strip it if it is already there. If var is + // not empty, then embed the variable name into the type (e.g., char v[3]). + // + static string + member_val_type (semantics::data_member& m, + bool make_const, + string const& var = "") + { + return type_val_type (m.type (), m.belongs ().hint (), make_const, var); + } + + static string + type_val_type (semantics::type&, + semantics::names* hint, + bool make_const, + string const& var = ""); + + // Member access helpers. Database can be empty. If type is not empty, + // then it should be the non-cvr type of the member (e.g., id_type). + // + void + set_member (semantics::data_member& m, + const std::string& obj, + const std::string& val, + const std::string& db, + const std::string& type = ""); + + void + inc_member (semantics::data_member& m, + const std::string& sobj, // Set expression object. + const std::string& gobj, // Get expression object. + const std::string& type = ""); + +public: + // Resolve data member name in the form "a.b.c" to the data member path, + // issuing diagnostics and throwing operation_filed in case of an error. + // This function stops if it encounters a container leaving lex usable + // to continue parsing. + // + void + resolve_data_members (data_member_path& append, + semantics::class_& scope, + const std::string& name, + const location&, + cxx_string_lexer&); + + data_member_path + resolve_data_members (semantics::class_& scope, + const std::string& name, + const location& l, + cxx_string_lexer& lex) + { + data_member_path r; + resolve_data_members (r, scope, name, l, lex); + return r; + } + + // Predicates. + // +public: + static bool + object (semantics::type& t) + { + return t.count ("object"); + } + + static bool + view (semantics::type& t) + { + return t.count ("view"); + } + + // Direct member of a view. + // + static bool + view_member (semantics::data_member& m) + { + return view (dynamic_cast<semantics::class_&> (m.scope ())); + } + + // Check whether the type is a wrapper. Return the wrapped type if + // it is a wrapper and NULL otherwise. Note that the returned type + // may be cvr-qualified. + // + static semantics::type* + wrapper (semantics::type& t) + { + return t.count ("wrapper") && t.get<bool> ("wrapper") + ? t.get<semantics::type*> ("wrapper-type") + : 0; + } + + static semantics::type* + wrapper (semantics::type& t, semantics::names*& hint) + { + if (t.count ("wrapper") && t.get<bool> ("wrapper")) + { + hint = t.get<semantics::names*> ("wrapper-hint"); + return t.get<semantics::type*> ("wrapper-type"); + } + else + return 0; + } + + // Composite value type is a class type that was explicitly marked + // as value type and there was no database type mapping provided for + // it by the user (specifying the database type makes the value type + // simple). + // + static bool + composite (semantics::class_& c) + { + if (c.count ("composite-value")) + return c.get<bool> ("composite-value"); + else + return composite_ (c); + } + + // Return the class object if this type is a composite value type + // and NULL otherwise. + // + static semantics::class_* + composite (semantics::type& t) + { + semantics::class_* c (dynamic_cast<semantics::class_*> (&t)); + return c != 0 && composite (*c) ? c : 0; + } + + // As above but also "sees through" wrappers. + // + static semantics::class_* + composite_wrapper (semantics::type& t) + { + if (semantics::class_* c = composite (t)) + return c; + else if (semantics::type* wt = wrapper (t)) + return composite (utype (*wt)); + else + return 0; + } + + // Check if a data member is a container. "Sees through" wrappers and + // returns the actual container type or NULL if not a container. + // + // We require data member as input instead of the type because the + // same type (e.g., vector<char>) can be used for both container + // and simple value members. + // + static semantics::type* + container (semantics::data_member& m) + { + // The same type can be used as both a container and a simple value. + // + if (m.count ("simple")) + return 0; + + semantics::type* t (&utype (m)); + + if (semantics::type* wt = wrapper (*t)) + t = &utype (*wt); + + return t->count ("container-kind") ? t : 0; + } + + static semantics::class_* + object_pointer (semantics::type& t) + { + return t.get<semantics::class_*> ("element-type", 0); + } + + // If this data member is or is part of an object pointer, then + // return the member that is the pointer. Otherwise, return 0. + // + static semantics::data_member* + object_pointer (data_member_path const&); + + static semantics::class_* + points_to (semantics::data_member& m) + { + return m.get<semantics::class_*> ("points-to", 0); + } + + static bool + abstract (semantics::class_& c) + { + // If a class is abstract in the C++ sense then it is also abstract in + // the database sense. + // + return c.abstract () || c.count ("abstract"); + } + + static bool + session (semantics::class_& c) + { + return c.get<bool> ("session"); + } + + static bool + transient (semantics::data_member& m) + { + return m.count ("transient"); + } + + // Return the deletion version or 0 if not soft-deleted. + // + static unsigned long long + deleted (semantics::class_& c, location_t* l = 0) + { + unsigned long long v (c.get<unsigned long long> ("deleted", 0)); + + if (v != 0 && l != 0) + *l = c.get<location_t> ("deleted-location"); + + return v; + } + + static unsigned long long + deleted (semantics::data_member& m, location_t* l = 0) + { + unsigned long long v (m.get<unsigned long long> ("deleted", 0)); + + if (v != 0 && l != 0) + *l = m.get<location_t> ("deleted-location"); + + return v; + } + + static unsigned long long + deleted (data_member_path const& mp, location_t* l = 0) + { + unsigned long long r (0); + + // Find the earliest version since this member was deleted. + // + for (data_member_path::const_reverse_iterator i (mp.rbegin ()); + i != mp.rend (); ++i) + { + unsigned long long v ((*i)->get<unsigned long long> ("deleted", 0)); + if (v != 0 && (r == 0 || v < r)) + { + r = v; + + if (l != 0) + *l = (*i)->get<location_t> ("deleted-location"); + } + } + + return r; + } + + static semantics::data_member* + deleted_member (data_member_path const& mp) + { + semantics::data_member* m (0); + + // Find the earliest version since this member was deleted. + // + unsigned long long r (0); + for (data_member_path::const_reverse_iterator i (mp.rbegin ()); + i != mp.rend (); ++i) + { + unsigned long long v ((*i)->get<unsigned long long> ("deleted", 0)); + if (v != 0 && (r == 0 || v < r)) + { + r = v; + m = *i; + } + } + + return m; + } + + // Return the addition version or 0 if not soft-added. + // + static unsigned long long + added (semantics::class_& c) // Used for composite only. + { + return c.get<unsigned long long> ("added", 0); + } + + static unsigned long long + added (semantics::data_member& m) + { + return m.get<unsigned long long> ("added", 0); + } + + static unsigned long long + added (data_member_path const& mp) + { + unsigned long long r (0); + + // Find the latest version since this member was added. + // + for (data_member_path::const_reverse_iterator i (mp.rbegin ()); + i != mp.rend (); ++i) + { + unsigned long long v ((*i)->get<unsigned long long> ("added", 0)); + if (v != 0 && v > r) + r = v; + } + + return r; + } + + static semantics::data_member* + added_member (data_member_path const& mp) + { + semantics::data_member* m (0); + + // Find the latest version since this member was added. + // + unsigned long long r (0); + for (data_member_path::const_reverse_iterator i (mp.rbegin ()); + i != mp.rend (); ++i) + { + unsigned long long v ((*i)->get<unsigned long long> ("added", 0)); + if (v != 0 && v > r) + { + r = v; + m = *i; + } + } + + return m; + } + + static bool + id (semantics::data_member& m) + { + return m.count ("id"); + } + + // If this data member is or is part of an id member, then return + // the member that is marked as the id. Otherwise, return 0. + // + static semantics::data_member* + id (data_member_path const&); + + static bool + auto_ (semantics::data_member& m) + { + return id (m) && m.count ("auto"); + } + + // Must be a path returned by id(). In other words, it assumes + // the path is to the id member. + // + static bool + auto_ (data_member_path& mp) + { + return mp.front ()->count ("auto"); + } + + // The member scope is used to override readonly status when a readonly + // class (object or composite value) inherits from a readwrite base. + // + static bool + readonly (data_member_path const&, data_member_scope const&); + + static bool + readonly (semantics::data_member&); + + static bool + readonly (semantics::class_& c) + { + return c.count ("readonly"); + } + + // Null-able. + // + bool + null (data_member_path const&) const; + + bool + null (semantics::data_member&) const; + + bool + null (semantics::data_member&, string const& key_prefix) const; + + // Optimistic concurrency. + // + static semantics::data_member* + optimistic (semantics::class_& c) + { + // Set by the validator. + // + return c.get<semantics::data_member*> ("optimistic-member", 0); + } + + static bool + version (semantics::data_member& m) + { + return m.count ("version"); + } + + static bool + version (const data_member_path& mp) + { + return mp.size () == 1 && mp.back ()->count ("version"); + } + + // Polymorphic inheritance. Return root of the hierarchy or NULL if + // not polymorphic. + // + static semantics::class_* + polymorphic (semantics::class_& c) + { + // Set by the validator. + // + return c.get<semantics::class_*> ("polymorphic-root", 0); + } + + static semantics::class_& + polymorphic_base (semantics::class_& c) + { + // Set by the validator. + // + return *c.get<semantics::class_*> ("polymorphic-base"); + } + + static size_t + polymorphic_depth (semantics::class_&); + + static bool + discriminator (semantics::data_member& m) + { + return m.count ("discriminator"); + } + + static semantics::data_member* + discriminator (semantics::class_& c) + { + // Set by type processor. + // + return c.get<semantics::data_member*> ("discriminator", 0); + } + + // Model version. + // + bool + versioned () const + { + return unit.count ("model-version") != 0; + } + + model_version const& + version () const + { + return unit.get<model_version> ("model-version"); + } + + // Versioned object, view, or composite. + // + static bool + versioned (semantics::class_& c) + { + // Set by processor. + // + return c.count ("versioned") != 0; + } + + // Versioned container. + // + static bool + versioned (semantics::data_member& m) + { + // Set by processor. + // + return container (m)->count ("versioned"); + } + + // Object sections. + // + static object_section& + section (semantics::data_member& m) + { + object_section* s (m.get<object_section*> ("section", 0)); + return s == 0 ? main_section : *s; + } + + static object_section& + section (data_member_path const& mp) + { + // The direct member of the object specifies the section. If the + // path is empty (which can happen, for example, for a container + // element), assume it is the main section. + // + // + return mp.empty () ? main_section : section (*mp.front ()); + } + + // Member belongs to a section that is loaded separately. + // + static bool + separate_load (semantics::data_member& m) + { + return section (m).separate_load (); + } + + static bool + separate_load (data_member_path const& mp) + { + return section (mp).separate_load (); + } + + // Member belongs to a section that is updated separately. + // + static bool + separate_update (semantics::data_member& m) + { + return section (m).separate_update (); + } + + static bool + separate_update (data_member_path const& mp) + { + return section (mp).separate_update (); + } + + // + // + typedef ::class_kind class_kind_type; + + static class_kind_type + class_kind (semantics::class_&); + + // Return class names. For ordinary classes, this will be the class + // name itself. For class template instantiations this will be the + // typedef name used in the pragma. + // + static string + class_name (semantics::class_&); + + static string + class_fq_name (semantics::class_&); + + // Return class scope. For ordinary classes, this will be the scope + // where the class is defined. For class template instantiations this + // will be the scope of the typedef name used in the pragma. + // + static semantics::scope& + class_scope (semantics::class_&); + + // Return the class file. For ordinary classes, this will be the file + // where the class is defined. For class template instantiations this + // will be the file containing the pragma. + // + static semantics::path + class_file (semantics::class_&); + + // Return the location (as location_t; useful for comparison) of + // an "ODB class" (i.e., an object, view, or composite value), + // taking into account things like definition point overrides, + // etc. + // + location_t + class_location (semantics::class_&); + + // Same as above, but returns "real" location, that is, ignoring + // the definition point overrides. + // + location_t + class_real_location (semantics::class_&); + + // Database names and types. + // +public: + // Schema name for a namespace. + // + qname + schema (semantics::scope&) const; + + // Table name prefix for a namespace. + // + string + table_name_prefix (semantics::scope&) const; + + // + // + struct table_prefix + { + table_prefix (): level (0), derived (false) {} + table_prefix (semantics::class_&); + + void + append (semantics::data_member&); + + qname ns_schema; // Object's namespace schema. + string ns_prefix; // Object's namespace table prefix. + qname prefix; + size_t level; + bool derived; // One of the components in the prefix was derived. + }; + + qname + table_name (semantics::class_&, bool* derived = 0) const; + + qname + table_name (semantics::class_&, data_member_path const&) const; + + // Table name for the container member. The table prefix passed as the + // second argument must include the table prefix specified with the + // --table-prefix option. + // + qname + table_name (semantics::data_member&, table_prefix const&) const; + + string + table_options (semantics::class_&); + + // Table options for the container member. + // + string + table_options (semantics::data_member&, semantics::type& ct); + + // + // + struct column_prefix + { + column_prefix (): derived (false), underscore (false) {} + + column_prefix (semantics::data_member& m, + string const& key_prefix = string (), + string const& default_name = string ()) + : derived (false), underscore (false) + { + append (m, key_prefix, default_name); + } + + // If the last argument is true, the prefix will include the last member + // in the path. + // + column_prefix (data_member_path const&, bool last = false); + + bool + empty () const {return prefix.empty ();} + + void + append (semantics::data_member&, + string const& key_prefix = string (), + string const& default_name = string ()); + + string prefix; + bool derived; // One of the components in the prefix was derived. + bool underscore; // Trailing underscore was automatically added. + }; + + string + column_name (semantics::data_member&, bool& derived) const; + + string + column_name (semantics::data_member&, column_prefix const&) const; + + string + column_name (semantics::data_member&, + string const& key_prefix, + string const& default_name, + bool& derived) const; + + string + column_name (semantics::data_member&, + string const& key_prefix, + string const& default_name, + column_prefix const&) const; + + string + column_name (data_member_path const&) const; + + // + // + string + column_type (const data_member_path&, + string const& key_prefix = string (), + bool id = false); // Pass true if this type is object id other + // than because of the members in the path. + string + column_type (semantics::data_member&, string const& key_prefix = string ()); + + string + column_options (semantics::data_member&); + + string + column_options (semantics::data_member&, string const& key_prefix); + + // Cleaned-up member name that can be used for database names. + // + string + public_name_db (semantics::data_member&) const; + + // Compose the name by inserting/removing an underscore, as necessary. + // + static string + compose_name (string const& prefix, string const& name); + + // SQL name transformations. + // + enum sql_name_type + { + sql_name_all, + sql_name_table, + sql_name_column, + sql_name_index, + sql_name_fkey, + sql_name_sequence, + sql_name_statement, + sql_name_count + }; + + string + transform_name (string const& name, sql_name_type) const; + + // C++ names. + // +public: + // Cleaned-up and potentially escaped member name that can be used + // in public C++ interfaces. + // + string + public_name (semantics::data_member&, bool escape = true) const; + + // "Flatten" fully-qualified C++ name by replacing '::' with '_' + // and removing leading '::', if any. + // + static string + flat_name (string const& fqname); + + // Escape C++ keywords, reserved names, and illegal characters. + // + string + escape (string const&) const; + + // Make C++ include guard name by split words, e.g., "FooBar" to + // "Foo_Bar" and converting everything to upper case. + // + string + make_guard (string const&) const; + + // Return a string literal that can be used in C++ source code. It + // includes "". + // + static string + strlit (string const&); + +public: + // Generate explicit instantiation headers with all the necessary + // extern and export symbols. + // + void + inst_header (bool decl, bool omit_exp = false); + + // Counts and other information. + // +public: + struct column_count_type + { + column_count_type () + : total (0), + id (0), + inverse (0), + readonly (0), + optimistic_managed (0), + discriminator (0), + added (0), + deleted (0), + soft (0), + separate_load (0), + separate_update (0) + { + } + + size_t total; + size_t id; + size_t inverse; + size_t readonly; + size_t optimistic_managed; + size_t discriminator; + + size_t added; // Soft-added. + size_t deleted; // Soft-deleted. + size_t soft; // Soft-added/deleted (a column can be both). + + size_t separate_load; + size_t separate_update; // Only readwrite. + }; + + static column_count_type + column_count (semantics::class_&, object_section* = 0); + + static data_member_path* + id_member (semantics::class_& c) + { + // Set by the processor. May not be there for reuse-abstract + // classes or classes without object id. + // + return c.count ("id-member") ? &c.get<data_member_path> ("id-member") : 0; + } + + // Object pointer information. + // +public: + typedef ::pointer_kind pointer_kind_type; + + pointer_kind_type + pointer_kind (semantics::type& p) + { + return p.get<pointer_kind_type> ("pointer-kind"); + } + + bool + lazy_pointer (semantics::type& p) + { + return p.get<bool> ("pointer-lazy"); + } + + bool + weak_pointer (semantics::type& p) + { + return pointer_kind (p) == pk_weak; + } + + static data_member_path* + inverse (semantics::data_member& m) + { + return object_pointer (utype (m)) && m.count ("inverse") + ? &m.get<data_member_path> ("inverse") + : 0; + } + + data_member_path* + inverse (semantics::data_member& m, string const& key_prefix) + { + if (key_prefix.empty ()) + return inverse (m); + + if (!object_pointer (utype (m, key_prefix))) + return 0; + + string k (key_prefix + "-inverse"); + return m.count (k) ? &m.get<data_member_path> (k) : 0; + } + + // Container information. + // +public: + typedef ::container_kind container_kind_type; + + static container_kind_type + container_kind (semantics::type& c) + { + return c.get<container_kind_type> ("container-kind"); + } + + static bool + container_smart (semantics::type& c) + { + return c.get<bool> ("container-smart"); + } + + static semantics::type& + container_idt (semantics::data_member& m, const custom_cxx_type** trans = 0) + { + return utype (m, "id", trans); + } + + static semantics::type& + container_vt (semantics::data_member& m, const custom_cxx_type** trans = 0) + { + return utype (m, "value", trans); + } + + static semantics::type& + container_it (semantics::data_member& m, const custom_cxx_type** trans = 0) + { + return utype (m, "index", trans); + } + + static semantics::type& + container_kt (semantics::data_member& m, const custom_cxx_type** trans = 0) + { + return utype (m, "key", trans); + } + + static bool + unordered (semantics::data_member& m) + { + if (m.count ("unordered")) + return true; + + if (semantics::type* c = container (m)) + return c->count ("unordered"); + + return false; + } + + // The 'is a' and 'has a' tests. The has_a() test currently does not + // cross the container boundaries. + // +public: + static unsigned short const test_pointer = 0x01; + static unsigned short const test_eager_pointer = 0x02; + static unsigned short const test_lazy_pointer = 0x04; + static unsigned short const test_container = 0x08; + static unsigned short const test_straight_container = 0x10; + static unsigned short const test_inverse_container = 0x20; + static unsigned short const test_readonly_container = 0x40; + static unsigned short const test_readwrite_container = 0x80; + static unsigned short const test_smart_container = 0x100; + + // Exclude versioned containers. + // + static unsigned short const exclude_versioned = 0x200; + + // Treat eager loaded members as belonging to the main section. + // If this flag is specified, then section must be main_section. + // + static unsigned short const include_eager_load = 0x800; + + // Exclude added/deleted members. + // + static unsigned short const exclude_added = 0x1000; + static unsigned short const exclude_deleted = 0x2000; + + // By default the test goes into bases for non-polymorphic + // hierarchies and doesn't go for polymorphic. The following + // flags can be used to alter this behavior. + // + static unsigned short const exclude_base = 0x4000; + static unsigned short const include_base = 0x8000; + + bool + is_a (data_member_path const& mp, + data_member_scope const& ms, + unsigned short flags) + { + return is_a (mp, ms, flags, utype (*mp.back ()), ""); + } + + bool + is_a (data_member_path const&, + data_member_scope const&, + unsigned short flags, + semantics::type&, + string const& key_prefix); + + // Return the number of matching entities. Can be used as a just + // a bool value (0 means no match). + // + size_t + has_a (semantics::class_&, unsigned short flags, object_section* = 0); + +public: + // Process include path by adding the prefix, putting it through + // the include regex list, and adding opening and closing include + // characters ("" or <>) if necessary. The prefix argument indicates + // whether the include prefix specified with the --include-prefix + // option should be added. The open argument can be used to specify + // the opening character. It can have three values: ", <, or \0. In + // case of \0, the character is determined based on the value of the + // --include-with-bracket option. + // + string + process_include_path (string const&, bool prefix = true, char open = '\0'); + + // Diverge output. + // +public: + void + diverge (std::ostream& os) + { + diverge (os.rdbuf ()); + } + + void + diverge (std::streambuf* sb); + + void + restore (); + + // Implementation details. + // +private: + static bool + composite_ (semantics::class_&); + + template <typename X> + static X + indirect_value (semantics::context const& c, string const& key) + { + typedef X (*func) (); + std::type_info const& ti (c.type_info (key)); + + if (ti == typeid (func)) + return c.get<func> (key) (); + else + return c.get<X> (key); + } + + static semantics::type* + indirect_type (semantics::context const& c, + string const& kp, + semantics::names*& hint) + { + typedef semantics::type* (*func) (semantics::names*&); + + string const tk (kp + "-tree-type"); + std::type_info const& ti (c.type_info (tk)); + + if (ti == typeid (func)) + return c.get<func> (tk) (hint); + else + { + hint = c.get<semantics::names*> (kp + "-tree-hint"); + return c.get<semantics::type*> (tk); + } + } + +public: + typedef std::set<string> keyword_set_type; + + struct db_type_type + { + db_type_type () {} + db_type_type (string const& t, string const& it, bool n) + : type (t), id_type (it), null (n) + { + } + + string type; + string id_type; + bool null; + }; + + struct type_map_type: std::map<string, db_type_type> + { + typedef std::map<string, db_type_type> base; + + const_iterator + find (semantics::type&, semantics::names* hint); + }; + +protected: + struct data + { + virtual + ~data () {} + + data (std::ostream& os) + : extra_ (0), + os_ (os.rdbuf ()), + in_comment_ (false), + top_object_ (0), + cur_object_ (0), + sql_name_upper_ ("(.+)", "\\U$1"), + sql_name_lower_ ("(.+)", "\\L$1") + { + } + + public: + void* extra_; + + std::ostream os_; + std::stack<std::streambuf*> os_stack_; + + bool in_comment_; + + semantics::class_* top_object_; + semantics::class_* cur_object_; + + string exp_; + string ext_; + + keyword_set_type keyword_set_; + type_map_type type_map_; + + regex_mapping sql_name_regex_[sql_name_count]; + regexsub sql_name_upper_; + regexsub sql_name_lower_; + + regex_mapping include_regex_; + regex_mapping accessor_regex_; + regex_mapping modifier_regex_; + }; + + typedef cutl::shared_ptr<data> data_ptr; + data_ptr data_; + +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; + features_type& features; + database const db; + + bool& in_comment; + + string& exp; // Export symbol (with trailing space if specified). + string& ext; // Extern symbol. + + keyword_set_type const& keyword_set; + + regex_mapping const& include_regex; + regex_mapping const& accessor_regex; + regex_mapping const& modifier_regex; + + bool embedded_schema; + bool separate_schema; + + bool multi_static; + bool multi_dynamic; + + bool force_versioned; // Force statement processing for debugging. + + // Outermost object or view currently being traversed. + // + semantics::class_*& top_object; + + // Object or view currently being traversed. It can be the same as + // top_object or it can a base of top_object. + // + semantics::class_*& cur_object; + + // Per-database customizable functionality. + // +protected: + // Return empty string if there is no mapping. The type passed is + // already cvr-unqualified. The null out argument indicates whether + // the column should allow NULL values by default. + // + string + database_type (semantics::type& t, + semantics::names* hint, + bool id, + bool* null = 0) + { + return current ().database_type_impl (t, hint, id, null); + } + + // The default implementation uses the type map (populated by the database- + // specific context implementation) to come up with a mapping. + // + virtual string + database_type_impl (semantics::type&, semantics::names*, bool, bool*); + +public: + typedef context root_context; + + virtual + ~context (); + context (); + context (std::ostream&, + semantics::unit&, + options_type const&, + features_type&, + data_ptr = data_ptr ()); + + static context& + current () + { + return *current_; + } + +private: + static context* current_; + +#if __cplusplus >= 201103L || defined(__GXX_EXPERIMENTAL_CXX0X__) + context& + operator= (context const&) = delete; +#else +private: + context& + operator= (context const&); +#endif +}; + +// Create concrete database context. +// +std::unique_ptr<context> +create_context (std::ostream&, + semantics::unit&, + options const&, + features&, + semantics::relational::model*); + +// Checks if scope Y names any of X. +// +template <typename X, typename Y> +bool +has (Y& y) +{ + for (semantics::scope::names_iterator i (y.names_begin ()), + e (y.names_end ()); i != e; ++i) + if (i->named (). template is_a<X> ()) + return true; + + return false; +} + +#include <odb/context.ixx> + +#endif // ODB_CONTEXT_HXX diff --git a/odb/odb/context.ixx b/odb/odb/context.ixx new file mode 100644 index 0000000..5018743 --- /dev/null +++ b/odb/odb/context.ixx @@ -0,0 +1,26 @@ +// file : odb/context.ixx +// license : GNU GPL v3; see accompanying LICENSE file + +inline bool user_section:: +load_empty () const +{ + return !separate_load () || (total == 0 && !containers && !optimistic ()); +} + +inline bool user_section:: +update_empty () const +{ + return total == inverse + readonly && !readwrite_containers; +} + +inline bool user_section:: +optimistic () const +{ + if (!context::optimistic (*object)) + return false; + else + { + semantics::class_* poly_root (context::polymorphic (*object)); + return poly_root == 0 || poly_root == object; + } +} diff --git a/odb/odb/cxx-lexer.cxx b/odb/odb/cxx-lexer.cxx new file mode 100644 index 0000000..67493b7 --- /dev/null +++ b/odb/odb/cxx-lexer.cxx @@ -0,0 +1,364 @@ +// file : odb/cxx-lexer.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <odb/gcc.hxx> + +#include <stdio.h> +#include <stdarg.h> + +#include <new> // std::bad_alloc +#include <cassert> +#include <iostream> + +#include <odb/cxx-lexer.hxx> + +using namespace std; + +// +// cxx_lexer +// + +// Token spelling. See cpplib.h for details. +// +#define OP(e, s) s , +#define TK(e, s) #e , +char const* cxx_lexer::token_spelling[N_TTYPES + 1] = { TTYPE_TABLE "KEYWORD"}; +#undef OP +#undef TK + +cxx_lexer:: +~cxx_lexer () +{ +} + +// +// cxx_tokens_lexer +// + +void cxx_tokens_lexer:: +start (cxx_tokens const& ts, location_t start_loc) +{ + tokens_ = &ts; + cur_ = ts.begin (); + loc_ = start_loc; +} + +cpp_ttype cxx_tokens_lexer:: +next (std::string& token, tree* node) +{ + if (cur_ != tokens_->end ()) + { + loc_ = cur_->loc; + token = cur_->literal; + if (node != 0) + *node = cur_->node; + return static_cast<cpp_ttype> (cur_++->type); + } + else + return CPP_EOF; +} + +location_t cxx_tokens_lexer:: +location () const +{ + return loc_; +} + +// +// cxx_pragma_lexer +// + +void cxx_pragma_lexer:: +start () +{ + token_ = &token_data_; + type_ = &type_data_; +} + +string cxx_pragma_lexer:: +start (tree& token, cpp_ttype& type) +{ + token_ = &token; + type_ = &type; + + return translate (); +} + +cpp_ttype cxx_pragma_lexer:: +next (string& token, tree* node) +{ + *type_ = pragma_lex (token_); + + // See if this is a keyword using the C++ parser machinery and + // the current C++ dialect. + // + if (*type_ == CPP_NAME && +#if BUILDING_GCC_MAJOR >= 8 + IDENTIFIER_KEYWORD_P (*token_) +#else + C_IS_RESERVED_WORD (*token_) +#endif + ) + *type_ = CPP_KEYWORD; + + if (node != 0 && node != token_) + *node = *token_; + + token = translate (); + return *type_; +} + +location_t cxx_pragma_lexer:: +location () const +{ + // Starting from GCC 6 the input location seem to require the same + // translation as what we do in real_source_location(). + // +#if BUILDING_GCC_MAJOR >= 6 + return linemap_resolve_location ( + line_table, input_location, LRK_MACRO_EXPANSION_POINT, 0); +#else + return input_location; +#endif +} + +string cxx_pragma_lexer:: +translate () +{ + string r; + + if (*type_ == CPP_NAME || *type_ == CPP_KEYWORD) + r = IDENTIFIER_POINTER (*token_); + else if (*type_ == CPP_STRING) + r = TREE_STRING_POINTER (*token_); + + return r; +} + +// +// cxx_string_lexer +// + +// Diagnostics callback. +// +extern "C" bool +cpp_diagnostic_callback ( + cpp_reader* reader, +#if BUILDING_GCC_MAJOR >= 9 + cpp_diagnostic_level level, +#else + int level, +#endif +#if BUILDING_GCC_MAJOR > 4 || BUILDING_GCC_MAJOR == 4 && BUILDING_GCC_MINOR > 5 +#if BUILDING_GCC_MAJOR >= 9 + cpp_warning_reason, +#else + int /*reason*/, // Added in GCC 4.6.0. +#endif +#endif +#if BUILDING_GCC_MAJOR <= 5 + location_t, + unsigned int, +#else + rich_location*, +#endif + char const* msg, + va_list *ap) +{ + char const* kind (0); + switch (level) + { + case CPP_DL_NOTE: + case CPP_DL_WARNING_SYSHDR: + case CPP_DL_WARNING: + case CPP_DL_PEDWARN: + // Ignore these. + break; + case CPP_DL_ERROR: + case CPP_DL_FATAL: + kind = "error"; + break; + case CPP_DL_ICE: + kind = "ice"; + break; + default: + kind = "unknown"; + break; + } + + if (kind != 0) + { + fprintf (stderr, "%s: ", kind); + vfprintf (stderr, msg, *ap); + fprintf (stderr, "\n"); + + // By resetting the callback we indicate to cxx_string_lexer that there + // was an error. + // +#if BUILDING_GCC_MAJOR >= 9 + cpp_get_callbacks (reader)->diagnostic = 0; +#else + cpp_get_callbacks (reader)->error = 0; +#endif + return true; + } + + return false; +} + +cxx_string_lexer:: +cxx_string_lexer () + : reader_ (0) +{ +#if BUILDING_GCC_MAJOR >= 5 + linemap_init (&line_map_, UNKNOWN_LOCATION); +#else + linemap_init (&line_map_); +#endif + +#if BUILDING_GCC_MAJOR > 4 || BUILDING_GCC_MAJOR == 4 && BUILDING_GCC_MINOR > 6 + line_map_.round_alloc_size = ggc_round_alloc_size; +#endif + + linemap_add (&line_map_, LC_ENTER, 0, "<memory>", 0); + + reader_ = cpp_create_reader ( + cxx_dialect == cxx0x // Nothing new for C++14. +#if BUILDING_GCC_MAJOR > 4 || BUILDING_GCC_MAJOR == 4 && BUILDING_GCC_MINOR > 6 + ? CLK_CXX11 +#else + ? CLK_CXX0X +#endif + : CLK_CXX98, + 0, + &line_map_); + + if (reader_ == 0) + throw bad_alloc (); + + callbacks_ = cpp_get_callbacks (reader_); +} + +cxx_string_lexer:: +~cxx_string_lexer () +{ + if (reader_ != 0) + cpp_destroy (reader_); + + // Was removed as "dead code" in GCC 4.7.0. + // +#if BUILDING_GCC_MAJOR == 4 && BUILDING_GCC_MINOR <= 6 + linemap_free (&line_map_); +#endif +} + +void cxx_string_lexer:: +start (string const& data) +{ + // The previous lexing session should have popped the buffer. + // + assert (cpp_get_buffer (reader_) == 0); + +#if BUILDING_GCC_MAJOR >= 9 + callbacks_->diagnostic = &cpp_diagnostic_callback; +#else + callbacks_->error = &cpp_diagnostic_callback; +#endif + + data_ = data; + buf_ = data; + buf_ += '\n'; + loc_ = 0; + + cpp_push_buffer ( + reader_, + reinterpret_cast<unsigned char const*> (buf_.c_str ()), + buf_.size (), + true); +} + +cpp_ttype cxx_string_lexer:: +next (string& token, tree* node) +{ + token.clear (); + cpp_token const* t (cpp_get_token (reader_)); + + // If there was an error, the callback will be reset to 0. Diagnostics has + // already been issued. + // +#if BUILDING_GCC_MAJOR >= 9 + if (callbacks_->diagnostic == 0) +#else + if (callbacks_->error == 0) +#endif + throw invalid_input (); + + cpp_ttype tt (t->type); + + switch (tt) + { + case CPP_NAME: + { + char const* name ( + reinterpret_cast<char const*> (NODE_NAME (t->val.node.node))); + + // See if this is a keyword using the C++ parser machinery and + // the current C++ dialect. + // + tree id (get_identifier (name)); + + if ( +#if BUILDING_GCC_MAJOR >= 8 + IDENTIFIER_KEYWORD_P (id) +#else + C_IS_RESERVED_WORD (id) +#endif + ) + tt = CPP_KEYWORD; + + if (node != 0) + *node = id; + + token = name; + break; + } + case CPP_STRING: + case CPP_NUMBER: + { + if (node != 0) + *node = 0; // Doesn't seem to be available. + + cpp_string const& s (t->val.str); + token.assign (reinterpret_cast<char const*> (s.text), s.len); + break; + } + default: + { + if (tt <= CPP_LAST_PUNCTUATOR) + { + if (node != 0) + *node = 0; + token = token_spelling[tt]; + } + else + { + cerr << "unexpected token '" << token_spelling[tt] << "' in '" << + data_ << "'" << endl; + throw invalid_input (); + } + break; + } + } + + // Cache the location of this token. + // + loc_ = t->src_loc; + + return tt; +} + +location_t cxx_string_lexer:: +location () const +{ + return loc_; +} diff --git a/odb/odb/cxx-lexer.hxx b/odb/odb/cxx-lexer.hxx new file mode 100644 index 0000000..ea16132 --- /dev/null +++ b/odb/odb/cxx-lexer.hxx @@ -0,0 +1,131 @@ +// file : odb/cxx-lexer.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_CXX_LEXER_HXX +#define ODB_CXX_LEXER_HXX + +#include <odb/gcc.hxx> + +#include <string> + +#include <odb/cxx-token.hxx> + +// A C++ keyword. This is an extension to libcpp token types. GCC 4.7.0 +// adds this define. +// +#if BUILDING_GCC_MAJOR == 4 && BUILDING_GCC_MINOR <= 6 +# define CPP_KEYWORD ((cpp_ttype) (N_TTYPES + 1)) +#endif + +class cxx_lexer +{ +public: + virtual + ~cxx_lexer (); + +public: + struct invalid_input {}; + + virtual cpp_ttype + next (std::string& token, tree* node = 0) = 0; + + // Location of the last returned token. + // + virtual location_t + location () const = 0; + +public: + static char const* token_spelling[N_TTYPES + 1]; +}; + + +// Adapter to scan a saved token sequence. It returns numbers in the same +// form as they were saved in the token sequence. +// +class cxx_tokens_lexer: public cxx_lexer +{ +public: + void + start (cxx_tokens const&, location_t start_loc = 0); + + virtual cpp_ttype + next (std::string& token, tree* node = 0); + + virtual location_t + location () const; + +private: + cxx_tokens const* tokens_; + cxx_tokens::const_iterator cur_; + location_t loc_; +}; + + +// A thin wrapper around the pragma_lex() function that recognizes +// CPP_KEYWORD. It returns numbers as tree nodes. +// +class cxx_pragma_lexer: public cxx_lexer +{ +public: + void + start (); + + // Start with an already extracted (using the pragma_lex() function) + // token. This function translates the CPP_NAME to CPP_KEYWORD if + // necessary and returns the string token. It also uses the passed + // token and type for subsequent calls to next() so after the last + // next() call they will contain the information about the last + // token parsed. + // + std::string + start (tree& token, cpp_ttype& type); + + virtual cpp_ttype + next (std::string& token, tree* node = 0); + + virtual location_t + location () const; + +private: + std::string + translate (); + +private: + tree* token_; + cpp_ttype* type_; + + tree token_data_; + cpp_ttype type_data_; +}; + +// A thin wrapper around cpp_reader for lexing C++ code fragments. It +// returns numbers as string literals. +// +class cxx_string_lexer: public cxx_lexer +{ +public: + cxx_string_lexer (); + + virtual + ~cxx_string_lexer (); + +public: + void + start (std::string const&); + + virtual cpp_ttype + next (std::string& token, tree* node = 0); + + virtual location_t + location () const; + +private: + std::string data_; + std::string buf_; + line_maps line_map_; + cpp_reader* reader_; + cpp_callbacks* callbacks_; + location_t loc_; +}; + +#endif // ODB_CXX_LEXER_HXX diff --git a/odb/odb/cxx-token.hxx b/odb/odb/cxx-token.hxx new file mode 100644 index 0000000..34b28d4 --- /dev/null +++ b/odb/odb/cxx-token.hxx @@ -0,0 +1,30 @@ +// file : odb/cxx-token.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_CXX_TOKEN_HXX +#define ODB_CXX_TOKEN_HXX + +#include <string> +#include <vector> + +#include <odb/gcc-fwd.hxx> + +struct cxx_token +{ + cxx_token (location_t l, + unsigned int t, + std::string const& lt = std::string (), + tree n = 0) + : loc (l), type (t), literal (lt), node (n) {} + + location_t loc; // Location of this token. + unsigned int type; // Untyped cpp_ttype. + std::string literal; // Only used for name, keyword, string, amd number. + tree node; // Tree node for the number. The number can be + // represented as either literal, tree node, or + // both, depending on which lexer was used. +}; + +typedef std::vector<cxx_token> cxx_tokens; + +#endif // ODB_CXX_TOKEN_HXX diff --git a/odb/odb/diagnostics.cxx b/odb/odb/diagnostics.cxx new file mode 100644 index 0000000..57166bb --- /dev/null +++ b/odb/odb/diagnostics.cxx @@ -0,0 +1,136 @@ +// file : odb/diagnostics.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <odb/gcc.hxx> + +#include <sstream> + +#include <odb/cxx-lexer.hxx> +#include <odb/diagnostics.hxx> + +using namespace std; +using cutl::fs::path; + +std::ostream& +error (path const& p, size_t line, size_t clmn) +{ + //@@ We only need to do this if we are still parsing (i.e., + // pragma parsing). Is there a way to detect this? + // + errorcount++; + + cerr << p << ':' << line << ':' << clmn << ": error: "; + return cerr; +} + +std::ostream& +warn (path const& p, size_t line, size_t clmn) +{ + warningcount++; + + cerr << p << ':' << line << ':' << clmn << ": warning: "; + return cerr; +} + +std::ostream& +info (path const& p, size_t line, size_t clmn) +{ + cerr << p << ':' << line << ':' << clmn << ": info: "; + return cerr; +} + +std::ostream& +error (location_t loc) +{ + errorcount++; + cerr << LOCATION_FILE (loc) << ':' + << LOCATION_LINE (loc) << ':' + << LOCATION_COLUMN (loc) << ':' + << " error: "; + return cerr; +} + +std::ostream& +warn (location_t loc) +{ + warningcount++; + cerr << LOCATION_FILE (loc) << ':' + << LOCATION_LINE (loc) << ':' + << LOCATION_COLUMN (loc) << ':' + << " warning: "; + return cerr; +} + +std::ostream& +info (location_t loc) +{ + cerr << LOCATION_FILE (loc) << ':' + << LOCATION_LINE (loc) << ':' + << LOCATION_COLUMN (loc) << ':' + << " info: "; + return cerr; +} + +std::ostream& +error (cxx_lexer& l) +{ + return error (l.location ()); +} + +std::ostream& +warn (cxx_lexer& l) +{ + return warn (l.location ()); +} + +std::ostream& +info (cxx_lexer& l) +{ + return info (l.location ()); +} + +std::string +location_string (path const& p, size_t line, size_t clmn, bool leaf) +{ + ostringstream ostr; + + if (leaf) + ostr << p.leaf (); + else + ostr << p; + + ostr << ':' << line << ':' << clmn; + return ostr.str (); +} + +std::string +location_string (location_t loc, bool leaf) +{ + ostringstream ostr; + + if (leaf) + ostr << path (LOCATION_FILE (loc)).leaf (); + else + ostr << LOCATION_FILE (loc); + + ostr << ':' << LOCATION_LINE (loc) << ':' << LOCATION_COLUMN (loc); + return ostr.str (); +} + +path +location_file (location_t loc) +{ + return path (LOCATION_FILE (loc)); +} + +size_t +location_line (location_t loc) +{ + return LOCATION_LINE (loc); +} + +size_t +location_column (location_t loc) +{ + return LOCATION_COLUMN (loc); +} diff --git a/odb/odb/diagnostics.hxx b/odb/odb/diagnostics.hxx new file mode 100644 index 0000000..46f2272 --- /dev/null +++ b/odb/odb/diagnostics.hxx @@ -0,0 +1,96 @@ +// file : odb/diagnostics.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_DIAGNOSTICS_HXX +#define ODB_DIAGNOSTICS_HXX + +#include <odb/gcc-fwd.hxx> + +#include <string> +#include <cstddef> +#include <iostream> + +#include <libcutl/fs/path.hxx> + +#include <odb/location.hxx> + +using std::endl; + +std::ostream& +error (cutl::fs::path const&, std::size_t line, std::size_t clmn); + +std::ostream& +warn (cutl::fs::path const&, std::size_t line, std::size_t clmn); + +std::ostream& +info (cutl::fs::path const&, std::size_t line, std::size_t clmn); + +inline std::ostream& +error (location const& l) +{ + return error (l.file, l.line, l.column); +} + +inline std::ostream& +warn (location const&l) +{ + return warn (l.file, l.line, l.column); +} + +inline std::ostream& +info (location const&l) +{ + return info (l.file, l.line, l.column); +} + +std::ostream& +error (location_t); + +std::ostream& +warn (location_t); + +std::ostream& +info (location_t); + +// +// +class cxx_lexer; + +std::ostream& +error (cxx_lexer&); + +std::ostream& +warn (cxx_lexer&); + +std::ostream& +info (cxx_lexer&); + +// Location as a string in the "<file>:<line>:<column>" format. +// +std::string +location_string (cutl::fs::path const&, + std::size_t line, + std::size_t clmn, + bool leaf = false); + +inline std::string +location_string (location const& l, bool leaf = false) +{ + return location_string (l.file, l.line, l.column, leaf); +} + +std::string +location_string (location_t, bool leaf = false); + +// location_t macro wrappers. +// +cutl::fs::path +location_file (location_t); + +std::size_t +location_line (location_t); + +std::size_t +location_column (location_t); + +#endif // ODB_DIAGNOSTICS_HXX diff --git a/odb/odb/emitter.cxx b/odb/odb/emitter.cxx new file mode 100644 index 0000000..d6d8eac --- /dev/null +++ b/odb/odb/emitter.cxx @@ -0,0 +1,50 @@ +// file : odb/emitter.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <odb/context.hxx> +#include <odb/emitter.hxx> + +using namespace std; + +void emitter:: +pre () +{ +} + +void emitter:: +post () +{ +} + +int emitter_ostream::streambuf:: +sync () +{ + string s (str ()); + + // Get rid of the trailing newline if any. + // + if (string::size_type n = s.size ()) + { + if (s[n - 1] == '\n') + s.resize (n - 1); + } + + // Temporary restore output diversion. + // + bool r (false); + context& ctx (context::current ()); + + if (ctx.os.rdbuf () == this) + { + ctx.restore (); + r = true; + } + + e_.line (s); + + if (r) + ctx.diverge (this); + + str (string ()); + return 0; +} diff --git a/odb/odb/emitter.hxx b/odb/odb/emitter.hxx new file mode 100644 index 0000000..1071dab --- /dev/null +++ b/odb/odb/emitter.hxx @@ -0,0 +1,47 @@ +// file : odb/emitter.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_EMITTER_HXX +#define ODB_EMITTER_HXX + +#include <string> +#include <sstream> + +// Emit a code construct one line at a time. +// +struct emitter +{ + virtual void + pre (); + + virtual void + line (const std::string&) = 0; + + virtual void + post (); +}; + +// Send output line-by-line (std::endl marker) to the emitter. +// +class emitter_ostream: public std::ostream +{ +public: + emitter_ostream (emitter& e): std::ostream (&buf_), buf_ (e) {} + +private: + class streambuf: public std::stringbuf + { + public: + streambuf (emitter& e): e_ (e) {} + + virtual int + sync (); + + private: + emitter& e_; + }; + + streambuf buf_; +}; + +#endif // ODB_EMITTER_HXX diff --git a/odb/odb/features.hxx b/odb/odb/features.hxx new file mode 100644 index 0000000..1093684 --- /dev/null +++ b/odb/odb/features.hxx @@ -0,0 +1,25 @@ +// file : odb/features.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_FEATURES_HXX +#define ODB_FEATURES_HXX + +#include <cstring> // std::memset + +// Optional features used by client code that affect generated code. +// +struct features +{ + features () {std::memset (this, 0, sizeof (features));} + + bool tr1_pointer; + bool boost_pointer; + bool simple_object; + bool polymorphic_object; + bool no_id_object; + bool session_object; + bool section; + bool view; +}; + +#endif // ODB_FEATURES_HXX diff --git a/odb/odb/gcc-fwd.hxx b/odb/odb/gcc-fwd.hxx new file mode 100644 index 0000000..83d3746 --- /dev/null +++ b/odb/odb/gcc-fwd.hxx @@ -0,0 +1,64 @@ +// file : odb/gcc-fwd.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_GCC_FWD_HXX +#define ODB_GCC_FWD_HXX + +#if __GNUC__ >= 5 +# if !__has_include(<bversion.h>) +# error GCC plugin headers are not installed +# endif +#endif + +#include <bversion.h> + +#if BUILDING_GCC_MAJOR >= 6 + +// If we include <system.h> here, it pulls in all kinds of GCC trouble that +// "poisons" standard C/C++ declarations; see safe-ctype.h. So instead we +// are going to "exclude" safe-ctype.h. To compensate, however, we will +// include it first thing in gcc.hxx. +// +# include <config.h> +# define SAFE_CTYPE_H +# include <system.h> +# undef SAFE_CTYPE_H +# include <coretypes.h> + +typedef unsigned int source_location; // <line-map.h> +typedef source_location location_t; // <input.h> + +#else // GCC < 6 + +#if BUILDING_GCC_MAJOR > 4 || BUILDING_GCC_MAJOR == 4 && BUILDING_GCC_MINOR > 8 +# include <limits.h> // CHAR_BIT +# include <config.h> +#endif + +#if BUILDING_GCC_MAJOR >= 5 +# include <stdint.h> // Needed by coretypes.h +#endif + +extern "C" +{ +// The hwint.h header uses gcc_checking_assert macro from system.h. But +// if we include system.h here, it pulls in all kinds of GCC trouble that +// "poisons" standard C/C++ declarations (see safe-ctype.h for an example). +// Instead we are just going to provide the no-op definition of this macro. +// +#if BUILDING_GCC_MAJOR > 4 || BUILDING_GCC_MAJOR == 4 && BUILDING_GCC_MINOR > 8 +# define gcc_checking_assert(expr) ((void)(0 & (expr))) +# include <hwint.h> +# undef gcc_checking_assert +#endif + +#include <coretypes.h> + +typedef unsigned int source_location; // <line-map.h> +typedef source_location location_t; // <input.h> + +} // extern "C" + +#endif + +#endif // ODB_GCC_FWD_HXX diff --git a/odb/odb/gcc.hxx b/odb/odb/gcc.hxx new file mode 100644 index 0000000..e5fecef --- /dev/null +++ b/odb/odb/gcc.hxx @@ -0,0 +1,215 @@ +// file : odb/gcc.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_GCC_HXX +#define ODB_GCC_HXX + +#include <odb/gcc-fwd.hxx> + +// Actually, let's keep it out. With it included we can compile in C++98 +// but not in C++14 (GCC 6 default). +// +// #if BUILDING_GCC_MAJOR >= 6 +// # include <safe-ctype.h> // See gcc-fwd.hxx. +// #endif + +// GCC header includes to get the plugin and parse tree declarations. +// The order is important and doesn't follow any kind of logic. +// + +#include <stdlib.h> +#include <gmp.h> + +#include <cstdlib> // Include before GCC poisons some declarations. + +// GCC 4.7 can be built using either C or C++ compiler. From 4.8 it +// is always built as C++. +// +#if BUILDING_GCC_MAJOR == 4 && BUILDING_GCC_MINOR <= 6 +# define ODB_GCC_PLUGIN_C +#elif BUILDING_GCC_MAJOR == 4 && BUILDING_GCC_MINOR <= 7 +# include <config.h> +# ifndef ENABLE_BUILD_WITH_CXX +# define ODB_GCC_PLUGIN_C +# endif +#endif + +#ifdef ODB_GCC_PLUGIN_C +extern "C" +{ +#endif + +// GCC's system.h below includes safe-ctype.h which "disables" versions +// from ctype.h. Well, now it's gonna learn how it feels to be disabled. +// +#define SAFE_CTYPE_H + +#include <gcc-plugin.h> + +#include <config.h> +#include <system.h> +#include <coretypes.h> +#include <tree.h> +#include <real.h> + +#include <cpplib.h> +#include <cp/cp-tree.h> + +#if BUILDING_GCC_MAJOR > 4 || BUILDING_GCC_MAJOR == 4 && BUILDING_GCC_MINOR > 5 +# include <c-family/c-common.h> +# include <c-family/c-pragma.h> +#else +# include <c-common.h> +# include <c-pragma.h> +#endif + +#if BUILDING_GCC_MAJOR > 4 || BUILDING_GCC_MAJOR == 4 && BUILDING_GCC_MINOR > 8 +# include <stringpool.h> // get_identifier +#endif + +#include <diagnostic.h> +#include <output.h> + +#ifdef ODB_GCC_PLUGIN_C +} // extern "C" +#endif + +// Get the value of INTEGER_CST reinterpreted as unsigned. +// +inline unsigned long long +integer_value (tree n) +{ + unsigned long long val; + +#if BUILDING_GCC_MAJOR >= 5 + if (tree_fits_uhwi_p (n)) + val = static_cast<unsigned long long> (tree_to_uhwi (n)); + else + val = static_cast<unsigned long long> (tree_to_shwi (n)); +#else + HOST_WIDE_INT hwl (TREE_INT_CST_LOW (n)); + HOST_WIDE_INT hwh (TREE_INT_CST_HIGH (n)); + unsigned short width (HOST_BITS_PER_WIDE_INT); + + if (hwh == 0) + val = static_cast<unsigned long long> (hwl); + else if (hwh == -1 && hwl != 0) + val = static_cast<unsigned long long> (hwl); + else + val = static_cast<unsigned long long> ((hwh << width) + hwl); +#endif + + return val; +} + +// Since 4.7.0 the location may point inside a macro rather than at +// the expansion point. We are only really interested in the expansion +// points so we use the real_source_location() wrapper rather than +// DECL_SOURCE_LOCATION() to do this at the source. +// +inline location_t +real_source_location (tree n) +{ + location_t l (DECL_SOURCE_LOCATION (n)); + +#if BUILDING_GCC_MAJOR > 4 || BUILDING_GCC_MAJOR == 4 && BUILDING_GCC_MINOR > 6 + l = linemap_resolve_location (line_table, l, LRK_MACRO_EXPANSION_POINT, 0); +#endif + + return l; +} + +// In 4.9.0 the tree code type was changed from int to enum tree_code. +// the tree_code_name array is also gone with the get_tree_code_name() +// function in its place. +// +#if BUILDING_GCC_MAJOR > 4 || BUILDING_GCC_MAJOR == 4 && BUILDING_GCC_MINOR > 8 +typedef enum tree_code gcc_tree_code_type; + +inline const char* +gcc_tree_code_name (gcc_tree_code_type tc) {return get_tree_code_name (tc);} +#else +typedef int gcc_tree_code_type; + +inline const char* +gcc_tree_code_name (gcc_tree_code_type tc) {return tree_code_name[tc];} +#endif + +// Only since GCC 4.7.0. +// +#ifndef LOCATION_COLUMN +#define LOCATION_COLUMN(LOC) (expand_location (LOC).column) +#endif + +#ifndef DECL_SOURCE_COLUMN +#define DECL_SOURCE_COLUMN(NODE) LOCATION_COLUMN (DECL_SOURCE_LOCATION (NODE)) +#endif + +// Only since GCC 4.6.0. +// +#ifndef DECL_CHAIN +#define DECL_CHAIN(x) TREE_CHAIN(x) +#endif + +// In GCC 6 ANON_AGGRNAME_P became anon_aggrname_p(). +// In GCC 10 anon_aggrname_p() became IDENTIFIER_ANON_P. +// +#if BUILDING_GCC_MAJOR < 6 +# define IDENTIFIER_ANON_P(X) ANON_AGGRNAME_P(X) +#elif BUILDING_GCC_MAJOR < 10 +# define IDENTIFIER_ANON_P(X) anon_aggrname_p(X) +#endif + +// In GCC 9: +// +// INCLUDED_FROM Became linemap_included_from_linemap(). +// +// LAST_SOURCE_LINE Was removed apparently as no longer used. Studying +// the line-map.h diff from 8.3 suggests that the old +// implementation should still work. +// +#if BUILDING_GCC_MAJOR >= 9 + +inline const line_map_ordinary* +INCLUDED_FROM (line_maps* set, const line_map_ordinary* map) +{ + return linemap_included_from_linemap (set, map); +} + +inline source_location +LAST_SOURCE_LINE_LOCATION (const line_map_ordinary* map) +{ + return (((map[1].start_location - 1 + - map->start_location) + & ~((1 << map->m_column_and_range_bits) - 1)) + + map->start_location); +} + +inline linenum_type +LAST_SOURCE_LINE (const line_map_ordinary* map) +{ + return SOURCE_LINE (map, LAST_SOURCE_LINE_LOCATION (map)); +} + +#endif + +// In GCC 11: +// +// lookup_qualified_name() has a new interface. +// +// DECL_IS_BUILTIN became DECL_IS_UNDECLARED_BUILTIN. +// +#if BUILDING_GCC_MAJOR >= 11 + +inline tree +lookup_qualified_name (tree scope, tree name, bool type, bool complain) +{ + return lookup_qualified_name ( + scope, name, (type ? LOOK_want::TYPE : LOOK_want::NORMAL), complain); +} + +#define DECL_IS_BUILTIN(decl) DECL_IS_UNDECLARED_BUILTIN(decl) + +#endif + +#endif // ODB_GCC_HXX diff --git a/odb/odb/generate.hxx b/odb/odb/generate.hxx new file mode 100644 index 0000000..b3b9c43 --- /dev/null +++ b/odb/odb/generate.hxx @@ -0,0 +1,31 @@ +// file : odb/generate.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_GENERATE_HXX +#define ODB_GENERATE_HXX + +namespace include +{ + bool + generate (bool header); +} + +namespace header +{ + void + generate (); +} + +namespace inline_ +{ + void + generate (); +} + +namespace source +{ + void + generate (); +} + +#endif // ODB_GENERATE_HXX diff --git a/odb/odb/generator.cxx b/odb/odb/generator.cxx new file mode 100644 index 0000000..ec0fefe --- /dev/null +++ b/odb/odb/generator.cxx @@ -0,0 +1,1044 @@ +// file : odb/generator.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <cctype> // std::toupper, std::is{alpha,upper,lower} +#include <string> +#include <memory> // std::unique_ptr +#include <iomanip> +#include <fstream> +#include <sstream> +#include <iostream> + +#include <libcutl/fs/auto-remove.hxx> + +#include <libcutl/compiler/code-stream.hxx> +#include <libcutl/compiler/cxx-indenter.hxx> +#include <libcutl/compiler/sloc-counter.hxx> + +#ifdef ODB_BUILD2 +#include <libstudxml/parser.hxx> +#include <libstudxml/serializer.hxx> +#else +#include <libcutl/xml/parser.hxx> +#include <libcutl/xml/serializer.hxx> +#endif + +#include <odb/version.hxx> +#include <odb/context.hxx> +#include <odb/generator.hxx> + +#include <odb/semantics/relational/model.hxx> +#include <odb/semantics/relational/changeset.hxx> +#include <odb/semantics/relational/changelog.hxx> + +#include <odb/generate.hxx> +#include <odb/relational/generate.hxx> + +using namespace std; +using namespace cutl; + +using semantics::path; +typedef vector<string> strings; +typedef vector<path> paths; +typedef vector<cutl::shared_ptr<ofstream> > ofstreams; + +namespace +{ + static char const cxx_file_header[] = + "// -*- C++ -*-\n" + "//\n" + "// This file was generated by ODB, object-relational mapping (ORM)\n" + "// compiler for C++.\n" + "//\n\n"; + + static char const sql_file_header[] = + "/* This file was generated by ODB, object-relational mapping (ORM)\n" + " * compiler for C++.\n" + " */\n\n"; + + void + open (ifstream& ifs, path const& p) + { + ifs.open (p.string ().c_str (), ios_base::in | ios_base::binary); + + if (!ifs.is_open ()) + { + cerr << "error: unable to open '" << p << "' in read mode" << endl; + throw generator_failed (); + } + } + + void + open (ofstream& ofs, path const& p, ios_base::openmode m = ios_base::out) + { + ofs.open (p.string ().c_str (), ios_base::out | m); + + if (!ofs.is_open ()) + { + cerr << "error: unable to open '" << p << "' in write mode" << endl; + throw generator_failed (); + } + } + + void + append (ostream& os, strings const& text) + { + for (strings::const_iterator i (text.begin ()); + i != text.end (); ++i) + { + os << *i << endl; + } + } + + void + append (ostream& os, path const& file) + { + ifstream ifs; + open (ifs, file); + + // getline() will set the failbit if it failed to extract anything, + // not even the delimiter and eofbit if it reached eof before seeing + // the delimiter. + // + // We used to just do: + // + // os << ifs.rdbuf (); + // + // But that has some drawbacks: it won't end with a newline if the file + // doesn't end with one. There were also some issues with Windows newlines + // (we ended up doubling them). + // + for (string s; getline (ifs, s); ) + os << s << endl; + } + + // Append prologue/interlude/epilogue. + // + void + append_logue (ostream& os, + database db, + database_map<vector<string> > const& text, + database_map<vector<string> > const& file, + char const* begin_comment, + char const* end_comment) + { + bool t (text.count (db) != 0); + bool f (file.count (db) != 0); + + if (t || f) + { + os << begin_comment << endl; + + if (t) + append (os, text[db]); + + if (f) + { + strings const& fs (file[db]); + + for (strings::const_iterator i (fs.begin ()); + i != fs.end (); ++i) + append (os, path (*i)); + } + + os << end_comment << endl + << endl; + } + } +} + +void +generate (options const& ops, + features& fts, + semantics::unit& unit, + path const& p, + paths const& inputs) +{ + namespace sema_rel = semantics::relational; + using cutl::shared_ptr; + + try + { + database db (ops.database ()[0]); + multi_database md (ops.multi_database ()); + + // First create the database model. + // + bool gen_schema (ops.generate_schema () && db != database::common); + + shared_ptr<sema_rel::model> model; + + if (gen_schema) + { + unique_ptr<context> ctx (create_context (cerr, unit, ops, fts, 0)); + + switch (db) + { + case database::mssql: + case database::mysql: + case database::oracle: + case database::pgsql: + case database::sqlite: + { + model = relational::model::generate (); + break; + } + case database::common: + break; + } + } + + // Input files. + // + path file (ops.input_name ().empty () + ? p.leaf () + : path (ops.input_name ()).leaf ()); + string base (file.base ().string ()); + + path in_log_path; + path log_dir (ops.changelog_dir ().count (db) != 0 + ? ops.changelog_dir ()[db] + : ""); + if (ops.changelog_in ().count (db) != 0) + { + in_log_path = path (ops.changelog_in ()[db]); + + if (!log_dir.empty () && !in_log_path.absolute ()) + in_log_path = log_dir / in_log_path; + } + else if (ops.changelog ().count (db) != 0) + { + in_log_path = path (ops.changelog ()[db]); + + if (!in_log_path.absolute () && !log_dir.empty ()) + in_log_path = log_dir / in_log_path; + } + else + { + string log_name (base + ops.changelog_file_suffix ()[db] + + ops.changelog_suffix ()); + in_log_path = path (log_name); + + if (!log_dir.empty ()) + in_log_path = log_dir / in_log_path; + else + in_log_path = p.directory () / in_log_path; // Use input directory. + } + + // Load the old changelog and generate a new one. + // + bool gen_changelog (gen_schema && unit.count ("model-version") != 0); + cutl::shared_ptr<sema_rel::changelog> changelog; + cutl::shared_ptr<sema_rel::changelog> old_changelog; + string old_changelog_xml; + + path out_log_path; + if (ops.changelog_out ().count (db)) + { + out_log_path = path (ops.changelog_out ()[db]); + + if (!log_dir.empty () && !out_log_path.absolute ()) + out_log_path = log_dir / out_log_path; + } + else + out_log_path = in_log_path; + + if (gen_changelog) + { + ifstream log; + + // Unless we are forced to re-initialize the changelog, load the + // old one. + // + if (!ops.init_changelog ()) + log.open (in_log_path.string ().c_str (), + ios_base::in | ios_base::binary); + + if (log.is_open ()) // The changelog might not exist. + { + try + { + // Get the XML into a buffer. We use it to avoid modifying the + // file when the changelog hasn't changed. + // + for (bool first (true); !log.eof (); ) + { + string line; + getline (log, line); + + if (log.fail ()) + ios_base::failure ("getline"); + + if (first) + first = false; + else + old_changelog_xml += '\n'; + + old_changelog_xml += line; + } + + istringstream is (old_changelog_xml); + is.exceptions (ios_base::badbit | ios_base::failbit); + + xml::parser p (is, in_log_path.string ()); + old_changelog.reset (new (shared) sema_rel::changelog (p)); + + if (old_changelog->database () != db.string ()) + { + cerr << in_log_path << ": error: wrong database '" << + old_changelog->database () << "', expected '" << db << + "'" << endl; + throw generator_failed (); + } + + string sn (ops.schema_name ()[db]); + if (old_changelog->schema_name () != sn) + { + cerr << in_log_path << ": error: wrong schema name '" << + old_changelog->schema_name () << "', expected '" << sn << + "'" << endl; + throw generator_failed (); + } + } + catch (const ios_base::failure& e) + { + cerr << in_log_path << ": read failure" << endl; + throw generator_failed (); + } + catch (const xml::parsing& e) + { + cerr << e.what () << endl; + throw generator_failed (); + } + } + + changelog = relational::changelog::generate ( + *model, + unit.get<model_version> ("model-version"), + old_changelog.get (), + in_log_path.string (), + out_log_path.string (), + ops); + } + + // Output files. + // + fs::auto_removes auto_rm; + + string hxx_name (base + ops.odb_file_suffix ()[db] + ops.hxx_suffix ()); + string ixx_name (base + ops.odb_file_suffix ()[db] + ops.ixx_suffix ()); + string cxx_name (base + ops.odb_file_suffix ()[db] + ops.cxx_suffix ()); + string sch_name (base + ops.schema_file_suffix ()[db] + ops.cxx_suffix ()); + string sql_name (base + ops.sql_file_suffix ()[db] + ops.sql_suffix ()); + + path hxx_path (hxx_name); + path ixx_path (ixx_name); + path cxx_path (cxx_name); + path sch_path (sch_name); + path sql_path (sql_name); + paths mig_pre_paths; + paths mig_post_paths; + + bool gen_migration (gen_changelog && !ops.suppress_migration ()); + bool gen_sql_migration ( + gen_migration && ops.schema_format ()[db].count (schema_format::sql)); + + if (gen_sql_migration) + { + for (sema_rel::changelog::contains_changeset_iterator i ( + changelog->contains_changeset_begin ()); + i != changelog->contains_changeset_end (); ++i) + { + sema_rel::changeset& cs (i->changeset ()); + + // Default format: %N[-D%]-%3V-{pre|post}.sql + // + string n (base); + + if (md != multi_database::disabled) + n += '-' + db.string (); + + ostringstream os; + os << setfill ('0') << setw (3) << cs.version (); + n += '-' + os.str (); + + mig_pre_paths.push_back (path (n + "-pre" + ops.sql_suffix ())); + mig_post_paths.push_back (path (n + "-post" + ops.sql_suffix ())); + } + } + + if (!ops.output_dir ().empty ()) + { + path dir (ops.output_dir ()); + hxx_path = dir / hxx_path; + ixx_path = dir / ixx_path; + cxx_path = dir / cxx_path; + sch_path = dir / sch_path; + sql_path = dir / sql_path; + + if (gen_sql_migration) + { + for (paths::size_type i (0); i < mig_pre_paths.size (); ++i) + { + mig_pre_paths[i] = dir / mig_pre_paths[i]; + mig_post_paths[i] = dir / mig_post_paths[i]; + } + } + } + + // + // + bool gen_cxx (!ops.generate_schema_only ()); + + ofstream hxx; + if (gen_cxx) + { + open (hxx, hxx_path); + auto_rm.add (hxx_path); + } + + // + // + ofstream ixx; + if (gen_cxx) + { + open (ixx, ixx_path); + auto_rm.add (ixx_path); + } + + // + // + ofstream cxx; + if (gen_cxx && (db != database::common || md == multi_database::dynamic)) + { + open (cxx, cxx_path); + auto_rm.add (cxx_path); + } + + // + // + bool gen_sep_schema ( + gen_schema && + ops.schema_format ()[db].count (schema_format::separate)); + + ofstream sch; + if (gen_sep_schema) + { + open (sch, sch_path); + auto_rm.add (sch_path); + } + + // + // + bool gen_sql_schema (gen_schema && + ops.schema_format ()[db].count (schema_format::sql)); + ofstream sql; + if (gen_sql_schema) + { + open (sql, sql_path); + auto_rm.add (sql_path); + } + + // + // + ofstreams mig_pre, mig_post; + if (gen_sql_migration) + { + for (paths::size_type i (0); i < mig_pre_paths.size (); ++i) + { + shared_ptr<ofstream> pre (new (shared) ofstream); + shared_ptr<ofstream> post (new (shared) ofstream); + + open (*pre, mig_pre_paths[i]); + auto_rm.add (mig_pre_paths[i]); + mig_pre.push_back (pre); + + open (*post, mig_post_paths[i]); + auto_rm.add (mig_post_paths[i]); + mig_post.push_back (post); + } + } + + // Print output file headers. + // + if (gen_cxx) + { + hxx << cxx_file_header; + ixx << cxx_file_header; + + if (db != database::common) + cxx << cxx_file_header; + } + + if (gen_sep_schema) + sch << cxx_file_header; + + if (gen_sql_schema) + sql << sql_file_header; + + if (gen_sql_migration) + { + for (ofstreams::size_type i (0); i < mig_pre.size (); ++i) + { + *mig_pre[i] << sql_file_header; + *mig_post[i] << sql_file_header; + } + } + + typedef compiler::ostream_filter<compiler::cxx_indenter, char> ind_filter; + typedef compiler::ostream_filter<compiler::sloc_counter, char> sloc_filter; + + size_t sloc_total (0); + + // Include settings. + // + string gp (ops.guard_prefix ()); + if (!gp.empty () && gp[gp.size () - 1] != '_') + gp.append ("_"); + + // HXX + // + if (gen_cxx) + { + unique_ptr<context> ctx ( + create_context (hxx, unit, ops, fts, model.get ())); + + sloc_filter sloc (ctx->os); + + string guard (ctx->make_guard (gp + hxx_name)); + + hxx << "#ifndef " << guard << endl + << "#define " << guard << endl + << endl; + + // Copy prologue. + // + append_logue (hxx, + db, + ops.hxx_prologue (), + ops.hxx_prologue_file (), + "// Begin prologue.\n//", + "//\n// End prologue."); + + // Version check. + // + hxx << "#include <odb/version.hxx>" << endl + << endl + << "#if (ODB_VERSION != " << ODB_VERSION << "UL)" << endl + << "#error ODB runtime version mismatch" << endl + << "#endif" << endl + << endl; + + hxx << "#include <odb/pre.hxx>" << endl + << endl; + + // Include main file(s). + // + for (paths::const_iterator i (inputs.begin ()); i != inputs.end (); ++i) + hxx << "#include " << + ctx->process_include_path (i->leaf ().string ()) << endl; + + hxx << endl; + + // There are no -odb.hxx includes if we are generating code for + // everything. + // + if (!ops.at_once ()) + if (include::generate (true)) + hxx << endl; + + { + // We don't want to indent prologues/epilogues. + // + ind_filter ind (ctx->os); + + switch (db) + { + case database::common: + { + header::generate (); + break; + } + case database::mssql: + case database::mysql: + case database::oracle: + case database::pgsql: + case database::sqlite: + { + if (md == multi_database::disabled) + header::generate (); + else + { + string n (base + + ops.odb_file_suffix ()[database::common] + + ops.hxx_suffix ()); + + ctx->os << "#include " << ctx->process_include_path (n) << endl + << endl; + } + + relational::header::generate (); + break; + } + } + } + + hxx << "#include " << ctx->process_include_path (ixx_name) << endl + << endl; + + hxx << "#include <odb/post.hxx>" << endl + << endl; + + // Copy epilogue. + // + append_logue (hxx, + db, + ops.hxx_epilogue (), + ops.hxx_epilogue_file (), + "// Begin epilogue.\n//", + "//\n// End epilogue."); + + hxx << "#endif // " << guard << endl; + + if (ops.show_sloc ()) + cerr << hxx_name << ": " << sloc.stream ().count () << endl; + + sloc_total += sloc.stream ().count (); + } + + // IXX + // + if (gen_cxx) + { + unique_ptr<context> ctx ( + create_context (ixx, unit, ops, fts, model.get ())); + + sloc_filter sloc (ctx->os); + + // Copy prologue. + // + append_logue (ixx, + db, + ops.ixx_prologue (), + ops.ixx_prologue_file (), + "// Begin prologue.\n//", + "//\n// End prologue."); + + { + // We don't want to indent prologues/epilogues. + // + ind_filter ind (ctx->os); + + switch (db) + { + case database::common: + { + inline_::generate (); + break; + } + case database::mssql: + case database::mysql: + case database::oracle: + case database::pgsql: + case database::sqlite: + { + if (md == multi_database::disabled) + inline_::generate (); + + relational::inline_::generate (); + break; + } + } + } + + // Copy epilogue. + // + append_logue (ixx, + db, + ops.ixx_epilogue (), + ops.ixx_epilogue_file (), + "// Begin epilogue.\n//", + "//\n// End epilogue."); + + if (ops.show_sloc ()) + cerr << ixx_name << ": " << sloc.stream ().count () << endl; + + sloc_total += sloc.stream ().count (); + } + + // CXX + // + if (gen_cxx && (db != database::common || md == multi_database::dynamic)) + { + unique_ptr<context> ctx ( + create_context (cxx, unit, ops, fts, model.get ())); + + sloc_filter sloc (ctx->os); + + // Copy prologue. + // + append_logue (cxx, + db, + ops.cxx_prologue (), + ops.cxx_prologue_file (), + "// Begin prologue.\n//", + "//\n// End prologue."); + + cxx << "#include <odb/pre.hxx>" << endl + << endl; + + // Include query columns implementations for explicit instantiations. + // + string impl_guard; + if (md == multi_database::dynamic && ctx->ext.empty ()) + { + impl_guard = ctx->make_guard ( + "ODB_" + db.string () + "_QUERY_COLUMNS_DEF"); + + cxx << "#define " << impl_guard << endl; + } + + cxx << "#include " << ctx->process_include_path (hxx_name) << endl; + + // There are no -odb.hxx includes if we are generating code for + // everything. + // + if (!ops.at_once ()) + include::generate (false); + + if (!impl_guard.empty ()) + cxx << "#undef " << impl_guard << endl; + + cxx << endl; + + { + // We don't want to indent prologues/epilogues. + // + ind_filter ind (ctx->os); + + switch (db) + { + case database::common: + { + // Dynamic multi-database support. + // + source::generate (); + break; + } + case database::mssql: + case database::mysql: + case database::oracle: + case database::pgsql: + case database::sqlite: + { + relational::source::generate (); + + if (gen_schema && + ops.schema_format ()[db].count (schema_format::embedded)) + relational::schema::generate_source (changelog.get ()); + + break; + } + } + } + + cxx << "#include <odb/post.hxx>" << endl; + + // Copy epilogue. + // + append_logue (cxx, + db, + ops.cxx_epilogue (), + ops.cxx_epilogue_file (), + "// Begin epilogue.\n//", + "//\n// End epilogue."); + + if (ops.show_sloc ()) + cerr << cxx_name << ": " << sloc.stream ().count () << endl; + + sloc_total += sloc.stream ().count (); + } + + // SCH + // + if (gen_sep_schema) + { + unique_ptr<context> ctx ( + create_context (sch, unit, ops, fts, model.get ())); + + sloc_filter sloc (ctx->os); + + // Copy prologue. + // + append_logue (sch, + db, + ops.schema_prologue (), + ops.schema_prologue_file (), + "// Begin prologue.\n//", + "//\n// End prologue."); + + sch << "#include <odb/pre.hxx>" << endl + << endl; + + sch << "#include <odb/database.hxx>" << endl + << "#include <odb/schema-catalog-impl.hxx>" << endl + << endl + << "#include <odb/details/unused.hxx>" << endl + << endl; + + { + // We don't want to indent prologues/epilogues. + // + ind_filter ind (ctx->os); + + switch (db) + { + case database::mssql: + case database::mysql: + case database::oracle: + case database::pgsql: + case database::sqlite: + { + relational::schema::generate_source (changelog.get ()); + break; + } + case database::common: + assert (false); + } + } + + sch << "#include <odb/post.hxx>" << endl; + + // Copy epilogue. + // + append_logue (sch, + db, + ops.schema_epilogue (), + ops.schema_epilogue_file (), + "// Begin epilogue.\n//", + "//\n// End epilogue."); + + if (ops.show_sloc ()) + cerr << sch_name << ": " << sloc.stream ().count () << endl; + + sloc_total += sloc.stream ().count (); + } + + // SQL + // + if (gen_sql_schema) + { + unique_ptr<context> ctx ( + create_context (sql, unit, ops, fts, model.get ())); + + switch (db) + { + case database::mssql: + case database::mysql: + case database::oracle: + case database::pgsql: + case database::sqlite: + { + // Prologue. + // + relational::schema::generate_prologue (); + append_logue (sql, + db, + ops.sql_prologue (), + ops.sql_prologue_file (), + "/* Begin prologue.\n */", + "/*\n * End prologue. */"); + + if (!ops.omit_drop ()) + relational::schema::generate_drop (); + + // Interlude. + // + append_logue (sql, + db, + ops.sql_interlude (), + ops.sql_interlude_file (), + "/* Begin interlude.\n */", + "/*\n * End interlude. */"); + + if (!ops.omit_create ()) + relational::schema::generate_create (); + + // Epilogue. + // + append_logue (sql, + db, + ops.sql_epilogue (), + ops.sql_epilogue_file (), + "/* Begin epilogue.\n */", + "/*\n * End epilogue. */"); + relational::schema::generate_epilogue (); + + break; + } + case database::common: + assert (false); + } + } + + // MIG + // + if (gen_sql_migration) + { + for (ofstreams::size_type i (0); i < mig_pre.size (); ++i) + { + sema_rel::changeset& cs ( + changelog->contains_changeset_at (i).changeset ()); + + // pre + // + { + ofstream& mig (*mig_pre[i]); + unique_ptr<context> ctx (create_context (mig, unit, ops, fts, 0)); + + switch (db) + { + case database::mssql: + case database::mysql: + case database::oracle: + case database::pgsql: + case database::sqlite: + { + // Prologue. + // + relational::schema::generate_prologue (); + append_logue (mig, + db, + ops.migration_prologue (), + ops.migration_prologue_file (), + "/* Begin prologue.\n */", + "/*\n * End prologue. */"); + + relational::schema::generate_migrate_pre (cs); + + // Epilogue. + // + append_logue (mig, + db, + ops.migration_epilogue (), + ops.migration_epilogue_file (), + "/* Begin epilogue.\n */", + "/*\n * End epilogue. */"); + relational::schema::generate_epilogue (); + + break; + } + case database::common: + assert (false); + } + } + + // post + // + { + ofstream& mig (*mig_post[i]); + unique_ptr<context> ctx (create_context (mig, unit, ops, fts, 0)); + + switch (db) + { + case database::mssql: + case database::mysql: + case database::oracle: + case database::pgsql: + case database::sqlite: + { + // Prologue. + // + relational::schema::generate_prologue (); + append_logue (mig, + db, + ops.migration_prologue (), + ops.migration_prologue_file (), + "/* Begin prologue.\n */", + "/*\n * End prologue. */"); + + relational::schema::generate_migrate_post (cs); + + // Epilogue. + // + append_logue (mig, + db, + ops.migration_epilogue (), + ops.migration_epilogue_file (), + "/* Begin epilogue.\n */", + "/*\n * End epilogue. */"); + relational::schema::generate_epilogue (); + + break; + } + case database::common: + assert (false); + } + } + } + } + + // Save the changelog if it has changed. + // + if (gen_changelog) + { + try + { + ostringstream os; + os.exceptions (ifstream::badbit | ifstream::failbit); + xml::serializer s (os, out_log_path.string ()); + changelog->serialize (s); + string const& changelog_xml (os.str ()); + + if (changelog_xml != old_changelog_xml) + { + ofstream log; + open (log, out_log_path, ios_base::binary); + + if (old_changelog == 0) + auto_rm.add (out_log_path); + + log.exceptions (ifstream::badbit | ifstream::failbit); + log << changelog_xml; + } + } + catch (const ios_base::failure& e) + { + cerr << out_log_path << ": write failure" << endl; + throw generator_failed (); + } + catch (const xml::serialization& e) + { + cerr << e.what () << endl; + throw generator_failed (); + } + } + + // Communicate the sloc count to the driver. This is necessary to + // correctly handle the total if we are compiling multiple files in + // one invocation. + // + if (ops.show_sloc () || ops.sloc_limit_specified ()) + cout << "odb:sloc:" << sloc_total << endl; + + auto_rm.cancel (); + } + catch (operation_failed const&) + { + // Code generation failed. Diagnostics has already been issued. + // + throw generator_failed (); + } + catch (semantics::invalid_path const& e) + { + cerr << "error: '" << e.path () << "' is not a valid filesystem path" + << endl; + throw generator_failed (); + } + catch (fs::error const&) + { + // Auto-removal of generated files failed. Ignore it. + // + throw generator_failed (); + } +} diff --git a/odb/odb/generator.hxx b/odb/odb/generator.hxx new file mode 100644 index 0000000..205043b --- /dev/null +++ b/odb/odb/generator.hxx @@ -0,0 +1,22 @@ +// file : odb/generator.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_GENERATOR_HXX +#define ODB_GENERATOR_HXX + +#include <vector> + +#include <odb/options.hxx> +#include <odb/features.hxx> +#include <odb/semantics/unit.hxx> + +class generator_failed {}; + +void +generate (options const&, + features&, + semantics::unit&, + semantics::path const& file, + std::vector<semantics::path> const& inputs); + +#endif // ODB_GENERATOR_HXX diff --git a/odb/odb/header.cxx b/odb/odb/header.cxx new file mode 100644 index 0000000..fad28b3 --- /dev/null +++ b/odb/odb/header.cxx @@ -0,0 +1,900 @@ +// file : odb/header.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <odb/common.hxx> +#include <odb/context.hxx> +#include <odb/generate.hxx> + +using namespace std; + +namespace header +{ + struct class1: traversal::class_, virtual context + { + class1 () + : typedefs_ (false), + query_columns_type_ (false, true, false), + pointer_query_columns_type_ (true, true, false) + { + *this >> defines_ >> *this; + *this >> typedefs_ >> *this; + } + + virtual void + traverse (type& c) + { + class_kind_type ck (class_kind (c)); + + if (ck == class_other || + (!options.at_once () && class_file (c) != unit.file ())) + return; + + names (c); + + switch (ck) + { + case class_object: traverse_object (c); break; + case class_view: traverse_view (c); break; + default: break; + } + } + + void + traverse_object (type&); + + void + traverse_view (type&); + + private: + traversal::defines defines_; + typedefs typedefs_; + + instance<query_columns_type> query_columns_type_; + instance<query_columns_type> pointer_query_columns_type_; + }; +} + +void header::class1:: +traverse_object (type& c) +{ + using semantics::data_member; + + data_member_path* id (id_member (c)); + data_member* idf (id ? id->front () : 0); + bool auto_id (id && auto_ (*id)); + bool base_id (id && &idf->scope () != &c); // Comes from base. + + data_member* opt (context::optimistic (c)); + + type* poly_root (polymorphic (c)); + bool poly (poly_root != 0); + bool poly_derived (poly && poly_root != &c); + type* poly_base (poly_derived ? &polymorphic_base (c) : 0); + data_member* discriminator (poly ? context::discriminator (*poly_root) : 0); + + bool abst (abstract (c)); + bool reuse_abst (abst && !poly); + + user_sections& uss (c.get<user_sections> ("user-sections")); + + string const& type (class_fq_name (c)); + + os << "// " << class_name (c) << endl + << "//" << endl; + + // class_traits + // + os << "template <>" << endl + << "struct class_traits< " << type << " >" + << "{" + << "static const class_kind kind = class_object;" + << "};"; + + // object_traits + // + os << "template <>" << endl + << "class " << exp << "access::object_traits< " << type << " >" + << "{" + << "public:" << endl; + + // object_type & pointer_type + // + os << "typedef " << type << " object_type;" + << "typedef " << c.get<string> ("object-pointer") << " pointer_type;" + << "typedef odb::pointer_traits<pointer_type> pointer_traits;" + << endl; + + // polymorphic, root_type, base_type, etc. + // + os << "static const bool polymorphic = " << poly << ";" + << endl; + + if (poly) + { + os << "typedef " << class_fq_name (*poly_root) << " root_type;"; + + if (poly_derived) + { + os << "typedef " << class_fq_name (*poly_base) << " base_type;" + << "typedef object_traits<root_type>::discriminator_type " << + "discriminator_type;" + << "typedef polymorphic_concrete_info<root_type> info_type;"; + + if (abst) + os << "typedef polymorphic_abstract_info<root_type> " << + "abstract_info_type;"; + + // Calculate our hierarchy depth (number of classes). + // + size_t depth (polymorphic_depth (c)); + + os << endl + << "static const std::size_t depth = " << depth << "UL;"; + } + else + { + semantics::names* hint; + semantics::type& t (utype (*discriminator, hint)); + + os << "typedef " << t.fq_name (hint) << " discriminator_type;" + << "typedef polymorphic_map<object_type> map_type;" + << "typedef polymorphic_concrete_info<object_type> info_type;"; + + if (abst) + os << "typedef polymorphic_abstract_info<object_type> " << + "abstract_info_type;"; + + os << endl + << "static const std::size_t depth = 1UL;"; + } + + os << endl; + } + + // id_type, version_type, etc. + // + if (id != 0) + { + if (base_id) + { + semantics::class_& b (dynamic_cast<semantics::class_&> (idf->scope ())); + string const& type (class_fq_name (b)); + + os << "typedef object_traits< " << type << " >::id_type id_type;"; + + if (opt != 0) + os << "typedef object_traits< " << type << " >::version_type " << + "version_type;"; + + os << endl; + + if (poly_derived) + os << "static const bool auto_id = false;"; + else + os << "static const bool auto_id = object_traits< " << type << + " >::auto_id;"; + } + else + { + { + semantics::names* hint; + semantics::type& t (utype (*id, hint)); + os << "typedef " << t.fq_name (hint) << " id_type;"; + } + + if (opt != 0) + { + semantics::names* hint; + semantics::type& t (utype (*opt, hint)); + os << "typedef " << t.fq_name (hint) << " version_type;"; + } + + os << endl + << "static const bool auto_id = " << auto_id << ";"; + } + + os << endl; + } + else if (!reuse_abst) + { + // Object without id. + // + os << "typedef void id_type;" + << endl + << "static const bool auto_id = false;" + << endl; + } + + // abstract + // + os << "static const bool abstract = " << abst << ";" + << endl; + + // id() + // + if (id != 0 || !reuse_abst) + { + // We want to generate a dummy void id() accessor even if this + // object has no id to help us in the runtime. This way we can + // write generic code that will work for both void and non-void + // ids. + // + os << "static id_type" << endl + << "id (const object_type&);" + << endl; + } + + // version() + // + if (opt != 0) + { + os << "static version_type" << endl + << "version (const object_type&);" + << endl; + } + + // Query. + // + if (options.generate_query ()) + { + // Generate object pointer tags here if we are generating dynamic + // multi-database support. + // + if (multi_dynamic && has_a (c, test_pointer | exclude_base)) + { + query_tags t; + t.traverse (c); + } + } + + // The rest does not apply to reuse-abstract objects. + // + if (!reuse_abst) + { + // Cache traits typedefs. + // + if (id == 0) + { + os << "typedef" << endl + << "no_id_pointer_cache_traits<pointer_type>" << endl + << "pointer_cache_traits;" + << endl + << "typedef" << endl + << "no_id_reference_cache_traits<object_type>" << endl + << "reference_cache_traits;" + << endl; + } + else + { + char const* obj (poly_derived ? "root_type" : "object_type"); + char const* ptr (poly_derived + ? "object_traits<root_type>::pointer_type" + : "pointer_type"); + if (session (c)) + { + string const& s (options.session_type ()); + + os << "typedef" << endl + << "odb::pointer_cache_traits<" << endl + << " " << ptr << "," << endl + << " " << s << " >" << endl + << "pointer_cache_traits;" + << endl + << "typedef" << endl + << "odb::reference_cache_traits<" << endl + << " " << obj << "," << endl + << " " << s << " >" << endl + << "reference_cache_traits;" + << endl; + } + else + { + os << "typedef" << endl + << "no_op_pointer_cache_traits<" << ptr << ">" << endl + << "pointer_cache_traits;" + << endl + << "typedef" << endl + << "no_op_reference_cache_traits<" << obj << ">" << endl + << "reference_cache_traits;" + << endl; + } + } + + // callback () + // + os << "static void" << endl + << "callback (database&, object_type&, callback_event);" + << endl; + + os << "static void" << endl + << "callback (database&, const object_type&, callback_event);" + << endl; + } + + os << "};"; + + // The rest only applies to dynamic milti-database support. + // + if (!multi_dynamic) + return; + + // pointer_query_columns & query_columns + // + if (options.generate_query ()) + { + // If we don't have object pointers, then also generate + // query_columns (in this case pointer_query_columns and + // query_columns are the same and the former inherits from + // the latter). Otherwise we have to postpone query_columns + // generation until the second pass to deal with forward- + // declared objects. + // + if (!has_a (c, test_pointer | include_base)) + query_columns_type_->traverse (c); + + pointer_query_columns_type_->traverse (c); + } + + // object_traits_impl + // + os << "template <>" << endl + << "class " << exp << "access::object_traits_impl< " << type << ", " << + "id_common >:" << endl + << " public access::object_traits< " << type << " >" + << "{"; + + // We don't need to generate anything else for reuse-abstract objects. + // + if (reuse_abst) + { + os << "};"; + return; + } + + os << "public:" << endl; + + if (options.generate_query ()) + { + // base_traits is needed for query support. + // + if (poly_derived) + os << "typedef object_traits_impl<base_type, id_common> base_traits;" + << endl; + + // query_base_type + // + os << "typedef odb::query_base query_base_type;" + << endl; + } + + // function_table_type + // + os << "struct function_table_type" + << "{"; + + // persist () + // + os << "void (*persist) (database&, " << (auto_id ? "" : "const ") << + "object_type&" << (poly ? ", bool, bool" : "") << ");"; + + if (id != 0) + { + // find (id) + // + if (c.default_ctor ()) + os << "pointer_type (*find1) (database&, const id_type&);"; + + // find (id, obj) + // + os << "bool (*find2) (database&, const id_type&, object_type&" << + (poly ? ", bool" : "") << ");"; + + // reload () + // + os << "bool (*reload) (database&, object_type&" << + (poly ? ", bool" : "") << ");"; + + // update () + // + if (!readonly (c) || poly) + { + os << "void (*update) (database&, const object_type&" << + (poly ? ", bool, bool" : "") << ");"; + } + + // erase () + // + os << "void (*erase1) (database&, const id_type&" << + (poly ? ", bool, bool" : "") << ");"; + + os << "void (*erase2) (database&, const object_type&" << + (poly ? ", bool, bool" : "") << ");"; + + // Sections. + // + if (uss.count (user_sections::count_total | + user_sections::count_load | + (poly ? user_sections::count_load_empty : 0)) != 0) + os << "bool (*load_section) (connection&, object_type&, section&" << + (poly ? ", const info_type*" : "") << ");"; + + if (uss.count (user_sections::count_total | + user_sections::count_update | + (poly ? user_sections::count_update_empty : 0)) != 0) + os << "bool (*update_section) (connection&, const object_type&, " << + "const section&" << (poly ? ", const info_type*" : "") << ");"; + } + + if (options.generate_query ()) + { + if (!options.omit_unprepared ()) + os << "result<object_type> (*query) (database&, const query_base_type&);"; + + os << "unsigned long long (*erase_query) (database&, " << + "const query_base_type&);"; + + if (options.generate_prepared ()) + { + os << "odb::details::shared_ptr<prepared_query_impl> " << + "(*prepare_query) (connection&, const char*, const query_base_type&);"; + + os << "odb::details::shared_ptr<result_impl> (*execute_query) (" + "prepared_query_impl&);"; + } + } + + os << "};" // function_table_type + << "static const function_table_type* function_table[database_count];" + << endl; + + // + // Forwarding functions. + // + + // persist () + // + os << "static void" << endl + << "persist (database&, " << (auto_id ? "" : "const ") << "object_type&);" + << endl; + + if (id != 0) + { + // find (id) + // + if (c.default_ctor ()) + os << "static pointer_type" << endl + << "find (database&, const id_type&);" + << endl; + + // find (id, obj) + // + os << "static bool" << endl + << "find (database&, const id_type&, object_type&);" + << endl; + + // reload () + // + os << "static bool" << endl + << "reload (database&, object_type&);" + << endl; + + // update () + // + if (!readonly (c) || poly) + { + os << "static void" << endl + << "update (database&, const object_type&);" + << endl; + } + + // erase () + // + os << "static void" << endl + << "erase (database&, const id_type&);" + << endl; + + os << "static void" << endl + << "erase (database&, const object_type&);" + << endl; + + // Sections. + // + if (uss.count (user_sections::count_total | + user_sections::count_load | + (poly ? user_sections::count_load_empty : 0)) != 0) + os << "static bool" << endl + << "load (connection&, object_type&, section&);" + << endl; + + if (uss.count (user_sections::count_total | + user_sections::count_update | + (poly ? user_sections::count_update_empty : 0)) != 0) + os << "static bool" << endl + << "update (connection&, const object_type&, const section&);" + << endl; + } + + if (options.generate_query ()) + { + if (!options.omit_unprepared ()) + { + os << "static result<object_type>" << endl + << "query (database&, const query_base_type&);" + << endl; + } + + os << "static unsigned long long" << endl + << "erase_query (database&, const query_base_type&);" + << endl; + + if (options.generate_prepared ()) + { + os << "static odb::details::shared_ptr<prepared_query_impl>" << endl + << "prepare_query (connection&, const char*, const query_base_type&);" + << endl; + + os << "static odb::details::shared_ptr<result_impl>" << endl + << "execute_query (prepared_query_impl&);" + << endl; + } + } + + os << "};"; // object_traits_impl +} + +void header::class1:: +traverse_view (type& c) +{ + string const& type (class_fq_name (c)); + + os << "// " << class_name (c) << endl + << "//" << endl; + + // class_traits + // + os << "template <>" << endl + << "struct class_traits< " << type << " >" + << "{" + << "static const class_kind kind = class_view;" + << "};"; + + // view_traits + // + os << "template <>" << endl + << "class " << exp << "access::view_traits< " << type << " >" + << "{" + << "public:" << endl; + + // view_type & pointer_type + // + os << "typedef " << type << " view_type;" + << "typedef " << c.get<string> ("object-pointer") << " pointer_type;" + << endl; + + // Generate associated object tags here if we are generating dynamic + // multi-database support. + // + if (multi_dynamic) + { + query_tags t; + t.traverse (c); + } + + // callback () + // + os << "static void" << endl + << "callback (database&, view_type&, callback_event);" + << endl; + + os << "};"; + + // The rest only applies to dynamic milti-database support. + // + if (!multi_dynamic) + return; + + size_t obj_count (c.get<size_t> ("object-count")); + + // view_traits_impl + // + os << "template <>" << endl + << "class " << exp << "access::view_traits_impl< " << type << ", " << + "id_common >:" << endl + << " public access::view_traits< " << type << " >" + << "{" + << "public:" << endl; + + // query_base_type and query_columns (definition generated by class2). + // + os << "typedef odb::query_base query_base_type;" + << "struct query_columns"; + + if (obj_count == 0) + os << "{" + << "};"; + else + os << ";" + << endl; + + // function_table_type + // + os << "struct function_table_type" + << "{"; + + if (!options.omit_unprepared ()) + os << "result<view_type> (*query) (database&, const query_base_type&);" + << endl; + + if (options.generate_prepared ()) + { + os << "odb::details::shared_ptr<prepared_query_impl> " << + "(*prepare_query) (connection&, const char*, const query_base_type&);" + << endl; + + os << "odb::details::shared_ptr<result_impl> (*execute_query) (" + "prepared_query_impl&);" + << endl; + } + + os << "};" // function_table_type + << "static const function_table_type* function_table[database_count];" + << endl; + + // + // Forwarding functions. + // + + if (!options.omit_unprepared ()) + os << "static result<view_type>" << endl + << "query (database&, const query_base_type&);" + << endl; + + if (options.generate_prepared ()) + { + os << "static odb::details::shared_ptr<prepared_query_impl>" << endl + << "prepare_query (connection&, const char*, const query_base_type&);" + << endl; + + os << "static odb::details::shared_ptr<result_impl>" << endl + << "execute_query (prepared_query_impl&);" + << endl; + } + + os << "};"; +} + +namespace header +{ + struct class2: traversal::class_, virtual context + { + class2 () + : typedefs_ (false), + query_columns_type_ (false, true, false), + query_columns_type_inst_ (false, false, true), + view_query_columns_type_ (true) + { + *this >> defines_ >> *this; + *this >> typedefs_ >> *this; + } + + virtual void + traverse (type& c) + { + class_kind_type ck (class_kind (c)); + + if (ck == class_other || + (!options.at_once () && class_file (c) != unit.file ())) + return; + + names (c); + + switch (ck) + { + case class_object: traverse_object (c); break; + case class_view: traverse_view (c); break; + default: break; + } + } + + void + traverse_object (type&); + + void + traverse_view (type&); + + private: + traversal::defines defines_; + typedefs typedefs_; + + instance<query_columns_type> query_columns_type_; + instance<query_columns_type> query_columns_type_inst_; + instance<view_query_columns_type> view_query_columns_type_; + }; +} + +void header::class2:: +traverse_object (type& c) +{ + if (options.generate_query ()) + { + os << "// " << class_name (c) << endl + << "//" << endl; + + // query_columns + // + // If we don't have any pointers, then query_columns is generated + // in pass 1 (see the comment in class1 for details). + // + if (has_a (c, test_pointer | include_base)) + query_columns_type_->traverse (c); + + // Generate extern template declarations. + // + query_columns_type_inst_->traverse (c); + } + + // Move header comment out of if-block if adding any code here. +} + +void header::class2:: +traverse_view (type& c) +{ + // query_columns + // + if (c.get<size_t> ("object-count") != 0) + { + os << "// " << class_name (c) << endl + << "//" << endl; + + view_query_columns_type_->traverse (c); + } + + // Move header comment out of if-block if adding any code here. +} + +namespace header +{ + void + generate () + { + context ctx; + ostream& os (ctx.os); + + os << "#include <memory>" << endl + << "#include <cstddef>" << endl; // std::size_t + + if (ctx.features.polymorphic_object) + os << "#include <string>" << endl; // For discriminator. + + if (ctx.options.std () >= cxx_version::cxx11) + os << "#include <utility>" << endl; // move() + + os << endl; + + os << "#include <odb/core.hxx>" << endl + << "#include <odb/traits.hxx>" << endl + << "#include <odb/callback.hxx>" << endl + << "#include <odb/wrapper-traits.hxx>" << endl + << "#include <odb/pointer-traits.hxx>" << endl; + +#ifndef ODB_BUILD2 + if (ctx.options.std () == cxx_version::cxx98) + { + // In case of a boost TR1 implementation, we cannot distinguish + // between the boost::shared_ptr and std::tr1::shared_ptr usage since + // the latter is just a using-declaration for the former. To resolve + // this we will include TR1 traits if the Boost TR1 header is included. + // + if (ctx.features.tr1_pointer) + { + os << "#include <odb/tr1/wrapper-traits.hxx>" << endl + << "#include <odb/tr1/pointer-traits.hxx>" << endl; + } + else if (ctx.features.boost_pointer) + { + os << "#ifdef BOOST_TR1_MEMORY_HPP_INCLUDED" << endl + << "# include <odb/tr1/wrapper-traits.hxx>" << endl + << "# include <odb/tr1/pointer-traits.hxx>" << endl + << "#endif" << endl; + } + } +#endif + + os << "#include <odb/container-traits.hxx>" << endl; + + if (ctx.features.session_object) + { + if (ctx.options.session_type () == "odb::session") + os << "#include <odb/session.hxx>" << endl; + + os << "#include <odb/cache-traits.hxx>" << endl; + } + else + os << "#include <odb/no-op-cache-traits.hxx>" << endl; + + if (ctx.features.polymorphic_object) + os << "#include <odb/polymorphic-info.hxx>" << endl; + + if (ctx.options.generate_query ()) + { + if (ctx.multi_dynamic) + os << "#include <odb/query-dynamic.hxx>" << endl; + + if (ctx.options.generate_prepared ()) + os << "#include <odb/prepared-query.hxx>" << endl; + + os << "#include <odb/result.hxx>" << endl; + + if (ctx.features.simple_object) + os << "#include <odb/simple-object-result.hxx>" << endl; + + if (ctx.features.polymorphic_object) + os << "#include <odb/polymorphic-object-result.hxx>" << endl; + + if (ctx.features.no_id_object) + os << "#include <odb/no-id-object-result.hxx>" << endl; + + if (ctx.features.view) + os << "#include <odb/view-image.hxx>" << endl + << "#include <odb/view-result.hxx>" << endl; + } + + os << endl + << "#include <odb/details/unused.hxx>" << endl; + + if (ctx.options.generate_query ()) + os << "#include <odb/details/shared-ptr.hxx>" << endl; + + os << endl; + + os << "namespace odb" + << "{"; + + // Generate common code. + // + { + traversal::unit unit; + traversal::defines unit_defines; + typedefs unit_typedefs (false); + traversal::namespace_ ns; + class1 c; + + unit >> unit_defines >> ns; + unit_defines >> c; + unit >> unit_typedefs >> c; + + traversal::defines ns_defines; + typedefs ns_typedefs (false); + + ns >> ns_defines >> ns; + ns_defines >> c; + ns >> ns_typedefs >> c; + + unit.dispatch (ctx.unit); + } + + if (ctx.multi_dynamic) + { + traversal::unit unit; + traversal::defines unit_defines; + typedefs unit_typedefs (false); + traversal::namespace_ ns; + class2 c; + + unit >> unit_defines >> ns; + unit_defines >> c; + unit >> unit_typedefs >> c; + + traversal::defines ns_defines; + typedefs ns_typedefs (false); + + ns >> ns_defines >> ns; + ns_defines >> c; + ns >> ns_typedefs >> c; + + unit.dispatch (ctx.unit); + } + + os << "}"; + } +} diff --git a/odb/odb/include.cxx b/odb/odb/include.cxx new file mode 100644 index 0000000..5fda7c0 --- /dev/null +++ b/odb/odb/include.cxx @@ -0,0 +1,738 @@ +// file : odb/include.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <odb/gcc.hxx> + +#include <set> +#include <map> +#include <locale> +#include <cassert> +#include <fstream> +#include <sstream> + +#include <odb/common.hxx> +#include <odb/context.hxx> +#include <odb/generate.hxx> + +#include <iostream> + +using namespace std; +using semantics::path; + +namespace +{ + struct include_directive + { + enum type { quote, bracket }; + + type type_; + path path_; + }; + +#if BUILDING_GCC_MAJOR >= 6 + typedef line_map_ordinary line_map_type; +#else + typedef line_map line_map_type; +# ifndef linemap_check_ordinary +# define linemap_check_ordinary(X) (X) +# endif +#endif + + struct includes + { + typedef std::map<line_map_type const*, include_directive> map_type; + bool trailing; // Included at the beginning or at the end of the main file. + map_type map; + }; + typedef std::map<path, includes> include_map; + + // Map of files to the lines which contain include directives + // that we are interested in. + // + typedef std::map<size_t, include_directive*> include_lines; + typedef std::map<string, include_lines> file_map; + + // Set of include directives sorted in the preference order. + // + struct include_comparator + { + bool + operator() (include_directive const* x, include_directive const* y) const + { + // Prefer <> over "". + // + if (x->type_ != y->type_) + return x->type_ < y->type_; + + // Otherwise, prefer longer (more qualified) paths over the + // shorter ones. + // + return x->path_.string ().size () < y->path_.string ().size (); + } + }; + + typedef + std::multiset<include_directive const*, include_comparator> + include_set; + + struct class_: traversal::class_, context + { + class_ (include_map& map) + : typedefs_ (true), main_file_loc_ (0), map_ (map) + { + *this >> defines_ >> *this; + *this >> typedefs_ >> *this; + } + + virtual void + traverse (type& c) + { + class_kind_type ck (class_kind (c)); + + if (ck == class_other) + return; + + names (c); // Check nested classes. + + // We only generate things for objects and composite value types. In + // particular, we don't care about views since they cannot be used in + // definitions of other views, objects, or composite values. + // + if (ck != class_object && ck != class_composite) + return; + + // Not interested in classes that we are generating. + // + // If we have an explicit definition location, use that. Otherwise, + // if this is a class template instantiation, then get the file + // corresponding to the pragma, not the instantiation itself, + // since that's where we are generation the code for this class. + // While at it, also get the location. + // + using semantics::path; + + path f; + location_t l; + + // Pretty much the same code as in context::class_location(). + // + if (c.count ("definition")) + { + l = c.get<location_t> ("definition"); + f = path (LOCATION_FILE (l)); + } + else if (c.is_a<semantics::class_instantiation> ()) + { + l = c.get<location_t> ("location"); + f = path (LOCATION_FILE (l)); + } + else + { + f = c.file (); + tree decl (TYPE_NAME (c.tree_node ())); + l = real_source_location (decl); + + // Any include directives that follow are trailing (specified at + // the end of the main file). Note that we ignore views in this + // test so if a file defines only views, then all includes will + // be treated as leading. This is ok since views cannot have + // circular dependencies. We also ignore overridden locations for + // the purpose of this test since they are not really in the file + // being compiled. We assume that any includes that come after + // such classes are still leading. + // + if (f == unit.file ()) + { + if (main_file_loc_ == 0) + main_file_loc_ = l; + return; + } + } + + // This is a persistent object or composite value type declared in + // another header file. Include its -odb header. + // + if (l > BUILTINS_LOCATION) + { + line_map_type const* lm ( + linemap_check_ordinary ( + linemap_lookup (line_table, l))); + + if (lm != 0 && !MAIN_FILE_P (lm)) + { + lm = INCLUDED_FROM (line_table, lm); + + f.complete (); + f.normalize (); + + if (map_.find (f) == map_.end ()) + { + includes& i (map_[f]); + i.trailing = (main_file_loc_ != 0 && l > main_file_loc_); + i.map[lm] = include_directive (); + } + } + } + } + + private: + traversal::defines defines_; + typedefs typedefs_; + + location_t main_file_loc_; + include_map& map_; + }; + + class include_parser + { + public: + include_parser (options const& options) + : loc_ ("C"), options_ (options) + { + } + + void + parse_file (string const& file, include_lines& lines) + { + string f (file); + size_t n (f.size ()); + database db (options_.database ()[0]); + + // Check if we have a synthesized prologue/epilogue fragment. + // + if (n != 0 && f[0] == '<' && f[n - 1] == '>') + { + size_t p (f.rfind ('-')); + + if (p != string::npos) + { + string name (f, 1, p - 1); + + if (name == "odb-prologue" || name == "odb-epilogue") + { + // Extract the fragment number. + // + { + istringstream istr (string (f, p + 1)); + istr >> n; + } + + n--; // Prologues/epilogues are counted from 1. + + stringstream ss; + f.clear (); + + // We don't need the #line part. + // + if (name == "odb-prologue") + { + size_t size (options_.odb_prologue ().size ()); + + if (n < size) + ss << options_.odb_prologue ()[db][n]; + else + f = options_.odb_prologue_file ()[db][n - size]; + } + else + { + size_t size (options_.odb_epilogue ().size ()); + + if (n < size) + ss << options_.odb_epilogue ()[db][n]; + else + f = options_.odb_epilogue_file ()[db][n - size]; + } + + if (f.empty ()) + { + parse_stream (ss, file, lines); + return; + } + // Otherwise use the code below to parse the file. + } + } + } + + ifstream is (f.c_str ()); + + if (!is.is_open ()) + { + cerr << "error: unable to open '" << f << "' in read mode" << endl; + throw operation_failed (); + } + + parse_stream (is, f, lines); + } + + void + parse_stream (istream& is, string const& name, include_lines& lines) + { + typedef char_traits<char>::int_type int_type; + + size_t lmax (lines.rbegin ()->first); + + string line; + bool bslash (false); + size_t lb (1), le (1); + bool eof (false); + + for (int_type c (is.get ()); !eof; c = is.get ()) + { + if (is.fail ()) + { + if (is.eof ()) + { + // If we are still in the range, treat this as the last newline. + // + c = '\n'; + eof = true; + } + else + break; // Some other failure -- bail out. + } + + if (c == '\n') + { + le++; + + if (!bslash) + { + //cerr << "line: " << lb << "-" << (le - 1) << " " << line << endl; + + // See if we are interested in this range of physical lines. + // + include_lines::iterator li (lines.lower_bound (lb)); + include_lines::iterator ui (lines.upper_bound (le - 1)); + + // We should have at most one entry per logical line. + // + for (; li != ui; ++li) + { + if (li->first >= lb && li->first <= (le - 1)) + { + if (!parse_line (line, *li->second)) + { + cerr << name << ":" << lb << ":1: error: " + << "unable to parse #include directive" << endl; + throw operation_failed (); + } + } + } + + if (le > lmax) + break; + + lb = le; + line.clear (); + } + + bslash = false; + continue; + } + + if (bslash) + { + line += '\\'; + bslash = false; + } + + if (c == '\\') + bslash = true; + else + { + line += char (c); + } + } + + if (is.bad () || (is.fail () && !is.eof ())) + { + cerr << "error: input error while reading '" << name << "'" << endl; + throw operation_failed (); + } + } + + private: + bool + parse_line (string const& l, include_directive& inc) + { + enum state + { + start_hash, + start_keyword, + parse_keyword, + start_path, + parse_path, + parse_done + }; + + bool com (false); // In C-style comment. + string lex; + char path_end ('\0'); + state s (start_hash); + + for (size_t i (0), n (l.size ()); i < n; ++i) + { + char c (l[i]); + + if (com) + { + if (c == '*' && (i + 1) < n && l[i + 1] == '/') + { + ++i; + com = false; + c = ' '; // Replace a comment with a single space. + } + else + continue; + } + + // We only ignore spaces in start states. + // + if (is_space (c)) + { + switch (s) + { + case start_hash: + case start_keyword: + case start_path: + { + continue; + } + default: + { + break; + } + } + } + + // C comment can be anywhere except in the path. + // + if (s != parse_path && c == '/' && (i + 1) < n && l[i + 1] == '*') + { + ++i; + com = true; + continue; + } + + switch (s) + { + case start_hash: + { + if (c != '#') + return false; + + s = start_keyword; + break; + } + case start_keyword: + { + lex.clear (); + s = parse_keyword; + } + // Fall through. + case parse_keyword: + { + if (is_alpha (c)) + { + lex += c; + break; + } + + if (lex != "include") + return false; + + s = start_path; + --i; // Re-parse the same character again. + break; + } + case start_path: + { + if (c == '"') + { + path_end = '"'; + inc.type_ = include_directive::quote; + } + else if (c == '<') + { + path_end = '>'; + inc.type_ = include_directive::bracket; + } + else + return false; + + lex.clear (); + s = parse_path; + break; + } + case parse_path: + { + if (c != path_end) + lex += c; + else + s = parse_done; + + break; + } + default: + { + assert (false); + break; + } + } + + if (s == parse_done) + break; + } + + if (s != parse_done) + return false; + + inc.path_ = path (lex); + return true; + } + + private: + bool + is_alpha (char c) const + { + return isalpha (c, loc_); + } + + bool + is_space (char c) const + { + return isspace (c, loc_); + } + + private: + std::locale loc_; + options const& options_; + }; + + bool + generate_impl (bool header) + { + bool r (false); + + // We do the same include directive collection and processing + // twice, once for the header file and once for the source file. + // If that proves to be too slow, we will need to do it only once + // and cache the result. + // + context ctx; + include_map imap; + + // Collect all the files that we need to include. + // + { + traversal::unit unit; + traversal::defines unit_defines; + typedefs unit_typedefs (true); + traversal::namespace_ ns; + class_ c (imap); + + unit >> unit_defines >> ns; + unit_defines >> c; + unit >> unit_typedefs >> c; + + traversal::defines ns_defines; + typedefs ns_typedefs (true); + + ns >> ns_defines >> ns; + ns_defines >> c; + ns >> ns_typedefs >> c; + + unit.dispatch (ctx.unit); + } + + // Add all the known include locations for each file in the map. + // +#if BUILDING_GCC_MAJOR == 4 && BUILDING_GCC_MINOR <= 6 + size_t used (line_table->used); + line_map_type const* maps (line_table->maps); +#else + size_t used (line_table->info_ordinary.used); + line_map_type const* maps (line_table->info_ordinary.maps); +#endif + + for (size_t i (0); i < used; ++i) + { + line_map_type const* m (maps + i); + + if (MAIN_FILE_P (m) || m->reason != LC_ENTER) + continue; + + line_map_type const* ifm (INCLUDED_FROM (line_table, m)); + +#if BUILDING_GCC_MAJOR == 4 && BUILDING_GCC_MINOR <= 6 + path f (m->to_file); +#else + path f (ORDINARY_MAP_FILE_NAME (m)); +#endif + + f.complete (); + f.normalize (); + + include_map::iterator it (imap.find (f)); + + if (it != imap.end ()) + it->second.map[ifm] = include_directive (); + } + + // + // + file_map fmap; + + for (include_map::iterator i (imap.begin ()), e (imap.end ()); i != e; ++i) + { + // Note that the LAST_SOURCE_LINE value of a map that includes another + // map is the line of that include. + + /* + cerr << endl + << i->first << " included from" << endl; + + for (includes::iterator j (i->second.begin ()); + j != i->second.end (); ++j) + { + line_map_type const* lm (j->first); + cerr << '\t' << lm->to_file << ":" << LAST_SOURCE_LINE (lm) << endl; + } + */ + + // First see if there is an include from the main file. If so, then + // it is preferred over all others. Use the first one if there are + // several. + // + line_map_type const* main_lm (0); + include_directive* main_inc (0); + + for (includes::map_type::iterator j (i->second.map.begin ()); + j != i->second.map.end (); ++j) + { + line_map_type const* lm (j->first); + + if (MAIN_FILE_P (lm)) + { + if (main_lm == 0 || + LAST_SOURCE_LINE (main_lm) > LAST_SOURCE_LINE (lm)) + { + main_lm = lm; + main_inc = &j->second; + } + } + } + + if (main_lm != 0) + { +#if BUILDING_GCC_MAJOR == 4 && BUILDING_GCC_MINOR <= 6 + string f (main_lm->to_file); +#else + string f (ORDINARY_MAP_FILE_NAME (main_lm)); +#endif + size_t n (f.size ()); + + // Check if this is a synthesized fragment. + // + if (!(n != 0 && f[0] == '<' && f[n - 1] == '>')) + { + path p (f); + p.complete (); + p.normalize (); + f = p.string (); + } + + fmap[f][LAST_SOURCE_LINE (main_lm)] = main_inc; + continue; + } + + // Otherwise, add all the entries. + // + for (includes::map_type::iterator j (i->second.map.begin ()); + j != i->second.map.end (); ++j) + { + line_map_type const* lm (j->first); + +#if BUILDING_GCC_MAJOR == 4 && BUILDING_GCC_MINOR <= 6 + string f (lm->to_file); +#else + string f (ORDINARY_MAP_FILE_NAME (lm)); +#endif + size_t n (f.size ()); + + // Check if this is a synthesized fragment. + // + if (!(n != 0 && f[0] == '<' && f[n - 1] == '>')) + { + path p (f); + p.complete (); + p.normalize (); + f = p.string (); + } + + fmap[f][LAST_SOURCE_LINE (lm)] = &j->second; + } + } + + // Parse the collected include directives. + // + include_parser ip (ctx.options); + + for (file_map::iterator i (fmap.begin ()), e (fmap.end ()); i != e; ++i) + { + ip.parse_file (i->first, i->second); + } + + // Finally, output the include directives. + // + for (include_map::const_iterator i (imap.begin ()), e (imap.end ()); + i != e; ++i) + { + includes const& is (i->second); + + // In header we generate only leading includes. In source -- only + // trailing. + // + if (header == is.trailing) + continue; + + include_directive const* inc (0); + + if (is.map.size () == 1) + { + inc = &is.map.begin ()->second; + } + else + { + include_set set; + + for (includes::map_type::const_iterator j (i->second.map.begin ()); + j != i->second.map.end (); ++j) + { + if (!j->second.path_.empty ()) + set.insert (&j->second); + } + + assert (set.size () > 0); + inc = *set.rbegin (); + } + + path f (inc->path_.base ()); + f += ctx.options.odb_file_suffix ()[ctx.options.database ()[0]]; + f += ctx.options.hxx_suffix (); + + char o (inc->type_ == include_directive::quote ? '"' : '<'); + ctx.os << "#include " << ctx.process_include_path ( + f.string (), false, o) << endl; + r = true; + } + + return r; + } +} + +namespace include +{ + bool + generate (bool header) {return generate_impl (header);} +} diff --git a/odb/odb/inline.cxx b/odb/odb/inline.cxx new file mode 100644 index 0000000..15482aa --- /dev/null +++ b/odb/odb/inline.cxx @@ -0,0 +1,506 @@ +// file : odb/inline.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <odb/common.hxx> +#include <odb/context.hxx> +#include <odb/generate.hxx> +#include <odb/diagnostics.hxx> + +using namespace std; + +namespace inline_ +{ + // + // + struct callback_calls: traversal::class_, virtual context + { + callback_calls () + { + *this >> inherits_ >> *this; + } + + void + traverse (type& c, bool constant) + { + const_ = constant; + traverse (c); + } + + virtual void + traverse (type& c) + { + bool obj (object (c)); + + // Ignore transient bases. + // + if (!(obj || view (c))) + return; + + if (c.count ("callback")) + { + string name (c.get<string> ("callback")); + + // In case of the const instance, we only generate the call if + // there is a const callback. Note also that we cannot use + // object_type/view_type alias because it can be a base type. + // + string const& type (class_fq_name (c)); + + if (const_) + { + if (c.count ("callback-const")) + os << "static_cast<const " << type << "&> (x)." << name << + " (e, db);"; + } + else + os << "static_cast< " << type << "&> (x)." << name << " (e, db);"; + } + else if (obj) + inherits (c); + } + + protected: + bool const_; + traversal::inherits inherits_; + }; + + struct class_: traversal::class_, virtual context + { + class_ () + : typedefs_ (false) + { + *this >> defines_ >> *this; + *this >> typedefs_ >> *this; + } + + virtual void + traverse (type& c) + { + class_kind_type ck (class_kind (c)); + + if (ck == class_other || + (!options.at_once () && class_file (c) != unit.file ())) + return; + + names (c); + + switch (ck) + { + case class_object: traverse_object (c); break; + case class_view: traverse_view (c); break; + default: break; + } + } + + void + traverse_object (type&); + + void + traverse_view (type&); + + private: + traversal::defines defines_; + typedefs typedefs_; + + callback_calls callback_calls_; + }; +} + +void inline_::class_:: +traverse_object (type& c) +{ + using semantics::data_member; + + data_member_path* id (id_member (c)); + data_member* idf (id ? id->front () : 0); + bool auto_id (id && auto_ (*id)); + bool base_id (id && &idf->scope () != &c); // Comes from base. + + data_member* opt (context::optimistic (c)); + + // Base class that contains the object id. + // + type* base (base_id ? dynamic_cast<type*> (&idf->scope ()) : 0); + + bool poly (polymorphic (c)); + bool abst (abstract (c)); + bool reuse_abst (abst && !poly); + + user_sections& uss (c.get<user_sections> ("user-sections")); + + string const& type (class_fq_name (c)); + string traits ("access::object_traits< " + type + " >"); + + os << "// " << class_name (c) << endl + << "//" << endl + << endl; + + // id (object_type) + // + if (id != 0 || !reuse_abst) + { + os << "inline" << endl + << traits << "::id_type" << endl + << traits << "::" << endl + << "id (const object_type&" << (id != 0 ? " o" : "") << ")" + << "{"; + + if (id != 0) + { + if (base_id) + os << "return object_traits< " << class_fq_name (*base) << + " >::id (o);"; + else + { + // Get the id using the accessor expressions. + // + string r ("o"); + + for (data_member_path::const_iterator b (id->begin ()), i (b); + i != id->end (); + ++i) + { + member_access& ma ((*i)->get<member_access> ("get")); + + // If this is not a synthesized expression, then output its + // location for easier error tracking. + // + if (!ma.synthesized) + os << "// From " << location_string (ma.loc, true) << endl; + + r = ma.translate (r); + } + + os << "return " << r << ";"; + } + } + + os << "}"; + } + + if (opt != 0) + { + os << "inline" << endl + << traits << "::version_type" << endl + << traits << "::" << endl + << "version (const object_type& o)" + << "{"; + + if (base_id) + os << "return object_traits< " << class_fq_name (*base) << + " >::version (o);"; + else + { + // Get the id using the accessor expression. If this is not + // a synthesized expression, then output its location for + // easier error tracking. + // + member_access& ma (opt->get<member_access> ("get")); + + if (!ma.synthesized) + os << "// From " << location_string (ma.loc, true) << endl; + + os << "return " << ma.translate ("o") << ";"; + } + + os << "}"; + } + + // The rest does not apply to reuse-abstract objects. + // + if (reuse_abst) + return; + + // callback () + // + os << "inline" << endl + << "void " << traits << "::" << endl + << "callback (database& db, object_type& x, callback_event e)" + << endl + << "{" + << "ODB_POTENTIALLY_UNUSED (db);" + << "ODB_POTENTIALLY_UNUSED (x);" + << "ODB_POTENTIALLY_UNUSED (e);" + << endl; + callback_calls_.traverse (c, false); + os << "}"; + + os << "inline" << endl + << "void " << traits << "::" << endl + << "callback (database& db, const object_type& x, callback_event e)" + << "{" + << "ODB_POTENTIALLY_UNUSED (db);" + << "ODB_POTENTIALLY_UNUSED (x);" + << "ODB_POTENTIALLY_UNUSED (e);" + << endl; + callback_calls_.traverse (c, true); + os << "}"; + + // The rest only applies to dynamic milti-database support. + // + if (!multi_dynamic) + return; + + traits = "access::object_traits_impl< " + type + ", id_common >"; + + // + // Forwarding functions. + // + + // persist () + // + os << "inline" << endl + << "void " << traits << "::" << endl + << "persist (database& db, " << (auto_id ? "" : "const ") << + "object_type& o)" + << "{" + << "function_table[db.id ()]->persist (db, o" << + (poly ? ", true, true" : "") << ");" + << "}"; + + if (id != 0) + { + // find (id) + // + if (c.default_ctor ()) + { + os << "inline" << endl + << traits << "::pointer_type" << endl + << traits << "::" << endl + << "find (database& db, const id_type& id)" + << "{" + << "return function_table[db.id ()]->find1 (db, id);" + << "}"; + } + + // find (id, obj) + // + os << "inline" << endl + << "bool " << traits << "::" << endl + << "find (database& db, const id_type& id, object_type& o)" + << "{" + << "return function_table[db.id ()]->find2 (db, id, o" << + (poly ? ", true" : "") << ");" + << "}"; + + // reload () + // + os << "inline" << endl + << "bool " << traits << "::" << endl + << "reload (database& db, object_type& o)" + << "{" + << "return function_table[db.id ()]->reload (db, o" << + (poly ? ", true" : "") << ");" + << "}"; + + // update () + // + // In case of a polymorphic object, we generate update() even if it is + // readonly since the potentially-readwrite base will rely on it to + // initialize the id image. + // + // + if (!readonly (c) || poly) + { + os << "inline" << endl + << "void " << traits << "::" << endl + << "update (database& db, const object_type& o)" + << "{" + << "function_table[db.id ()]->update (db, o" << + (poly ? ", true, true" : "") << ");" + << "}"; + } + + // erase () + // + os << "inline" << endl + << "void " << traits << "::" << endl + << "erase (database& db, const id_type& id)" + << "{" + << "function_table[db.id ()]->erase1 (db, id" << + (poly ? ", true, true" : "") << ");" + << "}"; + + os << "inline" << endl + << "void " << traits << "::" << endl + << "erase (database& db, const object_type& o)" + << "{" + << "function_table[db.id ()]->erase2 (db, o" << + (poly ? ", true, true" : "") << ");" + << "}"; + + // Sections. + // + if (uss.count (user_sections::count_total | + user_sections::count_load | + (poly ? user_sections::count_load_empty : 0)) != 0) + os << "inline" << endl + << "bool " << traits << "::" << endl + << "load (connection& c, object_type& o, section& s)" + << "{" + << "return function_table[c.database ().id ()]->load_section (" << + "c, o, s" << (poly ? ", 0" : "") << ");" + << "}"; + + if (uss.count (user_sections::count_total | + user_sections::count_update | + (poly ? user_sections::count_update_empty : 0)) != 0) + os << "inline" << endl + << "bool " << traits << "::" << endl + << "update (connection& c, const object_type& o, const section& s)" + << "{" + << "return function_table[c.database ().id ()]->update_section (" << + "c, o, s" << (poly ? ", 0" : "") << ");" + << "}"; + } + + if (options.generate_query ()) + { + if (!options.omit_unprepared ()) + { + os << "inline" << endl + << "result< " << traits << "::object_type >" << endl + << traits << "::" << endl + << "query (database& db, const query_base_type& q)" + << "{" + << "return function_table[db.id ()]->query (db, q);" + << "}"; + } + + os << "inline" << endl + << "unsigned long long " << traits << "::" << endl + << "erase_query (database& db, const query_base_type& q)" + << "{" + << "return function_table[db.id ()]->erase_query (db, q);" + << "}"; + + if (options.generate_prepared ()) + { + os << "inline" << endl + << "odb::details::shared_ptr<prepared_query_impl>" << endl + << traits << "::" << endl + << "prepare_query (connection& c, const char* n, " << + "const query_base_type& q)" + << "{" + << "return function_table[c.database ().id ()]->prepare_query (" << + "c, n, q);" + << "}"; + + os << "inline" << endl + << "odb::details::shared_ptr<result_impl>" << endl + << traits << "::" << endl + << "execute_query (prepared_query_impl& pq)" + << "{" + << "return function_table[pq.conn.database ().id ()]->" << + "execute_query (pq);" + << "}"; + } + } +} + +void inline_::class_:: +traverse_view (type& c) +{ + string const& type (class_fq_name (c)); + string traits ("access::view_traits< " + type + " >"); + + os << "// " << class_name (c) << endl + << "//" << endl + << endl; + + // callback () + // + os << "inline" << endl + << "void " << traits << "::" << endl + << "callback (database& db, view_type& x, callback_event e)" + << endl + << "{" + << "ODB_POTENTIALLY_UNUSED (db);" + << "ODB_POTENTIALLY_UNUSED (x);" + << "ODB_POTENTIALLY_UNUSED (e);" + << endl; + callback_calls_.traverse (c, false); + os << "}"; + + // The rest only applies to dynamic milti-database support. + // + if (!multi_dynamic) + return; + + traits = "access::view_traits_impl< " + type + ", id_common >"; + + // + // Forwarding functions. + // + + if (!options.omit_unprepared ()) + { + os << "inline" << endl + << "result< " << traits << "::view_type >" << endl + << traits << "::" << endl + << "query (database& db, const query_base_type& q)" + << "{" + << "return function_table[db.id ()]->query (db, q);" + << "}"; + } + + if (options.generate_prepared ()) + { + os << "inline" << endl + << "odb::details::shared_ptr<prepared_query_impl>" << endl + << traits << "::" << endl + << "prepare_query (connection& c, const char* n, " << + "const query_base_type& q)" + << "{" + << "return function_table[c.database ().id ()]->prepare_query (" << + "c, n, q);" + << "}"; + + os << "inline" << endl + << "odb::details::shared_ptr<result_impl>" << endl + << traits << "::" << endl + << "execute_query (prepared_query_impl& pq)" + << "{" + << "return function_table[pq.conn.database ().id ()]->" << + "execute_query (pq);" + << "}"; + } +} + +namespace inline_ +{ + void + generate () + { + context ctx; + ostream& os (ctx.os); + + if (ctx.multi_dynamic) + os << "#include <odb/database.hxx>" << endl + << endl; + + traversal::unit unit; + traversal::defines unit_defines; + typedefs unit_typedefs (false); + traversal::namespace_ ns; + class_ c; + + unit >> unit_defines >> ns; + unit_defines >> c; + unit >> unit_typedefs >> c; + + traversal::defines ns_defines; + typedefs ns_typedefs (false); + + ns >> ns_defines >> ns; + ns_defines >> c; + ns >> ns_typedefs >> c; + + os << "namespace odb" + << "{"; + + unit.dispatch (ctx.unit); + + os << "}"; + } +} diff --git a/odb/odb/instance.cxx b/odb/odb/instance.cxx new file mode 100644 index 0000000..2d10239 --- /dev/null +++ b/odb/odb/instance.cxx @@ -0,0 +1,73 @@ +// file : odb/instance.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <sstream> +#include <cstdlib> // abort +#include <cxxabi.h> // abi::__cxa_demangle + +#include <odb/instance.hxx> + +using namespace std; + +struct demangled_name +{ + demangled_name (): s (0), n (0) {} + ~demangled_name () {free (s);} + char* s; + size_t n; +}; + +static demangled_name name_; + +string entry_base:: +name (type_info const& ti) +{ + char*& s (name_.s); + + { + int r; + s = abi::__cxa_demangle (ti.name (), s, &name_.n, &r); + + if (r != 0) + abort (); // We are in static initialization, so this is fatal. + } + + string str (s), r; + + // Get the first component. It can be a database kind or name. + // + string::size_type p (str.find ("::")); + + if (p == string::npos) + abort (); // Derived type should be in a namespace. + + string n (str, 0, p); + + // See if it is one of the known kinds. + // + if (n == "relational") + { + r = n; + p = str.find ("::", 12); // 12 for "relational::" + n.assign (str, 12, p == string::npos ? p : p - 12); + } + + // See if it is one of the known databases. + // + database db; + istringstream is (n); + if (!(is >> db)) + { + if (r.empty ()) + abort (); // Has to have either kind or database. + } + else + { + if (!r.empty ()) + r += "::"; + + r += n; + } + + return r; +} diff --git a/odb/odb/instance.hxx b/odb/odb/instance.hxx new file mode 100644 index 0000000..2b47939 --- /dev/null +++ b/odb/odb/instance.hxx @@ -0,0 +1,295 @@ +// file : odb/instance.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_INSTANCE_HXX +#define ODB_INSTANCE_HXX + +#include <map> +#include <string> +#include <cstddef> // std::size_t +#include <typeinfo> + +#include <odb/option-types.hxx> +#include <odb/context.hxx> + +#include <odb/traversal/elements.hxx> +#include <odb/traversal/relational/elements.hxx> + +// +// Dynamic traversal instantiation support. +// + +template <typename B> +struct factory +{ + static B* + create (B const& prototype) + { + std::string kind, name; + database db (context::current ().options.database ()[0]); + + switch (db) + { + case database::common: + { + name = "common"; + break; + } + case database::mssql: + case database::mysql: + case database::oracle: + case database::pgsql: + case database::sqlite: + { + kind = "relational"; + name = kind + "::" + db.string (); + break; + } + } + + if (map_ != 0) + { + typename map::const_iterator i; + + if (!name.empty ()) + i = map_->find (name); + + if (i == map_->end ()) + i = map_->find (kind); + + if (i != map_->end ()) + return i->second (prototype); + } + + return new B (prototype); + } + +private: + template <typename> + friend struct entry; + + static void + init () + { + if (factory<B>::count_++ == 0) + factory<B>::map_ = new typename factory<B>::map; + } + + static void + term () + { + if (--factory<B>::count_ == 0) + delete factory<B>::map_; + } + + typedef B* (*create_func) (B const&); + typedef std::map<std::string, create_func> map; + static map* map_; + static std::size_t count_; +}; + +template <typename B> +typename factory<B>::map* factory<B>::map_; + +template <typename B> +std::size_t factory<B>::count_; + +struct entry_base +{ + static std::string + name (std::type_info const&); +}; + +template <typename D> +struct entry: entry_base +{ + typedef typename D::base base; + + entry () + { + factory<base>::init (); + (*factory<base>::map_)[name (typeid (D))] = &create; + } + + ~entry () + { + factory<base>::term (); + } + + static base* + create (base const& prototype) + { + return new D (prototype); + } +}; + +template <typename B> +struct instance +{ + typedef typename B::base base_type; + typedef ::factory<base_type> factory_type; + + ~instance () + { + delete x_; + } + + instance () + { + base_type prototype; + x_ = factory_type::create (prototype); + } + + template <typename A1> + instance (A1& a1) + { + base_type prototype (a1); + x_ = factory_type::create (prototype); + } + + template <typename A1> + instance (A1 const& a1) + { + base_type prototype (a1); + x_ = factory_type::create (prototype); + } + + template <typename A1, typename A2> + instance (A1& a1, A2& a2) + { + base_type prototype (a1, a2); + x_ = factory_type::create (prototype); + } + + template <typename A1, typename A2> + instance (A1 const& a1, A2 const& a2) + { + base_type prototype (a1, a2); + x_ = factory_type::create (prototype); + } + + template <typename A1, typename A2, typename A3> + instance (A1& a1, A2& a2, A3& a3) + { + base_type prototype (a1, a2, a3); + x_ = factory_type::create (prototype); + } + + template <typename A1, typename A2, typename A3> + instance (A1 const& a1, A2 const& a2, A3 const& a3) + { + base_type prototype (a1, a2, a3); + x_ = factory_type::create (prototype); + } + + template <typename A1, typename A2, typename A3, typename A4> + instance (A1& a1, A2& a2, A3& a3, A4& a4) + { + base_type prototype (a1, a2, a3, a4); + x_ = factory_type::create (prototype); + } + + template <typename A1, typename A2, typename A3, typename A4> + instance (A1 const& a1, A2 const& a2, A3 const& a3, A4 const& a4) + { + base_type prototype (a1, a2, a3, a4); + x_ = factory_type::create (prototype); + } + + template <typename A1, typename A2, typename A3, typename A4, typename A5> + instance (A1& a1, A2& a2, A3& a3, A4& a4, A5& a5) + { + base_type prototype (a1, a2, a3, a4, a5); + x_ = factory_type::create (prototype); + } + + template <typename A1, typename A2, typename A3, typename A4, typename A5> + instance (A1 const& a1, A2 const& a2, A3 const& a3, A4 const& a4, + A5 const& a5) + { + base_type prototype (a1, a2, a3, a4, a5); + x_ = factory_type::create (prototype); + } + + template <typename A1, typename A2, typename A3, typename A4, typename A5, + typename A6> + instance (A1& a1, A2& a2, A3& a3, A4& a4, A5& a5, A6 a6) + { + base_type prototype (a1, a2, a3, a4, a5, a6); + x_ = factory_type::create (prototype); + } + + template <typename A1, typename A2, typename A3, typename A4, typename A5, + typename A6> + instance (A1 const& a1, A2 const& a2, A3 const& a3, A4 const& a4, + A5 const& a5, A6 const& a6) + { + base_type prototype (a1, a2, a3, a4, a5, a6); + x_ = factory_type::create (prototype); + } + + instance (instance const& i) + { + // This is tricky: use the other instance as a prototype. + // + x_ = factory_type::create (*i.x_); + } + + base_type* + operator-> () const + { + return x_; + } + + base_type& + operator* () const + { + return *x_; + } + + base_type* + get () const + { + return x_; + } + +private: + instance& operator= (instance const&); + +private: + base_type* x_; +}; + +template <typename T> +inline traversal::edge_base& +operator>> (instance<T>& n, traversal::edge_base& e) +{ + n->edge_traverser (e); + return e; +} + +template <typename T> +inline traversal::relational::edge_base& +operator>> (instance<T>& n, traversal::relational::edge_base& e) +{ + n->edge_traverser (e); + return e; +} + +template <typename T> +inline traversal::node_base& +operator>> (traversal::edge_base& e, instance<T>& n) +{ + e.node_traverser (*n); + return *n; +} + +template <typename T> +inline traversal::relational::node_base& +operator>> (traversal::relational::edge_base& e, instance<T>& n) +{ + e.node_traverser (*n); + return *n; +} + +#endif // ODB_INSTANCE_HXX diff --git a/odb/odb/location.cxx b/odb/odb/location.cxx new file mode 100644 index 0000000..23bd7ef --- /dev/null +++ b/odb/odb/location.cxx @@ -0,0 +1,13 @@ +// file : odb/location.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <odb/location.hxx> +#include <odb/diagnostics.hxx> + +location:: +location (location_t l) + : file (location_file (l)), + line (location_line (l)), + column (location_column (l)) +{ +} diff --git a/odb/odb/location.hxx b/odb/odb/location.hxx new file mode 100644 index 0000000..cc59196 --- /dev/null +++ b/odb/odb/location.hxx @@ -0,0 +1,25 @@ +// file : odb/location.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_LOCATION_HXX +#define ODB_LOCATION_HXX + +#include <odb/gcc-fwd.hxx> + +#include <cstddef> +#include <libcutl/fs/path.hxx> + +struct location +{ + location (location_t); + location (cutl::fs::path const& f, std::size_t l, std::size_t c) + : file (f), line (l), column (c) + { + } + + cutl::fs::path file; + std::size_t line; + std::size_t column; +}; + +#endif // ODB_LOCATION_HXX diff --git a/odb/odb/lookup.cxx b/odb/odb/lookup.cxx new file mode 100644 index 0000000..a54ef15 --- /dev/null +++ b/odb/odb/lookup.cxx @@ -0,0 +1,374 @@ +// file : odb/lookup.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <odb/lookup.hxx> +#include <odb/semantics.hxx> + +using namespace std; + +namespace lookup +{ + // Return canonical fundamental type name (<length> <sign> <type>, e.g., + // short unsigned int) or empty string if it is not a fundamental type. + // Note that the type can still be invalid (e.g., unsigned float) so it + // needs to be resolved. + // + static string + parse_fundamental (cxx_lexer& l, + cpp_ttype& tt, + string& tl, + tree& tn, + cpp_ttype& ptt, + string& name) + { + bool + si (false), // signed + un (false), // unsigned + sh (false), // short + lo (false), // long + ll (false); // long long + + string type; + + for (; tt == CPP_KEYWORD; ptt = tt, tt = l.next (tl, &tn)) + { + if (!name.empty ()) + name += ' '; + name += tl; + + if (tl == "signed") + { + if (si || un) + throw invalid_name (); + + si = true; + } + else if (tl == "unsigned") + { + if (si || un) + throw invalid_name (); + + un = true; + } + else if (tl == "short") + { + if (sh || lo || ll) + throw invalid_name (); + + sh = true; + } + else if (tl == "long") + { + if (sh || ll) + throw invalid_name (); + + if (lo) + { + lo = false; + ll = true; + } + else + lo = true; + } + else if (tl == "bool" || + tl == "char" || + tl == "wchar_t" || + tl == "char16_t" || // C++11 + tl == "char32_t" || // C++11 + tl == "int" || + tl == "float" || + tl == "double") + { + if (!type.empty ()) + throw invalid_name (); + + type = tl; + } + else + break; + } + + if (type.empty () && (si || un || sh || lo || ll)) + type = "int"; // If type not specified, it defaults to int. + + if (type.empty ()) + return type; + + string r; + + if (sh) + r += "short "; + + if (lo) + r += "long "; + + if (ll) + r += "long long "; + + if (si && type == "char") + r += "signed "; + + if (un) + r += "unsigned "; + + r += type; + return r; + } + + string + parse_scoped_name (cxx_lexer& l, cpp_ttype& tt, string& tl, tree& tn) + { + string name; + + if (tt == CPP_SCOPE) + { + name += "::"; + tt = l.next (tl, &tn); + } + else if (tt == CPP_KEYWORD) + { + cpp_ttype ptt; // Not used. + string t (parse_fundamental (l, tt, tl, tn, ptt, name)); + + if (!t.empty ()) + return name; + } + + while (true) + { + if (tt != CPP_NAME) + throw invalid_name (); + + name += tl; + tt = l.next (tl, &tn); + + if (tt != CPP_SCOPE) + break; + + name += "::"; + tt = l.next (tl, &tn); + } + + return name; + } + + tree + resolve_scoped_name (cxx_lexer& l, + cpp_ttype& tt, + string& tl, + tree& tn, + cpp_ttype& ptt, + tree scope, + string& name, + bool is_type, + bool trailing_scope, + tree* end_scope) + { + tree id; + bool first (true); + + if (tt == CPP_SCOPE) + { + name += "::"; + scope = global_namespace; + first = false; + + ptt = tt; + tt = l.next (tl, &tn); + } + else if (tt == CPP_KEYWORD) + { + string t (parse_fundamental (l, tt, tl, tn, ptt, name)); + + if (!t.empty ()) + { + tree decl ( + lookup_qualified_name ( + global_namespace, get_identifier (t.c_str ()), true, false)); + + if (decl == error_mark_node) + throw unable_to_resolve (name, true); + + if (end_scope != 0) + *end_scope = global_namespace; + + return decl; + } + } + + while (true) + { + if (end_scope != 0) + *end_scope = scope; + + if (tt != CPP_NAME) + throw invalid_name (); + + name += tl; + id = get_identifier (tl.c_str ()); + ptt = tt; + tt = l.next (tl, &tn); + + bool last (true); + if (tt == CPP_SCOPE) + { + // If trailing scope names are allowed, then we also need to + // check what's after the scope. + // + if (trailing_scope) + { + ptt = tt; + tt = l.next (tl, &tn); + + if (tt == CPP_NAME) + last = false; + } + else + last = false; + } + + tree decl = lookup_qualified_name (scope, id, last && is_type, false); + + // If this is the first component in the name, then also search the + // outer scopes. + // + if (decl == error_mark_node && first && scope != global_namespace) + { + do + { + scope = TYPE_P (scope) + ? CP_TYPE_CONTEXT (scope) + : CP_DECL_CONTEXT (scope); + decl = lookup_qualified_name (scope, id, last && is_type, false); + } while (decl == error_mark_node && scope != global_namespace); + } + + if (decl == error_mark_node) + throw unable_to_resolve (name, last); + + scope = decl; + + if (last) + break; + + first = false; + + if (TREE_CODE (scope) == TYPE_DECL) + scope = TREE_TYPE (scope); + + name += "::"; + + if (!trailing_scope) + { + ptt = tt; + tt = l.next (tl, &tn); + } + } + + return scope; + } + + semantics::node& + resolve_scoped_name (cxx_lexer& l, + cpp_ttype& tt, + string& tl, + tree& tn, + cpp_ttype& ptt, + semantics::scope& start_scope, + string& name, + semantics::type_id const& tid, + bool trailing_scope, + semantics::scope** end_scope) + { + bool first (true); + semantics::scope* scope (&start_scope); + + if (tt == CPP_SCOPE) + { + name += "::"; + for (; !scope->global_scope (); scope = &scope->scope_ ()) ; + first = false; + + ptt = tt; + tt = l.next (tl, &tn); + } + else if (tt == CPP_KEYWORD) + { + string t (parse_fundamental (l, tt, tl, tn, ptt, name)); + + if (!t.empty ()) + { + for (; !scope->global_scope (); scope = &scope->scope_ ()) ; + + if (end_scope != 0) + *end_scope = scope; + + return scope->lookup<semantics::fund_type> (t); + } + } + + semantics::names* r; + + for (;;) + { + if (end_scope != 0) + *end_scope = scope; + + if (tt != CPP_NAME) + throw invalid_name (); + + name += tl; + string n (tl); + ptt = tt; + tt = l.next (tl, &tn); + + bool last (true); + if (tt == CPP_SCOPE) + { + // If trailing scope names are allowed, then we also need to + // check what's after the scope. + // + if (trailing_scope) + { + ptt = tt; + tt = l.next (tl, &tn); + + if (tt == CPP_NAME) + last = false; + } + else + last = false; + } + + // If this is the first component in the name, then also search the + // outer scopes. + // + bool hidden (false); + r = scope->lookup ( + n, + (last ? tid : typeid (semantics::scope)), + (first ? 0 : semantics::scope::exclude_outer) | + (last ? semantics::scope::include_hidden : 0), + (last ? &hidden : 0)); + + if (r == 0) + throw semantics::unresolved (name, hidden); + + if (last) + break; + + scope = &dynamic_cast<semantics::scope&> (r->named ()); + first = false; + + name += "::"; + + if (!trailing_scope) + { + ptt = tt; + tt = l.next (tl, &tn); + } + } + + return r->named (); + } +} diff --git a/odb/odb/lookup.hxx b/odb/odb/lookup.hxx new file mode 100644 index 0000000..86c65b2 --- /dev/null +++ b/odb/odb/lookup.hxx @@ -0,0 +1,97 @@ +// file : odb/lookup.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_LOOKUP_HXX +#define ODB_LOOKUP_HXX + +#include <odb/gcc.hxx> + +#include <string> + +#include <odb/cxx-lexer.hxx> +#include <odb/semantics/elements.hxx> + +namespace lookup +{ + struct invalid_name + { + }; + + struct unable_to_resolve + { + unable_to_resolve (std::string const& n, bool l): name_ (n), last_ (l) {} + + std::string const& + name () const {return name_;} + + // Last component in the name. + // + bool + last () const {return last_;} + + private: + std::string name_; + bool last_; + }; + + std::string + parse_scoped_name (cxx_lexer&, + cpp_ttype&, + std::string& tl, // Token literal. + tree& tn); // Token node. + + // If trailing_scope is true, then this function also handles + // names in the 'foo::bar::<something-other-than-name>' form. + // In this case token will be <something-other-than-name> and + // ptt will be CPP_SCOPE. + // + // The names are appended to the 'name' variable as they are + // being resolved. + // + tree + resolve_scoped_name (cxx_lexer&, + cpp_ttype&, + std::string& tl, // Token literal. + tree& tn, // Token node. + cpp_ttype& ptt, // Previous token type. + tree start_scope, + std::string& name, + bool is_type, + bool trailing_scope = false, + tree* end_scope = 0); + + // The same but using semantic graph instead of GCC tree. Also + // throws semantics::unresolved instead of unable_to_resolve. + // + semantics::node& + resolve_scoped_name (cxx_lexer&, + cpp_ttype&, + std::string& tl, // Token literal. + tree& tn, // Token node. + cpp_ttype& ptt, // Previous token type. + semantics::scope& start_scope, + std::string& name, + semantics::type_id const&, + bool trailing_scope = false, + semantics::scope** end_scope = 0); + + template <typename T> + T& + resolve_scoped_name (cxx_lexer& l, + cpp_ttype& tt, + std::string& tl, // Token literal. + tree& tn, // Token node. + cpp_ttype& ptt, // Previous token type. + semantics::scope& start_scope, + std::string& name, + bool trailing_scope = false, + semantics::scope** end_scope = 0) + { + return dynamic_cast<T&> ( + resolve_scoped_name ( + l, tt, tl, tn, ptt, + start_scope, name, typeid (T), trailing_scope, end_scope)); + } +} + +#endif // ODB_LOOKUP_HXX diff --git a/odb/odb/odb.cxx b/odb/odb/odb.cxx new file mode 100644 index 0000000..701f6e1 --- /dev/null +++ b/odb/odb/odb.cxx @@ -0,0 +1,2178 @@ +// file : odb/odb.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <errno.h> +#include <stdlib.h> // getenv, setenv +#include <string.h> // strerror, memset +#include <unistd.h> // stat, close +#include <sys/types.h> // stat +#include <sys/stat.h> // stat + +// Process. +// +#ifndef _WIN32 +# include <unistd.h> // execvp, fork, dup2, pipe, {STDIN,STDERR}_FILENO +# include <sys/types.h> // waitpid +# include <sys/wait.h> // waitpid +#else +# ifndef WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +# endif +# include <windows.h> // CreatePipe, CreateProcess, GetTemp*, MAX_PATH +# include <io.h> // _open_osfhandle +# include <fcntl.h> // _O_TEXT +#endif + +#include <string> +#include <vector> +#include <cstddef> // size_t +#include <sstream> +#include <fstream> +#include <iostream> +#include <ext/stdio_filebuf.h> + +#include <libcutl/fs/path.hxx> +#include <libcutl/fs/auto-remove.hxx> + +#include <odb/version.hxx> +#include <odb/options.hxx> +#include <odb/profile.hxx> + +#ifdef HAVE_CONFIG_H +# include <odb/config.h> +#endif + +using namespace std; +using cutl::fs::path; +using cutl::fs::invalid_path; +using cutl::fs::auto_remove; + +typedef vector<string> strings; +typedef vector<path> paths; + +// +// Path manipulation. +// + +// Escape backslashes in the path. +// +static string +escape_path (string const&); + +// Search the PATH environment variable for the file. +// +static path +path_search (path const&); + +// Driver path with the directory part (search PATH). +// +static path +driver_path (path const& driver); + +#ifndef ODB_STATIC_PLUGIN +static path +plugin_path (path const& driver, string const& gxx); +#endif + +// +// Process manipulation. +// +struct process_info +{ +#ifndef _WIN32 + pid_t id; +#else + HANDLE id; +#endif + + int out_fd; // Write to this fd to send to the new process' stdin. + int in_efd; // Read from this fd to receive from the new process' stderr. + int in_ofd; // Read from this fd to receive from the new process' stdout. +}; + +struct process_failure {}; + +#ifdef _WIN32 +// Deal with Windows command line length limit. +// +static auto_remove +fixup_cmd_line (vector<const char*>& args, + size_t start, + const char* name, + string& arg); +#endif + +// Start another process using the specified command line. Connect the +// newly created process' stdin to out_fd. Also if connect_* are true, +// connect the created process' stdout and stderr to in_*fd. Issue +// diagnostics and throw process_failure if anything goes wrong. The +// name argument is the name of the current process for diagnostics. +// +static process_info +start_process (char const* args[], + char const* name, + bool connect_stderr = false, + bool connect_stdout = false); + +// Wait for the process to terminate. Return true if the process terminated +// normally and with the zero exit status. Issue diagnostics and throw +// process_failure if anything goes wrong. The name argument is the name +// of the current process for diagnostics. +// +static bool +wait_process (process_info, char const* name); + +// +// +static string +encode_plugin_flag (string const& k); + +static string +encode_plugin_option (string const& k, string const& v); + +// Extract header search paths from GCC's -v output. May throw the +// profile_failure, process_failure and invalid_path exceptions. Name +// is the program name (argv[0]) for diagnostics. +// +static paths +profile_paths (strings const& args, char const* name); + +static char const* const db_macro[] = +{ + "-DODB_DATABASE_COMMON", + "-DODB_DATABASE_MSSQL", + "-DODB_DATABASE_MYSQL", + "-DODB_DATABASE_ORACLE", + "-DODB_DATABASE_PGSQL", + "-DODB_DATABASE_SQLITE" +}; + +int +main (int argc, char* argv[]) +{ + ostream& e (cerr); + + try + { + strings args, plugin_args; + bool v (false); + + // The first argument points to the program name, which is + // g++ by default. + // +#ifdef ODB_GXX_NAME + path gxx (ODB_GXX_NAME); + + if (gxx.empty ()) + { + e << argv[0] << ": error: embedded g++ compile name is empty" << endl; + return 1; + } + + // If the g++ name is a relative path (starts with '.'), then use + // our own path as base. + // + if (gxx.string ()[0] == '.') + { + path dp (driver_path (path (argv[0]))); + path d (dp.directory ()); + + if (!d.empty ()) + gxx = d / gxx; + } + + args.push_back (gxx.string ()); + + // Also modify LD_LIBRARY_PATH to include the lib path. + // +#ifndef _WIN32 + { +#ifdef __APPLE__ + char const name[] = "DYLD_LIBRARY_PATH"; +#else + char const name[] = "LD_LIBRARY_PATH"; +#endif + + string ld_paths; + + if (char const* s = getenv (name)) + ld_paths = s; + + path d (gxx.directory ()); + + if (!d.empty ()) + { + d.complete (); + d /= path ("..") / path ("lib"); + + if (ld_paths.empty ()) + ld_paths = d.string (); + else + ld_paths = d.string () + path::traits::path_separator + ld_paths; + + if (setenv (name, ld_paths.c_str (), 1) != 0) + { + e << argv[0] << ": error: unable to update environment" << endl; + return 1; + } + } + } +#endif // _WIN32 + +#else + args.push_back ("g++"); +#endif // ODB_GXX_NAME + + // Default options. + // + args.push_back ("-x"); + args.push_back ("c++"); + args.push_back (""); // Reserve space for -std=c++XX. + args.push_back ("-S"); + args.push_back ("-Wunknown-pragmas"); + args.push_back ("-Wno-deprecated"); + args.push_back (""); // Reserve space for -fplugin=path. + + // Parse the default options file if we have one. + // + strings def_inc_dirs; + strings def_defines; +#ifdef ODB_DEFAULT_OPTIONS_FILE + { + path file (ODB_DEFAULT_OPTIONS_FILE); + + // If the path is relative, then use the driver's path as a base. If + // the file is not found in that directory, then also try outer + // directory (so that we can find /etc if driver is in /usr/bin). + // + if (file.relative ()) + { + bool found (false); + path dd (driver_path (path (argv[0])).directory ()); + + for (path d (dd);; d = d.directory ()) + { + path f (d / file); + // Check that the file exist without checking for permissions, etc. + // + struct stat s; + if (stat (f.string ().c_str (), &s) == 0 && S_ISREG (s.st_mode)) + { + file = f; + found = true; + break; + } + + if (d.root ()) + break; + } + + if (!found) + file = dd / file; // For diagnostics. + } + + cli::argv_file_scanner s (file.string ()); + + bool first_x (true); + + while (s.more ()) + { + string a (s.next ()); + size_t n (a.size ()); + + // -x + // + if (a == "-x") + { + if (!s.more () || (a = s.next ()).empty ()) + { + e << file << ": error: expected argument for the " << a + << " option" << endl; + return 1; + } + + if (first_x) + { + first_x = false; + + // If it doesn't start with '-', then it must be the g++ + // executable name. Update the first argument with it. + // + if (a[0] != '-') + args[0] = a; + else + args.push_back (a); + } + else + args.push_back (a); + } + // -I + // + else if (n > 1 && a[0] == '-' && a[1] == 'I') + { + def_inc_dirs.push_back (a); + + if (n == 2) // -I /path + { + if (!s.more () || (a = s.next ()).empty ()) + { + e << file << ": error: expected argument for the -I option" + << endl; + return 1; + } + + def_inc_dirs.push_back (a); + } + } + // -isystem, -iquote, -idirafter, and -framework (Mac OS X) + // + else if (a == "-isystem" || + a == "-iquote" || + a == "-idirafter" || + a == "-framework") + { + def_inc_dirs.push_back (a); + + if (!s.more () || (a = s.next ()).empty ()) + { + e << file << ": error: expected argument for the " << a + << " option" << endl; + return 1; + } + + def_inc_dirs.push_back (a); + } + // -D + // + else if (n > 1 && a[0] == '-' && a[1] == 'D') + { + def_defines.push_back (a); + + if (n == 2) // -D macro + { + if (!s.more () || (a = s.next ()).empty ()) + { + e << file << ": error: expected argument for the -D option" + << endl; + return 1; + } + + def_defines.push_back (a); + } + } + // -U + // + else if (n > 1 && a[0] == '-' && a[1] == 'U') + { + def_defines.push_back (a); + + if (n == 2) // -U macro + { + if (!s.more () || (a = s.next ()).empty ()) + { + e << file << ": error: expected argument for the -U option" + << endl; + return 1; + } + + def_defines.push_back (a); + } + } + else + plugin_args.push_back (a); + } + } +#endif + + // Add the default preprocessor defines (-D/-U) before the user-supplied + // ones. + // + args.insert (args.end (), def_defines.begin (), def_defines.end ()); + + // Parse driver options. + // + // We scan expanding --options-file in order to allow specifying ad hoc + // options (-I, etc) in options files. + // + bool first_x (true); + + for (cli::argv_file_scanner scan (argc, argv, "--options-file"); + scan.more (); ) + { + string a (scan.next ()); + size_t n (a.size ()); + + // -v + // + if (a == "-v") + { + v = true; + args.push_back (a); + } + // -x + // + else if (a == "-x") + { + const char* v; + if (!scan.more () || (v = scan.next ())[0] == '\0') + { + e << argv[0] << ": error: expected argument for the -x option" + << endl; + return 1; + } + + if (first_x) + { + first_x = false; + + // If it doesn't start with '-', then it must be the g++ + // executable name. Update the first argument with it. + // + if (v[0] != '-') + args[0] = v; + else + args.push_back (v); + } + else + args.push_back (v); + } + // -I + // + else if (n > 1 && a[0] == '-' && a[1] == 'I') + { + args.push_back (a); + + if (n == 2) // -I /path + { + const char* v; + if (!scan.more () || (v = scan.next ())[0] == '\0') + { + e << argv[0] << ": error: expected argument for the -I option" + << endl; + return 1; + } + + args.push_back (v); + } + } + // -isystem, -iquote, -idirafter, and -framework (Mac OS X) + // + else if (a == "-isystem" || + a == "-iquote" || + a == "-idirafter" || + a == "-framework") + { + args.push_back (a); + + const char* v; + if (!scan.more () || (v = scan.next ())[0] == '\0') + { + e << argv[0] << ": error: expected argument for the " << a + << " option" << endl; + return 1; + } + + args.push_back (v); + } + // -D + // + else if (n > 1 && a[0] == '-' && a[1] == 'D') + { + args.push_back (a); + + if (n == 2) // -D macro + { + const char* v; + if (!scan.more () || (v = scan.next ())[0] == '\0') + { + e << argv[0] << ": error: expected argument for the -D option" + << endl; + return 1; + } + + args.push_back (v); + } + } + // -U + // + else if (n > 1 && a[0] == '-' && a[1] == 'U') + { + args.push_back (a); + + if (n == 2) // -U macro + { + const char* v; + if (!scan.more () || (v = scan.next ())[0] == '\0') + { + e << argv[0] << ": error: expected argument for the -U option" + << endl; + return 1; + } + + args.push_back (v); + } + } + // Store everything else in a list so that we can parse it with the + // cli parser. This is the only reliable way to find out where the + // options end. + // + else + plugin_args.push_back (a); + } + + // Add the default include directories (-I) after the user-supplied + // ones. + // + args.insert (args.end (), def_inc_dirs.begin (), def_inc_dirs.end ()); + + // Find the plugin. + // + { +#ifndef ODB_STATIC_PLUGIN + path plugin (plugin_path (path (argv[0]), args[0])); +#else + // Use a dummy name if the plugin is linked into the compiler. + // + path plugin ("odb"); +#endif + + if (plugin.empty ()) + return 1; // Diagnostics has already been issued. + +#ifdef ODB_BUILD2 +#ifdef _WIN32 + // Here is the problem: since the plugin is loaded by GCC (cc1plus.exe + // to be precise), the DLL assembly magic we have for executables won't + // help here. + // + // To allow executing the ODB compiler in-place we add the odb.exe.dlls/ + // directory to PATH. It is a bit of hack but then DLL assemblies for + // DLLs is whole new level of insanity that we are unlikely to ever + // touch. + // + // And it turns out we have the same problem in the installed case: if + // the installation directory is not in PATH, then GCC won't find the + // DLLs the plugin needs. So we handle both here. + // + { + path d (plugin.directory ()); + d.complete (); + d.normalize (); + d /= path ("odb.exe.dlls"); + + struct stat st; + if (stat (d.string ().c_str (), &st) != 0 || !S_ISDIR (st.st_mode)) + d = d.directory (); + + string v ("PATH=" + d.string ()); + + if (char const* p = getenv ("PATH")) + { + v += ';'; + v += p; + } + + _putenv (v.c_str ()); + } +#endif +#endif + + args[7] = "-fplugin=" + plugin.string (); + } + + // Parse plugin options. We have to do it twice to get the target + // database which is needed while loading profiles. + // + vector<char*> av; + av.push_back (argv[0]); + + for (strings::iterator i (plugin_args.begin ()), end (plugin_args.end ()); + i != end; ++i) + { + av.push_back (const_cast<char*> (i->c_str ())); + } + + int ac (static_cast<int> (av.size ())); + + cli::argv_file_scanner::option_info oi[3]; + oi[0].option = "--options-file"; // Keep in case profile uses it. + oi[0].search_func = 0; + oi[1].option = "-p"; + oi[2].option = "--profile"; + + vector<database> dbs; + bool show_sloc; + size_t sloc_limit; + { + oi[1].search_func = &profile_search_ignore; + oi[2].search_func = &profile_search_ignore; + + cli::argv_file_scanner scan (ac, &av[0], oi, 3); + options ops (scan); + + // Handle --build2-metadata (see also buildfile). + // + if (ops.build2_metadata_specified ()) + { + ostream& o (cout); + + // Note that the export.metadata variable should be the first non- + // blank/comment line. + // + o << "# build2 buildfile odb" << endl + << "export.metadata = 1 odb" << endl + << "odb.name = [string] odb" << endl + << "odb.version = [string] '" << ODB_COMPILER_VERSION_STR << '\'' << endl + << "odb.checksum = [string] '" << ODB_COMPILER_VERSION_STR << '\'' << endl + << "odb.environment = [strings] CPATH CPLUS_INCLUDE_PATH GCC_EXEC_PREFIX COMPILER_PATH" << endl; + + return 0; + } + + // Handle --version. + // + if (ops.version ()) + { + ostream& o (cout); + + o << "ODB object-relational mapping (ORM) compiler for C++ " + ODB_COMPILER_VERSION_STR << endl; + +#ifdef ODB_BUILD2 + o << "Copyright (c) " << ODB_COPYRIGHT << "." << endl; +#endif + + o << "This is free software; see the source for copying conditions. " + << "There is NO\nwarranty; not even for MERCHANTABILITY or FITNESS " + << "FOR A PARTICULAR PURPOSE." << endl; + + return 0; + } + + // Handle --help. + // + if (ops.help ()) + { + ostream& o (cout); + + o << "Usage: " << argv[0] << " [options] file [file ...]" << endl + << "Options:" << endl; + + options::print_usage (cout); + return 0; + } + + // Check that required options were specifed. + // + dbs = ops.database (); + + if (dbs.empty ()) + { + e << argv[0] << ": error: no database specified with the --database " + << "option" << endl; + return 1; + } + + if (dbs.size () > 1 && !ops.multi_database_specified ()) + { + e << argv[0] << ": error: --multi-database option required when " << + "multiple databases are specified"<< endl; + return 1; + } + + show_sloc = ops.show_sloc (); + sloc_limit = ops.sloc_limit_specified () ? ops.sloc_limit () : 0; + + // Translate some ODB options to GCC options. + // + switch (ops.std ()) + { + case cxx_version::cxx98: + { + args[3] = "-std=c++98"; + break; + } + case cxx_version::cxx11: + { + args[3] = "-std=c++0x"; // c++11 was only added in GCC 4.7.0. + break; + } + case cxx_version::cxx14: + { + args[3] = "-std=c++1y"; + break; + } + case cxx_version::cxx17: + { + args[3] = "-std=c++1z"; + break; + } + case cxx_version::cxx20: + { + args[3] = "-std=c++2a"; + break; + } + } + } + + // Obtain profile (-I) search paths. + // + paths prof_paths (profile_paths (args, argv[0])); + + if (v) + { + e << "Profile search paths:" << endl; + + for (paths::const_iterator i (prof_paths.begin ()); + i != prof_paths.end (); ++i) + e << " " << *i << endl; + } + + // Pass profile search paths (svc-path option). + // + for (paths::const_iterator i (prof_paths.begin ()); + i != prof_paths.end (); ++i) + { + args.push_back (encode_plugin_option ("svc-path", i->string ())); + } + + // Add common ODB macros. + // + args.push_back ("-DODB_COMPILER"); + + { + ostringstream ostr; + ostr << ODB_COMPILER_VERSION; + args.push_back ("-DODB_COMPILER_VERSION=" + ostr.str ()); + } + + // Compile for each database. + // + size_t sloc_total (0); + + for (vector<database>::iterator i (dbs.begin ()); i != dbs.end (); ++i) + { + database db (*i); + strings db_args (args); + + // Add database-specific ODB macro. + // + db_args.push_back (db_macro[db]); + + // Second parse. + // + profile_data pd (prof_paths, db, argv[0]); + oi[1].search_func = &profile_search; + oi[2].search_func = &profile_search; + oi[1].arg = &pd; + oi[2].arg = &pd; + + cli::argv_file_scanner scan (ac, &av[0], oi, 3); + options ops (scan); + + size_t end (scan.end () - 1); // We have one less in plugin_args. + + if (end == plugin_args.size ()) + { + e << argv[0] << ": error: input file expected" << endl; + return 1; + } + + // Encode plugin options. + // + // Add the database we are compiling for first. More databases + // could be specified in options files but they will be ignored + // by the plugin (it only cares about the first). + // + db_args.push_back (encode_plugin_option ("database", db.string ())); + + cli::options const& desc (options::description ()); + for (size_t i (0); i < end; ++i) + { + string k, a (plugin_args[i]); + + // Ignore certain options. + // + if (a == "--") + { + // Ignore the option seperator since GCC doesn't understand it. + // + continue; + } + else if (a == "-d" || a == "--database") + { + // Ignore all other databases. + // + i++; // Skip the value. + continue; + } + + cli::options::const_iterator it (desc.find (a)); + + if (it == desc.end ()) + { + e << argv[0] << ": ice: unexpected option '" << a << "'" << endl; + return 1; + } + + if (a.size () > 2 && a[0] == '-' && a[1] == '-') + k = string (a, 2); // long format + else + k = string (a, 1); // short format + + if (it->flag ()) + db_args.push_back (encode_plugin_flag (k)); + else + { + // If there are more arguments then we may have a value. + // + if (i + 1 == end) + { + e << argv[0] << ": ice: expected argument for '" << a << "'" + << endl; + return 1; + } + + db_args.push_back (encode_plugin_option (k, plugin_args[++i])); + } + } + + // Reserve space for and remember the position of the svc-file + // option. + // + size_t svc_file_pos (db_args.size ()); + db_args.push_back (""); + + // If compiling multiple input files at once, pass them also with + // the --svc-file option. + // + bool at_once (ops.at_once () && plugin_args.size () - end > 1); + if (at_once) + { + if (ops.input_name ().empty ()) + { + e << "error: --input-name required when compiling multiple " << + "input files at once (--at-once)" << endl; + return 1; + } + + for (size_t i (end); i < plugin_args.size (); ++i) + db_args.push_back ( + encode_plugin_option ("svc-file", plugin_args[i])); + } + + // Create an execvp-compatible argument array. + // + typedef vector<char const*> cstrings; + cstrings exec_args; + + for (strings::const_iterator i (db_args.begin ()), end (db_args.end ()); + i != end; ++i) + { + exec_args.push_back (i->c_str ()); + } + + exec_args.push_back ("-"); // Compile stdin. + exec_args.push_back (0); + + // Iterate over the input files and compile each of them. + // + for (; end < plugin_args.size (); ++end) + { + string name (at_once ? ops.input_name () : plugin_args[end]); + + // Set the --svc-file option. + // + db_args[svc_file_pos] = encode_plugin_option ("svc-file", name); + exec_args[svc_file_pos] = db_args[svc_file_pos].c_str (); + + // + // + ifstream ifs; + + if (!at_once) + { + ifs.open (name.c_str (), ios_base::in | ios_base::binary); + + if (!ifs.is_open ()) + { + e << name << ": error: unable to open in read mode" << endl; + return 1; + } + } + + if (v) + { + e << "Compiling " << name << endl; + for (cstrings::const_iterator i (exec_args.begin ()); + i != exec_args.end (); ++i) + { + if (*i != 0) + e << *i << (*(i + 1) != 0 ? ' ' : '\n'); + } + } + + // Deal with Windows command line length limit. + // +#ifdef _WIN32 + string ops_file_arg; + auto_remove opt_file_rm ( + fixup_cmd_line (exec_args, 1, argv[0], ops_file_arg)); +#endif + + process_info pi (start_process (&exec_args[0], argv[0], false, true)); + + { + __gnu_cxx::stdio_filebuf<char> fb ( + pi.out_fd, ios_base::out | ios_base::binary); + ostream os (&fb); + + if (!at_once) + { + // See if we there is a UTF-8 BOM in the input file. If so, + // then we need to write it before prologues. + // + if (ifs.peek () == 0xEF) + { + ifs.get (); + if (ifs.get () != 0xBB || ifs.get () != 0xBF) + { + e << name << ": error: invalid UTF-8 BOM sequence" << endl; + fb.close (); + wait_process (pi, argv[0]); + return 1; + } + + os.put (0xEF); + os.put (0xBB); + os.put (0xBF); + } + } + + if (!ops.trace ()) + { + // Add the standard prologue. + // + os << "#line 1 \"<standard-odb-prologue>\"" << endl; + + // Make sure ODB compiler and libodb versions are compatible. + // + os << "#include <odb/version.hxx>" << endl + << endl + << "#if ODB_VERSION != " << ODB_VERSION << endl + << "# error incompatible ODB compiler and runtime " << + "versions" << endl + << "#endif" << endl + << endl; + + // Include std::string. It is used as a default type for + // the implicit discriminator member in polymorphism + // support. + // + os << "#include <string>" << endl + << endl; + + // Add ODB compiler metaprogramming tests. + // + os << "namespace odb" << endl + << "{" << endl + << "namespace compiler" << endl + << "{" << endl; + + // operator< test, used in validator. + // + os << "template <typename T>" << endl + << "bool" << endl + << "has_lt_operator (const T& x, const T& y)" << endl + << "{" << endl + << "bool r (x < y);" << endl + << "return r;" << endl + << "}" << endl; + + os << "}" << endl + << "}" << endl; + } + + // Add custom prologue if any. + // + // NOTE: if you change the format, you also need to update code + // in include.cxx + // + size_t pro_count (1); + if (ops.odb_prologue ().count (db) != 0) + { + strings const& pro (ops.odb_prologue ()[db]); + for (size_t i (0); i < pro.size (); ++i, ++pro_count) + { + os << "#line 1 \"<odb-prologue-" << pro_count << ">\"" << endl + << pro[i] << endl; + } + } + + if (ops.odb_prologue_file ().count (db) != 0) + { + strings const& prof (ops.odb_prologue_file ()[db]); + for (size_t i (0); i < prof.size (); ++i, ++pro_count) + { + os << "#line 1 \"<odb-prologue-" << pro_count << ">\"" + << endl; + + ifstream ifs (prof[i].c_str (), ios_base::in | ios_base::binary); + + if (!ifs.is_open ()) + { + e << prof[i] << ": error: unable to open in read mode" << endl; + fb.close (); + wait_process (pi, argv[0]); + return 1; + } + + if (!(os << ifs.rdbuf ())) + { + e << prof[i] << ": error: io failure" << endl; + fb.close (); + wait_process (pi, argv[0]); + return 1; + } + + os << endl; + } + } + + if (at_once) + { + // Include all the input files (no need to escape). + // + os << "#line 1 \"<command-line>\"" << endl; + + bool b (ops.include_with_brackets ()); + char op (b ? '<' : '"'), cl (b ? '>' : '"'); + + for (; end < plugin_args.size (); ++end) + os << "#include " << op << plugin_args[end] << cl << endl; + } + else + { + // Write the synthesized translation unit to stdout. + // + os << "#line 1 \"" << escape_path (name) << "\"" << endl; + + if (!(os << ifs.rdbuf ())) + { + e << name << ": error: io failure" << endl; + fb.close (); + wait_process (pi, argv[0]); + return 1; + } + + // Add a new line in case the input file doesn't end with one. + // + os << endl; + } + + // Add custom epilogue if any. + // + // NOTE: if you change the format, you also need to update code + // in include.cxx + // + size_t epi_count (1); + if (ops.odb_epilogue ().count (db) != 0) + { + strings const& epi (ops.odb_epilogue ()[db]); + for (size_t i (0); i < epi.size (); ++i, ++epi_count) + { + os << "#line 1 \"<odb-epilogue-" << epi_count << ">\"" << endl + << epi[i] << endl; + } + } + + if (ops.odb_epilogue_file ().count (db) != 0) + { + strings const& epif (ops.odb_epilogue_file ()[db]); + for (size_t i (0); i < epif.size (); ++i, ++epi_count) + { + os << "#line 1 \"<odb-epilogue-" << epi_count << ">\"" + << endl; + + ifstream ifs (epif[i].c_str (), ios_base::in | ios_base::binary); + + if (!ifs.is_open ()) + { + e << epif[i] << ": error: unable to open in read mode" << endl; + fb.close (); + wait_process (pi, argv[0]); + return 1; + } + + if (!(os << ifs.rdbuf ())) + { + e << epif[i] << ": error: io failure" << endl; + fb.close (); + wait_process (pi, argv[0]); + return 1; + } + + os << endl; + } + } + + if (!ops.trace ()) + { + // Add the standard epilogue at the end so that we see all + // the declarations. + // + os << "#line 1 \"<standard-odb-epilogue>\"" << endl; + + // Includes for standard smart pointers. The Boost TR1 header + // may or may not delegate to the GCC implementation. In either + // case, the necessary declarations will be provided so we don't + // need to do anything. + // + os << "#include <memory>" << endl; + + // Standard wrapper traits. + // + os << "#include <odb/wrapper-traits.hxx>" << endl; + + // Standard pointer traits. + // + os << "#include <odb/pointer-traits.hxx>" << endl; + + // Standard container traits. + // + os << "#include <odb/container-traits.hxx>" << endl; + + // TR1 wrapper/pointer traits. + // +#ifndef ODB_BUILD2 + if (ops.std () == cxx_version::cxx98) + os << endl + << "#ifndef BOOST_TR1_MEMORY_HPP_INCLUDED" << endl + << "# include <tr1/memory>" << endl + << "#endif" << endl + << "#include <odb/tr1/wrapper-traits.hxx>" << endl + << "#include <odb/tr1/pointer-traits.hxx>" << endl; +#endif + } + } + + // Filter the output stream looking for communication from the + // plugin. + // + { + __gnu_cxx::stdio_filebuf<char> fb (pi.in_ofd, ios_base::in); + istream is (&fb); + + for (bool first (true); !is.eof (); ) + { + string line; + getline (is, line); + + if (is.fail () && !is.eof ()) + { + e << argv[0] << ": error: io failure while parsing output" + << endl; + wait_process (pi, argv[0]); + return 1; + } + + if (line.compare (0, 9, "odb:sloc:") == 0) + { + if (show_sloc || sloc_limit != 0) + { + size_t n; + istringstream is (string (line, 9, string::npos)); + + if (!(is >> n && is.eof ())) + { + e << argv[0] << ": error: invalid odb:sloc value" << endl; + wait_process (pi, argv[0]); + return 1; + } + + sloc_total += n; + } + + continue; + } + + if (first) + first = false; + else + cout << endl; + + cout << line; + } + } + + if (!wait_process (pi, argv[0])) + return 1; + } // End input file loop. + } // End database loop. + + // Handle SLOC. + // + if (show_sloc) + e << "total: " << sloc_total << endl; + + if (sloc_limit != 0 && sloc_limit < sloc_total) + { + e << argv[0] << ": error: SLOC limit of " << sloc_limit << " lines " << + "has been exceeded" << endl; + + if (!show_sloc) + e << argv[0] << ": info: use the --show-sloc option to see the " + << "current total" << endl; + + return 1; + } + } + catch (profile_failure const&) + { + // Diagnostics has already been issued. + // + return 1; + } + catch (process_failure const&) + { + // Diagnostics has already been issued. + // + return 1; + } + catch (invalid_path const& ex) + { + e << argv[0] << ": error: invalid path '" << ex.path () << "'" << endl; + return 1; + } + catch (cli::exception const& ex) + { + e << ex << endl; + return 1; + } +} + +static inline string +encode_plugin_flag (string const& k) +{ + return "-fplugin-arg-odb-" + k; +} + +static string +encode_plugin_option (string const& k, string const& cv) +{ + string o ("-fplugin-arg-odb-"); + o += k; + o += '='; + + if (!cv.empty ()) + { + // A value cannot contain '='. Encode it as the backspace + // character. + // + string v (cv); + for (size_t i (0); i < v.size (); ++i) + if (v[i] == '=') + v[i] = '\b'; + + o += v; + } + + return o; +} + +static paths +profile_paths (strings const& sargs, char const* name) +{ + // Copy some of the arguments from the passed list. We also need + // the g++ executable. + // + strings args; + + args.push_back (sargs[0]); + args.push_back ("-v"); + args.push_back ("-x"); + args.push_back ("c++"); + args.push_back ("-E"); + args.push_back ("-P"); + + for (strings::const_iterator i (++sargs.begin ()), end (sargs.end ()); + i != end; ++i) + { + string const& a (*i); + + // -I + // + if (a.size () > 1 && a[0] == '-' && a[1] == 'I') + { + args.push_back (a); + + if (a.size () == 2) // -I /path + { + args.push_back (*(++i)); + } + } + // -framework + // + else if (a == "-isystem" || + a == "-iquote" || + a == "-idirafter" || + a == "-isysroot" || + a == "-framework") + { + args.push_back (a); + + if (++i == end) + { + cerr << name << ": error: expected argument for the " << a + << " option" << endl; + throw profile_failure (); + } + + args.push_back (*i); + } + // --sysroot + // + else if (a.compare (0, 10, "--sysroot=") == 0) + args.push_back (a); + // -std + // + else if (a.compare (0, 5, "-std=") == 0) + args.push_back (a); + } + + // Create an execvp-compatible argument array. + // + vector<char const*> exec_args; + + for (strings::const_iterator i (args.begin ()), end (args.end ()); + i != end; ++i) + { + exec_args.push_back (i->c_str ()); + } + + exec_args.push_back ("-"); // Compile stdin. + exec_args.push_back (0); + +#ifdef _WIN32 + string ops_file_arg; + auto_remove opt_file_rm (fixup_cmd_line (exec_args, 1, name, ops_file_arg)); +#endif + + process_info pi (start_process (&exec_args[0], name, true)); + close (pi.out_fd); // Preprocess empty file. + + // Read the output into a temporary string stream. We don't parse + // it on the fly because we don't know whether it is the data or + // diagnostics until after the process is terminated and we get + // the exit code. We also cannot first wait for the exist code + // and then read the output because the process might get blocked. + // + stringstream ss; + { + __gnu_cxx::stdio_filebuf<char> fb (pi.in_efd, ios_base::in); + istream is (&fb); + + for (bool first (true); !is.eof (); ) + { + string line; + getline (is, line); + + if (is.fail () && !is.eof ()) + { + cerr << name << ": error: " + << "io failure while parsing profile paths" << endl; + + wait_process (pi, name); + throw profile_failure (); + } + + if (first) + first = false; + else + ss << endl; + + ss << line; + } + } + + if (!wait_process (pi, name)) + { + // Things didn't go well and ss should contain the diagnostics. + // In case it is empty, issue our own. + // + if (!ss.str ().empty ()) + cerr << ss.rdbuf (); + else + cerr << name << ": error: unable to extract profile paths" << endl; + + throw profile_failure (); + } + + // Parse the cached output. + // + paths r; + { + enum + { + read_prefix, + read_path, + read_suffix + } state = read_prefix; + + while (!ss.eof () && state != read_suffix) + { + string line; + getline (ss, line); + + if (ss.fail () && !ss.eof ()) + { + cerr << name << ": error: " + << "io failure while parsing profile paths" << endl; + throw profile_failure (); + } + + switch (state) + { + case read_prefix: + { + // The English string that we are looking for is "#include <...> + // search starts here:" but it can be translated. However, all + // the translations seems to have the "#include" and "<...>" + // parts, so we can search for those. + // + if (line.find ("#include") != string::npos && + line.find ("<...>") != string::npos) + state = read_path; + break; + } + case read_path: + { + // The end of the list is terminated with the "End of search + // list." line, which, again, can be translated. Here we don't + // have any invariable parts that we can use. Instead, we will + // rely on the fact that all the paths are space-indented. + // + if (!line.empty () && line[0] != ' ') + state = read_suffix; + else + // Paths are indented with a space. + // + r.push_back (path (string (line, 1))); + + break; + } + case read_suffix: + { + // We shouldn't get here. + break; + } + } + } + + if (state != read_suffix) + { + cerr << name << ": error: unable to parse profile paths" << endl; + throw profile_failure (); + } + } + + return r; +} + +// +// Path manipulation. +// + +static string +escape_path (string const& p) +{ + string r; + + for (size_t i (0); i < p.size (); ++i) + { + if (p[i] == '\\') + r += "\\\\"; + else + r += p[i]; + } + + return r; +} + +static path +path_search (path const& f) +{ + typedef path::traits traits; + + // If there is a directory component in the file, then the PATH + // search does not apply. + // + if (!f.directory ().empty ()) + return f; + + string paths; + + // If there is no PATH in environment then the default search + // path is the current directory. + // + if (char const* s = getenv ("PATH")) + paths = s; + else + paths = traits::path_separator; + + // On Windows also check the current directory. + // +#ifdef _WIN32 + paths += traits::path_separator; +#endif + + struct stat info; + + for (size_t b (0), e (paths.find (traits::path_separator)); + b != string::npos;) + { + path p (string (paths, b, e != string::npos ? e - b : e)); + + // Empty path (i.e., a double colon or a colon at the beginning + // or end of PATH) means search in the current dirrectory. + // + if (p.empty ()) + p = path ("."); + + path dp (p / f); + + // Just check that the file exist without checking for permissions, etc. + // + if (stat (dp.string ().c_str (), &info) == 0 && S_ISREG (info.st_mode)) + return dp; + + // On Windows also try the path with the .exe extension. + // +#ifdef _WIN32 + dp += ".exe"; + + if (stat (dp.string ().c_str (), &info) == 0 && S_ISREG (info.st_mode)) + return dp; +#endif + + if (e == string::npos) + b = e; + else + { + b = e + 1; + e = paths.find (traits::path_separator, b); + } + } + + return path (); +} + +static path +driver_path (path const& drv) +{ + return drv.directory ().empty () ? path_search (drv) : drv; +} + +#ifndef ODB_STATIC_PLUGIN +static path +plugin_path (path const& drv, +#ifdef ODB_GCC_PLUGIN_DIR + string const& gxx) +#else + string const&) +#endif +{ +#ifdef _WIN32 + char const plugin_ext[] = ".dll"; + +// While GCC 8 switched to using .dylib as the plugin extension, there is a +// bug in the extension stripping code. So for now we use the .so extension +// everywhere (see also buildfile if changing this). +// +//#elif defined(__APPLE__) && defined(ODB_BUILD2) +// char const plugin_ext[] = ".dylib"; +#else + char const plugin_ext[] = ".so"; +#endif + + // Figure out the plugin base name which is just the driver name (but + // without the .exe extension on Windows). If the driver name starts with + // 'lt-', then we are running through the libtool script. Strip this prefix + // -- the shared object should be in the same directory. + // +#ifdef _WIN32 + string b (drv.leaf ().base ().string ()); +#else + string b (drv.leaf ().string ()); +#endif + + bool lt (b.size () > 3 && b[0] == 'l' && b[1] == 't' && b[2] == '-'); + if (lt) + b = string (b, 3, string::npos); + + path dp (driver_path (drv)); + + if (dp.empty ()) + { + cerr << drv << ": error: unable to resolve ODB driver path" << endl; + return path (); + } + + dp = dp.directory (); + struct stat info; + + // Regardless of whether we were given a plugin path, first try + // the current directory for the .la file. This will make sure + // running ODB from the build directory works as expected. + // + // @@ BUILD2: not going to work for build2 build. + // + path pp (dp / path (b + ".la")); + if (stat (pp.string ().c_str (), &info) == 0) + { + pp = dp / path (".libs") / path (b + ".so"); + if (stat (pp.string ().c_str (), &info) == 0) + return pp; + } + +#ifdef ODB_GCC_PLUGIN_DIR + // Plugin should be installed into the GCC default plugin directory. + // Ideally, in this situation, we would simply pass the plugin name and + // let GCC append the correct directory. Unfortunately, this mechanism + // was only added in GCC 4.6 so in order to support 4.5 we will have to + // emulate it ourselves. + // + if (!lt) + { + //@@ BUILD2: if/when dropping old GCC should just get rid of this. +#if 1 + // First get the default GCC plugin directory. + // + path d; + vector<char const*> exec_args; + exec_args.push_back (gxx.c_str ()); + exec_args.push_back ("-print-file-name=plugin"); + exec_args.push_back (0); + + process_info pi ( + start_process ( + &exec_args[0], drv.string ().c_str (), false, true)); + close (pi.out_fd); + + // Read the path from stdout. + // + { + __gnu_cxx::stdio_filebuf<char> fb (pi.in_ofd, ios_base::in); + istream is (&fb); + string line; + getline (is, line); + d = path (line); + } + + if (!wait_process (pi, drv.string ().c_str ())) + return path (); // Assume GCC issued some diagnostics. + + if (d.string () == "plugin") + { + cerr << drv << ": error: unable to obtain GCC plugin directory" << endl; + return path (); + } + + // See if the plugin is there. + // + pp = d / path (b + plugin_ext); + if (stat (pp.string ().c_str (), &info) != 0) + { + cerr << drv << ": error: no ODB plugin in GCC plugin directory '" << + d << "'" << endl; + return path (); + } + + return pp; +#else + return path (b); +#endif + } +#elif defined (ODB_PLUGIN_PATH) + // If we were given a plugin path, use that unless we are running + // via libtool. + // + if (!lt) + { + string rp (ODB_PLUGIN_PATH); + if (!rp.empty ()) + dp /= path (rp); + + pp = dp / path (b + plugin_ext); + + if (stat (pp.string ().c_str (), &info) != 0) + { + cerr << drv << ": error: no ODB plugin in '" << dp << "'" << endl; + return path (); + } + + return pp; + } +#endif + + // Try in the current directory. + // + pp = dp / path (b + plugin_ext); + if (stat (pp.string ().c_str (), &info) != 0) + { + cerr << drv << ": error: unable to locate ODB plugin" << endl; + return path (); + } + + return pp; +} +#endif + +// +// Process manipulation. +// + +#ifndef _WIN32 + +static process_info +start_process (char const* args[], char const* name, bool err, bool out) +{ + int out_fd[2]; + int in_efd[2]; + int in_ofd[2]; + + if (pipe (out_fd) == -1 || + (err && pipe (in_efd) == -1) || + (out && pipe (in_ofd) == -1)) + { + char const* err (strerror (errno)); + cerr << name << ": error: " << err << endl; + throw process_failure (); + } + + pid_t pid (fork ()); + + if (pid == -1) + { + char const* err (strerror (errno)); + cerr << name << ": error: " << err << endl; + throw process_failure (); + } + + if (pid == 0) + { + // Child. Close the write end of the pipe and duplicate the read end + // to stdin. Then close the original read end descriptors. + // + if (close (out_fd[1]) == -1 || + dup2 (out_fd[0], STDIN_FILENO) == -1 || + close (out_fd[0]) == -1) + { + char const* err (strerror (errno)); + cerr << name << ": error: " << err << endl; + throw process_failure (); + } + + // Do the same for the stderr if requested. + // + if (err) + { + if (close (in_efd[0]) == -1 || + dup2 (in_efd[1], STDERR_FILENO) == -1 || + close (in_efd[1]) == -1) + { + char const* err (strerror (errno)); + cerr << name << ": error: " << err << endl; + throw process_failure (); + } + } + + // Do the same for the stdout if requested. + // + if (out) + { + if (close (in_ofd[0]) == -1 || + dup2 (in_ofd[1], STDOUT_FILENO) == -1 || + close (in_ofd[1]) == -1) + { + char const* err (strerror (errno)); + cerr << name << ": error: " << err << endl; + throw process_failure (); + } + } + + if (execvp (args[0], const_cast<char**> (&args[0])) == -1) + { + char const* err (strerror (errno)); + cerr << args[0] << ": error: " << err << endl; + throw process_failure (); + } + } + else + { + // Parent. Close the other ends of the pipes. + // + if (close (out_fd[0]) == -1 || + (err && close (in_efd[1]) == -1) || + (out && close (in_ofd[1]) == -1)) + { + char const* err (strerror (errno)); + cerr << name << ": error: " << err << endl; + throw process_failure (); + } + } + + process_info r; + r.id = pid; + r.out_fd = out_fd[1]; + r.in_efd = err ? in_efd[0] : 0; + r.in_ofd = out ? in_ofd[0] : 0; + return r; +} + +static bool +wait_process (process_info pi, char const* name) +{ + int status; + + if (waitpid (pi.id, &status, 0) == -1) + { + char const* err (strerror (errno)); + cerr << name << ": error: " << err << endl; + throw process_failure (); + } + + return WIFEXITED (status) && WEXITSTATUS (status) == 0; +} + +#else // _WIN32 + +static void +print_error (char const* name) +{ + LPTSTR msg; + DWORD e (GetLastError()); + + if (!FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + 0, + e, + MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPTSTR) &msg, + 0, + 0)) + { + cerr << name << ": error: unknown error code " << e << endl; + return; + } + + cerr << name << ": error: " << msg << endl; + LocalFree (msg); +} + +// On Windows we need to protect command line arguments with spaces using +// quotes. Since there could be actual quotes in the value, we need to escape +// them. +// +static void +append_quoted (string& cmd_line, const char* ca) +{ + string a (ca); + bool quote (a.find (' ') != string::npos); + + if (quote) + cmd_line += '"'; + + for (size_t i (0); i < a.size (); ++i) + { + if (a[i] == '"') + cmd_line += "\\\""; + else + cmd_line += a[i]; + } + + if (quote) + cmd_line += '"'; +} + +// Deal with Windows command line length limit. +// +// The best approach seems to be passing the command line in an "options file" +// ("response file" in Microsoft's terminology). +// +static auto_remove +fixup_cmd_line (vector<const char*>& args, + size_t start, + const char* name, + string& arg) +{ + // Calculate the would-be command line length similar to how start_process() + // implementation does it. + // + size_t n (0); + string s; + for (const char* a: args) + { + if (a != nullptr) + { + if (n != 0) + n++; // For the space separator. + + s.clear (); + append_quoted (s, a); + n += s.size (); + } + } + + if (n <= 32766) // 32768 - "Unicode terminating null character". + return auto_remove (); + + // Create the temporary file. + // + char d[MAX_PATH + 1], p[MAX_PATH + 1]; + if (GetTempPathA (sizeof (d), d) == 0 || + GetTempFileNameA (d, "odb-options-", 0, p) == 0) + { + print_error (name); + throw process_failure (); + } + + auto_remove rm = auto_remove (path (p)); + try + { + ofstream ofs (p); + if (!ofs.is_open ()) + { + cerr << name << ": error: unable to open '" << p << "' in write mode" + << endl; + throw process_failure (); + } + + ofs.exceptions (ios_base::badbit | ios_base::failbit); + + // Write the arguments to file. + // + // The format is a space-separated list of potentially-quoted arguments + // with support for backslash-escaping. + // + string b; + for (size_t i (start), n (args.size () - 1); i != n; ++i) + { + const char* a (args[i]); + + // We will most likely have backslashes so just do it. + // + { + for (b.clear (); *a != '\0'; ++a) + { + if (*a != '\\') + b += *a; + else + b += "\\\\"; + } + + a = b.c_str (); + } + + s.clear (); + append_quoted (s, a); + ofs << (i != start ? " " : "") << s; + } + + ofs << '\n'; + ofs.close (); + } + catch (const ios_base::failure&) + { + cerr << name << ": error: unable to write to '" << p << "'" << endl; + throw process_failure (); + } + + // Rewrite the command line. + // + arg = string ("@") + p; + args.resize (start); + args.push_back (arg.c_str()); + args.push_back (nullptr); + + return rm; +} + +static process_info +start_process (char const* args[], char const* name, bool err, bool out) +{ + HANDLE out_h[2]; + HANDLE in_eh[2]; + HANDLE in_oh[2]; + SECURITY_ATTRIBUTES sa; + + sa.nLength = sizeof (SECURITY_ATTRIBUTES); + sa.bInheritHandle = true; + sa.lpSecurityDescriptor = 0; + + if (!CreatePipe (&out_h[0], &out_h[1], &sa, 0) || + !SetHandleInformation (out_h[1], HANDLE_FLAG_INHERIT, 0)) + { + print_error (name); + throw process_failure (); + } + + if (err) + { + if (!CreatePipe (&in_eh[0], &in_eh[1], &sa, 0) || + !SetHandleInformation (in_eh[0], HANDLE_FLAG_INHERIT, 0)) + { + print_error (name); + throw process_failure (); + } + } + + if (out) + { + if (!CreatePipe (&in_oh[0], &in_oh[1], &sa, 0) || + !SetHandleInformation (in_oh[0], HANDLE_FLAG_INHERIT, 0)) + { + print_error (name); + throw process_failure (); + } + } + + // Create the process. + // + path file (args[0]); + + // Do PATH search. + // + if (file.directory ().empty ()) + file = path_search (file); + else if (file.base () == file) // No extension + file += ".exe"; // Assume .exe. + + if (file.empty ()) + { + cerr << args[0] << ": error: file not found" << endl; + throw process_failure (); + } + + // Serialize the arguments to string. + // + string cmd_line; + + for (char const** p (args); *p != 0; ++p) + { + if (p != args) + cmd_line += ' '; + + append_quoted (cmd_line, *p); + } + + // Prepare other info. + // + STARTUPINFO si; + PROCESS_INFORMATION pi; + + memset (&si, 0, sizeof (STARTUPINFO)); + memset (&pi, 0, sizeof (PROCESS_INFORMATION)); + + si.cb = sizeof(STARTUPINFO); + + if (err) + si.hStdError = in_eh[1]; + else + si.hStdError = GetStdHandle (STD_ERROR_HANDLE); + + if (out) + si.hStdOutput = in_oh[1]; + else + si.hStdOutput = GetStdHandle (STD_OUTPUT_HANDLE); + + si.hStdInput = out_h[0]; + si.dwFlags |= STARTF_USESTDHANDLES; + + if (!CreateProcess ( + file.string ().c_str (), + const_cast<char*> (cmd_line.c_str ()), + 0, // Process security attributes. + 0, // Primary thread security attributes. + true, // Inherit handles. + 0, // Creation flags. + 0, // Use our environment. + 0, // Use our current directory. + &si, + &pi)) + { + print_error (name); + throw process_failure (); + } + + CloseHandle (pi.hThread); + CloseHandle (out_h[0]); + + if (err) + CloseHandle (in_eh[1]); + + if (out) + CloseHandle (in_oh[1]); + + process_info r; + r.id = pi.hProcess; + r.out_fd = _open_osfhandle ((intptr_t) (out_h[1]), 0); + + if (r.out_fd == -1) + { + cerr << name << ": error: unable to obtain C file handle" << endl; + throw process_failure (); + } + + if (err) + { + // Pass _O_TEXT to get newline translation. + // + r.in_efd = _open_osfhandle ((intptr_t) (in_eh[0]), _O_TEXT); + + if (r.in_efd == -1) + { + cerr << name << ": error: unable to obtain C file handle" << endl; + throw process_failure (); + } + } + else + r.in_efd = 0; + + if (out) + { + // Pass _O_TEXT to get newline translation. + // + r.in_ofd = _open_osfhandle ((intptr_t) (in_oh[0]), _O_TEXT); + + if (r.in_ofd == -1) + { + cerr << name << ": error: unable to obtain C file handle" << endl; + throw process_failure (); + } + } + else + r.in_ofd = 0; + + return r; +} + +static bool +wait_process (process_info pi, char const* name) +{ + DWORD status; + + if (WaitForSingleObject (pi.id, INFINITE) != WAIT_OBJECT_0 || + !GetExitCodeProcess (pi.id, &status)) + { + print_error (name); + throw process_failure (); + } + + CloseHandle (pi.id); + return status == 0; +} + +#endif // _WIN32 diff --git a/odb/odb/option-functions.cxx b/odb/odb/option-functions.cxx new file mode 100644 index 0000000..7eda934 --- /dev/null +++ b/odb/odb/option-functions.cxx @@ -0,0 +1,132 @@ +// file : odb/option-functions.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <set> +#include <utility> // std::make_pair() + +#include <odb/option-functions.hxx> + +using namespace std; + +void +process_options (options& o) +{ + database db (o.database ()[0]); + + // If --generate-schema-only was specified, then set --generate-schema + // as well. + // + if (o.generate_schema_only ()) + o.generate_schema (true); + + // If --warn-hard was specified, then set both --warn-hard-{add,delete}. + // + if (o.warn_hard ()) + { + o.warn_hard_add (true); + o.warn_hard_delete (true); + } + + // Set the default schema format depending on the database. + // + if (o.generate_schema () && o.schema_format ()[db].empty ()) + { + set<schema_format>& f (o.schema_format ()[db]); + + switch (db) + { + case database::common: + { + break; // No schema for common. + } + case database::mssql: + case database::mysql: + case database::oracle: + case database::pgsql: + { + f.insert (schema_format::sql); + break; + } + case database::sqlite: + { + f.insert (schema_format::embedded); + break; + } + } + } + + // Set default --schema-version-table value. + // + if (o.schema_version_table ().count (db) == 0) + o.schema_version_table ()[db] = "schema_version"; + + // Set default --schema-name value. + // + if (o.schema_name ().count (db) == 0) + o.schema_name ()[db] = ""; + + // Set default --fkeys-deferrable-mode value. + // + if (o.fkeys_deferrable_mode ().count (db) == 0) + o.fkeys_deferrable_mode ()[db] = deferrable::deferred; + + // Set default --{export,extern}-symbol values. + // + if (o.export_symbol ().count (db) == 0) + o.export_symbol ()[db] = ""; + + if (o.extern_symbol ().count (db) == 0) + o.extern_symbol ()[db] = ""; + + // Set default --*-file-suffix values. + // + { + database cm (database::common); + + o.odb_file_suffix ().insert (make_pair (cm, string ("-odb"))); + o.sql_file_suffix ().insert (make_pair (cm, string (""))); + o.schema_file_suffix ().insert (make_pair (cm, string ("-schema"))); + o.changelog_file_suffix ().insert (make_pair (cm, string (""))); + } + + if (o.multi_database () == multi_database::disabled) + { + o.odb_file_suffix ().insert (make_pair (db, string ("-odb"))); + o.sql_file_suffix ().insert (make_pair (db, string (""))); + o.schema_file_suffix ().insert (make_pair (db, string ("-schema"))); + o.changelog_file_suffix ().insert (make_pair (db, string (""))); + } + else + { + o.odb_file_suffix ().insert (make_pair (db, "-odb-" + db.string ())); + o.sql_file_suffix ().insert (make_pair (db, "-" + db.string ())); + o.schema_file_suffix ().insert (make_pair (db, "-schema-" + db.string ())); + o.changelog_file_suffix ().insert (make_pair (db, '-' + db.string ())); + } + + // Set default --default-database value. + // + if (!o.default_database_specified ()) + { + switch (o.multi_database ()) + { + case multi_database::disabled: + { + o.default_database (db); + o.default_database_specified (true); + break; + } + case multi_database::dynamic: + { + o.default_database (database::common); + o.default_database_specified (true); + break; + } + case multi_database::static_: + { + // No default database unless explicitly specified. + break; + } + } + } +} diff --git a/odb/odb/option-functions.hxx b/odb/odb/option-functions.hxx new file mode 100644 index 0000000..a4b072c --- /dev/null +++ b/odb/odb/option-functions.hxx @@ -0,0 +1,12 @@ +// file : odb/option-functions.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_OPTION_FUNCTIONS_HXX +#define ODB_OPTION_FUNCTIONS_HXX + +#include <odb/options.hxx> + +void +process_options (options&); + +#endif // ODB_OPTION_FUNCTIONS_HXX diff --git a/odb/odb/option-parsers.hxx b/odb/odb/option-parsers.hxx new file mode 100644 index 0000000..2daa6eb --- /dev/null +++ b/odb/odb/option-parsers.hxx @@ -0,0 +1,199 @@ +// file : odb/option-parsers.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_OPTION_PARSERS_HXX +#define ODB_OPTION_PARSERS_HXX + +#include <vector> +#include <sstream> + +#include <odb/option-types.hxx> +#include <odb/options.hxx> + +namespace cli +{ + // Return true if there is a database prefix. + // + template <typename V> + bool + parse_option_value (std::string const& o, std::string const& ov, + database& k, V& v) + { + bool r (false); + std::string::size_type p = ov.find (':'); + + std::string vstr; + if (p != std::string::npos) + { + std::string kstr (ov, 0, p); + + // See if this prefix resolves to the database name. If not, + // assume there is no prefix. + // + std::istringstream ks (kstr); + + if (ks >> k && ks.eof ()) + { + r = true; + vstr.assign (ov, p + 1, std::string::npos); + } + } + + if (!r) + vstr = ov; // Use the whole value. + + if (!vstr.empty ()) + { + std::istringstream vs (vstr); + + if (!(vs >> v && vs.eof ())) + throw invalid_value (o, ov); + } + else + v = V (); + + return r; + } + + // Specialization for std::string. + // + bool + parse_option_value (std::string const&, std::string const& ov, + database& k, std::string& v) + { + bool r (false); + std::string::size_type p = ov.find (':'); + + if (p != std::string::npos) + { + std::string kstr (ov, 0, p); + + // See if this prefix resolves to the database name. If not, + // assume there is no prefix. + // + std::istringstream ks (kstr); + + if (ks >> k && ks.eof ()) + { + r = true; + v.assign (ov, p + 1, std::string::npos); + } + } + + if (!r) + v = ov; // Use the whole value. + + return r; + } + + template <typename V> + struct parser<database_map<V> > + { + typedef database_map<V> map; + + static void + parse (map& m, bool& xs, scanner& s) + { + xs = true; + std::string o (s.next ()); + + if (s.more ()) + { + database k; + V v; + + if (parse_option_value (o, s.next (), k, v)) + m[k] = v; // Override any old value. + else + { + // No database prefix is specified which means it applies to + // all the databases. We also don't want to override database- + // specific values, so use insert(). + // + m.insert (typename map::value_type (database::common, v)); + m.insert (typename map::value_type (database::mssql, v)); + m.insert (typename map::value_type (database::mysql, v)); + m.insert (typename map::value_type (database::oracle, v)); + m.insert (typename map::value_type (database::pgsql, v)); + m.insert (typename map::value_type (database::sqlite, v)); + } + } + else + throw missing_value (o); + } + }; + + template <typename V> + struct parser<database_map<std::vector<V> > > + { + typedef database_map<std::vector<V> > map; + + static void + parse (map& m, bool& xs, scanner& s) + { + xs = true; + std::string o (s.next ()); + + if (s.more ()) + { + database k; + V v; + + if (parse_option_value (o, s.next (), k, v)) + m[k].push_back (v); + else + { + // No database prefix is specified which means it applies to + // all the databases. + // + m[database::common].push_back (v); + m[database::mssql].push_back (v); + m[database::mysql].push_back (v); + m[database::oracle].push_back (v); + m[database::pgsql].push_back (v); + m[database::sqlite].push_back (v); + } + } + else + throw missing_value (o); + } + }; + + template <typename V> + struct parser<database_map<std::set<V> > > + { + typedef database_map<std::set<V> > map; + + static void + parse (map& m, bool& xs, scanner& s) + { + xs = true; + std::string o (s.next ()); + + if (s.more ()) + { + database k; + V v; + + if (parse_option_value (o, s.next (), k, v)) + m[k].insert (v); + else + { + // No database prefix is specified which means it applies to + // all the databases. + // + m[database::common].insert (v); + m[database::mssql].insert (v); + m[database::mysql].insert (v); + m[database::oracle].insert (v); + m[database::pgsql].insert (v); + m[database::sqlite].insert (v); + } + } + else + throw missing_value (o); + } + }; +} + +#endif // ODB_OPTION_PARSERS_HXX diff --git a/odb/odb/option-types.cxx b/odb/odb/option-types.cxx new file mode 100644 index 0000000..c4a030b --- /dev/null +++ b/odb/odb/option-types.cxx @@ -0,0 +1,352 @@ +// file : odb/option-types.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <istream> +#include <ostream> +#include <algorithm> // std::lower_bound + +#include <odb/option-types.hxx> + +using namespace std; + +// +// cxx_version +// + +static const char* cxx_version_[] = +{ + "c++98", + "c++11", + "c++14", + "c++17", + "c++20" +}; + +string cxx_version:: +string () const +{ + return cxx_version_[v_]; +} + +istream& +operator>> (istream& is, cxx_version& v) +{ + string s; + is >> s; + + if (!is.fail ()) + { + if (s == "c++98") + v = cxx_version::cxx98; + else if (s == "c++11") + v = cxx_version::cxx11; + else if (s == "c++14") + v = cxx_version::cxx14; + else if (s == "c++17") + v = cxx_version::cxx17; + else if (s == "c++20") + v = cxx_version::cxx20; + else + is.setstate (istream::failbit); + } + + return is; +} + +// +// database +// + +static const char* database_[] = +{ + "common", + "mssql", + "mysql", + "oracle", + "pgsql", + "sqlite" +}; + +static const char* database_name_[] = +{ + "Common Interface", + "SQL Server", + "MySQL", + "Oracle", + "PostgreSQL", + "SQLite" +}; + +string database:: +string () const +{ + return database_[v_]; +} + +string database:: +name () const +{ + return database_name_[v_]; +} + +istream& +operator>> (istream& is, database& db) +{ + string s; + is >> s; + + if (!is.fail ()) + { + const char** e (database_ + sizeof (database_) / sizeof (char*)); + const char** i (lower_bound (database_, e, s)); + + if (i != e && *i == s) + db = database::value (i - database_); + else + is.setstate (istream::failbit); + } + + return is; +} + +ostream& +operator<< (ostream& os, database db) +{ + return os << db.string (); +} + +// +// multi_database +// + +static const char* multi_database_[] = +{ + "dynamic", + "static", + "disabled" +}; + +string multi_database:: +string () const +{ + return multi_database_[v_]; +} + +istream& +operator>> (istream& is, multi_database& db) +{ + string s; + is >> s; + + if (!is.fail ()) + { + const char** e ( + multi_database_ + sizeof (multi_database_) / sizeof (char*) - 1); + const char** i (lower_bound (multi_database_, e, s)); + + if (i != e && *i == s) + db = multi_database::value (i - multi_database_); + else + is.setstate (istream::failbit); + } + + return is; +} + +ostream& +operator<< (ostream& os, multi_database db) +{ + return os << db.string (); +} + +// +// schema_format +// + +static const char* schema_format_[] = +{ + "embedded", + "separate", + "sql" +}; + +string schema_format:: +string () const +{ + return schema_format_[v_]; +} + +istream& +operator>> (istream& is, schema_format& sf) +{ + string s; + is >> s; + + if (!is.fail ()) + { + const char** e (schema_format_ + sizeof (schema_format_) / sizeof (char*)); + const char** i (lower_bound (schema_format_, e, s)); + + if (i != e && *i == s) + sf = schema_format::value (i - schema_format_); + else + is.setstate (istream::failbit); + } + + return is; +} + +ostream& +operator<< (ostream& os, schema_format sf) +{ + return os << sf.string (); +} + +// +// name_case +// + +istream& +operator>> (istream& is, name_case& v) +{ + string s; + is >> s; + + if (!is.fail ()) + { + if (s == "upper") + v = name_case::upper; + else if (s == "lower") + v = name_case::lower; + else + is.setstate (istream::failbit); + } + + return is; +} + +// +// pgsql_version +// + +istream& +operator>> (istream& is, pgsql_version& v) +{ + unsigned short major, minor; + + // Extract the major version. + // + is >> major; + + if (!is.fail ()) + { + // Extract the decimal point. + // + char p; + is >> p; + + if (!is.fail () && p == '.') + { + // Extract the minor version. + // + is >> minor; + + if (!is.fail ()) + v = pgsql_version (major, minor); + } + else + is.setstate (istream::failbit); + } + + return is; +} + +ostream& +operator<< (ostream& os, pgsql_version v) +{ + return os << v.ver_major () << '.' << v.ver_minor (); +} + +// +// oracle_version +// + +istream& +operator>> (istream& is, oracle_version& v) +{ + unsigned short major, minor; + + // Extract the major version. + // + is >> major; + + if (!is.fail ()) + { + // Extract the decimal point. + // + char p; + is >> p; + + if (!is.fail () && p == '.') + { + // Extract the minor version. + // + is >> minor; + + if (!is.fail ()) + v = oracle_version (major, minor); + } + else + is.setstate (istream::failbit); + } + + return is; +} + +ostream& +operator<< (ostream& os, oracle_version v) +{ + return os << v.ver_major () << '.' << v.ver_minor (); +} + +// +// mssql_version +// + +istream& +operator>> (istream& is, mssql_version& v) +{ + unsigned short major, minor; + + // Extract the major version. + // + is >> major; + + if (!is.fail ()) + { + // Extract the decimal point. + // + char p; + is >> p; + + if (!is.fail () && p == '.') + { + // Extract the minor version. + // + is >> minor; + + if (!is.fail ()) + v = mssql_version (major, minor); + } + else + is.setstate (istream::failbit); + } + + return is; +} + +ostream& +operator<< (ostream& os, mssql_version v) +{ + return os << v.ver_major () << '.' << v.ver_minor (); +} diff --git a/odb/odb/option-types.hxx b/odb/odb/option-types.hxx new file mode 100644 index 0000000..869fc83 --- /dev/null +++ b/odb/odb/option-types.hxx @@ -0,0 +1,391 @@ +// file : odb/option-types.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_OPTION_TYPES_HXX +#define ODB_OPTION_TYPES_HXX + +#include <map> +#include <iosfwd> +#include <string> +#include <cassert> + +#include <odb/semantics/relational/name.hxx> +#include <odb/semantics/relational/deferrable.hxx> + +using semantics::relational::qname; +using semantics::relational::deferrable; + +struct cxx_version +{ + enum value + { + cxx98, + cxx11, + cxx14, + cxx17, + cxx20 + }; + + cxx_version (value v = value (0)) : v_ (v) {} + operator value () const {return v_;} + + std::string + string () const; + +private: + value v_; +}; + +std::istream& +operator>> (std::istream&, cxx_version&); + +// +// +struct database +{ + enum value + { + // Keep in alphabetic order. + // + common, + mssql, + mysql, + oracle, + pgsql, + sqlite + }; + + database (value v = value (0)) : v_ (v) {} + operator value () const {return v_;} + + std::string + string () const; + + // Full name (e.g., PostgreSQL). + // + std::string + name () const; + +private: + value v_; +}; + +std::istream& +operator>> (std::istream&, database&); + +std::ostream& +operator<< (std::ostream&, database); + +// +// +template <typename V> +struct database_map: std::map<database, V> +{ + typedef std::map<database, V> base_type; + + using base_type::operator[]; + + V const& + operator[] (database const& k) const + { + typename base_type::const_iterator i (this->find (k)); + assert (i != this->end ()); + return i->second; + } +}; + +// +// +struct multi_database +{ + enum value + { + // Keep in alphabetic order. + // + dynamic, + static_, + disabled // Special value. + }; + + multi_database (value v = disabled) : v_ (v) {} + operator value () const {return v_;} + + std::string + string () const; + +private: + value v_; +}; + +std::istream& +operator>> (std::istream&, multi_database&); + +std::ostream& +operator<< (std::ostream&, multi_database); + +// +// +struct schema_format +{ + enum value + { + // Keep in alphabetic order. + // + embedded, + separate, + sql + }; + + schema_format (value v = value (0)) : v_ (v) {} + operator value () const {return v_;} + + std::string + string () const; + +private: + value v_; +}; + +std::istream& +operator>> (std::istream&, schema_format&); + +std::ostream& +operator<< (std::ostream&, schema_format); + +// +// +struct name_case +{ + enum value + { + upper, + lower + }; + + name_case (value v = value (0)) : v_ (v) {} + operator value () const {return v_;} + +private: + value v_; +}; + +std::istream& +operator>> (std::istream&, name_case&); + +// +// +struct pgsql_version +{ + pgsql_version (unsigned short major, unsigned short minor) + : major_ (major), minor_ (minor) + { + } + + unsigned short + ver_major () const + { + return major_; + } + + unsigned short + ver_minor () const + { + return minor_; + } + +private: + unsigned short major_; + unsigned short minor_; +}; + +inline bool +operator== (const pgsql_version& x, const pgsql_version& y) +{ + return x.ver_major () == y.ver_major (); +} + +inline bool +operator!= (const pgsql_version& x, const pgsql_version& y) +{ + return !(x == y); +} + +inline bool +operator< (const pgsql_version& x, const pgsql_version& y) +{ + return x.ver_major () < y.ver_major () || + (x.ver_major () == y.ver_major () && + x.ver_minor () < y.ver_minor ()); +} + +inline bool +operator> (const pgsql_version& x, const pgsql_version& y) +{ + return x.ver_major () > y.ver_major () || + (x.ver_major () == y.ver_major () && + x.ver_minor () > y.ver_minor ()); +} + +inline bool +operator<= (const pgsql_version& x, const pgsql_version& y) +{ + return !(x > y); +} + +inline bool +operator>= (const pgsql_version& x, const pgsql_version& y) +{ + return !(x < y); +} + +std::istream& +operator>> (std::istream&, pgsql_version&); + +std::ostream& +operator<< (std::ostream&, pgsql_version); + +// +// +struct oracle_version +{ + oracle_version (unsigned short major, unsigned short minor) + : major_ (major), minor_ (minor) + { + } + + unsigned short + ver_major () const + { + return major_; + } + + unsigned short + ver_minor () const + { + return minor_; + } + +private: + unsigned short major_; + unsigned short minor_; +}; + +inline bool +operator== (const oracle_version& x, const oracle_version& y) +{ + return x.ver_major () == y.ver_major (); +} + +inline bool +operator!= (const oracle_version& x, const oracle_version& y) +{ + return !(x == y); +} + +inline bool +operator< (const oracle_version& x, const oracle_version& y) +{ + return x.ver_major () < y.ver_major () || + (x.ver_major () == y.ver_major () && + x.ver_minor () < y.ver_minor ()); +} + +inline bool +operator> (const oracle_version& x, const oracle_version& y) +{ + return x.ver_major () > y.ver_major () || + (x.ver_major () == y.ver_major () && + x.ver_minor () > y.ver_minor ()); +} + +inline bool +operator<= (const oracle_version& x, const oracle_version& y) +{ + return !(x > y); +} + +inline bool +operator>= (const oracle_version& x, const oracle_version& y) +{ + return !(x < y); +} + +std::istream& +operator>> (std::istream&, oracle_version&); + +std::ostream& +operator<< (std::ostream&, oracle_version); + +// +// +struct mssql_version +{ + mssql_version (unsigned short major, unsigned short minor) + : major_ (major), minor_ (minor) + { + } + + unsigned short + ver_major () const + { + return major_; + } + + unsigned short + ver_minor () const + { + return minor_; + } + +private: + unsigned short major_; + unsigned short minor_; +}; + +inline bool +operator== (const mssql_version& x, const mssql_version& y) +{ + return x.ver_major () == y.ver_major (); +} + +inline bool +operator!= (const mssql_version& x, const mssql_version& y) +{ + return !(x == y); +} + +inline bool +operator< (const mssql_version& x, const mssql_version& y) +{ + return x.ver_major () < y.ver_major () || + (x.ver_major () == y.ver_major () && + x.ver_minor () < y.ver_minor ()); +} + +inline bool +operator> (const mssql_version& x, const mssql_version& y) +{ + return x.ver_major () > y.ver_major () || + (x.ver_major () == y.ver_major () && + x.ver_minor () > y.ver_minor ()); +} + +inline bool +operator<= (const mssql_version& x, const mssql_version& y) +{ + return !(x > y); +} + +inline bool +operator>= (const mssql_version& x, const mssql_version& y) +{ + return !(x < y); +} + +std::istream& +operator>> (std::istream&, mssql_version&); + +std::ostream& +operator<< (std::ostream&, mssql_version); + +#endif // ODB_OPTION_TYPES_HXX diff --git a/odb/odb/options.cli b/odb/odb/options.cli new file mode 100644 index 0000000..17ee438 --- /dev/null +++ b/odb/odb/options.cli @@ -0,0 +1,1086 @@ +// file : odb/options.cli +// license : GNU GPL v3; see accompanying LICENSE file + +include <set>; +include <vector>; +include <string>; +include <cstddef>; +include <cstdint>; + +include <odb/option-types.hxx>; + +class options +{ + // + // Wrapper options. These are not passed to the plugin. + // + + std::uint64_t --build2-metadata; // Leave undocumented/hidden. + + bool --help {"Print usage information and exit."}; + bool --version {"Print version and exit."}; + + // + // C++ preprocessor options. Also not passed to the plugin. + // + std::vector<std::string> -I + { + "<dir>", + "Add <dir> to the beginning of the list of directories to be searched + for included header files." + }; + + std::vector<std::string> -D + { + "<name>[=<def>]", + "Define macro <name> with definition <def>. If definition is omitted, + define <name> to be 1." + }; + + std::vector<std::string> -U + { + "<name>", + "Cancel any previous definitions of macro <name>, either built-in or + provided with the \cb{-D} option." + }; + + // + // Plugin options. + // + std::vector< ::database> --database | -d + { + "<db>", + "Generate code for the <db> database. Valid values are \cb{mssql}, + \cb{mysql}, \cb{oracle}, \cb{pgsql}, \cb{sqlite}, and \cb{common} + (multi-database mode only)." + }; + + ::multi_database --multi-database | -m = ::multi_database::disabled + { + "<type>", + "Enable multi-database support and specify its type. Valid values + for this option are \cb{static} and \cb{dynamic}. + + In the multi-database mode, options that determine the kind (for + example, \cb{--schema-format}), names (for example, + \cb{--odb-file-suffix}), or content (for example, prologue and + epilogue options) of the output files can be prefixed with the + database name followed by a colon, for example, \cb{mysql:value}. + This restricts the value of such an option to only apply to + generated files corresponding to this database." + }; + + ::database --default-database + { + "<db>", + "When static multi-database support is used, specify the database that + should be made the default. When dynamic multi-database support is used, + \cb{common} is always made the default database." + }; + + bool --generate-query | -q + { + "Generate query support code. Without this support you cannot use views + and can only load objects via their ids." + }; + + bool --generate-prepared + { + "Generate prepared query execution support code." + }; + + bool --omit-unprepared + { + "Omit un-prepared (once-off) query execution support code." + }; + + bool --generate-session | -e + { + "Generate session support code. With this option session support will + be enabled by default for all the persistent classes except those for + which it was explicitly disabled using the \cb{db session} pragma." + }; + + bool --generate-schema | -s + { + "Generate the database schema. The database schema contains SQL + statements that create database tables necessary to store persistent + classes defined in the file being compiled. Note that by applying + this schema, all the existing information stored in such tables will + be lost. + + Depending on the database being used (\cb{--database} option), the + schema is generated either as a standalone SQL file or embedded into + the generated C++ code. By default the SQL file is generated for + the MySQL, PostgreSQL, Oracle, and Microsoft SQL Server databases + and the schema is embedded into the C++ code for the SQLite database. + Use the \cb{--schema-format} option to alter the default schema format. + + If database schema evolution support is enabled (that is, the object + model version is specified), then this option also triggers the + generation of database schema migration statements, again either as + standalong SQL files or embedded into the generated C++ code. You can + suppress the generation of schema migration statements by specifying + the \cb{--suppress-migration} option." + }; + + bool --generate-schema-only + { + "Generate only the database schema. Note that this option is only valid + when generating schema as a standalone SQL file (see \cb{--schema-format} + for details)." + }; + + bool --suppress-migration + { + "Suppress the generation of database schema migration statements." + }; + + bool --suppress-schema-version + { + "Suppress the generation of schema version table. If you specify this + option then you are also expected to manually specify the database + schema version and migration state at runtime using the + \cb{odb::database::schema_version()} function." + }; + + database_map<qname> --schema-version-table + { + "<name>", + "Specify the alternative schema version table name instead of the default + \cb{schema_version}. If you specify this option then you are also + expected to manually specify the schema version table name at runtime + using the \cb{odb::database::schema_version_table()} function. The table + name can be qualified." + }; + + database_map<std::set< ::schema_format> > --schema-format + { + "<format>", + "Generate the database schema in the specified format. Pass \cb{sql} as + <format> to generate the database schema as a standalone SQL file or + pass \cb{embedded} to embed the schema into the generated C++ code. + The \cb{separate} value is similar to \cb{embedded} except the schema + creation code is generated into a separate C++ file (\cb{name-schema.cxx} + by default). This value is primarily useful if you want to place the + schema creation functionality into a separate program or library. + Repeat this option to generate the same database schema in multiple + formats." + }; + + bool --omit-drop + { + "Omit \cb{DROP} statements from the generated database schema." + }; + + bool --omit-create + { + "Omit \cb{CREATE} statements from the generated database schema." + }; + + database_map<std::string> --schema-name + { + "<name>", + "Use <name> as the database schema name. Schema names are primarily + used to distinguish between multiple embedded schemas in the schema + catalog. They are not to be confused with database schemas (database + namespaces) which are specified with the \cb{--schema} option. If + this option is not specified, the empty name, which is the default + schema name, is used." + }; + + database_map<deferrable> --fkeys-deferrable-mode + { + "<m>", + "Use constraint checking mode <m> in foreign keys generated for object + relationships. Valid values for this option are \cb{not_deferrable}, + \cb{immediate}, and \cb{deferred} (default). MySQL and SQL Server do + not support deferrable foreign keys and for these databases such keys + are generated commented out. Other foreign keys generated by the ODB + compiler (such as the ones used to support containers and polymorphic + hierarchies) are always generated as not deferrable. + + Note also that if you use either \cb{not_deferrable} or \cb{immediate} + mode, then the order in which you persist, update, and erase objects + within a transaction becomes important." + }; + + std::string --default-pointer = "*" + { + "<ptr>", + "Use <ptr> as the default pointer for persistent objects and views. + Objects and views that do not have a pointer assigned with the + \cb{db pointer} pragma will use this pointer by default. The value + of this option can be '\cb{*}' which denotes the raw pointer and is + the default, or qualified name of a smart pointer class template, + for example, \cb{std::shared_ptr}. In the latter case, the ODB compiler + constructs the object or view pointer by adding a single template + argument of the object or view type to the qualified name, for example + \cb{std::shared_ptr<object>}. The ODB runtime uses object and view + pointers to return, and, in case of objects, pass and cache + dynamically allocated instances of object and view types. + + Except for the raw pointer and the standard smart pointers defined + in the \cb{<memory>} header file, you are expected to include the + definition of the default pointer at the beginning of the generated + header file. There are two common ways to achieve this: you can either + include the necessary header in the file being compiled or you can use + the \cb{--hxx-prologue} option to add the necessary \cb{#include} + directive to the generated code." + }; + + std::string --session-type = "odb::session" + { + "<type>", + "Use <type> as the alternative session type instead of the default + \cb{odb::session}. This option can be used to specify a custom + session implementation to be use by the persistent classes. Note + that you will also need to include the definition of the custom + session type into the generated header file. This is normally + achieved with the \cb{--hxx-prologue*} options." + }; + + // The following option is "fake" in that it is actually handled by + // argv_file_scanner. We have it here to get the documentation. + // + std::string --profile | -p + { + "<name>", + "Specify a profile that should be used during compilation. A + profile is an options file. The ODB compiler first looks for + a database-specific version with the name constructed by appending + the \cb{-}\ci{database}\cb{.options} suffix to <name>, where + \ci{database} is the database name as specified with the + \cb{--database} option. If this file is not found, then the + ODB compiler looks for a database-independant version with the + name constructed by appending just the \cb{.options} suffix. + + The profile options files are searched for in the same set of + directories as C++ headers included with the \cb{#include <...>} + directive (built-in paths plus those specified with the \cb{-I} + options). The options file is first searched for in the directory + itself and then in its \cb{odb/} subdirectory. + + For the format of the options file refer to the \cb{--options-file} + option below. You can repeat this option to specify more than one + profile." + }; + + bool --at-once + { + "Generate code for all the input files as well as for all the files that + they include at once. The result is a single set of source/schema files + that contain all the generated code. If more than one input file is + specified together with this option, then the \cb{--input-name} option + must also be specified in order to provide the base name for the output + files. In this case, the directory part of such a base name is used as + the location of the combined file. This can be important for the + \cb{#include} directive resolution." + }; + + database_map<qname> --schema + { + "<schema>", + "Specify a database schema (database namespace) that should be + assigned to the persistent classes in the file being compiled. + Database schemas are not to be confused with database schema + names (schema catalog names) which are specified with the + \cb{--schema-name} option." + }; + + // Export control. + // + database_map<std::string> --export-symbol + { + "<symbol>", + "Insert <symbol> in places where DLL export/import control statements + (\cb{__declspec(dllexport/dllimport)}) are necessary. See also the + \cb{--extern-symbol} option below." + }; + + database_map<std::string> --extern-symbol + { + "<symbol>", + "If <symbol> is defined, insert it in places where a template + instantiation must be declared \cb{extern}. This option is normally + used together with \cb{--export-symbol} when both multi-database + support and queries are enabled." + }; + + // Language. + // + // @@ TODO: perhaps we should switch to latest to match how we build + // runtime by default? + // + cxx_version --std = cxx_version::cxx98 + { + "<version>", + "Specify the C++ standard that should be used during compilation. + Valid values are \cb{c++98} (default), \cb{c++11}, \cb{c++14}, + \cb{c++17}, and \cb{c++20}." + }; + + // Diagnostics. + // + bool --warn-hard-add + { + "Warn about hard-added data members." + }; + + bool --warn-hard-delete + { + "Warn about hard-deleted data members and persistent classes." + }; + + bool --warn-hard + { + "Warn about both hard-added and hard-deleted data members and + persistent classes." + }; + + // Output. + // + std::string --output-dir | -o + { + "<dir>", + "Write the generated files to <dir> instead of the current directory." + }; + + std::string --input-name + { + "<name>", + "Use <name> instead of the input file to derive the names of the + generated files. If the \cb{--at-once} option is specified, then + the directory part of <name> is used as the location of the + combined file. Refer to the \cb{--at-once} option for details." + }; + + database_map<std::string> --changelog + { + "<file>", + "Read/write changelog from/to <file> instead of the default changelog + file. The default changelog file name is derived from the input file + name and it is placed into the same directory as the input file. Note + that the \cb{--output-dir} option does not affect the changelog file + location. In other words, by default, the changelog file is treated + as another input rather than output even though the ODB compiler may + modify it. Use the \cb{--changelog-in} and \cb{--changelog-out} + options to specify different input and output chaneglog files." + }; + + database_map<std::string> --changelog-in + { + "<file>", + "Read changelog from <file> instead of the default changelog file. If + this option is specified, then you must also specify the output + chanegelog file with \cb{--changelog-out}." + }; + + database_map<std::string> --changelog-out + { + "<file>", + "Write changelog to <file> instead of the default changelog file. If + this option is specified, then you must also specify the input + chanegelog file with \cb{--changelog-in}." + }; + + database_map<std::string> --changelog-dir + { + "<dir>", + "Use <dir> instead of the input file directory as the changelog file + directory. This directory is also added to changelog files specified + with the \cb{--changelog}, \cb{--changelog-in}, and \cb{--changelog-in} + options unless they are absolute paths." + }; + + bool --init-changelog + { + "Force re-initialization of the changelog even if one exists (all the + existing change history will be lost). This option is primarily useful + for automated testing." + }; + + database_map<std::string> --odb-file-suffix + { + "<suffix>", + "Use <suffix> to construct the names of the generated C++ files. In + the single-database mode the default value for this option is \cb{-odb}. + In the multi-database mode it is \cb{-odb} for the files corresponding + to the \cb{common} database and \c{\b{-odb-}\i{db}} (where \ci{db} is + the database name) for other databases." + }; + + database_map<std::string> --sql-file-suffix + { + "<suffix>", + "Use <suffix> to construct the name of the generated schema SQL file. + In the single-database mode by default no suffix is used. In the + multi-database mode the default value for this option is + \c{\b{-}\i{db}} (where \ci{db} is the database name)." + }; + + database_map<std::string> --schema-file-suffix + { + "<suffix>", + "Use <suffix> to construct the name of the generated schema C++ source + file. In the single-database mode the default value for this option is + \cb{-schema}. In the multi-database mode it is \c{\b{-schema-}\i{db}} + (where \ci{db} is the database name). See the \cb{--schema-format} + option for details." + }; + + database_map<std::string> --changelog-file-suffix + { + "<sfx>", + "Use <sfx> to construct the name of the changelog file. In the + single-database mode by default no suffix is used. In the + multi-database mode the default value for this option is + \c{\b{-}\i{db}} (where \ci{db} is the database name)." + }; + + std::string --hxx-suffix = ".hxx" + { + "<suffix>", + "Use <suffix> instead of the default \cb{.hxx} to construct the name of + the generated C++ header file." + }; + + std::string --ixx-suffix = ".ixx" + { + "<suffix>", + "Use <suffix> instead of the default \cb{.ixx} to construct the name of + the generated C++ inline file." + }; + + std::string --cxx-suffix = ".cxx" + { + "<suffix>", + "Use <suffix> instead of the default \cb{.cxx} to construct the name of + the generated C++ source file." + }; + + std::string --sql-suffix = ".sql" + { + "<suffix>", + "Use <suffix> instead of the default \cb{.sql} to construct the name of + the generated database schema file." + }; + + std::string --changelog-suffix = ".xml" + { + "<suffix>", + "Use <suffix> instead of the default \cb{.xml} to construct the name of + the changelog file." + }; + + // Prologues. + // + database_map<std::vector<std::string> > --hxx-prologue + { + "<text>", + "Insert <text> at the beginning of the generated C++ header file." + }; + + database_map<std::vector<std::string> > --ixx-prologue + { + "<text>", + "Insert <text> at the beginning of the generated C++ inline file." + }; + + database_map<std::vector<std::string> > --cxx-prologue + { + "<text>", + "Insert <text> at the beginning of the generated C++ source file." + }; + + database_map<std::vector<std::string> > --schema-prologue + { + "<text>", + "Insert <text> at the beginning of the generated schema C++ source file." + }; + + database_map<std::vector<std::string> > --sql-prologue + { + "<text>", + "Insert <text> at the beginning of the generated database schema file." + }; + + database_map<std::vector<std::string> > --migration-prologue + { + "<text>", + "Insert <text> at the beginning of the generated database migration file." + }; + + // Interludes. + // + database_map<std::vector<std::string> > --sql-interlude + { + "<text>", + "Insert <text> after all the \cb{DROP} and before any \cb{CREATE} + statements in the generated database schema file." + }; + + // Epilogues. + // + database_map<std::vector<std::string> > --hxx-epilogue + { + "<text>", + "Insert <text> at the end of the generated C++ header file." + }; + + database_map<std::vector<std::string> > --ixx-epilogue + { + "<text>", + "Insert <text> at the end of the generated C++ inline file." + }; + + database_map<std::vector<std::string> > --cxx-epilogue + { + "<text>", + "Insert <text> at the end of the generated C++ source file." + }; + + database_map<std::vector<std::string> > --schema-epilogue + { + "<text>", + "Insert <text> at the end of the generated schema C++ source file." + }; + + database_map<std::vector<std::string> > --sql-epilogue + { + "<text>", + "Insert <text> at the end of the generated database schema file." + }; + + database_map<std::vector<std::string> > --migration-epilogue + { + "<text>", + "Insert <text> at the end of the generated database migration file." + }; + + // Prologue files. + // + database_map<std::vector<std::string> > --hxx-prologue-file + { + "<file>", + "Insert the content of <file> at the beginning of the generated C++ + header file." + }; + + database_map<std::vector<std::string> > --ixx-prologue-file + { + "<file>", + "Insert the content of <file> at the beginning of the generated C++ + inline file." + }; + + database_map<std::vector<std::string> > --cxx-prologue-file + { + "<file>", + "Insert the content of <file> at the beginning of the generated C++ + source file." + }; + + database_map<std::vector<std::string> > --schema-prologue-file + { + "<file>", + "Insert the content of <file> at the beginning of the generated schema + C++ source file." + }; + + database_map<std::vector<std::string> > --sql-prologue-file + { + "<file>", + "Insert the content of <file> at the beginning of the generated + database schema file." + }; + + database_map<std::vector<std::string> > --migration-prologue-file + { + "<f>", + "Insert the content of file <f> at the beginning of the generated database + migration file." + }; + + // Interlude files. + // + database_map<std::vector<std::string> > --sql-interlude-file + { + "<file>", + "Insert the content of <file> after all the \cb{DROP} and before any + \cb{CREATE} statements in the generated database schema file." + }; + + // Epilogue files. + // + database_map<std::vector<std::string> > --hxx-epilogue-file + { + "<file>", + "Insert the content of <file> at the end of the generated C++ header + file." + }; + + database_map<std::vector<std::string> > --ixx-epilogue-file + { + "<file>", + "Insert the content of <file> at the end of the generated C++ inline + file." + }; + + database_map<std::vector<std::string> > --cxx-epilogue-file + { + "<file>", + "Insert the content of <file> at the end of the generated C++ source + file." + }; + + database_map<std::vector<std::string> > --schema-epilogue-file + { + "<file>", + "Insert the content of <file> at the end of the generated schema C++ + source file." + }; + + database_map<std::vector<std::string> > --sql-epilogue-file + { + "<file>", + "Insert the content of <file> at the end of the generated database + schema file." + }; + + database_map<std::vector<std::string> > --migration-epilogue-file + { + "<f>", + "Insert the content of file <f> at the end of the generated database + migration file." + }; + + // ODB compilation prologue/epilogue. + // + database_map<std::vector<std::string> > --odb-prologue + { + "<text>", + "Compile <text> before the input header file. This option allows you + to add additional declarations, such as custom traits specializations, + to the ODB compilation process." + }; + + database_map<std::vector<std::string> > --odb-prologue-file + { + "<file>", + "Compile <file> contents before the input header file. Prologue files + are compiled after all the prologue text fragments (\cb{--odb-prologue} + option)." + }; + + database_map<std::vector<std::string> > --odb-epilogue + { + "<text>", + "Compile <text> after the input header file. This option allows you + to add additional declarations, such as custom traits specializations, + to the ODB compilation process." + }; + + database_map<std::vector<std::string> > --odb-epilogue-file + { + "<file>", + "Compile <file> contents after the input header file. Epilogue files + are compiled after all the epilogue text fragments (\cb{--odb-epilogue} + option)." + }; + + // SQL names. + // + database_map<std::string> --table-prefix + { + "<prefix>", + "Add <prefix> to table names and, for databases that have global index + and/or foreign key names, to those names as well. The prefix is added to + both names that were specified with the \cb{db table} and \cb{db index} + pragmas and those that were automatically derived from class and data + member names. If you require a separator, such as an underscore, + between the prefix and the name, then you should include it into the + prefix value." + }; + + database_map<std::string> --index-suffix + { + "<suffix>", + "Use <suffix> instead of the default \cb{_i} to construct index names. + The suffix is only added to names that were automatically derived from + data member names. If you require a separator, such as an underscore, + between the name and the suffix, then you should include it into the + suffix value." + }; + + database_map<std::string> --fkey-suffix + { + "<suffix>", + "Use <suffix> instead of the default \cb{_fk} to construct foreign key + names. If you require a separator, such as an underscore, between the + name and the suffix, then you should include it into the suffix value." + }; + + database_map<std::string> --sequence-suffix + { + "<suffix>", + "Use <suffix> instead of the default \cb{_seq} to construct sequence + names. If you require a separator, such as an underscore, between the + name and the suffix, then you should include it into the suffix value." + }; + + database_map<name_case> --sql-name-case + { + "<case>", + "Convert all automatically-derived SQL names to upper or lower case. + Valid values for this option are \cb{upper} and \cb{lower}." + }; + + database_map<std::vector<std::string> > --table-regex + { + "<regex>", + "Add <regex> to the list of regular expressions that is used to + transform automatically-derived table names. See the SQL NAME + TRANSFORMATIONS section below for details." + }; + + database_map<std::vector<std::string> > --column-regex + { + "<regex>", + "Add <regex> to the list of regular expressions that is used to + transform automatically-derived column names. See the SQL NAME + TRANSFORMATIONS section below for details." + }; + + database_map<std::vector<std::string> > --index-regex + { + "<regex>", + "Add <regex> to the list of regular expressions that is used to + transform automatically-derived index names. See the SQL NAME + TRANSFORMATIONS section below for details." + }; + + database_map<std::vector<std::string> > --fkey-regex + { + "<regex>", + "Add <regex> to the list of regular expressions that is used to + transform automatically-derived foreign key names. See the SQL NAME + TRANSFORMATIONS section below for details." + }; + + database_map<std::vector<std::string> > --sequence-regex + { + "<regex>", + "Add <regex> to the list of regular expressions that is used to + transform automatically-derived sequence names. See the SQL NAME + TRANSFORMATIONS section below for details." + }; + + database_map<std::vector<std::string> > --statement-regex + { + "<regex>", + "Add <regex> to the list of regular expressions that is used to + transform automatically-derived prepared statement names. See + the SQL NAME TRANSFORMATIONS section below for details." + }; + + database_map<std::vector<std::string> > --sql-name-regex + { + "<regex>", + "Add <regex> to the list of regular expressions that is used to + transform all automatically-derived SQL names. See the SQL NAME + TRANSFORMATIONS section below for details." + }; + + bool --sql-name-regex-trace + { + "Trace the process of applying regular expressions specified with the + SQL name \cb{--*-regex} options. Use this option to find out why your + regular expressions don't do what you expected them to do." + }; + + // Accessor/modifier options. + // + std::vector<std::string> --accessor-regex + { + "<regex>", + "Add <regex> to the list of regular expressions used to transform + data member names to function names when searching for a suitable + accessor function. The argument to this option is a Perl-like regular + expression in the form \c{\b{/}\i{pattern}\b{/}\i{replacement}\b{/}}. + Any character can be used as a delimiter instead of '\cb{/}' and the + delimiter can be escaped inside \ci{pattern} and \ci{replacement} + with a backslash (\cb{\\}). You can specify multiple regular + expressions by repeating this option. + + All the regular expressions are tried in the order specified and + the first expression that produces a suitable accessor function is + used. Each expression is tried twice: first with the actual member + name and then with the member's \i{public name} which is obtained by + removing the common member name decorations, such as leading and + trailing underscores, the \cb{m_} prefix, etc. The ODB compiler also + includes a number of built-in expressions for commonly used accessor + names, such as \cb{get_foo}, \cb{getFoo}, \cb{getfoo}, and just + \cb{foo}. The built-in expressions are tried last. + + As an example, the following expression transforms data members with + public names in the form \cb{foo} to accessor names in the form + \cb{GetFoo}: + + \cb{/(.+)/Get\\u$1/} + + See also the REGEX AND SHELL QUOTING section below." + }; + + bool --accessor-regex-trace + { + "Trace the process of applying regular expressions specified with the + \cb{--accessor-regex} option. Use this option to find out why your + regular expressions don't do what you expected them to do." + }; + + std::vector<std::string> --modifier-regex + { + "<regex>", + "Add <regex> to the list of regular expressions used to transform + data member names to function names when searching for a suitable + modifier function. The argument to this option is a Perl-like regular + expression in the form \c{\b{/}\i{pattern}\b{/}\i{replacement}\b{/}}. + Any character can be used as a delimiter instead of '\cb{/}' and the + delimiter can be escaped inside \ci{pattern} and \ci{replacement} + with a backslash (\cb{\\}). You can specify multiple regular + expressions by repeating this option. + + All the regular expressions are tried in the order specified and + the first expression that produces a suitable modifier function is + used. Each expression is tried twice: first with the actual member + name and then with the member's \i{public name} which is obtained by + removing the common member name decorations, such as leading and + trailing underscores, the \cb{m_} prefix, etc. The ODB compiler also + includes a number of built-in expressions for commonly used modifier + names, such as \cb{set_foo}, \cb{setFoo}, \cb{setfoo}, and just + \cb{foo}. The built-in expressions are tried last. + + As an example, the following expression transforms data members with + public names in the form \cb{foo} to modifier names in the form + \cb{SetFoo}: + + \cb{/(.+)/Set\\u$1/} + + See also the REGEX AND SHELL QUOTING section below." + }; + + bool --modifier-regex-trace + { + "Trace the process of applying regular expressions specified with the + \cb{--modifier-regex} option. Use this option to find out why your + regular expressions don't do what you expected them to do." + }; + + // Include options. + // + bool --include-with-brackets + { + "Use angle brackets (<>) instead of quotes (\"\") in the generated + \cb{#include} directives." + }; + + std::string --include-prefix + { + "<prefix>", + "Add <prefix> to the generated \cb{#include} directive paths." + }; + + std::vector<std::string> --include-regex + { + "<regex>", + "Add <regex> to the list of regular expressions used to transform + generated \cb{#include} directive paths. The argument to this option + is a Perl-like regular expression in the form + \c{\b{/}\i{pattern}\b{/}\i{replacement}\b{/}}. Any character can be + used as a delimiter instead of '\cb{/}' and the delimiter can be escaped + inside \ci{pattern} and \ci{replacement} with a backslash (\cb{\\}). + You can specify multiple regular expressions by repeating this option. + All the regular expressions are tried in the order specified and the + first expression that matches is used. + + As an example, the following expression transforms include paths in + the form \cb{foo/bar-odb.h} to paths in the form + \cb{foo/generated/bar-odb.h}: + + \cb{%foo/(.+)-odb.h%foo/generated/$1-odb.h%} + + See also the REGEX AND SHELL QUOTING section below." + }; + + bool --include-regex-trace + { + "Trace the process of applying regular expressions specified with the + \cb{--include-regex} option. Use this option to find out why your + regular expressions don't do what you expected them to do." + }; + + std::string --guard-prefix + { + "<prefix>", + "Add <prefix> to the generated header inclusion guards. The prefix is + transformed to upper case and characters that are illegal in a + preprocessor macro name are replaced with underscores." + }; + + bool --show-sloc + { + "Print the number of generated physical source lines of code (SLOC)." + }; + + std::size_t --sloc-limit + { + "<num>", + "Check that the number of generated physical source lines of code (SLOC) + does not exceed <num>." + }; + + // The following option is "fake" in that it is actually handled by + // argv_file_scanner. We have it here to get the documentation. + // + std::string --options-file + { + "<file>", + "Read additional options from <file>. Each option should appear on a + separate line optionally followed by space or equal sign (\cb{=}) and an + option value. Empty lines and lines starting with \cb{#} are ignored. + Option values can be enclosed in double (\cb{\"}) or single (\cb{'}) + quotes to preserve leading and trailing whitespaces as well as to specify + empty values. If the value itself contains trailing or leading quotes, + enclose it with an extra pair of quotes, for example \cb{'\"x\"'}. + Non-leading and non-trailing quotes are interpreted as being part of the + option value. + + The semantics of providing options in a file is equivalent to providing + the same set of options in the same order on the command line at the + point where the \cb{--options-file} option is specified except that + the shell escaping and quoting is not required. Repeat this option + to specify more than one options file." + }; + + std::vector<std::string> -x + { + "<option>", + "Pass <option> to the underlying C++ compiler (\cb{g++}). The <option> + value that doesn't start with '\cb{-}' is considered the \cb{g++} + executable name." + }; + + bool -v {"Print the commands executed to run the stages of compilation."}; + + bool --trace {"Trace the compilation process."}; + + // + // MySQL-specific options. + // + + std::string --mysql-engine = "InnoDB" + { + "<engine>", + "Use <engine> instead of the default \cb{InnoDB} in the generated + database schema file. For more information on the storage engine + options see the MySQL documentation. If you would like to use the + database-default engine, pass \cb{default} as the value for this + option." + }; + + // + // SQLite-specific options. + // + + bool --sqlite-override-null + { + "Make all columns in the generated database schema allow \cb{NULL} + values. This is primarily useful in schema migration since SQLite + does not support dropping of columns. By making all columns \cb{NULL} + we can later \"delete\" them by setting their values to \cb{NULL}. + Note that this option overrides even the \cb{not_null} pragma." + }; + + bool --sqlite-lax-auto-id + { + "Do not force monotonically increasing automatically-assigned + object ids. In this mode the generated database schema omits the + \cb{AUTOINCREMENT} keyword which results in faster object persistence + but may lead to automatically-assigned ids not being in a strictly + ascending order. Refer to the SQLite documentation for details." + }; + + // + // PostgreSQL-specific options. + // + + ::pgsql_version --pgsql-server-version (7, 4) + { + "<ver>", + "Specify the minimum PostgreSQL server version with which the generated + C++ code and schema will be used. This information is used to enable + version-specific optimizations and workarounds in the generated C++ + code and schema. The version must be in the \c{\i{major}\b{.}\i{minor}} + form, for example, \cb{9.1}. If this option is not specified, then + \cb{7.4} or later is assumed." + }; + + // + // Oracle-specific options. + // + + ::oracle_version --oracle-client-version (10, 1) + { + "<ver>", + "Specify the minimum Oracle client library (OCI) version with which the + generated C++ code will be linked. This information is used to enable + version-specific optimizations and workarounds in the generated C++ + code. The version must be in the \c{\i{major}\b{.}\i{minor}} form, + for example, \cb{11.2}. If this option is not specified, then + \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. + // + + ::mssql_version --mssql-server-version (10, 0) + { + "<ver>", + "Specify the minimum SQL Server server version with which the generated + C++ code and schema will be used. This information is used to enable + version-specific optimizations and workarounds in the generated C++ + code and schema. The version must be in the \c{\i{major}\b{.}\i{minor}} + form, for example, \cb{9.0} (SQL Server 2005), \cb{10.5} (2008R2), or + \cb{11.0} (2012). If this option is not specified, then \cb{10.0} (SQL + Server 2008) or later is assumed." + }; + + unsigned int --mssql-short-limit = 1024 + { + "<size>", + "Specify the short data size limit. If a character, national character, or + binary data type has a maximum length (in bytes) less than or equal to + this limit, then it is treated as \i{short data}, otherwise it is \i{long + data}. For short data ODB pre-allocates an intermediate buffer of + the maximum size and binds it directly to a parameter or result + column. This way the underlying API (ODBC) can read/write directly + from/to this buffer. In the case of long data, the data is read/written + in chunks using the \cb{SQLGetData()}/\cb{SQLPutData()} ODBC functions. + While the long data approach reduces the amount of memory used by the + application, it may require greater CPU resources. The default short + data limit is 1024 bytes. When setting a custom short data limit, make + sure that it is sufficiently large so that no object id in the + application is treated as long data." + }; +}; diff --git a/odb/odb/parser.cxx b/odb/odb/parser.cxx new file mode 100644 index 0000000..c026808 --- /dev/null +++ b/odb/odb/parser.cxx @@ -0,0 +1,2403 @@ +// file : odb/parser.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <odb/gcc.hxx> // Keep it first. + +#include <set> +#include <map> +#include <vector> +#include <string> +#include <cassert> +#include <sstream> +#include <iostream> + +#include <odb/diagnostics.hxx> +#include <odb/parser.hxx> +#include <odb/semantics.hxx> + +using namespace std; +using namespace semantics; + +class parser::impl +{ +public: + typedef parser::failed failed; + + impl (options const&, loc_pragmas&, ns_loc_pragmas&, decl_pragmas&); + + unique_ptr<unit> + parse (tree global_scope, path const& main_file); + +private: + typedef semantics::access access; + + // Extended GCC tree declaration that is either a GCC tree + // declaration, a virtual declaration, or a pragma. If it is + // a pragma, then the assoc flag indicated whether this pragma + // has been associated with a declaration. Otherwise, the assoc + // flag indicates whether pragmas have been associated with this + // declaration (we use this to ignore certain declarations for + // pragma association purposes, e.g., the anonymous type in + // struct {...} m_). + // + struct tree_decl + { + tree decl; + virt_declaration const* vdecl; + pragma const* prag; + mutable bool assoc; // Allow modification via std::set iterator. + + tree_decl (tree d): decl (d), vdecl (0), prag (0), assoc (false) {} + tree_decl (virt_declaration const& d) + : decl (0), vdecl (&d), prag (0), assoc (false) {} + tree_decl (pragma const& p) + : decl (0), vdecl (0), prag (&p), assoc (false) {} + + bool + operator< (tree_decl const& y) const; + }; + + typedef multiset<tree_decl> decl_set; + +private: + void + collect (tree ns); + + void + emit (); + + // Emit a type declaration. This is either a named class-type definition/ + // declaration or a typedef. In the former case the function returns the + // newly created type node. In the latter case it returns 0. + // + type* + emit_type_decl (tree); + + // Emit a template declaration. + // + void + emit_template_decl (tree); + + class_template& + emit_class_template (tree, bool stub = false); + + union_template& + emit_union_template (tree, bool stub = false); + + template <typename T> + T& + emit_class (tree, path const& f, size_t l, size_t c, bool stub = false); + + template <typename T> + T& + emit_union (tree, path const& f, size_t l, size_t c, bool stub = false); + + // Access is not used when creating a stub. + // + enum_& + emit_enum (tree, + access, + path const& f, + size_t l, + size_t c, + bool stub = false); + + // Create new or find existing semantic graph type. + // + type& + emit_type (tree, access, path const& f, size_t l, size_t c); + + type& + create_type (tree, access, path const& f, size_t l, size_t c); + + string + emit_type_name (tree, bool direct = true); + + + // + // Pragma handling. + // + void + add_pragma (node&, pragma const&); + + // Process positioned and named pragmas. + // + void + process_pragmas (declaration const&, + node&, + string const& name, + decl_set::const_iterator begin, + decl_set::const_iterator cur, + decl_set::const_iterator end); + + // Process named pragmas only. + // + void + process_named_pragmas (declaration const&, node&); + + void + diagnose_unassoc_pragmas (decl_set const&); + + // Return declaration's fully-qualified scope name (e.g., ::foo::bar). + // + string + fq_scope (tree); + + // Return declaration's access. + // + access + decl_access (tree decl) + { + // Note that TREE_PUBLIC() returns something other than what we need. + // + if (TREE_PRIVATE (decl)) + return access::private_; + + if (TREE_PROTECTED (decl)) + return access::protected_; + + return access::public_; + } + + // + // + template <typename T> + void + define_fund (tree); + +private: + options const& ops_; + loc_pragmas& loc_pragmas_; + ns_loc_pragmas& ns_loc_pragmas_; + decl_pragmas& decl_pragmas_; + + bool trace; + ostream& ts; + + unit* unit_; + scope* scope_; + vector<scope*> class_scopes_; // Current hierarchy of class-like scopes. + size_t error_; + + decl_set decls_; + + typedef map<location_t, tree> decl_map; + decl_map all_decls_; +}; + +bool parser::impl::tree_decl:: +operator< (tree_decl const& y) const +{ + location_t xl, yl; + int xb (0), yb (0); + + if (decl != 0) + xl = real_source_location (decl); + else if (vdecl != 0) + { + xl = vdecl->ord; + xb = vdecl->ord_bias; + } + else + xl = prag->loc; + + if (y.decl != 0) + yl = real_source_location (y.decl); + else if (y.vdecl != 0) + { + yl = y.vdecl->ord; + yb = y.vdecl->ord_bias; + } + else + yl = y.prag->loc; + + // If both are virtual and at the same location, use the definition + // location to order them. + // + if (vdecl != 0 && y.vdecl != 0 && xl == yl && xb == yb) + { + xl = vdecl->loc; + yl = y.vdecl->loc; + } + + return xl < yl || (xl == yl && xb < yb); +} + +// +// Function templates. +// + +template <typename T> +void parser::impl:: +define_fund (tree t) +{ + t = TYPE_MAIN_VARIANT (t); + char const* name (IDENTIFIER_POINTER (DECL_NAME (TYPE_NAME (t)))); + + T& node (unit_->new_fund_node<T> (t)); + unit_->new_edge<defines> (*scope_, node, name); + unit_->insert (t, node); + + process_named_pragmas (t, node); +} + +template <typename T> +T& parser::impl:: +emit_class (tree c, path const& file, size_t line, size_t clmn, bool stub) +{ + c = TYPE_MAIN_VARIANT (c); + + // See if there is a stub already for this type. + // + T* c_node (0); + + if (node* n = unit_->find (c)) + { + c_node = &dynamic_cast<T&> (*n); + } + else + { + c_node = &unit_->new_node<T> (file, line, clmn, c); + unit_->insert (c, *c_node); + } + + if (stub || !COMPLETE_TYPE_P (c)) + return *c_node; + + // Note: "include" the base classes into the class scope (see comment for + // self-typedefs in emit_type_decl()). + // + class_scopes_.push_back (c_node); + + // Traverse base information. + // + tree bis (TYPE_BINFO (c)); + size_t n (bis ? BINFO_N_BASE_BINFOS (bis) : 0); + + for (size_t i (0); i < n; i++) + { + tree bi (BINFO_BASE_BINFO (bis, i)); + access a (access::public_); + + if (BINFO_BASE_ACCESSES (bis)) + { + tree ac (BINFO_BASE_ACCESS (bis, i)); + + if (ac == NULL_TREE || ac == access_public_node) + { + a = access::public_; + } + else if (ac == access_protected_node) + { + a = access::protected_; + } + else + { + assert (ac == access_private_node); + a = access::private_; + } + } + + bool virt (BINFO_VIRTUAL_P (bi)); + tree base (TYPE_MAIN_VARIANT (BINFO_TYPE (bi))); + + // Find the corresponding graph node. If we cannot find one then + // the base is a template instantiation since an ordinary class + // has to be defined (complete) in order to be a base. + // + class_* b_node (0); + string name; + + if (node* n = unit_->find (base)) + { + b_node = &dynamic_cast<class_&> (*n); + + if (trace) + name = IDENTIFIER_POINTER (DECL_NAME (TYPE_NAME (base))); + } + else + { + // Use public access for a template instantiation in the inheritance + // declaration. + // + b_node = &dynamic_cast<class_&> ( + emit_type (base, access::public_, file, line, clmn)); + + if (trace) + name = emit_type_name (base); + } + + unit_->new_edge<inherits> (*c_node, *b_node, a, virt); + + if (trace) + ts << "\t" << a.string () << (virt ? " virtual" : "") << " base " + << name << " (" << static_cast<type*> (b_node) << ")" << endl; + } + + // Collect member declarations so that we can traverse them in + // the source code order. + // + decl_set decls; + + for (tree d (TYPE_FIELDS (c)); d != NULL_TREE; d = TREE_CHAIN (d)) + { + switch (TREE_CODE (d)) + { + case TYPE_DECL: + { + if (!DECL_SELF_REFERENCE_P (d)) + decls.insert (d); + break; + } + case TEMPLATE_DECL: + { + if (DECL_CLASS_TEMPLATE_P (d)) + decls.insert (d); + break; + } + case FIELD_DECL: + { + if (!DECL_ARTIFICIAL (d)) + decls.insert (d); + break; + } + default: + { + break; + } + } + } + + // Add virtual declarations if any. + // + { + virt_declarations::const_iterator i (virt_declarations_.find (c)); + + if (i != virt_declarations_.end ()) + decls.insert (i->second.begin (), i->second.end ()); + } + + // Add location pragmas if any. + // + { + loc_pragmas::const_iterator i (loc_pragmas_.find (c)); + + if (i != loc_pragmas_.end ()) + decls.insert (i->second.begin (), i->second.end ()); + } + + scope* prev_scope (scope_); + scope_ = c_node; + + for (decl_set::const_iterator b (decls.begin ()), i (b), e (decls.end ()); + i != e; ++i) + { + // Skip pragmas. + // + if (i->prag != 0) + continue; + + // Handle virtual declarations. + // + if (i->vdecl != 0) + { + virt_declaration const& vd (*i->vdecl); + + switch (vd.tree_code) + { + case FIELD_DECL: + { + // First check that it doesn't conflict with any of the real + // data members defined in this class. + // + tree d ( + lookup_qualified_name ( + c, get_identifier (vd.name.c_str ()), false, false)); + + if (d != error_mark_node && TREE_CODE (d) == FIELD_DECL) + { + error (vd.loc) << "virtual data member declaration '" << vd.name + << "' conflicts with a previous declaration" + << endl; + + location_t l (real_source_location (d)); + info (l) << "'" << vd.name << "' was previously declared here" + << endl; + + throw failed (); + } + + path file (LOCATION_FILE (vd.loc)); + size_t line (LOCATION_LINE (vd.loc)); + size_t clmn (LOCATION_COLUMN (vd.loc)); + + access a (access::public_); + + type& type_node (emit_type (vd.type, a, file, line, clmn)); + data_member& member_node ( + unit_->new_node<data_member> (file, line, clmn, tree (0))); + + unit_->new_edge<names> (*c_node, member_node, vd.name, a); + belongs& edge (unit_->new_edge<belongs> (member_node, type_node)); + + // See if there is a name hint for this type. + // + if (names* hint = unit_->find_hint (vd.type)) + edge.hint (*hint); + + // Process pragmas that may be associated with this field. + // + process_pragmas (vd, member_node, vd.name, b, i, e); + break; + } + default: + { + assert (false); + break; + } + } + continue; + } + + tree d (i->decl); + + switch (TREE_CODE (d)) + { + case TYPE_DECL: + { + type* n (emit_type_decl (d)); + + // If this is a named class-type definition, then handle + // the pragmas. + // + if (n != 0) + process_pragmas (n->tree_node (), *n, n->name (), b, i, e); + + break; + } + case TEMPLATE_DECL: + { + emit_template_decl (d); + break; + } + case FIELD_DECL: + { + // If this is a bit-field then TREE_TYPE may be a modified type + // with lesser precision. In this case, DECL_BIT_FIELD_TYPE + // will be the type that was original specified. Use that type + // for now. Furthermore, bitfields can be anonymous, which we + // ignore. + // + // + bool bf (DECL_C_BIT_FIELD (d)); + + if (bf && DECL_NAME (d) == 0) + break; + + // Another case where we can have NULL name is anonymous struct + // or union extension, for example: + // + // struct s + // { + // union + // { + // int a; + // int b; + // }; + // }; + // + // GCC appears to create a fake member for such a struct/union + // without any name. Ignore such members for now. + // + if (DECL_NAME (d) == 0) + break; + + tree t (bf ? DECL_BIT_FIELD_TYPE (d) : TREE_TYPE (d)); + + char const* name (IDENTIFIER_POINTER (DECL_NAME (d))); + + path file (DECL_SOURCE_FILE (d)); + size_t line (DECL_SOURCE_LINE (d)); + size_t clmn (DECL_SOURCE_COLUMN (d)); + + access a (decl_access (d)); + + type& type_node (emit_type (t, a, file, line, clmn)); + data_member& member_node ( + unit_->new_node<data_member> (file, line, clmn, d)); + unit_->insert (d, member_node); + + unit_->new_edge<names> (*c_node, member_node, name, a); + belongs& edge (unit_->new_edge<belongs> (member_node, type_node)); + + // See if there is a name hint for this type. + // + if (names* hint = unit_->find_hint (t)) + edge.hint (*hint); + + if (trace) + { + string type_name (emit_type_name (t)); + + ts << "\t" << a.string () << " data member " << type_name + << " (" << &type_node << ") " << name << " at " + << file << ":" << line << endl; + } + + // Process pragmas that may be associated with this field. + // + process_pragmas (d, member_node, name, b, i, e); + + break; + } + default: + { + assert (false); + break; + } + } + } + + // Diagnose any position pragmas that haven't been associated. + // + diagnose_unassoc_pragmas (decls); + + scope_ = prev_scope; + class_scopes_.pop_back (); + + return *c_node; +} + +template <typename T> +T& parser::impl:: +emit_union (tree u, path const& file, size_t line, size_t clmn, bool stub) +{ + u = TYPE_MAIN_VARIANT (u); + + // See if there is a stub already for this type. + // + T* u_node (0); + + if (node* n = unit_->find (u)) + { + u_node = &dynamic_cast<T&> (*n); + } + else + { + u_node = &unit_->new_node<T> (file, line, clmn, u); + unit_->insert (u, *u_node); + } + + if (stub || !COMPLETE_TYPE_P (u)) + return *u_node; + + class_scopes_.push_back (u_node); + + // Collect member declarations so that we can traverse them in + // the source code order. + // + decl_set decls; + + for (tree d (TYPE_FIELDS (u)); d != NULL_TREE ; d = TREE_CHAIN (d)) + { + switch (TREE_CODE (d)) + { + case TYPE_DECL: + { + if (!DECL_SELF_REFERENCE_P (d)) + decls.insert (d); + break; + } + case TEMPLATE_DECL: + { + if (DECL_CLASS_TEMPLATE_P (d)) + decls.insert (d); + break; + } + case FIELD_DECL: + { + if (!DECL_ARTIFICIAL (d)) + decls.insert (d); + break; + } + default: + { + break; + } + } + } + + // Add location pragmas if any. + // + { + loc_pragmas::const_iterator i (loc_pragmas_.find (u)); + + if (i != loc_pragmas_.end ()) + decls.insert (i->second.begin (), i->second.end ()); + } + + scope* prev_scope (scope_); + scope_ = u_node; + + for (decl_set::const_iterator b (decls.begin ()), i (b), e (decls.end ()); + i != e; ++i) + { + // Skip pragmas. + // + if (i->prag) + continue; + + tree d (i->decl); + + switch (TREE_CODE (d)) + { + case TYPE_DECL: + { + type* n (emit_type_decl (d)); + + // If this is a named class-type definition, then handle + // the pragmas. + // + if (n != 0) + process_pragmas (n->tree_node (), *n, n->name (), b, i, e); + + break; + } + case TEMPLATE_DECL: + { + emit_template_decl (d); + break; + } + case FIELD_DECL: + { + // We can have NULL name in case of anonymous struct or union + // extension, for example: + // + // union s + // { + // struct + // { + // int a; + // int b; + // }; + // int c; + // }; + // + // GCC appears to create a fake member for such a struct/union + // without any name. Ignore such members for now. + // + if (DECL_NAME (d) == 0) + break; + + tree t (TREE_TYPE (d)); + char const* name (IDENTIFIER_POINTER (DECL_NAME (d))); + + path file (DECL_SOURCE_FILE (d)); + size_t line (DECL_SOURCE_LINE (d)); + size_t clmn (DECL_SOURCE_COLUMN (d)); + + access a (decl_access (d)); + + type& type_node (emit_type (t, a, file, line, clmn)); + data_member& member_node ( + unit_->new_node<data_member> (file, line, clmn, d)); + unit_->insert (d, member_node); + + unit_->new_edge<names> (*u_node, member_node, name, a); + belongs& edge (unit_->new_edge<belongs> (member_node, type_node)); + + // See if there is a name hint for this type. + // + if (names* hint = unit_->find_hint (t)) + edge.hint (*hint); + + if (trace) + { + string type_name (emit_type_name (t)); + + ts << "\t" << a.string () << " union member " << type_name + << " (" << &type_node << ") " << name << " at " + << file << ":" << line << endl; + } + + // Process pragmas that may be associated with this field. + // + process_pragmas (d, member_node, name, b, i, e); + + break; + } + default: + { + break; + } + } + } + + // Diagnose any position pragmas that haven't been associated. + // + diagnose_unassoc_pragmas (decls); + + scope_ = prev_scope; + class_scopes_.pop_back (); + return *u_node; +} + +// +// Functions. +// + +parser::impl:: +impl (options const& ops, + loc_pragmas & lp, + ns_loc_pragmas& nslp, + decl_pragmas& dp) + : ops_ (ops), + loc_pragmas_ (lp), + ns_loc_pragmas_ (nslp), + decl_pragmas_ (dp), + trace (ops.trace ()), + ts (cerr) +{ +} + +unique_ptr<unit> parser::impl:: +parse (tree global_scope, path const& main_file) +{ + unique_ptr<unit> u (new unit (main_file)); + u->insert (global_namespace, *u); + process_named_pragmas (global_namespace, *u); + + unit_ = u.get (); + scope_ = unit_; + error_ = 0; + + // Define fundamental types. + // + define_fund<fund_void> (void_type_node); + define_fund<fund_bool> (boolean_type_node); + define_fund<fund_char> (char_type_node); + define_fund<fund_wchar> (wchar_type_node); + + if (ops_.std () >= cxx_version::cxx11) + { + define_fund<fund_char16> (char16_type_node); + define_fund<fund_char32> (char32_type_node); + } + + define_fund<fund_signed_char> (signed_char_type_node); + define_fund<fund_unsigned_char> (unsigned_char_type_node); + define_fund<fund_short> (short_integer_type_node); + define_fund<fund_unsigned_short> (short_unsigned_type_node); + define_fund<fund_int> (integer_type_node); + define_fund<fund_unsigned_int> (unsigned_type_node); + define_fund<fund_long> (long_integer_type_node); + define_fund<fund_unsigned_long> (long_unsigned_type_node); + define_fund<fund_long_long> (long_long_integer_type_node); + define_fund<fund_unsigned_long_long> (long_long_unsigned_type_node); + define_fund<fund_float> (float_type_node); + define_fund<fund_double> (double_type_node); + define_fund<fund_long_double> (long_double_type_node); + + // First collect all the namespace-level declarations we are + // interested in in the line-decl map so that they appear in + // the source code order. + // + collect (global_scope); + + // Add namespace-level position pragmas if any. + // + { + loc_pragmas::const_iterator i (loc_pragmas_.find (global_namespace)); + + if (i != loc_pragmas_.end ()) + decls_.insert (i->second.begin (), i->second.end ()); + } + + // Convert position namespace pragmas to name pragmas. + // + for (ns_loc_pragmas::const_iterator i (ns_loc_pragmas_.begin ()); + i != ns_loc_pragmas_.end (); ++i) + { + pragma const& p (i->pragma); + + decl_map::const_iterator j (all_decls_.lower_bound (p.loc)); + + if (j == all_decls_.end ()) + { + error (p.loc) + << "db pragma '" << p.pragma_name << "' is not associated with a " + << "namespace declaration" << endl; + error_++; + continue; + } + + // Find the "namespace difference" between this declaration and + // the pragma's namespace. The outermost namespace in the result + // is what we are looking for. + // + tree ns (0); + + for (tree prev (j->second), scope (CP_DECL_CONTEXT (prev));; + scope = CP_DECL_CONTEXT (scope)) + { + if (scope == i->ns) + { + ns = prev; + break; + } + + if (scope == global_namespace) + break; + + prev = scope; + } + + if (ns == 0 || TREE_CODE (ns) != NAMESPACE_DECL) + { + error (p.loc) + << "db pragma '" << p.pragma_name << "' is not associated with a " + << "namespace declaration" << endl; + error_++; + continue; + } + + pragma_set& s (decl_pragmas_[ns]); + pragma_set::iterator it (s.find (p.context_name)); + + // Make sure we override only if this pragma came after the one + // already in the set. + // + if (it == s.end () || it->second.loc <= p.loc) + s.insert (p); + } + + // Construct the semantic graph. + // + if (error_ == 0) + emit (); + + if (error_ > 0) + throw failed (); + + return u; +} + +void parser::impl:: +collect (tree ns) +{ + cp_binding_level* level = NAMESPACE_LEVEL (ns); + tree decl = level->names; + + // Collect declarations. + // + for (; decl != NULL_TREE; decl = TREE_CHAIN (decl)) + { + all_decls_[real_source_location (decl)] = decl; + + if (DECL_IS_BUILTIN (decl)) + continue; + + switch (TREE_CODE (decl)) + { + case TYPE_DECL: + { + // Skip special type declarations. + // + if (DECL_NAME (decl) == NULL_TREE) + continue; + + tree type (TREE_TYPE (decl)); + if (LAMBDA_TYPE_P (type)) + continue; + + decls_.insert (decl); + break; + } + case TEMPLATE_DECL: + { + if (DECL_CLASS_TEMPLATE_P (decl)) + decls_.insert (decl); + + break; + } + default: + { + break; + } + } + } + + // Traverse namespaces. + // + for ( +#if BUILDING_GCC_MAJOR >= 8 + decl = level->names; +#else + decl = level->namespaces; +#endif + decl != NULL_TREE; + decl = TREE_CHAIN (decl)) + { +#if BUILDING_GCC_MAJOR >= 8 + // Now namespaces are interleaved with other declarations. In fact, we + // could probably collect everything in a single pass. + // + if (TREE_CODE (decl) != NAMESPACE_DECL) + continue; +#endif + + // Ignore namespace aliases. + // + if (DECL_NAMESPACE_ALIAS (decl)) + continue; + + if (!DECL_IS_BUILTIN (decl) || DECL_NAMESPACE_STD_P (decl)) + { + tree dn (DECL_NAME (decl)); + + if (trace) + { + char const* name (dn ? IDENTIFIER_POINTER (dn) : "<anonymous>"); + + ts << "namespace " << name << " at " + << DECL_SOURCE_FILE (decl) << ":" + << DECL_SOURCE_LINE (decl) << endl; + } + + // Skip anonymous namespaces (there could be nothing of interest to us + // inside but they wreck havoc with our attempts to sort declarations + // into namespaces). + // + if (dn != 0) + collect (decl); + } + } +} + +void parser::impl:: +emit () +{ + for (decl_set::const_iterator b (decls_.begin ()), i (b), + e (decls_.end ()); i != e; ++i) + { + // Skip pragmas. + // + if (i->prag) + continue; + + tree decl (i->decl); + + // Get this declaration's namespace and unwind our scope until + // we find a common prefix of namespaces. + // + string pfx; + string ns (fq_scope (decl)); + + for (pfx = scope_->fq_name (); !pfx.empty (); pfx = scope_->fq_name ()) + { + size_t n (pfx.size ()); + + // Make sure we handle cases like ns="::foobar", pfx="::foo". + // + if (ns.compare (0, n, pfx) == 0 && (ns.size () == n || ns[n - 1] == ':')) + break; + + if (trace) + ts << "closing namespace " << scope_->name () << endl; + + scope_ = &scope_->scope_ (); + } + + // Build the rest of the namespace hierarchy for this declaration. + // + if (ns != pfx) + { + path f (DECL_SOURCE_FILE (decl)); + size_t l (DECL_SOURCE_LINE (decl)); + size_t c (DECL_SOURCE_COLUMN (decl)); + + for (size_t b (pfx.size () + 2), e (ns.find ("::", b)); + b != string::npos;) + { + string n (ns, b, e == string::npos ? e : e - b); + + if (trace) + ts << "opening namespace " << n << " for " + << DECL_SOURCE_FILE (decl) << ":" + << DECL_SOURCE_LINE (decl) << endl; + + // Use the declarations's file, line, and column as an + // approximation for this namespace origin. Also resolve + // the tree node for this namespace. + // +#if BUILDING_GCC_MAJOR >= 8 + tree tree_node ( + get_namespace_binding ( + scope_->tree_node (), get_identifier (n.c_str ()))); +#else + tree tree_node ( + namespace_binding ( + get_identifier (n.c_str ()), scope_->tree_node ())); +#endif + + namespace_& node (unit_->new_node<namespace_> (f, l, c, tree_node)); + unit_->new_edge<defines> (*scope_, node, n); + + if (namespace_* orig = + dynamic_cast<namespace_*> (unit_->find (tree_node))) + { + // This is an extension. + // + node.original (*orig); + } + else + { + // This is the original. Add it to the map and process any + // pragmas it might have (at this stage namespaces can only + // have name pragmas). + // + unit_->insert (tree_node, node); + process_named_pragmas (tree_node, node); + } + + scope_ = &node; + + if (e == string::npos) + b = e; + else + { + b = e + 2; + e = ns.find ("::", b); + } + } + } + + switch (TREE_CODE (decl)) + { + case TYPE_DECL: + { + type* n (emit_type_decl (decl)); + + // If this is a named class-type definition, then handle + // the pragmas. + // + if (n != 0) + process_pragmas (n->tree_node (), *n, n->name (), b, i, e); + + break; + } + case TEMPLATE_DECL: + { + emit_template_decl (decl); + break; + } + default: + { + break; + } + } + + assert (class_scopes_.empty ()); + } + + // Diagnose any position pragmas that haven't been associated. + // + diagnose_unassoc_pragmas (decls_); +} + +type* parser::impl:: +emit_type_decl (tree decl) +{ + tree t (TREE_TYPE (decl)); + gcc_tree_code_type tc (TREE_CODE (t)); + + tree decl_name (DECL_NAME (decl)); + char const* name (IDENTIFIER_POINTER (decl_name)); + + if (DECL_ARTIFICIAL (decl) && + (tc == RECORD_TYPE || tc == UNION_TYPE || tc == ENUMERAL_TYPE)) + { + // If we have an anonymous class typedef, use the user- + // supplied name instead of the synthesized one. ARM + // says that in typedef struct {} S; S becomes struct's + // name. + // + if (IDENTIFIER_ANON_P (decl_name)) + { + tree d (TYPE_NAME (t)); + + if (d != NULL_TREE && + !DECL_ARTIFICIAL (d) && + DECL_NAME (d) != NULL_TREE && + !IDENTIFIER_ANON_P (DECL_NAME (d))) + { + decl = d; + decl_name = DECL_NAME (decl); + name = IDENTIFIER_POINTER (decl_name); + } + else + { + // This type has only the synthesized name which means that + // it is either typedef'ed as a derived type or it is used + // to declare a varibale or similar. The first case will be + // covered by the typedef handling code below. The second + // case will be covere by emit_type(). + // + return 0; + } + } + + path file (DECL_SOURCE_FILE (decl)); + size_t line (DECL_SOURCE_LINE (decl)); + size_t clmn (DECL_SOURCE_COLUMN (decl)); + + type* node (0); + + // Pointers to member functions are represented as record + // types. Detect and handle this case. + // + if (TYPE_PTRMEMFUNC_P (t)) + { + t = TYPE_MAIN_VARIANT (t); + node = &unit_->new_node<unsupported_type> ( + file, line, clmn, t, "pointer_to_member_function_type"); + unit_->insert (t, *node); + } + else + { + + if (trace) + ts << "start " << gcc_tree_code_name(tc) << " " << name + << " at " << file << ":" << line << endl; + + switch (tc) + { + case RECORD_TYPE: + { + node = &emit_class<class_> (t, file, line, clmn); + break; + } + case UNION_TYPE: + { + node = &emit_union<union_> (t, file, line, clmn); + break; + } + case ENUMERAL_TYPE: + { + node = &emit_enum (t, decl_access (decl), file, line, clmn); + break; + } + default: + break; + } + + if (trace) + ts << "end " << gcc_tree_code_name(tc) << " " << name + << " (" << node << ") at " + << DECL_SOURCE_FILE (decl) << ":" + << DECL_SOURCE_LINE (decl) << endl; + } + + if (COMPLETE_TYPE_P (t)) + unit_->new_edge<defines> (*scope_, *node, name); + else + unit_->new_edge<declares> (*scope_, *node, name); + + return node; + } + else + { + // Normal typedef. We need to detect and ignore the anonymous + // class typedef case described above since we already used + // this name to define the class. + // + if ((tc == RECORD_TYPE || tc == UNION_TYPE || tc == ENUMERAL_TYPE) && + TYPE_NAME (TYPE_MAIN_VARIANT (t)) == decl) + return 0; + + path f (DECL_SOURCE_FILE (decl)); + size_t l (DECL_SOURCE_LINE (decl)); + size_t c (DECL_SOURCE_COLUMN (decl)); + + type& node (emit_type (t, decl_access (decl), f, l, c)); + + // Omit inner self-typedefs (e.g., a class typedefs itself in its own + // scope). Such aliases don't buy us anything (in particular, they cannot + // be used to form an fq-name) but they do cause scoping cycles if this + // name happens to be used to find outer scope (see scope::scope_()). + // Note that this means we can now have class template instantiations that + // are not named and therefore don't belong to any scope. + // + // Note that emit_type() might still enter this decl as a hint. It's fuzzy + // whether this is harmless or not. + // + // Note also that using the normal scope hierarchy does not work in more + // complex cases where templates cross-self-typedef. So instead we now use + // a special-purpose mechanism (class_scopes_). Note for this to work + // correctly (hopefully), the class should be "in scope" for its bases. + // Here is a representative examples (inspired by code in Eigen): + // + // template <typename M> + // struct PlainObjectBase + // { + // typedef M Self; + // }; + // + // template <typename T, int X, int Y> + // struct Matrix: PlainObjectBase<Matrix<T, X, Y>> + // { + // typedef PlainObjectBase<Matrix> Base; + // typedef Matrix Self; + // }; + // + // typedef Matrix<double, 3, 1> Vector3d; + // + // Here we want both Self's (but not Base) to be skipped. + // + if (scope* s = dynamic_cast<scope*> (&node)) + { + for (auto i (class_scopes_.rbegin ()); i != class_scopes_.rend (); ++i) + { + if (s == *i) + { + if (trace) + { + string s (emit_type_name (t, false)); + + ts << "omitting inner self-typedef " << s << " (" << &node + << ") -> " << name << " at " << f << ":" << l << endl; + } + return 0; + } + } + } + + typedefs& edge (unit_->new_edge<typedefs> (*scope_, node, name)); + + // Find our hint. + // + if (tree ot = DECL_ORIGINAL_TYPE (decl)) + { + if (names* hint = unit_->find_hint (ot)) + edge.hint (*hint); + } + + // Add this edge to the hint map. It may already be there if we + // are handling something like this: + // + // typedef foo bar; + // typedef bar foo; + // + // GCC also appears to re-purpose a node for another name (not + // sure if its a bug or a feature), so use the latest seen name. + // + unit_->insert_hint (t, edge); + + if (trace) + { + string s (emit_type_name (t, false)); + + ts << "typedef " << s << " (" << &node << ") -> " << name + << " at " << f << ":" << l << endl; + } + + return 0; + } +} + +void parser::impl:: +emit_template_decl (tree decl) +{ + // Currently we only handle class/union templates. + // + tree t (TREE_TYPE (DECL_TEMPLATE_RESULT (decl))); + gcc_tree_code_type tc (TREE_CODE (t)); + + if (trace) + { + ts << gcc_tree_code_name(tc) << " template (" << decl << ") " + << IDENTIFIER_POINTER (DECL_NAME (decl)) << " (" << t << ") at " + << DECL_SOURCE_FILE (decl) << ":" + << DECL_SOURCE_LINE (decl) << endl; + + ts << "specializations:" << endl; + + for (tree s (DECL_TEMPLATE_SPECIALIZATIONS (decl)); + s != NULL_TREE; s = TREE_CHAIN (s)) + { + tree t (TREE_TYPE (s)); + tree d (TYPE_NAME (t)); + + ts << "\tspecialization " << t << " at " + << DECL_SOURCE_FILE (d) << ":" + << DECL_SOURCE_LINE (d) << endl; + } + + ts << "instantiations:" << endl; + + for (tree i (DECL_TEMPLATE_INSTANTIATIONS (decl)); + i != NULL_TREE; i = TREE_CHAIN (i)) + { + tree t (TREE_VALUE (i)); + tree d (TYPE_NAME (t)); + + ts << "\tinstantiation " << t << " at " + << DECL_SOURCE_FILE (d) << ":" + << DECL_SOURCE_LINE (d) << endl; + } + } + + char const* name (IDENTIFIER_POINTER (DECL_NAME (decl))); + + if (trace) + ts << "start " << gcc_tree_code_name(tc) << " template " << name << " at " + << DECL_SOURCE_FILE (decl) << ":" + << DECL_SOURCE_LINE (decl) << endl; + + type_template* t_node (0); + + if (tc == RECORD_TYPE) + t_node = &emit_class_template (decl); + else + t_node = &emit_union_template (decl); + + if (COMPLETE_TYPE_P (t)) + unit_->new_edge<defines> (*scope_, *t_node, name); + else + unit_->new_edge<declares> (*scope_, *t_node, name); + + if (trace) + ts << "end " << gcc_tree_code_name(tc) << " template " << name + << " (" << t_node << ") at " + << DECL_SOURCE_FILE (decl) << ":" + << DECL_SOURCE_LINE (decl) << endl; +} + +class_template& parser::impl:: +emit_class_template (tree t, bool stub) +{ + // See if there is a stub already for this template. + // + class_template* ct_node (0); + tree c (TREE_TYPE (DECL_TEMPLATE_RESULT (t))); + + if (node* n = unit_->find (t)) + { + ct_node = &dynamic_cast<class_template&> (*n); + } + else + { + path f (DECL_SOURCE_FILE (t)); + size_t ln (DECL_SOURCE_LINE (t)); + size_t cl (DECL_SOURCE_COLUMN (t)); + + ct_node = &unit_->new_node<class_template> (f, ln, cl, c); + unit_->insert (t, *ct_node); + } + + if (stub || !COMPLETE_TYPE_P (c)) + return *ct_node; + + class_scopes_.push_back (ct_node); + + // Collect member declarations so that we can traverse them in + // the source code order. For now we are only interested in + // nested class template declarations. + // + decl_set decls; + + for (tree d (TYPE_FIELDS (c)); d != NULL_TREE ; d = TREE_CHAIN (d)) + { + switch (TREE_CODE (d)) + { + case TEMPLATE_DECL: + { + if (DECL_CLASS_TEMPLATE_P (d)) + decls.insert (d); + break; + } + default: + { + break; + } + } + } + + scope* prev_scope (scope_); + scope_ = ct_node; + + for (decl_set::const_iterator i (decls.begin ()), e (decls.end ()); + i != e; ++i) + { + // Skip pragmas. + // + if (i->prag) + continue; + + tree d (i->decl); + + switch (TREE_CODE (d)) + { + case TEMPLATE_DECL: + { + emit_template_decl (d); + break; + } + default: + { + break; + } + } + } + + // Diagnose any position pragmas that haven't been associated. + // + diagnose_unassoc_pragmas (decls); + + scope_ = prev_scope; + class_scopes_.pop_back (); + return *ct_node; +} + +union_template& parser::impl:: +emit_union_template (tree t, bool stub) +{ + // See if there is a stub already for this template. + // + union_template* ut_node (0); + tree u (TREE_TYPE (DECL_TEMPLATE_RESULT (t))); + + if (node* n = unit_->find (t)) + { + ut_node = &dynamic_cast<union_template&> (*n); + } + else + { + path f (DECL_SOURCE_FILE (t)); + size_t l (DECL_SOURCE_LINE (t)); + size_t c (DECL_SOURCE_COLUMN (t)); + + ut_node = &unit_->new_node<union_template> (f, l, c, u); + unit_->insert (t, *ut_node); + } + + if (stub || !COMPLETE_TYPE_P (u)) + return *ut_node; + + class_scopes_.push_back (ut_node); + + // Collect member declarations so that we can traverse them in + // the source code order. For now we are only interested in + // nested class template declarations. + // + decl_set decls; + + for (tree d (TYPE_FIELDS (u)); d != NULL_TREE ; d = TREE_CHAIN (d)) + { + switch (TREE_CODE (d)) + { + case TEMPLATE_DECL: + { + if (DECL_CLASS_TEMPLATE_P (d)) + decls.insert (d); + break; + } + default: + { + break; + } + } + } + + scope* prev_scope (scope_); + scope_ = ut_node; + + for (decl_set::const_iterator i (decls.begin ()), e (decls.end ()); + i != e; ++i) + { + // Skip pragmas. + // + if (i->prag) + continue; + + tree d (i->decl); + + switch (TREE_CODE (d)) + { + case TEMPLATE_DECL: + { + emit_template_decl (d); + break; + } + default: + { + break; + } + } + } + + // Diagnose any position pragmas that haven't been associated. + // + diagnose_unassoc_pragmas (decls); + + scope_ = prev_scope; + class_scopes_.pop_back (); + return *ut_node; +} + +enum_& parser::impl:: +emit_enum (tree e, + access access, + path const& file, + size_t line, + size_t clmn, + bool stub) +{ + e = TYPE_MAIN_VARIANT (e); + + // See if there is a stub already for this type. + // + enum_* e_node (0); + + if (node* n = unit_->find (e)) + e_node = &dynamic_cast<enum_&> (*n); + else + { + e_node = &unit_->new_node<enum_> (file, line, clmn, e); + + // Set the underlying type even for incomplete (forward-declared) enums. + // + tree ut (ENUM_UNDERLYING_TYPE (e)); + names* hint (unit_->find_hint (ut)); + integral_type* un = dynamic_cast<integral_type*> ( + unit_->find (TYPE_MAIN_VARIANT (ut))); + + // For "old" enums GCC creates a distinct type node and the only way to + // get to one of the known integrals is via its name. + // + if (un == 0) + { + ut = TREE_TYPE (TYPE_NAME (ut)); + un = dynamic_cast<integral_type*> (unit_->find (TYPE_MAIN_VARIANT (ut))); + } + + underlies& edge (unit_->new_edge<underlies> (*un, *e_node)); + + if (hint != 0) + edge.hint (*hint); + + unit_->insert (e, *e_node); + } + + if (stub || !COMPLETE_TYPE_P (e)) + return *e_node; + + // Traverse enumerators. + // + for (tree er (TYPE_VALUES (e)); er != NULL_TREE ; er = TREE_CHAIN (er)) + { + char const* name (IDENTIFIER_POINTER (TREE_PURPOSE (er))); + tree decl (TREE_VALUE (er)); + tree tval (DECL_INITIAL (decl)); + + unsigned long long val (integer_value (tval)); + + // There doesn't seem to be a way to get the proper position for + // each enumerator. + // + enumerator& er_node = unit_->new_node<enumerator> ( + file, line, clmn, er, val); + unit_->new_edge<enumerates> (*e_node, er_node); + unit_->insert (decl, er_node); + + // In C++11 the enumerators are always available in the enum's + // scope, even for old enums. + // + if (ops_.std () >= cxx_version::cxx11) + unit_->new_edge<names> (*e_node, er_node, name, access::public_); + + // Inject enumerators into the outer scope unless this is an + // enum class. + // + if (UNSCOPED_ENUM_P (e)) + unit_->new_edge<names> (*scope_, er_node, name, access); + + if (trace) + ts << "\tenumerator " << name << " at " << file << ":" << line << endl; + } + + return *e_node; +} + +type& parser::impl:: +emit_type (tree t, + access access, + path const& file, + size_t line, + size_t clmn) +{ + tree mv (TYPE_MAIN_VARIANT (t)); + + if (trace) + { + ts << gcc_tree_code_name(TREE_CODE (t)) << " " << t + << " main " << mv << endl; + + for (tree v (TYPE_MAIN_VARIANT (t)); v != 0; v = TYPE_NEXT_VARIANT (v)) + ts << "\tvariant " << v << " " << CP_TYPE_CONST_P (v) << endl; + } + + node* n (unit_->find (mv)); + + type& r (n != 0 + ? dynamic_cast<type&> (*n) + : create_type (t, access, file, line, clmn)); + + if (trace && n != 0) + ts << "found node " << &r << " for type " << mv << endl; + + if (cp_type_quals (t) == TYPE_UNQUALIFIED) + { + unit_->insert (t, r); // Add this variant to the map. + return r; + } + + // See if this type already has this variant. + // + bool qc (CP_TYPE_CONST_P (t)); + bool qv (CP_TYPE_VOLATILE_P (t)); + bool qr (CP_TYPE_RESTRICT_P (t)); + + for (type::qualified_iterator i (r.qualified_begin ()); + i != r.qualified_end (); ++i) + { + qualifier& q (i->qualifier ()); + + if (q.const_ () == qc && q.volatile_ () == qv && q.restrict_ () == qr) + { + if (trace) + ts << "found qualifier variant " << &q << endl; + + unit_->insert (t, q); // Add this variant to the map. + return q; + } + } + + // No such variant yet. Create a new one. Qualified types are not + // unique in the tree so don't add this node to the map. + // + qualifier& q (unit_->new_node<qualifier> (file, line, clmn, t, qc, qv, qr)); + qualifies& e (unit_->new_edge<qualifies> (q, r)); + unit_->insert (t, q); + + // See if there is a name hint for this type. + // + // If TREE_TYPE (TYPE_NAME (t)) != t then we have an inline qualifier, + // as in: + // + // const foo x; + // + // If they are equal, then there are two possible cases. The first is + // when we have a qualifier typedef, as in: + // + // typedef const foo cfoo; + // cfoo x; + // + // The second is when we have a qualifier typedef but what is actually + // used in the declaration is an inline qualifier, as in: + // + // typedef const foo cfoo; + // const foo x; + // + // Unfortunately, in GCC, these two cases are indistinguishable. In + // certain cases this can lead to a wrong hint being used for the base + // type, for example: + // + // typedef foo my_foo; + // typedef foo foo_t; + // typedef const foo_t cfoo; + // + // const my_foo x; + // + // Above, the hint will be foo_t while it should be my_foo. + // + tree bt (0); + + if (tree decl = TYPE_NAME (t)) + { + bt = TREE_TYPE (decl); + + if (t == bt) + { + // A const type can be named only with a typedef. Get the + // original type. + // + tree ot (DECL_ORIGINAL_TYPE (decl)); + + // And chase it one more time to get rid of the qualification. + // + decl = TYPE_NAME (ot); + bt = decl != 0 ? TREE_TYPE (decl) : 0; + } + } + + if (bt != 0) + { + if (names* hint = unit_->find_hint (bt)) + e.hint (*hint); + } + + process_named_pragmas (t, q); + + return q; +} + +type& parser::impl:: +create_type (tree t, + access access, + path const& file, + size_t line, + size_t clmn) +{ + type* r (0); + gcc_tree_code_type tc (TREE_CODE (t)); + + switch (tc) + { + // + // User-defined types. + // + case RECORD_TYPE: + case UNION_TYPE: + { + t = TYPE_MAIN_VARIANT (t); + tree ti (TYPE_TEMPLATE_INFO (t)); + + if (ti == NULL_TREE) + { + // Ordinary class. There are two situations which can lead + // here. First is when we have an anonymous class that is + // part of the declaration, for example: + // + // typedef const struct {...} s; + // + // The second situation is a named class definition which + // we haven't parsed yet. In this case we are going to + // create a "stub" class node which will be processed and + // filled in later. + // + + // Pointers to member functions are represented as record + // types. Detect and handle this case. + // + if (TYPE_PTRMEMFUNC_P (t)) + { + r = &unit_->new_node<unsupported_type> ( + file, line, clmn, t, "pointer_to_member_function_type"); + unit_->insert (t, *r); + } + else + { + tree d (TYPE_NAME (t)); + + if (trace) + ts << "start anon/stub " << gcc_tree_code_name(tc) << " at " + << file << ":" << line << endl; + + if (d == NULL_TREE || IDENTIFIER_ANON_P (DECL_NAME (d))) + { + if (tc == RECORD_TYPE) + r = &emit_class<class_> (t, file, line, clmn); + else + r = &emit_union<union_> (t, file, line, clmn); + } + else + { + // Use the "defining" declaration's file, line, and column + // information to create the stub. + // + path f (DECL_SOURCE_FILE (d)); + size_t l (DECL_SOURCE_LINE (d)); + size_t c (DECL_SOURCE_COLUMN (d)); + + if (tc == RECORD_TYPE) + r = &emit_class<class_> (t, f, l, c, true); + else + r = &emit_union<union_> (t, f, l, c, true); + } + + if (trace) + ts << "end anon/stub " << gcc_tree_code_name(tc) << " (" << r << ")" + << " at " << file << ":" << line << endl; + } + } + else + { + // Template instantiation. + // + tree decl (TI_TEMPLATE (ti)); // DECL_TEMPLATE + + // Get to the most general template declaration. + // + while (DECL_TEMPLATE_INFO (decl)) + decl = DECL_TI_TEMPLATE (decl); + + type_template* t_node (0); + + // Find the template node or create a stub if none exist. + // + if (node* n = unit_->find (decl)) + t_node = &dynamic_cast<type_template&> (*n); + else + { + if (trace) + ts << "start stub " << gcc_tree_code_name(tc) << " template for (" + << decl << ") at " << file << ":" << line << endl; + + if (tc == RECORD_TYPE) + t_node = &emit_class_template (decl, true); + else + t_node = &emit_union_template (decl, true); + + if (trace) + ts << "end stub " << gcc_tree_code_name(tc) << " template (" + << t_node << ") at " << file << ":" << line << endl; + } + + if (trace) + ts << "start " << gcc_tree_code_name(tc) << " instantiation (" + << t << ") for template (" << t_node << ")" + << " at " << file << ":" << line << endl; + + type_instantiation* i_node (0); + + if (tc == RECORD_TYPE) + i_node = &emit_class<class_instantiation> (t, file, line, clmn); + else + i_node = &emit_union<union_instantiation> (t, file, line, clmn); + + if (trace) + ts << "end " << gcc_tree_code_name(tc) << " instantiation (" + << static_cast<type*> (i_node) << ")" + << " at " << file << ":" << line << endl; + + unit_->new_edge<instantiates> (*i_node, *t_node); + process_named_pragmas (t, *i_node); + r = i_node; + } + + break; + } + case ENUMERAL_TYPE: + { + // The same logic as in the "ordinary class" case above + // applies here. + // + + t = TYPE_MAIN_VARIANT (t); + tree d (TYPE_NAME (t)); + + if (trace) + ts << "start anon/stub " << gcc_tree_code_name(tc) << " at " + << file << ":" << line << endl; + + if (d == NULL_TREE || IDENTIFIER_ANON_P (DECL_NAME (d))) + { + r = &emit_enum (t, access, file, line, clmn); + } + else + { + // Use the "defining" declaration's file, line, and column + // information to create the stub. + // + path f (DECL_SOURCE_FILE (d)); + size_t l (DECL_SOURCE_LINE (d)); + size_t c (DECL_SOURCE_COLUMN (d)); + + r = &emit_enum (t, access, f, l, c, true); + } + + if (trace) + ts << "end anon/stub " << gcc_tree_code_name(tc) << " (" << r << ")" + << " at " << file << ":" << line << endl; + + break; + } + + // + // Derived types. + // + + case ARRAY_TYPE: + { + unsigned long long size (0); + + if (tree index = TYPE_DOMAIN (t)) + { + tree max (TYPE_MAX_VALUE (index)); + + if (TREE_CODE (max) == INTEGER_CST) + { + size = integer_value (max); + + // The docs say that TYPE_DOMAIN will be NULL if the + // array doesn't specify bounds. In reality, it is + // set to ~0. + // + if (size == ~(unsigned long long) (0)) + size = 0; + + size++; // Convert max index to size. + } + else + { + error (file, line, clmn) + << "non-integer array index " << + gcc_tree_code_name(TREE_CODE (max)) << endl; + + throw failed (); + } + } + + // In GCC tree a const array has both the array type itself and the + // element type marked as const. This doesn't bode well with our + // semantic graph model where we have a separate type node for + // qualifiers. To fix this, we are going to strip the const + // qualification from the element type and only preserve it in + // the array type. In other words, we view it as "constant array" + // rather than "array of constant elements". + // + using semantics::array; // vs std::array. + + tree bt (TREE_TYPE (t)); + tree bt_mv (TYPE_MAIN_VARIANT (bt)); + type& bt_node (emit_type (bt_mv, access::public_, file, line, clmn)); + t = TYPE_MAIN_VARIANT (t); + array& a (unit_->new_node<array> (file, line, clmn, t, size)); + unit_->insert (t, a); + contains& edge (unit_->new_edge<contains> (a, bt_node)); + + // See if there is a name hint for the base type. + // + if (names* hint = unit_->find_hint ( + cp_type_quals (bt) == TYPE_UNQUALIFIED ? bt : bt_mv)) + edge.hint (*hint); + + process_named_pragmas (t, a); + r = &a; + break; + } + case REFERENCE_TYPE: + { + tree bt (TREE_TYPE (t)); + type& bt_node (emit_type (bt, access::public_, file, line, clmn)); + t = TYPE_MAIN_VARIANT (t); + reference& ref (unit_->new_node<reference> (file, line, clmn, t)); + unit_->insert (t, ref); + references& edge (unit_->new_edge<references> (ref, bt_node)); + + // See if there is a name hint for the base type. + // + if (names* hint = unit_->find_hint (bt)) + edge.hint (*hint); + + process_named_pragmas (t, ref); + r = &ref; + break; + } + case POINTER_TYPE: + { + if (!TYPE_PTRMEM_P (t)) + { + tree bt (TREE_TYPE (t)); + type& bt_node (emit_type (bt, access::public_, file, line, clmn)); + t = TYPE_MAIN_VARIANT (t); + pointer& p (unit_->new_node<pointer> (file, line, clmn, t)); + unit_->insert (t, p); + points& edge (unit_->new_edge<points> (p, bt_node)); + + // See if there is a name hint for the base type. + // + if (names* hint = unit_->find_hint (bt)) + edge.hint (*hint); + + process_named_pragmas (t, p); + r = &p; + } + else + { + t = TYPE_MAIN_VARIANT (t); + r = &unit_->new_node<unsupported_type> ( + file, line, clmn, t, "pointer_to_data_member_type"); + unit_->insert (t, *r); + + if (trace) + ts << "unsupported pointer_to_data_member_type (" << r << ")" + << " at " << file << ":" << line << endl; + } + break; + } + default: + { + t = TYPE_MAIN_VARIANT (t); + r = &unit_->new_node<unsupported_type> ( + file, line, clmn, t, gcc_tree_code_name(tc)); + unit_->insert (t, *r); + + if (trace) + ts << "unsupported " << gcc_tree_code_name(tc) << " (" << r << ")" + << " at " << file << ":" << line << endl; + + break; + } + } + + return *r; +} + +string parser::impl:: +emit_type_name (tree type, bool direct) +{ + // First see if there is a "direct" name for this type. + // + if (direct) + { + if (tree decl = TYPE_NAME (type)) + { + tree t (TREE_TYPE (decl)); + + if (t != 0 && same_type_p (type, t)) + return IDENTIFIER_POINTER (DECL_NAME (decl)); + } + } + + string r; + + if (CP_TYPE_CONST_P (type)) + r += " const"; + + if (CP_TYPE_VOLATILE_P (type)) + r += " volatile"; + + if (CP_TYPE_RESTRICT_P (type)) + r += " __restrict"; + + gcc_tree_code_type tc (TREE_CODE (type)); + + switch (tc) + { + // + // User-defined types. + // + + case RECORD_TYPE: + case UNION_TYPE: + { + tree ti (TYPE_TEMPLATE_INFO (type)); + + if (ti == NULL_TREE) + { + // Ordinary class. + // + type = TYPE_MAIN_VARIANT (type); + + // Pointers to member functions are represented as record + // types and don't have names, not even the synthesized ones. + // + if (TYPE_PTRMEMFUNC_P (type)) + r = "<pointer-to-member-function>" + r; + else + { + tree name (TYPE_NAME (type)); + r = IDENTIFIER_POINTER (DECL_NAME (name)) + r; + } + } + else + { + // Template instantiation. + // + tree decl (TI_TEMPLATE (ti)); // DECL_TEMPLATE + string id (IDENTIFIER_POINTER (DECL_NAME (decl))); + + id += '<'; + + tree args (INNERMOST_TEMPLATE_ARGS (TI_ARGS (ti))); + + for (size_t i (0), n (TREE_VEC_LENGTH (args)); i < n ; ++i) + { + tree a (TREE_VEC_ELT (args, i)); + + if (i != 0) + id += ", "; + + // Assume integer and type-only arguments. + // + if (TREE_CODE (a) == INTEGER_CST) + id += to_string (integer_value (a)); + else + id += emit_type_name (a); + } + + id += '>'; + + r = id + r; + } + + break; + } + + case ENUMERAL_TYPE: + { + type = TYPE_MAIN_VARIANT (type); + tree decl (TYPE_NAME (type)); + r = IDENTIFIER_POINTER (DECL_NAME (decl)) + r; + break; + } + + // + // Derived types. + // + + case ARRAY_TYPE: + { + unsigned long long size (0); + + if (tree index = TYPE_DOMAIN (type)) + { + tree max (TYPE_MAX_VALUE (index)); + + if (TREE_CODE (max) == INTEGER_CST) + { + size = integer_value (max); + + // Same as above. + // + if (size == ~(unsigned long long) (0)) + size = 0; + + size++; + } + else + { + // Non-integer array index which we do not support. The + // error has been/will be issued in emit_type. + // + } + } + + tree t (TREE_TYPE (type)); + + if (size != 0) + { + ostringstream ostr; + ostr << size; + r = emit_type_name (t) + "[" + ostr.str () + "]" + r; + } + else + r = emit_type_name (t) + "[]" + r; + + break; + } + case REFERENCE_TYPE: + { + tree t (TREE_TYPE (type)); + r = emit_type_name (t) + "&" + r; + break; + } + case POINTER_TYPE: + { + if (!TYPE_PTRMEM_P (type)) + { + tree t (TREE_TYPE (type)); + r = emit_type_name (t) + "*" + r; + } + else + r = "<pointer_to_member_type>"; + + break; + } + + // + // Fundamental types. + // + + case VOID_TYPE: + case REAL_TYPE: + case BOOLEAN_TYPE: + case INTEGER_TYPE: + { + type = TYPE_MAIN_VARIANT (type); + tree decl (TYPE_NAME (type)); + r = IDENTIFIER_POINTER (DECL_NAME (decl)) + r; + break; + } + default: + { + r = "<" + string (gcc_tree_code_name(tc)) + ">"; + break; + } + } + + return r; +} + +void parser::impl:: +process_pragmas (declaration const& decl, + node& node, + string const& name, + decl_set::const_iterator begin, + decl_set::const_iterator cur, + decl_set::const_iterator /*end*/) +{ + // First process the position pragmas by iterating backwards until + // we get to the preceding non-pragma declaration that has been + // associated. + // + pragma_set prags; + + if (cur != begin) + { + decl_set::const_iterator i (cur); + for (--i; i != begin && (i->prag != 0 || !i->assoc); --i) ; + + for (; i != cur; ++i) + { + if (i->prag == 0) // Skip declarations. + continue; + + assert (!i->assoc); + + if (i->prag->check (decl, name, i->prag->pragma_name, i->prag->loc)) + prags.insert (*i->prag); + else + error_++; // Diagnostic has already been issued. + + i->assoc = true; // Mark this pragma as associated. + } + + cur->assoc = true; // Mark the declaration as associated. + } + + // Now see if there are any named pragmas for this declaration. By + // doing this after handling the position pragmas we ensure correct + // overriding. + // + { + decl_pragmas::const_iterator i (decl_pragmas_.find (decl)); + + if (i != decl_pragmas_.end ()) + prags.insert (i->second.begin (), i->second.end ()); + } + + // Finally, copy the resulting pragma set to context. + // + for (pragma_set::iterator i (prags.begin ()); i != prags.end (); ++i) + add_pragma (node, i->second); +} + +void parser::impl:: +process_named_pragmas (declaration const& decl, node& node) +{ + pragma_set prags; + + decl_pragmas::const_iterator i (decl_pragmas_.find (decl)); + + if (i != decl_pragmas_.end ()) + prags.insert (i->second.begin (), i->second.end ()); + + // Copy the resulting pragma set to context. + // + for (pragma_set::iterator i (prags.begin ()); i != prags.end (); ++i) + add_pragma (node, i->second); +} + +void parser::impl:: +add_pragma (node& n, pragma const& p) +{ + if (trace) + ts << "\t\t pragma " << p.pragma_name << endl; + + string const& k (p.context_name); + + if (p.add == 0) + { + n.set (k, p.value); + n.set (k + "-location", p.loc); + } + else + p.add (n, k, p.value, p.loc); +} + +void parser::impl:: +diagnose_unassoc_pragmas (decl_set const& decls) +{ + for (decl_set::const_iterator i (decls.begin ()), e (decls.end ()); + i != e; ++i) + { + if (i->prag && !i->assoc) + { + pragma const& p (*i->prag); + error (p.loc) + << "db pragma '" << p.pragma_name << "' is not associated with a " + << "declaration" << endl; + error_++; + } + } +} + +string parser::impl:: +fq_scope (tree decl) +{ + string s, tmp; + + for (tree scope (CP_DECL_CONTEXT (decl)); + scope != global_namespace;) + { + tree prev (CP_DECL_CONTEXT (scope)); + + // If this is an inline namespace, pretend it doesn't exist. + // +#if BUILDING_GCC_MAJOR >= 8 + if (!is_nested_namespace (prev, scope, true)) +#else + if (!is_associated_namespace (prev, scope)) +#endif + { + tree n = DECL_NAME (scope); + + tmp = "::"; + tmp += (n != NULL_TREE ? IDENTIFIER_POINTER (n) : ""); + tmp += s; + s.swap (tmp); + } + + scope = prev; + } + + return s; +} + +// +// parser +// + +parser:: +~parser () +{ + // Needs parser::impl definition. +} + +parser:: +parser (options const& ops, + loc_pragmas& lp, + ns_loc_pragmas& nslp, + decl_pragmas& dp) + : impl_ (new impl (ops, lp, nslp, dp)) +{ +} + +unique_ptr<unit> parser:: +parse (tree global_scope, path const& main_file) +{ + return impl_->parse (global_scope, main_file); +} diff --git a/odb/odb/parser.hxx b/odb/odb/parser.hxx new file mode 100644 index 0000000..b26c8f1 --- /dev/null +++ b/odb/odb/parser.hxx @@ -0,0 +1,37 @@ +// file : odb/parser.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_PARSER_HXX +#define ODB_PARSER_HXX + +#include <odb/gcc.hxx> + +#include <memory> // std::unique_ptr + +#include <odb/pragma.hxx> +#include <odb/options.hxx> +#include <odb/semantics/unit.hxx> + +class parser +{ +public: + class failed {}; + + ~parser (); + parser (options const&, loc_pragmas&, ns_loc_pragmas&, decl_pragmas&); + + std::unique_ptr<semantics::unit> + parse (tree global_scope, semantics::path const& main_file); + +private: + parser (parser const&); + + parser& + operator= (parser const&); + +private: + class impl; + std::unique_ptr<impl> impl_; +}; + +#endif // ODB_PARSER_HXX diff --git a/odb/odb/plugin.cxx b/odb/odb/plugin.cxx new file mode 100644 index 0000000..c065a8a --- /dev/null +++ b/odb/odb/plugin.cxx @@ -0,0 +1,458 @@ +// file : odb/plugin.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <odb/gcc.hxx> // Keep it first. + +#include <unistd.h> // stat() +#include <sys/types.h> // stat +#include <sys/stat.h> // stat + +#include <memory> // std::unique_ptr +#include <string> +#include <vector> +#include <cstring> // std::strcpy, std::strstr +#include <cassert> +#include <iostream> + +#include <libcutl/re.hxx> +#include <libcutl/fs/path.hxx> + +#include <odb/pragma.hxx> +#include <odb/parser.hxx> +#include <odb/options.hxx> +#include <odb/option-functions.hxx> +#include <odb/features.hxx> +#include <odb/profile.hxx> +#include <odb/version.hxx> +#include <odb/validator.hxx> +#include <odb/processor.hxx> +#include <odb/generator.hxx> +#include <odb/semantics/unit.hxx> + +using namespace std; +using namespace semantics; + +using cutl::fs::path; +using cutl::fs::invalid_path; + +typedef vector<path> paths; + +#if defined(_WIN32) && !defined(ODB_STATIC_PLUGIN) +__declspec(dllexport) +#endif +int plugin_is_GPL_compatible; + +unique_ptr<options const> options_; +paths profile_paths_; +path file_; // File being compiled. +paths inputs_; // List of input files in at-once mode or just file_. + +bool (*cpp_diagnostic_prev) ( + cpp_reader*, +#if BUILDING_GCC_MAJOR >= 9 + cpp_diagnostic_level, + cpp_warning_reason, +#else + int, + int, +#endif +#if BUILDING_GCC_MAJOR >= 6 + rich_location*, +#else + location_t, + unsigned int, +#endif + const char*, + va_list*); + +static bool +cpp_diagnostic_filter (cpp_reader* r, +#if BUILDING_GCC_MAJOR >= 9 + cpp_diagnostic_level level, + cpp_warning_reason reason, +#else + int level, + int reason, +#endif +#if BUILDING_GCC_MAJOR >= 6 + rich_location* l, +#else + location_t l, + unsigned int column_override, +#endif + const char* msg, + va_list* ap) +{ + // #pragma once in the main file. Note that the message that we get is + // potentially translated so we search for the substring (there is + // currently only one warning in libcpp that mentions #pragma once). + // Unfortunately, some translations translate the 'once' word (e.g, + // #pragma uno; I wonder if one can actually specify it like that in + // the source code). Oh, well, there is only so much we can do. + // + if (strstr (msg, "#pragma once") != 0) + return true; + + return cpp_diagnostic_prev ( + r, + level, + reason, +#if BUILDING_GCC_MAJOR >= 6 + l, +#else + l, + column_override, +#endif + msg, + ap); +} + +// A prefix of the _cpp_file struct. This struct is not part of the +// public interface so we have to resort to this technique (based on +// libcpp/files.c). +// +struct cpp_file_prefix +{ + char const* name; + char const* path; + char const* pchname; + char const* dir_name; + _cpp_file* next_file; + const uchar* buffer; + const uchar* buffer_start; + const cpp_hashnode *cmacro; + cpp_dir *dir; + struct stat st; +}; + +extern "C" void +start_unit_callback (void*, void*) +{ + // Set the preprocessor error callback to filter out useless diagnostics. + // + cpp_callbacks* cb (cpp_get_callbacks (parse_in)); + +#if BUILDING_GCC_MAJOR >= 9 + cpp_diagnostic_prev = cb->diagnostic; + cb->diagnostic = &cpp_diagnostic_filter; +#else + cpp_diagnostic_prev = cb->error; + cb->error = &cpp_diagnostic_filter; +#endif + + if (cpp_diagnostic_prev == 0) + { + cerr << "ice: expected cpp diagnostic callback to be set" << endl; + exit (1); + } + + // Set the directory of the main file (stdin) to that of the orginal + // file so that relative inclusion works. Also adjust the path and + // re-stat the file so that #pragma once works. + // + cpp_buffer* b (cpp_get_buffer (parse_in)); + _cpp_file* f (cpp_get_file (b)); + cpp_dir* d (cpp_get_dir (f)); + char const* p (cpp_get_path (f)); + + cpp_file_prefix* fp (reinterpret_cast<cpp_file_prefix*> (f)); + + // Perform some sanity checks. + // + if (p != 0 && *p == '\0' // The path should be empty (stdin). + && cpp_get_prev (b) == 0 // This is the only buffer (main file). + && fp->path == p // Our prefix corresponds to the actual type. + && fp->dir == d // Our prefix corresponds to the actual type. + && fp->dir_name == 0) // The directory part hasn't been initialized. + { + // The dir_name is initialized by libcpp lazily so we can preemptively + // set it to what we need. + // + path d (file_.directory ()); + char* s; + + if (d.empty ()) + { + s = XNEWVEC (char, 1); + *s = '\0'; + } + else + { + size_t n (d.string ().size ()); + s = XNEWVEC (char, n + 2); + strcpy (s, d.string ().c_str ()); + s[n] = path::traits::directory_separator; + s[n + 1] = '\0'; + } + + fp->dir_name = s; + + // Unless we are in the at-once mode (where input files are actually + // #include'ed into the synthesized stdin), pretend that we are the + // actual input file. This is necessary for the #pragma once to work. + // + // All this relies on the way things are implemented in libcpp. In + // particular, the #pragma once code first checks if the mtime and + // size of files match (that's why we need stat() below). If they + // do, then it goes ahead and compares their contents. To re-load + // the contents of the file libcpp uses the path (that's why we + // need to adjust that as well). + // + if (inputs_.size () == 1) + { + string const& f (file_.string ()); + + XDELETEVEC (fp->path); + size_t n (f.size ()); + char* p (XNEWVEC (char, n + 1)); + strcpy (p, f.c_str ()); + p[n] = '\0'; + fp->path = p; + + // This call shouldn't fail since we've just opened it in the driver. + stat (fp->path, &fp->st); + } + } + else + { + cerr << "ice: unable to initialize main file directory" << endl; + exit (1); + } +} + +extern "C" void +gate_callback (void*, void*) +{ + // If there were errors during compilation, let GCC handle the + // exit. + // + if (errorcount || sorrycount) + return; + + int r (0); + + try + { + // Post process pragmas. + // + post_process_pragmas (); + + // Parse the GCC tree to semantic graph. + // + parser p (*options_, loc_pragmas_, ns_loc_pragmas_, decl_pragmas_); + unique_ptr<unit> u (p.parse (global_namespace, file_)); + + features f; + + // Process, pass 1. + // + process (*options_, f, *u, file_, 1); + + // Validate, pass 1. + // + validate (*options_, f, *u, file_, 1); + + // Process, pass 2. + // + process (*options_, f, *u, file_, 2); + + // Validate, pass 2. + // + validate (*options_, f, *u, file_, 2); + + // Generate. + // + generate (*options_, f, *u, file_, inputs_); + } + catch (cutl::re::format const& e) + { + cerr << "error: invalid regex: '" << e.regex () << "': " << + e.description () << endl; + r = 1; + } + catch (pragmas_failed const&) + { + // Diagnostics has aready been issued. + // + r = 1; + } + catch (parser::failed const&) + { + // Diagnostics has aready been issued. + // + r = 1; + } + catch (validator_failed const&) + { + // Diagnostics has aready been issued. + // + r = 1; + } + catch (processor_failed const&) + { + // Diagnostics has aready been issued. + // + r = 1; + } + catch (generator_failed const&) + { + // Diagnostics has aready been issued. + // + r = 1; + } + + exit (r); +} + +static char const* const odb_version = ODB_COMPILER_VERSION_STR; + +typedef vector<string> strings; + +extern "C" +#if defined(_WIN32) && !defined(ODB_STATIC_PLUGIN) +__declspec(dllexport) +#endif +int +plugin_init (plugin_name_args* plugin_info, plugin_gcc_version*) +{ + int r (0); + plugin_info->version = odb_version; + + try + { + // Parse options. + // + { + strings argv_str; + argv_str.push_back (plugin_info->base_name); + + for (int i (0); i < plugin_info->argc; ++i) + { + plugin_argument& a (plugin_info->argv[i]); + + // A value cannot contain '=' so it is passed as the backspace + // character. + // + string v (a.value != 0 ? a.value : ""); + for (size_t i (0); i < v.size (); ++i) + if (v[i] == '\b') + v[i] = '='; + + // Handle service options. + // + if (strcmp (a.key, "svc-path") == 0) + { + profile_paths_.push_back (path (v)); + continue; + } + + if (strcmp (a.key, "svc-file") == 0) + { + // First is the main file. Subsequent are inputs in the at-once + // mode. + // + if (file_.empty ()) + file_ = path (v); + else + inputs_.push_back (path (v)); + + continue; + } + + string opt (strlen (a.key) > 1 ? "--" : "-"); + opt += a.key; + + argv_str.push_back (opt); + + if (a.value != 0) + argv_str.push_back (v); + } + + vector<char*> argv; + for (strings::iterator i (argv_str.begin ()); i != argv_str.end (); ++i) + argv.push_back (const_cast<char*> (i->c_str ())); + + int argc (static_cast<int> (argv.size ())); + + if (inputs_.empty ()) + inputs_.push_back (file_); + + // Two-phase options parsing, similar to the driver. + // + cli::argv_file_scanner::option_info oi[3]; + oi[0].option = "--options-file"; + oi[0].search_func = 0; + oi[1].option = "-p"; + oi[2].option = "--profile"; + + database db; + { + oi[1].search_func = &profile_search_ignore; + oi[2].search_func = &profile_search_ignore; + + cli::argv_file_scanner scan (argc, &argv[0], oi, 3); + options ops (scan); + assert (ops.database_specified ()); + db = ops.database ()[0]; + } + + profile_data pd (profile_paths_, db, "odb plugin"); + oi[1].search_func = &profile_search; + oi[2].search_func = &profile_search; + oi[1].arg = &pd; + oi[2].arg = &pd; + + cli::argv_file_scanner scan (argc, &argv[0], oi, 3); + unique_ptr<options> ops ( + new options (scan, cli::unknown_mode::fail, cli::unknown_mode::fail)); + + // Process options. + // + process_options (*ops); + + options_ = move (ops); + pragma_db_ = db; + pragma_multi_ = options_->multi_database (); + } + + if (options_->trace ()) + cerr << "starting plugin " << plugin_info->base_name << endl; + + // Disable assembly output. GCC doesn't define HOST_BIT_BUCKET + // correctly for MinGW (it still used /dev/null which fails to + // open). + // +#ifdef _WIN32 + asm_file_name = "nul"; +#else + asm_file_name = HOST_BIT_BUCKET; +#endif + + // Register callbacks. + // + register_callback (plugin_info->base_name, + PLUGIN_PRAGMAS, + register_odb_pragmas, + 0); + + register_callback (plugin_info->base_name, + PLUGIN_START_UNIT, + start_unit_callback, + 0); + + register_callback (plugin_info->base_name, + PLUGIN_OVERRIDE_GATE, + &gate_callback, + 0); + } + catch (cli::exception const& ex) + { + cerr << ex << endl; + r = 1; + } + + if (r != 0) + exit (r); + + return r; +} diff --git a/odb/odb/pragma.cxx b/odb/odb/pragma.cxx new file mode 100644 index 0000000..6668733 --- /dev/null +++ b/odb/odb/pragma.cxx @@ -0,0 +1,4442 @@ +// file : odb/pragma.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <odb/gcc.hxx> + +#include <cctype> // std::isalnum +#include <limits> +#include <vector> +#include <sstream> + +#include <odb/diagnostics.hxx> +#include <odb/lookup.hxx> +#include <odb/pragma.hxx> +#include <odb/cxx-token.hxx> +#include <odb/cxx-lexer.hxx> + +#include <odb/context.hxx> +#include <odb/relational/context.hxx> + +using namespace std; +using namespace cutl; + +using container::any; + +virt_declarations virt_declarations_; +loc_pragmas loc_pragmas_; +decl_pragmas decl_pragmas_; +ns_loc_pragmas ns_loc_pragmas_; + +database pragma_db_; +multi_database pragma_multi_; + +template <typename X> +void +accumulate (compiler::context& ctx, string const& k, any const& v, location_t) +{ + // Empty values are used to indicate that this pragma must be ignored. + // + if (v.empty ()) + return; + + typedef vector<X> container; + + container& c (ctx.count (k) + ? ctx.get<container> (k) + : ctx.set (k, container ())); + + c.push_back (v.value<X> ()); +} + +// Parse a qualified string. It can be in one of the following formats: +// +// "foo.bar.baz" (can have empty leading component, e.g., ".bar.baz") +// "foo"."bar"."baz" (can have empty leading component, e.g., ""."bar"."baz") +// ."foo.bar" (used to specify unqualifed names with periods) +// +// Empty leading components means fully qualified (similar to ::foo in C++). +// +static bool +parse_qname (cxx_lexer& l, + cpp_ttype& tt, + string& tl, + tree& tn, + string const& p, // Pragma name for diagnostic. + qname& name, + bool* expr = 0, // If specified, detect an expression + string* expr_str = 0) // and store it here. +{ + assert (tt == CPP_STRING || tt == CPP_DOT); + + // Handle the special case of unqualified name which can contain periods. + // + if (tt == CPP_DOT) + { + tt = l.next (tl, &tn); + + if (tt != CPP_STRING) + { + error (l) << "name expected after '.' in db pragma " << p << endl; + return false; + } + + name = tl; + tt = l.next (tl, &tn); + return true; + } + + name.clear (); + string str (tl); + + // See what comes after the string. + // + tt = l.next (tl, &tn); + + if (tt == CPP_DOT) + { + name.append (str); + + for (; tt == CPP_DOT; tt = l.next (tl, &tn)) + { + tt = l.next (tl, &tn); + + if (tt != CPP_STRING) + { + error (l) << "name expected after '.' in db pragma " << p << endl; + return false; + } + + name.append (tl); + } + + return true; + } + + if (expr != 0 && tt == CPP_PLUS) + { + *expr = true; + *expr_str = str; + return true; + } + + // Scan the string looking for '.' as well as for non-identifier + // characters if we need to detect expressions. + // + string::size_type prev (string::npos); + + for (size_t i (0); i < str.size (); ++i) + { + char c (str[i]); + + if (c == '.') + { + if (prev == string::npos) + name.append (string (str, 0, i)); + else + name.append (string (str, prev + 1, i - prev - 1)); + + prev = i; + } + else if (expr != 0 && !(isalnum (c) || c == '_')) + { + *expr = true; + *expr_str = str; + return true; + } + } + + if (prev == string::npos) + name.append (str); + else + name.append (string (str, prev + 1, string::npos)); + + return true; +} + +static bool +parse_expression (cxx_lexer& l, + cpp_ttype& tt, + string& tl, + tree& tn, + cxx_tokens& ts, + string const& prag) +{ + // Keep reading tokens until we see a mis-matching ')' or ',' while + // keeping track of the '()' and '{}' balance. + // + size_t p_balance (0), b_balance (0); + + for (; tt != CPP_EOF; tt = l.next (tl, &tn)) + { + bool done (false); + cxx_token ct (l.location (), tt); + + switch (tt) + { + case CPP_OPEN_BRACE: + { + b_balance++; + break; + } + case CPP_CLOSE_BRACE: + { + b_balance--; + break; + } + case CPP_OPEN_PAREN: + { + p_balance++; + break; + } + case CPP_CLOSE_PAREN: + { + if (p_balance == 0 && b_balance == 0) + done = true; + else + p_balance--; + break; + } + case CPP_COMMA: + { + if (p_balance == 0 && b_balance == 0) + done = true; + else + break; + } + case CPP_STRING: + { + ct.literal = tl; + break; + } + case CPP_NAME: + //case CPP_KEYWORD: see default: + { + ct.literal = tl; + break; + } + case CPP_NUMBER: + { + switch (TREE_CODE (tn)) + { + case INTEGER_CST: + { + tree type (TREE_TYPE (tn)); + unsigned long long v (integer_value (tn)); + + ostringstream os; + os << v; + + if (type == long_long_integer_type_node) + os << "LL"; + else if (type == long_long_unsigned_type_node) + os << "ULL"; + else if (type == long_integer_type_node) + os << "L"; + else if (type == long_unsigned_type_node) + os << "UL"; + else if ( + TYPE_UNSIGNED (type) && + TYPE_PRECISION (type) >= TYPE_PRECISION (integer_type_node)) + os << "U"; + + ct.literal = os.str (); + break; + } + case REAL_CST: + { + tree type (TREE_TYPE (tn)); + REAL_VALUE_TYPE val (TREE_REAL_CST (tn)); + + // This is the best we can do. val cannot be INF or NaN. + // + char tmp[256]; + real_to_decimal (tmp, &val, sizeof (tmp), 0, true); + istringstream is (tmp); + ostringstream os; + + if (type == float_type_node) + { + float f; + is >> f; + os << f << 'F'; + } + else + { + double d; + is >> d; + os << d; + } + + ct.literal = os.str (); + break; + } + default: + { + error (l) << "unexpected numeric constant in db pragma " + << prag << endl; + return false; + } + } + + break; + } + default: + { + // CPP_KEYWORD is not in the cpp_ttype enumeration. + // + if (tt == CPP_KEYWORD) + ct.literal = tl; + + break; + } + } + + if (done) + break; + + // We don't store the tree node in ct since we converted numbers to + // string literals. + // + ts.push_back (ct); + } + + return true; +} + +static string +parse_scoped_name (cxx_lexer& l, + cpp_ttype& tt, + string& tl, + tree& tn, + string const& prag) +{ + try + { + return lookup::parse_scoped_name (l, tt, tl, tn); + } + catch (lookup::invalid_name const&) + { + error (l) << "invalid name in db pragma " << prag << endl; + return ""; + } +} + +static tree +resolve_scoped_name (cxx_lexer& l, + cpp_ttype& tt, + string& tl, + tree& tn, + tree start_scope, + string& name, + bool is_type, + string const& prag, + bool trailing_scope = false, + cpp_ttype* prev_tt = 0) +{ + try + { + cpp_ttype ptt; // Not used. + tree r ( + lookup::resolve_scoped_name ( + l, tt, tl, tn, ptt, start_scope, name, is_type, trailing_scope)); + + if (prev_tt != 0) + *prev_tt = ptt; + + return r; + } + catch (lookup::invalid_name const&) + { + error (l) << "invalid name in db pragma " << prag << endl; + return 0; + } + catch (lookup::unable_to_resolve const& e) + { + if (e.last ()) + error (l) << "unable to resolve " << (is_type ? "type " : "") << "name " + << "'" << e.name () << "' in db pragma " << prag << endl; + else + error (l) << "unable to resolve name '" << e.name () << "' in db pragma " + << prag << endl; + + return 0; + } +} + +// Resolve a data member in the specified scope taking into account virtual +// member declarations. +// +declaration +resolve_data_member (tree scope, + const cxx_tokens& name, + string& decl_name, // Note: appended to. + string const& prag) +{ + declaration decl; + + if (name.size () == 1 && name.back ().type == CPP_NAME) + { + virt_declarations::iterator i (virt_declarations_.find (scope)); + + if (i != virt_declarations_.end ()) + { + string const& n (name.back ().literal); + + virt_declaration_set::const_iterator j (i->second.find (n, FIELD_DECL)); + + if (j != i->second.end ()) + { + decl = declaration (*j); + decl_name += n; + } + } + } + + if (!decl) + { + cxx_tokens_lexer l; + l.start (name); + + tree tn; + string tl; + cpp_ttype tt (l.next (tl)); + + tree d (resolve_scoped_name ( + l, tt, tl, tn, scope, decl_name, false, prag)); + + if (d == 0) + return decl; // Diagnostics has already been issued. + + decl = declaration (d); + } + + return decl; +} + +static bool +check_spec_decl_type (declaration const& d, + string const& name, + string const& p, + location_t l) +{ + gcc_tree_code_type tc (d.tree_code ()); + bool type (TREE_CODE_CLASS (tc) == tcc_type); + + if (p == "no_id") + { + // No_id can be used on objects only. + // + if (tc != RECORD_TYPE) + { + error (l) << "name '" << name << "' in db pragma " << p << " does " + << "not refer to a class" << endl; + return false; + } + } + else if (p == "id" || + p == "auto" || + p == "column" || + p == "inverse" || + p == "on_delete" || + p == "points_to" || + p == "section" || + p == "load" || + p == "update" || + p == "version" || + p == "index" || + p == "unique" || + p == "get" || + p == "set" || + p == "access") + { + if (tc != FIELD_DECL) + { + error (l) << "name '" << name << "' in db pragma " << p << " does " + << "not refer to a data member" << endl; + return false; + } + } + else if (p == "transient") + { + // Transient can be used for both data members and classes (object, + // view, or composite value). + // + if (tc != FIELD_DECL && tc != RECORD_TYPE) + { + error (l) << "name '" << name << "' in db pragma " << p << " does " + << "not refer to a data member or class" << endl; + return false; + } + } + else if (p == "added") + { + // Added can be used for data members only. + // + if (tc != FIELD_DECL) + { + error (l) << "name '" << name << "' in db pragma " << p << " does " + << "not refer to a data member" << endl; + return false; + } + } + else if (p == "deleted") + { + // Deleted can be used for both data members and classes (object, + // view of composite value). + // + if (tc != FIELD_DECL && tc != RECORD_TYPE) + { + error (l) << "name '" << name << "' in db pragma " << p << " does " + << "not refer to a data member or class" << endl; + return false; + } + } + else if (p == "readonly") + { + // Readonly can be used for both data members and classes (object or + // composite value). + // + if (tc != FIELD_DECL && tc != RECORD_TYPE) + { + error (l) << "name '" << name << "' in db pragma " << p << " does " + << "not refer to a data member or class" << endl; + return false; + } + } + else if (p == "abstract" || + p == "callback" || + p == "query" || + p == "object" || + p == "optimistic" || + p == "polymorphic" || + p == "definition" || + p == "sectionable" || + p == "bulk") + { + if (tc != RECORD_TYPE) + { + error (l) << "name '" << name << "' in db pragma " << p << " does " + << "not refer to a class" << endl; + return false; + } + } + else if (p == "pointer") + { + // Table can be used for namespaces and classes (object or view). + // + if (tc != NAMESPACE_DECL && tc != RECORD_TYPE) + { + error (l) << "name '" << name << "' in db pragma " << p << " does " + << "not refer to a class" << endl; + return false; + } + } + else if (p == "table") + { + // Table can be used for namespaces, members (container), and types + // (container, object, or view). + // + if (tc != NAMESPACE_DECL && tc != FIELD_DECL && !type) + { + error (l) << "name '" << name << "' in db pragma " << p << " does " + << "not refer to a namespace, type, or data member" << endl; + return false; + } + } + else if (p == "session") + { + // Session can be used only for namespaces and objects. + // + if (tc != NAMESPACE_DECL && tc != RECORD_TYPE) + { + error (l) << "name '" << name << "' in db pragma " << p << " does " + << "not refer to a namespace or class" << endl; + return false; + } + } + else if (p == "schema") + { + // For now schema can be used only for namespaces and objects. + // + if (tc != NAMESPACE_DECL && tc != RECORD_TYPE) + { + error (l) << "name '" << name << "' in db pragma " << p << " does " + << "not refer to a namespace or class" << endl; + return false; + } + } + else if (p == "type" || + p == "id_type" || + p == "value_type" || + p == "index_type" || + p == "key_type") + { + // Type can be used for both members and types. + // + if (tc != FIELD_DECL && !type) + { + error (l) << "name '" << name << "' in db pragma " << p << " does " + << "not refer to a type or data member" << endl; + return false; + } + } + else if (p == "default") + { + // Default can be used for both members and types. + // + if (tc != FIELD_DECL && !type) + { + error (l) << "name '" << name << "' in db pragma " << p << " does " + << "not refer to a type or data member" << endl; + return false; + } + } + else if (p == "value_column" || + p == "index_column" || + p == "key_column" || + p == "id_column") + { + // Container columns can be used for both members (container) and + // types (container). + // + if (tc != FIELD_DECL && !type) + { + error (l) << "name '" << name << "' in db pragma " << p << " does " + << "not refer to a type or data member" << endl; + return false; + } + } + else if (p == "options" || + p == "value_options" || + p == "index_options" || + p == "key_options" || + p == "id_options") + { + // Options can be used for both members and types. + // + if (tc != FIELD_DECL && !type) + { + error (l) << "name '" << name << "' in db pragma " << p << " does " + << "not refer to a type or data member" << endl; + return false; + } + } + else if (p == "null" || + p == "not_null" || + p == "key_null" || + p == "key_not_null" || + p == "value_null" || + p == "value_not_null") + { + // Null pragmas can be used for both members and types (values, + // containers, and pointers). + // + if (tc != FIELD_DECL && !type) + { + error (l) << "name '" << name << "' in db pragma " << p << " does " + << "not refer to a type or data member" << endl; + return false; + } + } + else if (p == "unordered") + { + // Unordered can be used for both members (container) and + // types (container). + // + if (tc != FIELD_DECL && !type) + { + error (l) << "name '" << name << "' in db pragma " << p << " does " + << "not refer to a type or data member" << endl; + return false; + } + } + else if (p == "virtual") + { + // Virtual is specified for a member. + // + if (tc != FIELD_DECL) + { + error (l) << "name '" << name << "' in db pragma " << p << " does " + << "not refer to a data member" << endl; + return false; + } + } + else if (p == "simple" || + p == "container") + { + // Apply to both members and types. + // + if (tc != FIELD_DECL && !type) + { + error (l) << "name '" << name << "' in db pragma " << p << " does " + << "not refer to a type or data member" << endl; + return false; + } + } + else + { + error (l) << "unknown db pragma " << p << endl; + return false; + } + + return true; +} + +static void +add_pragma (pragma const& prag, declaration const& decl, bool ns) +{ + if (decl) + decl_pragmas_[decl].insert (prag); + else + { + tree scope (current_scope ()); + + if (!ns) + { + if (!CLASS_TYPE_P (scope)) + scope = global_namespace; + + loc_pragmas_[scope].push_back (prag); + } + else + ns_loc_pragmas_.push_back (ns_loc_pragma (scope, prag)); + } +} + +static void +handle_pragma (cxx_lexer& l, + string db, + string p, + string const& qualifier, + any& qualifier_value, + declaration const& decl, + string const& decl_name, + bool ns) // True if this is a position namespace pragma. +{ + cpp_ttype tt; + string tl; + tree tn; + + // See if there is a database prefix. The db argument may already + // contain it. + // + if (db.empty () && + (p == "mysql" || + p == "sqlite" || + p == "pgsql" || + p == "oracle" || + p == "mssql")) + { + tt = l.next (tl); + + if (tt != CPP_COLON) + { + error (l) << "':' expected after database prefix " << p << endl; + return; + } + + // Specifier prefix. + // + db = p; + tt = l.next (p); + + if (tt != CPP_NAME && tt != CPP_KEYWORD) + { + error (l) << "expected specifier after databas prefix " << db << endl; + return; + } + } + + string name (p); // Pragma name. + any val; // Pragma value. + pragma::add_func adder (0); // Custom context adder. + location_t loc (l.location ()); // Pragma location. + + if (qualifier == "model") + { + // version(unsigned long long base, + // unsigned long long current, + // open|closed) + // + + // Make sure we've got the correct declaration type. + // + assert (decl == global_namespace); + + if (p == "version") + { + if (l.next (tl, &tn) != CPP_OPEN_PAREN) + { + error (l) << "'(' expected after db pragma " << p << endl; + return; + } + + model_version v; + + // base + // + if (l.next (tl, &tn) != CPP_NUMBER || TREE_CODE (tn) != INTEGER_CST) + { + error (l) << "unsigned integer expected as base version" << endl; + return; + } + + v.base = integer_value (tn); + + if (v.base == 0) + { + error (l) << "base version cannot be zero" << endl; + return; + } + + // current + // + if (l.next (tl, &tn) != CPP_COMMA) + { + error (l) << "current version expected after base version" << endl; + return; + } + + if (l.next (tl, &tn) != CPP_NUMBER || TREE_CODE (tn) != INTEGER_CST) + { + error (l) << "unsigned integer expected as current version" << endl; + return; + } + + v.current = integer_value (tn); + + if (v.current == 0) + { + error (l) << "current version cannot be zero" << endl; + return; + } + + if (v.base > v.current) + { + error (l) << "current version should be greater than or equal to " << + "base version" << endl; + return; + } + + // open|closed + // + tt = l.next (tl, &tn); + if (tt == CPP_COMMA) + { + if (l.next (tl, &tn) != CPP_NAME || (tl != "open" && tl != "closed")) + { + error (l) << "open or closed expected after current version" << endl; + return; + } + + v.open = (tl == "open"); + tt = l.next (tl, &tn); + } + else + v.open = true; + + if (tt != CPP_CLOSE_PAREN) + { + error (l) << "')' expected at the end of db pragma " << p << endl; + return; + } + + name = "model-version"; + val = v; + tt = l.next (tl, &tn); + } + else + { + error (l) << "unknown db pragma " << p << endl; + return; + } + } + else if (qualifier == "map") + { + // type("<regex>") | type(<name>) + // as("<subst>") | as(<name>) + // to("<subst>") | to(<expr>) + // from("<subst>") | from(<expr>) + // + + if (p != "type" && + p != "as" && + p != "to" && + p != "from") + { + error (l) << "unknown db pragma " << p << endl; + return; + } + + // Make sure we've got the correct declaration type. + // + assert (decl == global_namespace); + + if (l.next (tl, &tn) != CPP_OPEN_PAREN) + { + error (l) << "'(' expected after db pragma " << p << endl; + return; + } + + tt = l.next (tl, &tn); + + if (qualifier_value.type_info () == typeid (custom_cxx_type)) + { + // C++ type mapping. + // + custom_cxx_type& ct (qualifier_value.value<custom_cxx_type> ()); + + if (p == "type" || p == "as") + { + // Can be built-in type (e.g., bool). + // + if (tt == CPP_NAME || tt == CPP_KEYWORD || tt == CPP_SCOPE) + { + string name; + tree decl ( + resolve_scoped_name ( + l, tt, tl, tn, current_scope (), name, true, p)); + + if (decl == 0) + return; // Diagnostics has already been issued. + + if (TREE_CODE (decl) != TYPE_DECL) + { + error (loc) << "name '" << name << "' in db pragma " + << p << " does not refer to a type" << endl; + return; + } + + (p == "type" ? ct.type_node : ct.as_node) = TREE_TYPE (decl); + (p == "type" ? ct.type_name : ct.as_name) = name; + } + else + { + error (l) << "type name expected in db pragma " << p << endl; + return; + } + } + else if (p == "to" || p == "from") + { + if (tt != CPP_CLOSE_PAREN) // Empty expression is ok. + { + if (!parse_expression ( + l, tt, tl, tn, (p == "to" ? ct.to : ct.from), p)) + return; // Diagnostics has already been issued. + } + } + } + else + { + using relational::custom_db_type; + + // Database type mapping. + // + custom_db_type& ct (qualifier_value.value<custom_db_type> ()); + + if (p == "type") + { + if (tt != CPP_STRING) + { + error (l) << "type name regex expected in db pragma " << p << endl; + return; + } + + try + { + // Make it case-insensitive. + // + ct.type.assign (tl, true); + } + catch (regex_format const& e) + { + error (l) << "invalid regex: '" << e.regex () << "' in db pragma " + << p << ": " << e.description () << endl; + return; + } + } + else if (p == "as") + { + if (tt != CPP_STRING) + { + error (l) << "type name expected in db pragma " << p << endl; + return; + } + + ct.as = tl; + } + else if (p == "to") + { + if (tt != CPP_STRING) + { + error (l) << "expression expected in db pragma " << p << endl; + return; + } + + ct.to = tl; + } + else if (p == "from") + { + if (tt != CPP_STRING) + { + error (l) << "expression expected in db pragma " << p << endl; + return; + } + + ct.from = tl; + } + + tt = l.next (tl, &tn); + } + + if (tt != CPP_CLOSE_PAREN) + { + error (l) << "')' expected at the end of db pragma " << p << endl; + return; + } + + name.clear (); // We don't need to add anything for this pragma. + tt = l.next (tl, &tn); + } + else if (qualifier == "index") + { + // unique + // type("<type>") + // method("<method>") + // options("<options>") + // member(<name>[, "<options>"]) + // members(<name>[, <name>...]) + // + + if (p != "unique" && + p != "type" && + p != "method" && + p != "options" && + p != "member" && + p != "members") + { + error (l) << "unknown db pragma " << p << endl; + return; + } + + using relational::index; + index& in (qualifier_value.value<index> ()); + + if (p == "unique") + in.type = "UNIQUE"; + else + { + if (l.next (tl, &tn) != CPP_OPEN_PAREN) + { + error (l) << "'(' expected after db pragma " << p << endl; + return; + } + + tt = l.next (tl, &tn); + + if (p == "type") + { + if (tt != CPP_STRING) + { + error (l) << "index type expected in db pragma " << p << endl; + return; + } + + in.type = tl; + tt = l.next (tl, &tn); + } + else if (p == "method") + { + if (tt != CPP_STRING) + { + error (l) << "index method expected in db pragma " << p << endl; + return; + } + + in.method = tl; + tt = l.next (tl, &tn); + } + else if (p == "options") + { + if (tt != CPP_STRING) + { + error (l) << "index options expected in db pragma " << p << endl; + return; + } + + in.options = tl; + tt = l.next (tl, &tn); + } + else if (p == "member") + { + if (tt != CPP_NAME) + { + error (l) << "data member name expected in db pragma " << p << endl; + return; + } + + index::member m; + m.loc = loc; + m.name = tl; + + tt = l.next (tl, &tn); + + // Parse nested members if any. + // + for (; tt == CPP_DOT; tt = l.next (tl, &tn)) + { + if (l.next (tl, &tn) != CPP_NAME) + { + error (l) << "name expected after '.' in db pragma " << p << endl; + return; + } + + m.name += '.'; + m.name += tl; + } + + // Parse member options, if any. + // + if (tt == CPP_COMMA) + { + if (l.next (tl, &tn) != CPP_STRING) + { + error (l) << "index member options expected in db pragma " << p + << endl; + return; + } + + m.options = tl; + tt = l.next (tl, &tn); + } + + in.members.push_back (m); + } + else if (p == "members") + { + for (;;) + { + if (tt != CPP_NAME) + { + error (l) << "data member name expected in db pragma " << p + << endl; + return; + } + + index::member m; + m.loc = l.location (); + m.name = tl; + + tt = l.next (tl, &tn); + + // Parse nested members if any. + // + for (; tt == CPP_DOT; tt = l.next (tl, &tn)) + { + if (l.next (tl, &tn) != CPP_NAME) + { + error (l) << "name expected after '.' in db pragma " << p + << endl; + return; + } + + m.name += '.'; + m.name += tl; + } + + in.members.push_back (m); + + if (tt == CPP_COMMA) + tt = l.next (tl, &tn); + else + break; + } + } + + if (tt != CPP_CLOSE_PAREN) + { + error (l) << "')' expected at the end of db pragma " << p << endl; + return; + } + } + + name.clear (); // We don't need to add anything for this pragma. + tt = l.next (tl, &tn); + } + else if (p == "index" || + p == "unique") + { + // index + // unique + // + + // Make sure we've got the correct declaration type. + // + if (decl && !check_spec_decl_type (decl, decl_name, p, loc)) + return; + + tt = l.next (tl, &tn); + } + else if (p == "get" || + p == "set" || + p == "access") + { + // get(name|expr) + // set(name|expr) + // access(name|expr) + // + + // Make sure we've got the correct declaration type. + // + if (decl && !check_spec_decl_type (decl, decl_name, p, loc)) + return; + + if (l.next (tl, &tn) != CPP_OPEN_PAREN) + { + error (l) << "'(' expected after db pragma " << p << endl; + return; + } + + member_access ma (loc, p == "set" ? "modifier" : "accessor", false); + + tt = l.next (tl, &tn); + if (tt != CPP_CLOSE_PAREN) // Empty expression are ok. + { + if (!parse_expression (l, tt, tl, tn, ma.expr, p)) + return; // Diagnostics has already been issued. + + if (tt != CPP_CLOSE_PAREN) + { + error (l) << "')' expected at the end of db pragma " << p << endl; + return; + } + } + + val = ma; + + // Convert access to the get/set pair. + // + if (p == "access") + { + if (db.empty () || db == pragma_db_.string ()) + add_pragma ( + pragma (p, "get", val, loc, &check_spec_decl_type, 0), decl, ns); + + ma.kind = "modifier"; + val = ma; + name = "set"; + } + + tt = l.next (tl, &tn); + } + else if (p == "table") + { + // table (<name>) + // table (<name> [= "<alias>"] [type] [: "<cond>"] (view only) + // + // <name> := "name" | "name.name" | "name"."name" + // + + // Make sure we've got the correct declaration type. + // + if (decl && !check_spec_decl_type (decl, decl_name, p, loc)) + return; + + if (l.next (tl, &tn) != CPP_OPEN_PAREN) + { + error (l) << "'(' expected after db pragma " << p << endl; + return; + } + + tt = l.next (tl, &tn); + + if (tt != CPP_STRING && tt != CPP_DOT) + { + error (l) << "table name expected in db pragma " << p << endl; + return; + } + + // The table specifier is used for both objects and views. In case + // of an object, the context values is just a string. In case of a + // view, the context value is a view_object entry. The problem is + // that at this stage we don't know whether we are dealing with an + // object or a view. To resolve this in a somewhat hackish way, we + // are going to create both a string and a view_object entry. + // + view_object vo; + vo.kind = view_object::table; + + if (!parse_qname (l, tt, tl, tn, p, vo.tbl_name)) + return; // Diagnostics has already been issued. + + if (tt == CPP_EQ) + { + // We have an alias. + // + if (l.next (tl, &tn) != CPP_STRING) + { + error (l) << "table alias expected after '=' in db pragma " << p + << endl; + return; + } + + vo.alias = tl; + tt = l.next (tl, &tn); + } + + if (tt == CPP_NAME) + { + // We have a JOIN type. + // + if (tl == "left") + vo.join = view_object::left; + else if (tl == "right") + vo.join = view_object::right; + else if (tl == "full") + vo.join = view_object::full; + else if (tl == "inner") + vo.join = view_object::inner; + else if (tl == "cross") + vo.join = view_object::cross; + else + { + error (l) << "unknown join type '" << tl << "'" << endl; + return; + } + + tt = l.next (tl, &tn); + } + else + vo.join = view_object::left; + + if (tt == CPP_COLON) + { + // We have a condition. + // + if (vo.join == view_object::cross) + { + error (l) + << "no join condition can be specified for a cross join" << endl; + return; + } + + tt = l.next (tl, &tn); + + if (!parse_expression (l, tt, tl, tn, vo.cond, p)) + return; // Diagnostics has already been issued. + + if (vo.cond.empty ()) + { + error (l) << "join condition expected after ':' in db pragma " << p + << endl; + return; + } + } + + if (tt != CPP_CLOSE_PAREN) + { + error (l) << "')' expected at the end of db pragma " << p << endl; + return; + } + + // Add the "table" pragma. + // + if (vo.alias.empty () && vo.cond.empty ()) + { + if (db.empty () || db == pragma_db_.string ()) + { + add_pragma ( + pragma (p, name, vo.tbl_name, loc, &check_spec_decl_type, 0), + decl, + ns); + } + } + + vo.scope = current_scope (); + vo.loc = loc; + val = vo; + name = "objects"; + adder = &accumulate<view_object>; + + tt = l.next (tl, &tn); + } + else if (p == "session") + { + // session + // session (true|false) + // + + // Make sure we've got the correct declaration type. + // + if (decl && !check_spec_decl_type (decl, decl_name, p, loc)) + return; + + tt = l.next (tl, &tn); + + if (tt == CPP_OPEN_PAREN) + { + tt = l.next (tl, &tn); + + if (tt != CPP_KEYWORD || (tl != "true" && tl != "false")) + { + error (l) << "true or false expected in db pragma " << p << endl; + return; + } + + val = (tl == "true"); + + tt = l.next (tl, &tn); + if (tt != CPP_CLOSE_PAREN) + { + error (l) << "')' expected at the end of db pragma " << p << endl; + return; + } + + tt = l.next (tl, &tn); + } + } + else if (p == "schema") + { + // schema (<name>) + // + // <name> := "name" | "name.name" | "name"."name" + // + + // Make sure we've got the correct declaration type. + // + if (decl && !check_spec_decl_type (decl, decl_name, p, loc)) + return; + + if (l.next (tl, &tn) != CPP_OPEN_PAREN) + { + error (l) << "'(' expected after db pragma " << p << endl; + return; + } + + tt = l.next (tl, &tn); + + if (tt != CPP_STRING && tt != CPP_DOT) + { + error (l) << "table name expected in db pragma " << p << endl; + return; + } + + qname s; + if (!parse_qname (l, tt, tl, tn, p, s)) + return; // Diagnostics has already been issued. + + if (tt != CPP_CLOSE_PAREN) + { + error (l) << "')' expected at the end of db pragma " << p << endl; + return; + } + + val = s; + tt = l.next (tl, &tn); + } + else if (p == "pointer") + { + // pointer (qname) + // + + // Make sure we've got the correct declaration type. + // + if (decl && !check_spec_decl_type (decl, decl_name, p, loc)) + return; + + if (l.next (tl, &tn) != CPP_OPEN_PAREN) + { + error (l) << "'(' expected after db pragma " << p << endl; + return; + } + + class_pointer cp; + size_t pb (0); + bool punc (false); + + for (tt = l.next (tl, &tn); + tt != CPP_EOF && (tt != CPP_CLOSE_PAREN || pb != 0); + tt = l.next (tl, &tn)) + { + if (punc && tt > CPP_LAST_PUNCTUATOR) + cp.name += ' '; + + punc = false; + + if (tt == CPP_OPEN_PAREN) + pb++; + else if (tt == CPP_CLOSE_PAREN) + pb--; + + // @@ Need to handle literals, at least integer. + // + switch (tt) + { + case CPP_LESS: + { + cp.name += "< "; + break; + } + case CPP_GREATER: + { + cp.name += " >"; + break; + } + case CPP_COMMA: + { + cp.name += ", "; + break; + } + case CPP_NAME: + // case CPP_KEYWORD: // see default: + { + cp.name += tl; + punc = true; + break; + } + default: + { + if (tt == CPP_KEYWORD) + { + cp.name += tl; + punc = true; + } + else if (tt <= CPP_LAST_PUNCTUATOR) + cp.name += cxx_lexer::token_spelling[tt]; + else + { + error (l) << "unexpected token '" << cxx_lexer::token_spelling[tt] + << "' in db pragma " << p << endl; + return; + } + break; + } + } + } + + if (tt != CPP_CLOSE_PAREN) + { + error (l) << "')' expected at the end of db pragma " << p << endl; + return; + } + + if (cp.name.empty ()) + { + error (l) << "expected pointer name in db pragma " << p << endl; + return; + } + + cp.scope = current_scope (); + cp.loc = loc; + val = cp; + + tt = l.next (tl, &tn); + } + else if (p == "abstract") + { + // abstract + // + + // Make sure we've got the correct declaration type. + // + if (decl && !check_spec_decl_type (decl, decl_name, p, loc)) + return; + + tt = l.next (tl, &tn); + } + else if (p == "optimistic") + { + // optimistic + // + + // Make sure we've got the correct declaration type. + // + if (decl && !check_spec_decl_type (decl, decl_name, p, loc)) + return; + + tt = l.next (tl, &tn); + } + else if (p == "polymorphic") + { + // polymorphic + // + + // Make sure we've got the correct declaration type. + // + if (decl && !check_spec_decl_type (decl, decl_name, p, loc)) + return; + + tt = l.next (tl, &tn); + } + else if (p == "definition") + { + // definition + // + + // Make sure we've got the correct declaration type. + // + if (decl && !check_spec_decl_type (decl, decl_name, p, loc)) + return; + + val = l.location (); + tt = l.next (tl, &tn); + } + else if (p == "sectionable") + { + // sectionable + // + + // Make sure we've got the correct declaration type. + // + if (decl && !check_spec_decl_type (decl, decl_name, p, loc)) + return; + + tt = l.next (tl, &tn); + } + else if (p == "callback") + { + // callback (name) + // + + // Make sure we've got the correct declaration type. + // + if (decl && !check_spec_decl_type (decl, decl_name, p, loc)) + return; + + if (l.next (tl, &tn) != CPP_OPEN_PAREN) + { + error (l) << "'(' expected after db pragma " << p << endl; + return; + } + + tt = l.next (tl, &tn); + + if (tt != CPP_NAME) + { + error (l) << "member function name expected in db pragma " << p << endl; + return; + } + + val = tl; + + if (l.next (tl, &tn) != CPP_CLOSE_PAREN) + { + error (l) << "')' expected at the end of db pragma " << p << endl; + return; + } + + tt = l.next (tl, &tn); + } + else if (p == "bulk") + { + // bulk (batch-size) + // + + // Make sure we've got the correct declaration type. + // + if (decl && !check_spec_decl_type (decl, decl_name, p, loc)) + return; + + if (l.next (tl, &tn) != CPP_OPEN_PAREN) + { + error (l) << "'(' expected after db pragma " << p << endl; + return; + } + + if (l.next (tl, &tn) != CPP_NUMBER || TREE_CODE (tn) != INTEGER_CST) + { + error (l) << "unsigned integer expected as batch size" << endl; + return; + } + + unsigned long long b (integer_value (tn)); + + if (b == 0 || b == 1) + { + error (l) << "batch size has to be greater than 1" << endl; + return; + } + + val = b; + + if (l.next (tl, &tn) != CPP_CLOSE_PAREN) + { + error (l) << "')' expected at the end of db pragma " << p << endl; + return; + } + + tt = l.next (tl, &tn); + } + else if (p == "query") + { + // query () + // query ("statement") + // query (expr) + // + + // Make sure we've got the correct declaration type. + // + if (decl && !check_spec_decl_type (decl, decl_name, p, loc)) + return; + + if (l.next (tl, &tn) != CPP_OPEN_PAREN) + { + error (l) << "'(' expected after db pragma " << p << endl; + return; + } + + tt = l.next (tl, &tn); + + view_query vq; + + bool s (false); + string str; + + if (tt == CPP_STRING) + { + s = true; + str = tl; + tt = l.next (tl, &tn); + } + + if (tt == CPP_CLOSE_PAREN) + { + if (s) + vq.literal = str; + else + { + // Empty query() pragma indicates that the statement will be + // provided at runtime. Encode this case as empty literal + // and expression. + // + } + } + else + { + // Expression. + // + if (s) + vq.expr.push_back (cxx_token (0, CPP_STRING, str)); + + if (!parse_expression (l, tt, tl, tn, vq.expr, p)) + return; // Diagnostics has already been issued. + } + + // Disallow query(, distinct). + // + if (tt == CPP_COMMA && vq.expr.empty ()) + { + error (l) << "query expression expected in db pragma " << p << endl; + return; + } + + // The query expression can be omitted with the modifier in its + // place. Handle this case. + // + if (vq.expr.size () == 1 && vq.expr.front ().type == CPP_NAME) + { + string const& n (vq.expr.front ().literal); + + if (n == "distinct") + vq.distinct = true; + else if (n == "for_update") + vq.for_update = true; + + if (vq.distinct || vq.for_update) + vq.expr.clear (); + } + + if (tt == CPP_COMMA) + { + do + { + if (l.next (tl, &tn) != CPP_NAME) + { + error (l) << "result modifier expected in db pragma " << p << endl; + return; + } + + if (tl == "distinct") + vq.distinct = true; + else if (tl == "for_update") + vq.for_update = true; + else + { + error (l) << "unknown result modifier '" << tl << "'" << endl; + return; + } + + tt = l.next (tl, &tn); + + } while (tt == CPP_COMMA); + } + + if (tt != CPP_CLOSE_PAREN) + { + error (l) << "')' expected at the end of db pragma " << p << endl; + return; + } + + vq.scope = current_scope (); + vq.loc = loc; + val = vq; + tt = l.next (tl, &tn); + } + else if (p == "object") + { + // object (fq-name [ = name] [type] [: expr]) + // + + // Make sure we've got the correct declaration type. + // + if (decl && !check_spec_decl_type (decl, decl_name, p, loc)) + return; + + if (l.next (tl, &tn) != CPP_OPEN_PAREN) + { + error (l) << "'(' expected after db pragma " << p << endl; + return; + } + + tt = l.next (tl, &tn); + + if (tt != CPP_NAME && tt != CPP_SCOPE) + { + error (l) << "type name expected in db pragma " << p << endl; + return; + } + + view_object vo; + vo.kind = view_object::object; + vo.obj_node = resolve_scoped_name ( + l, tt, tl, tn, current_scope (), vo.obj_name, true, p); + + if (vo.obj_node == 0) + return; // Diagnostics has already been issued. + + // Get the actual type if this is a TYPE_DECL. Also get the main variant. + // + if (TREE_CODE (vo.obj_node) == TYPE_DECL) + vo.obj_node = TREE_TYPE (vo.obj_node); + + if (TYPE_P (vo.obj_node)) // Can be a template. + vo.obj_node = TYPE_MAIN_VARIANT (vo.obj_node); + + if (tt == CPP_EQ) + { + // We have an alias. + // + if (l.next (tl, &tn) != CPP_NAME) + { + error (l) << "alias name expected after '=' in db pragma " << p + << endl; + return; + } + + vo.alias = tl; + tt = l.next (tl, &tn); + } + + if (tt == CPP_NAME) + { + // We have a JOIN type. + // + if (tl == "left") + vo.join = view_object::left; + else if (tl == "right") + vo.join = view_object::right; + else if (tl == "full") + vo.join = view_object::full; + else if (tl == "inner") + vo.join = view_object::inner; + else if (tl == "cross") + vo.join = view_object::cross; + else + { + error (l) << "unknown join type '" << tl << "'" << endl; + return; + } + + tt = l.next (tl, &tn); + } + else + vo.join = view_object::left; + + if (tt == CPP_COLON) + { + // We have a condition. + // + if (vo.join == view_object::cross) + { + error (l) + << "no join condition can be specified for a cross join" << endl; + return; + } + + tt = l.next (tl, &tn); + + if (!parse_expression (l, tt, tl, tn, vo.cond, p)) + return; // Diagnostics has already been issued. + + if (vo.cond.empty ()) + { + error (l) << "join condition expected after ':' in db pragma " << p + << endl; + return; + } + } + + if (tt != CPP_CLOSE_PAREN) + { + error (l) << "')' expected at the end of db pragma " << p << endl; + return; + } + + vo.scope = current_scope (); + vo.loc = loc; + val = vo; + name = "objects"; // Change the context entry name. + adder = &accumulate<view_object>; + + tt = l.next (tl, &tn); + } + else if (p == "id") + { + // id[(member-path)] + // + + // Make sure we've got the correct declaration type. + // + if (decl && !check_spec_decl_type (decl, decl_name, p, loc)) + return; + + string name; + + tt = l.next (tl, &tn); + if (tt == CPP_OPEN_PAREN) + { + if (l.next (tl, &tn) != CPP_NAME) + { + error (l) << "data member name expected in db pragma " << p + << endl; + return; + } + + name = tl; + + for (tt = l.next (tl, &tn); tt == CPP_DOT; tt = l.next (tl, &tn)) + { + if (l.next (tl, &tn) != CPP_NAME) + { + error (l) << "name expected after '.' in db pragma " << p << endl; + return; + } + + name += '.'; + name += tl; + } + + if (tt != CPP_CLOSE_PAREN) + { + error (l) << "')' expected at the end of db pragma " << p << endl; + return; + } + + tt = l.next (tl, &tn); + } + + val = name; + } + else if (p == "no_id") + { + // Make sure we've got the correct declaration type. + // + if (decl && !check_spec_decl_type (decl, decl_name, p, loc)) + return; + + name = "id"; + val = false; + + tt = l.next (tl, &tn); + } + else if (p == "auto") + { + // auto + // + + // Make sure we've got the correct declaration type. + // + if (decl && !check_spec_decl_type (decl, decl_name, p, loc)) + return; + + tt = l.next (tl, &tn); + } + else if (p == "column") + { + // column ("<name>") + // column ("<name>.<name>") (view only) + // column ("<name>"."<name>") (view only) + // column (fq-name) (view only) + // column (expr) (view only) + // + + // Make sure we've got the correct declaration type. + // + if (decl && !check_spec_decl_type (decl, decl_name, p, loc)) + return; + + if (l.next (tl, &tn) != CPP_OPEN_PAREN) + { + error (l) << "'(' expected after db pragma " << p << endl; + return; + } + + tt = l.next (tl, &tn); + + bool expr (false); + string expr_str; + if (tt == CPP_STRING || tt == CPP_DOT) + { + qname qn; + + if (!parse_qname (l, tt, tl, tn, p, qn, &expr, &expr_str)) + return; // Diagnostics has already been issued. + + if (tt == CPP_CLOSE_PAREN) + { + table_column tc; + tc.expr = expr; + + if (expr) + tc.column = expr_str; + else + { + tc.table = qn.qualifier (); + tc.column = qn.uname (); + } + + val = tc; + } + else if (!expr) + { + error (l) << "column name, expression, or data member name expected " + << "in db pragma " << p << endl; + return; + } + } + + if (val.empty ()) + { + // We have an expression. + // + column_expr e; + + if (expr) + { + e.push_back (column_expr_part ()); + e.back ().kind = column_expr_part::literal; + e.back ().value = expr_str; + + if (tt != CPP_PLUS) + { + error (l) << "'+' or ')' expected in db pragma " << p << endl; + return; + } + + tt = l.next (tl, &tn); + } + + for (;;) + { + if (tt == CPP_STRING) + { + e.push_back (column_expr_part ()); + e.back ().kind = column_expr_part::literal; + e.back ().value = tl; + + tt = l.next (tl, &tn); + } + else if (tt == CPP_NAME || tt == CPP_SCOPE) + { + string name (parse_scoped_name (l, tt, tl, tn, p)); + + if (name.empty ()) + return; // Diagnostics has already been issued. + + // Resolve nested members if any. + // + for (; tt == CPP_DOT; tt = l.next (tl, &tn)) + { + if (l.next (tl, &tn) != CPP_NAME) + { + error (l) << "name expected after '.' in db pragma " << p + << endl; + return; + } + + name += '.'; + name += tl; + } + + e.push_back (column_expr_part ()); + e.back ().kind = column_expr_part::reference; + e.back ().value = name; + e.back ().scope = current_scope (); + e.back ().loc = loc; + } + else + { + error (l) << "column name, expression, or data member name expected " + << "in db pragma " << p << endl; + return; + } + + if (tt == CPP_PLUS) + tt = l.next (tl, &tn); + else if (tt == CPP_CLOSE_PAREN) + break; + else + { + error (l) << "'+' or ')' expected in db pragma " << p << endl; + return; + } + } + + e.loc = loc; + val = e; + name = "column-expr"; + } + + tt = l.next (tl, &tn); + } + else if (p == "value_column" || + p == "index_column" || + p == "key_column" || + p == "id_column") + { + // value_column ("<name>") + // index_column ("<name>") + // key_column ("<name>") + // id_column ("<name>") + // + + // Make sure we've got the correct declaration type. + // + if (decl && !check_spec_decl_type (decl, decl_name, p, loc)) + return; + + if (l.next (tl, &tn) != CPP_OPEN_PAREN) + { + error (l) << "'(' expected after db pragma " << p << endl; + return; + } + + tt = l.next (tl, &tn); + + if (tt != CPP_STRING) + { + error (l) << "column name expected in db pragma " << p << endl; + return; + } + + val = tl; + + if (l.next (tl, &tn) != CPP_CLOSE_PAREN) + { + error (l) << "')' expected at the end of db pragma " << p << endl; + return; + } + + tt = l.next (tl, &tn); + } + else if (p == "options" || + p == "value_options" || + p == "index_options" || + p == "key_options" || + p == "id_options") + { + // options (["<name>"]) + // value_options (["<name>"]) + // index_options (["<name>"]) + // key_options (["<name>"]) + // id_options (["<name>"]) + // + + // Make sure we've got the correct declaration type. + // + if (decl && !check_spec_decl_type (decl, decl_name, p, loc)) + return; + + if (l.next (tl, &tn) != CPP_OPEN_PAREN) + { + error (l) << "'(' expected after db pragma " << p << endl; + return; + } + + tt = l.next (tl, &tn); + + if (tt == CPP_STRING) + { + // Ignore empty options strings. Internally, we use them to + // indicate options reset (see below). + // + if (!tl.empty ()) + val = tl; + + tt = l.next (tl, &tn); + } + else if (tt == CPP_CLOSE_PAREN) + { + // Empty options specifier signals options reset. Encode it as an + // empty string. + // + val = string (); + } + else + { + error (l) << "options string expected in db pragma " << p << endl; + return; + } + + if (tt != CPP_CLOSE_PAREN) + { + error (l) << "')' expected at the end of db pragma " << p << endl; + return; + } + + adder = &accumulate<string>; + tt = l.next (tl, &tn); + } + else if (p == "type" || + p == "id_type" || + p == "value_type" || + p == "index_type" || + p == "key_type") + { + // type ("<type>") + // id_type ("<type>") + // value_type ("<type>") + // index_type ("<type>") + // key_type ("<type>") + // + + // Make sure we've got the correct declaration type. + // + if (decl && !check_spec_decl_type (decl, decl_name, p, loc)) + return; + + if (l.next (tl, &tn) != CPP_OPEN_PAREN) + { + error (l) << "'(' expected after db pragma " << p << endl; + return; + } + + tt = l.next (tl, &tn); + + if (tt != CPP_STRING) + { + error (l) << "type name expected in db pragma " << p << endl; + return; + } + + val = tl; + + if (l.next (tl, &tn) != CPP_CLOSE_PAREN) + { + error (l) << "')' expected at the end of db pragma " << p << endl; + return; + } + + tt = l.next (tl, &tn); + } + else if (p == "null" || + p == "not_null" || + p == "key_null" || + p == "key_not_null" || + p == "value_null" || + p == "value_not_null") + { + // null + // not_null + // key_null + // key_not_null + // value_null + // value_not_null + // + + // Make sure we've got the correct declaration type. + // + if (decl && !check_spec_decl_type (decl, decl_name, p, loc)) + return; + + tt = l.next (tl, &tn); + } + else if (p == "default") + { + // default () (<empty>) + // default (null) (n) + // default (true|false) (t|f) + // default ([+|-]<number>) (-|+) + // default ("string") (s) + // default (<enumerator>) (e) + // + // + + // Make sure we've got the correct declaration type. + // + if (decl && !check_spec_decl_type (decl, decl_name, p, loc)) + return; + + if (l.next (tl, &tn) != CPP_OPEN_PAREN) + { + error (l) << "'(' expected after db pragma " << p << endl; + return; + } + + tt = l.next (tl, &tn); + + default_value dv; + + switch (tt) + { + case CPP_CLOSE_PAREN: + { + // Default value reset. + // + dv.kind = default_value::reset; + break; + } + case CPP_STRING: + { + dv.kind = default_value::string; + dv.literal = tl; + tt = l.next (tl, &tn); + break; + } + case CPP_NAME: + { + // This can be null or an enumerator name. + // + if (tl == "null") + { + dv.kind = default_value::null; + tt = l.next (tl, &tn); + break; + } + } + // Fall through. + case CPP_SCOPE: + { + // We have a potentially scopped enumerator name. + // + dv.enum_value = resolve_scoped_name ( + l, tt, tl, tn, current_scope (), dv.literal, false, p); + + if (dv.enum_value == 0) + return; // Diagnostics has already been issued. + + dv.kind = default_value::enumerator; + break; + } + case CPP_MINUS: + case CPP_PLUS: + { + if (tt == CPP_MINUS) + dv.literal = "-"; + + tt = l.next (tl, &tn); + + if (tt != CPP_NUMBER) + { + error (l) << "expected numeric constant after '" + << (tt == CPP_MINUS ? "-" : "+") << "' in db pragma " + << p << endl; + return; + } + } + // Fall through. + case CPP_NUMBER: + { + switch (TREE_CODE (tn)) + { + case INTEGER_CST: + { + dv.int_value = integer_value (tn); + dv.kind = default_value::integer; + break; + } + case REAL_CST: + { + REAL_VALUE_TYPE d (TREE_REAL_CST (tn)); + + if (REAL_VALUE_ISINF (d)) + dv.float_value = numeric_limits<double>::infinity (); + else if (REAL_VALUE_ISNAN (d)) + dv.float_value = numeric_limits<double>::quiet_NaN (); + else + { + char tmp[256]; + real_to_decimal (tmp, &d, sizeof (tmp), 0, true); + istringstream is (tmp); + is >> dv.float_value; + } + + if (dv.literal == "-") + dv.float_value = -dv.float_value; + + dv.kind = default_value::floating; + break; + } + default: + { + error (l) << "unexpected numeric constant in db pragma " << p + << endl; + return; + } + } + + tt = l.next (tl, &tn); + break; + } + default: + { + // This can be the true or false keyword. + // + if (tt == CPP_KEYWORD && (tl == "true" || tl == "false")) + { + dv.kind = default_value::boolean; + dv.literal = tl; + tt = l.next (tl, &tn); + } + else + { + error (l) << "unexpected expression in db pragma " << p << endl; + return; + } + + break; + } + } + + if (tt != CPP_CLOSE_PAREN) + { + error (l) << "')' expected at the end of db pragma " << p << endl; + return; + } + + val = dv; + tt = l.next (tl, &tn); + } + else if (p == "inverse") + { + // inverse (name) + // + + // Make sure we've got the correct declaration type. + // + if (decl && !check_spec_decl_type (decl, decl_name, p, loc)) + return; + + if (l.next (tl, &tn) != CPP_OPEN_PAREN) + { + error (l) << "'(' expected after db pragma " << p << endl; + return; + } + + tt = l.next (tl, &tn); + + if (tt != CPP_NAME) + { + error (l) << "member name expected in db pragma " << p << endl; + return; + } + + string name (tl); + + tt = l.next (tl, &tn); + + // Parse nested members if any. + // + for (; tt == CPP_DOT; tt = l.next (tl, &tn)) + { + if (l.next (tl, &tn) != CPP_NAME) + { + error (l) << "name expected after '.' in db pragma " << p << endl; + return; + } + + name += '.'; + name += tl; + } + + if (tt != CPP_CLOSE_PAREN) + { + error (l) << "')' expected at the end of db pragma " << p << endl; + return; + } + + val = name; + tt = l.next (tl, &tn); + } + else if (p == "on_delete") + { + // on_delete (cascade|set_null) + // + + // Make sure we've got the correct declaration type. + // + if (decl && !check_spec_decl_type (decl, decl_name, p, loc)) + return; + + if (l.next (tl, &tn) != CPP_OPEN_PAREN) + { + error (l) << "'(' expected after db pragma " << p << endl; + return; + } + + if (l.next (tl, &tn) != CPP_NAME || (tl != "cascade" && tl != "set_null")) + { + error (l) << "cascade or set_null expected after '('" << endl; + return; + } + + using semantics::relational::foreign_key; + val = (tl == "cascade" ? foreign_key::cascade : foreign_key::set_null); + + if (l.next (tl, &tn) != CPP_CLOSE_PAREN) + { + error (l) << "')' expected at the end of db pragma " << p << endl; + return; + } + + tt = l.next (tl, &tn); + } + else if (p == "points_to") + { + // points_to(<fq-name>) + // + + tree type; + string type_name; + + string p (tl); + location_t loc (l.location ()); + + if (l.next (tl, &tn) != CPP_OPEN_PAREN) + { + error (l) << "'(' expected after db pragma " << p << endl; + return; + } + + tt = l.next (tl, &tn); + + if (tt == CPP_NAME || tt == CPP_SCOPE) + { + type = resolve_scoped_name ( + l, tt, tl, tn, current_scope (), type_name, true, p); + + if (type == 0) + return; // Diagnostics has already been issued. + + // Get the actual type if this is a TYPE_DECL. Also get the main + // variant. + // + if (TREE_CODE (type) == TYPE_DECL) + type = TREE_TYPE (type); + + if (TYPE_P (type)) // Can be a template. + type = TYPE_MAIN_VARIANT (type); + + if (TREE_CODE (type) != RECORD_TYPE) + { + error (loc) << "name '" << type_name << "' in db pragma " << p + << " does not refer to a class" << endl; + return; + } + + val = type; + } + else + { + error (l) << "class name expected in db pragma " << p << endl; + return; + } + + if (tt != CPP_CLOSE_PAREN) + { + error (l) << "')' expected at the end of db pragma " << p << endl; + return; + } + + tt = l.next (tl, &tn); + } + else if (p == "section") + { + // section (name) + // + + // Make sure we've got the correct declaration type. + // + if (decl && !check_spec_decl_type (decl, decl_name, p, loc)) + return; + + if (l.next (tl, &tn) != CPP_OPEN_PAREN) + { + error (l) << "'(' expected after db pragma " << p << endl; + return; + } + + tt = l.next (tl, &tn); + + if (tt != CPP_NAME) + { + error (l) << "member name expected in db pragma " << p << endl; + return; + } + + name = "section-member"; + val = tl; + + if (l.next (tl, &tn) != CPP_CLOSE_PAREN) + { + error (l) << "')' expected at the end of db pragma " << p << endl; + return; + } + + tt = l.next (tl, &tn); + } + else if (p == "load") + { + // load (eager|lazy) + // + + // Make sure we've got the correct declaration type. + // + if (decl && !check_spec_decl_type (decl, decl_name, p, loc)) + return; + + if (l.next (tl, &tn) != CPP_OPEN_PAREN) + { + error (l) << "'(' expected after db pragma " << p << endl; + return; + } + + tt = l.next (tl, &tn); + + if (tt != CPP_NAME || (tl != "eager" && tl != "lazy")) + { + error (l) << "eager or lazy expected in db pragma " << p << endl; + return; + } + + name = "section-load"; + val = (tl == "eager" + ? user_section::load_eager + : user_section::load_lazy); + + if (l.next (tl, &tn) != CPP_CLOSE_PAREN) + { + error (l) << "')' expected at the end of db pragma " << p << endl; + return; + } + + tt = l.next (tl, &tn); + } + else if (p == "update") + { + // update (always|change|manual) + // + + // Make sure we've got the correct declaration type. + // + if (decl && !check_spec_decl_type (decl, decl_name, p, loc)) + return; + + if (l.next (tl, &tn) != CPP_OPEN_PAREN) + { + error (l) << "'(' expected after db pragma " << p << endl; + return; + } + + tt = l.next (tl, &tn); + + if (tt != CPP_NAME || + (tl != "always" && tl != "change" && tl != "manual")) + { + error (l) << "always, change, or manual expected in db pragma " << + p << endl; + return; + } + + name = "section-update"; + + if (tl == "always") + val = user_section::update_always; + else if (tl == "change") + val = user_section::update_change; + else + val = user_section::update_manual; + + if (l.next (tl, &tn) != CPP_CLOSE_PAREN) + { + error (l) << "')' expected at the end of db pragma " << p << endl; + return; + } + + tt = l.next (tl, &tn); + } + else if (p == "unordered") + { + // unordered + // + + // Make sure we've got the correct declaration type. + // + if (decl && !check_spec_decl_type (decl, decl_name, p, loc)) + return; + + tt = l.next (tl, &tn); + } + else if (p == "readonly") + { + // readonly + // + + // Make sure we've got the correct declaration type. + // + if (decl && !check_spec_decl_type (decl, decl_name, p, loc)) + return; + + tt = l.next (tl, &tn); + } + else if (p == "transient") + { + // transient + // + + // Make sure we've got the correct declaration type. + // + if (decl && !check_spec_decl_type (decl, decl_name, p, loc)) + return; + + tt = l.next (tl, &tn); + } + else if (p == "added" || p == "deleted") + { + // added (unsigned long long version) + // deleted (unsigned long long version) + // + + // Make sure we've got the correct declaration type. + // + if (decl && !check_spec_decl_type (decl, decl_name, p, loc)) + return; + + char const* n (p == "added" ? "addition" : "deletion"); + + if (l.next (tl, &tn) != CPP_OPEN_PAREN) + { + error (l) << "'(' expected after db pragma " << p << endl; + return; + } + + if (l.next (tl, &tn) != CPP_NUMBER || TREE_CODE (tn) != INTEGER_CST) + { + error (l) << "unsigned integer expected as " << n << " version" << endl; + return; + } + + unsigned long long v (integer_value (tn)); + + if (v == 0) + { + error (l) << n << " version cannot be zero" << endl; + return; + } + + if (l.next (tl, &tn) != CPP_CLOSE_PAREN) + { + error (l) << "')' expected at the end of db pragma " << p << endl; + return; + } + + val = v; + tt = l.next (tl, &tn); + } + else if (p == "version") + { + // version + // + + // Make sure we've got the correct declaration type. + // + if (decl && !check_spec_decl_type (decl, decl_name, p, loc)) + return; + + tt = l.next (tl, &tn); + } + else if (p == "virtual") + { + // Stray virtual specifier (i.e., without explicit member name). + // + error (l) << "virtual member declaration requires name" << endl; + return; + } + else if (p == "before" || p == "after") + { + // Stray before/after specifier (i.e., without preceding virtual). + // + error (l) << p << " specifier must follow virtual" << endl; + return; + } + else + { + error (l) << "unknown db pragma " << p << endl; + return; + } + + // Add the pragma unless was indicated otherwise. + // + if (!name.empty () && (db.empty () || db == pragma_db_.string ())) + { + // If the value is not specified and we don't use a custom adder, + // then make it bool (flag). + // + if (adder == 0 && val.empty ()) + val = true; + + // Convert '_' to '-' in the context name so we get foo-bar instead + // of foo_bar (that's the convention used). + // + for (size_t i (0); i < name.size (); ++i) + if (name[i] == '_') + name[i] = '-'; + + // Record this pragma. + // + add_pragma ( + pragma (p, name, val, loc, &check_spec_decl_type, adder), decl, ns); + } + + // Mark the type or member as simple value or container, depending + // on the pragma. For static multi-database support we only do it + // if the pragma applies to this database since in this case we can + // have different mappings for different databases (e.g., composite + // in one and simple in another). For dynamic multi-database support + // we do this regardless of the database since here the mapping + // should the consistent. + // + // @@ Did we add new simple value or container pragmas and forgot to + // account for them here? + // + if ((qualifier == "value" || qualifier == "member") && + (pragma_multi_ == multi_database::dynamic || db.empty () || + db == pragma_db_.string ())) + { + // We assume a data member is simple only if the database type was + // specified explicitly. + // + if (name == "type" || + name == "id-type" || + (qualifier == "value" && + (name == "null" || + name == "not-null" || + name == "default" || + name == "options"))) + { + add_pragma (pragma (p, "simple", true, loc, &check_spec_decl_type, 0), + decl, + false); + } + else if (name == "table" || + name == "value-type" || + name == "index-type" || + name == "key-type" || + + name == "key-null" || + name == "key-not-null" || + name == "value-null" || + name == "value-not-null" || + + name == "value-column" || + name == "index-column" || + name == "key-column" || + name == "index-column" || + + name == "value-options" || + name == "index-options" || + name == "key-options" || + name == "index-options" || + + name == "unordered") + { + add_pragma (pragma (p, "container", true, loc, &check_spec_decl_type, 0), + decl, + false); + } + } + + // See if there are any more pragmas. + // + if (tt == CPP_NAME || tt == CPP_KEYWORD) + { + handle_pragma (l, + "", + tl, + qualifier, + qualifier_value, + decl, + decl_name, + ns); + } + else if (tt != CPP_EOF) + error (l) << "unexpected text after " << p << " in db pragma" << endl; +} + +// +// Qualifiers. +// + +static bool +check_qual_decl_type (declaration const& d, + string const& name, + string const& p, + location_t l) +{ + gcc_tree_code_type tc (d.tree_code ()); + bool type (TREE_CODE_CLASS (tc) == tcc_type); + + if (p == "model" || + p == "map") + { + assert (d == global_namespace); + } + else if (p == "index") + { + if (tc != RECORD_TYPE) + { + // For an index, name is not empty only if the class name was + // specified explicitly. Otherwise, the index definition scope + // is assumed. + // + if (name.empty ()) + { + error (l) << "db pragma " << p << " outside of a class scope" << endl; + info (l) << "use the db pragma " << p << "(<class-name>) syntax " + << " instead" << endl; + } + else + error (l) << "name '" << name << "' in db pragma " << p << " does " + << "not refer to a class" << endl; + return false; + } + } + else if (p == "namespace") + { + if (tc != NAMESPACE_DECL) + { + error (l) << "name '" << name << "' in db pragma " << p << " does " + << "not refer to a namespace" << endl; + return false; + } + } + else if (p == "object" || + p == "view") + { + if (tc != RECORD_TYPE) + { + error (l) << "name '" << name << "' in db pragma " << p << " does " + << "not refer to a class" << endl; + return false; + } + } + else if (p == "value") + { + if (!type) + { + error (l) << "name '" << name << "' in db pragma " << p << " does " + << "not refer to a type" << endl; + return false; + } + } + else if (p == "member") + { + if (tc != FIELD_DECL) + { + error (l) << "name '" << name << "' in db pragma " << p << " does " + << "not refer to a data member" << endl; + return false; + } + } + else + { + error (l) << "unknown db pragma " << p << endl; + return false; + } + + return true; +} + +static void +add_qual_entry (compiler::context& ctx, + string const& k, + any const& v, + location_t l) +{ + // Store the TYPE_DECL node that was referred to in the pragma. This + // can be used later as a name hint in case the type is a template + // instantiation. Also store the pragma location which is used as + // the "definition point" for this instantiation. + // + ctx.set ("tree-node", v); + ctx.set ("location", l); + + ctx.set (k, true); +} + +static void +handle_pragma_qualifier (cxx_lexer& l, string p) +{ + cpp_ttype tt; + string tl; + tree tn; + + declaration decl; + tree orig_decl (0); // Original declarations as used in the pragma. + string decl_name; + + // Check for a database prefix. + // + string db; + + if (p == "mysql" || + p == "sqlite" || + p == "pgsql" || + p == "oracle" || + p == "mssql") + { + tt = l.next (tl); + + if (tt == CPP_COLON) + { + // Specifier prefix. + // + db = p; + tt = l.next (p); + } + else + { + // Qualifier prefix. Ignore the rest if the databases don't match. + // + if (p != pragma_db_.string ()) + return; + + p = tl; + } + + if (tt != CPP_NAME && tt != CPP_KEYWORD) + { + error (l) << "expected specifier after db " << db << " pragma" << endl; + return; + } + + // Make sure a qualifier prefix is not used before a specifier. + // + if (!db.empty () && + (p == "model" || + p == "map" || + p == "namespace" || + p == "object" || + p == "view" || + p == "value" || + p == "member")) + { + error (l) << "specifier prefix '" << db << ":' used before qualifier " << + p << endl; + return; + } + } + + // + // + string name (p); // Pragma name. + any val; // Pragma value. + location_t loc (l.location ()); // Pragma location. + pragma::add_func adder (0); // Custom context adder. + bool ns (false); // Namespace location pragma. + + cxx_tokens saved_tokens; // Saved token sequence to be replayed. + + // Pragma qualifiers. + // + if (p == "model") + { + orig_decl = global_namespace; + decl = declaration (orig_decl); + tt = l.next (tl, &tn); + } + else if (p == "map") + { + // map type("<regex>") as("<subst>") [to("<subst>")] [from("<subst>")] + // map type(<c++-type>) as(<c++-type>) [to(<expr>)] [from(<expr>)] + // + + // The thing that determines whether this is a database or C++ type + // mapping is what we have inside 'type'. So to handle this we are + // going to pre-scan the pragma looking for 'type' and saving the + // tokens. Once we determine what this is, we replay the saved + // tokens to actually parse them. + // + + // Determine what this is by scanning the pragma until we see + // the 'type' qualifier or EOF. + // + bool db (true); + + bool done (false); + size_t balance (0); + for (tt = l.next (tl, &tn); + !(done && balance == 0) && tt != CPP_EOF; + tt = l.next (tl, &tn)) + { + saved_tokens.push_back (cxx_token (l.location (), tt, tl, tn)); + + switch (tt) + { + case CPP_OPEN_PAREN: + { + balance++; + continue; + } + case CPP_CLOSE_PAREN: + { + if (balance > 0) + balance--; + else + { + error (l) << "unbalanced parenthesis in db pragma " << p << endl; + return; + } + continue; + } + case CPP_NAME: + { + if (balance == 0 && tl == "type") + break; + + continue; + } + default: + continue; + } + + tt = l.next (tl, &tn); + saved_tokens.push_back (cxx_token (l.location (), tt, tl, tn)); + + if (tt == CPP_OPEN_PAREN) + { + balance++; + + tt = l.next (tl, &tn); + saved_tokens.push_back (cxx_token (l.location (), tt, tl, tn)); + + db = (tt == CPP_STRING); + } + + done = true; // Scan until the closing ')'. + } + + if (balance != 0) + { + error (l) << "unbalanced parenthesis in db pragma " << p << endl; + return; + } + + orig_decl = global_namespace; + decl = declaration (orig_decl); + + if (db) + { + using relational::custom_db_type; + + custom_db_type ct; + ct.loc = loc; + val = ct; + name = "custom-db-types"; + adder = &accumulate<custom_db_type>; + } + else + { + custom_cxx_type ct; + ct.loc = loc; + ct.scope = current_scope (); + val = ct; + name = "custom-cxx-types"; + adder = &accumulate<custom_cxx_type>; + } + } + else if (p == "index") + { + // Index can be both a qualifier and a specifier. Things are complicated + // by the fact that when it is a specifier, it belongs to a member which + // means that the actual qualifier ('member') can be omitted. So we need + // to distinguish between cases like these: + // + // #pragma db index type("INTEGER") // specifier + // #pragma db index type("UNIQUE") member(foo_) // qualifier + // + // The thing that determines whether this is a qualifier or a specifier + // is the presence of the 'member' or 'members' specifier. So to handle + // this we are going to pre-scan the pragma looking for 'member' or + // 'members' and saving the tokens. Once we determine what this is, + // we replay the saved tokens to actually parse them. + // + tt = l.next (tl, &tn); + + if (tt != CPP_OPEN_PAREN) + { + // Determine what this is by scanning the pragma until we see + // the 'member' qualifier or EOF. + // + bool qual (false); + size_t balance (0); + + for (; tt != CPP_EOF; tt = l.next (tl, &tn)) + { + switch (tt) + { + case CPP_OPEN_PAREN: + { + balance++; + break; + } + case CPP_CLOSE_PAREN: + { + if (balance > 0) + balance--; + else + { + error (l) << "unbalanced parenthesis in db pragma " << p << endl; + return; + } + break; + } + case CPP_NAME: + { + if (balance == 0 && (tl == "member" || tl == "members")) + qual = true; + break; + } + default: + break; + } + + if (qual) + break; + + saved_tokens.push_back (cxx_token (l.location (), tt, tl, tn)); + } + + if (balance != 0) + { + error (l) << "unbalanced parenthesis in db pragma " << p << endl; + return; + } + + if (qual) + { + // This is a qualifer. The saved tokens sequence contains tokens + // until the first 'member' or 'members' specifier. So we will + // first need to re-play these tokens and then continue parsing + // as if we just saw the 'member' or 'members' specifier. The + // token type (tt) and token literal (tl) variables should contain + // the correct values. + // + + // Also check that no specifier prefix was used for this qualifer. + // + if (!db.empty ()) + { + error (loc) << "specifier prefix '" << db << ":' used before " << + "qualifier index" << endl; + return; + } + + orig_decl = current_scope (); + decl = declaration (orig_decl); + } + else + { + // This is a specifier. The saved tokens sequence contains all the + // tokens in this pragma until EOF. + // + cxx_tokens_lexer l; + l.start (saved_tokens, loc); + handle_pragma ( + l, db, "index", "member", val, declaration (), "", false); + return; + } + } + + relational::index in; + in.loc = loc; + + if (tt == CPP_OPEN_PAREN) + { + // Specifier with the class fq-name, index name, or both. + // + // index(<fq-name>) + // index("<name>") + // index(<fq-name>::"<name>") + // + tt = l.next (tl, &tn); + + // Resolve class name, if any. + // + if (tt == CPP_NAME || tt == CPP_SCOPE) + { + cpp_ttype ptt; + orig_decl = resolve_scoped_name ( + l, tt, tl, tn, current_scope (), decl_name, true, p, true, &ptt); + + if (orig_decl == 0) + return; // Diagnostics has already been issued. + + // Get the actual type if this is a TYPE_DECL. Also get the main + // variant. + // + if (TREE_CODE (orig_decl) == TYPE_DECL) + orig_decl = TREE_TYPE (orig_decl); + + if (TYPE_P (orig_decl)) // Can be a template. + decl = declaration (TYPE_MAIN_VARIANT (orig_decl)); + else + decl = declaration (orig_decl); + + if (tt == CPP_STRING && ptt != CPP_SCOPE) + { + error (l) << "'::' expected before index name in db pragma " << p + << endl; + return; + } + + if (tt != CPP_STRING && ptt == CPP_SCOPE) + { + error (l) << "index name expected after '::' in db pragma " << p + << endl; + return; + } + } + else + { + orig_decl = current_scope (); + decl = declaration (orig_decl); + } + + // Make sure we've got the correct declaration type. + // + if (!check_qual_decl_type (decl, decl_name, p, loc)) + return; + + if (tt == CPP_STRING) + { + in.name = tl; + tt = l.next (tl, &tn); + } + + if (tt != CPP_CLOSE_PAREN) + { + error (l) << "')' expected at the end of db pragma " << p << endl; + return; + } + + tt = l.next (tl, &tn); + } + + val = in; + adder = &accumulate<relational::index>; + } + else if (p == "namespace") + { + // namespace [(<identifier>)] + // namespace () (refers to global namespace) + // + + tt = l.next (tl, &tn); + + if (tt == CPP_OPEN_PAREN) + { + tt = l.next (tl, &tn); + + if (tt == CPP_NAME || tt == CPP_SCOPE) + { + orig_decl = resolve_scoped_name ( + l, tt, tl, tn, current_scope (), decl_name, false, p); + + if (orig_decl == 0) + return; // Diagnostics has already been issued. + + // Make sure we've got the correct declaration type. + // + if (!check_qual_decl_type (orig_decl, decl_name, p, loc)) + return; + + // Resolve namespace aliases if any. + // + orig_decl = ORIGINAL_NAMESPACE (orig_decl); + decl = declaration (orig_decl); + + if (tt != CPP_CLOSE_PAREN) + { + error (l) << "')' expected at the end of db pragma " << p << endl; + return; + } + + tt = l.next (tl, &tn); + } + else if (tt == CPP_CLOSE_PAREN) + { + orig_decl = global_namespace; + decl = declaration (orig_decl); + tt = l.next (tl, &tn); + } + else + { + error (l) << "data member name expected in db pragma " << p << endl; + return; + } + } + else + { + // Make sure we are in a namespace scope. + // + if (TREE_CODE (current_scope ()) != NAMESPACE_DECL) + { + error (l) << "db pragma " << p << " is not in a namespace scope" + << endl; + return; + } + + ns = true; + } + } + else if (p == "object" || + p == "view" || + p == "value") + { + // object [(<identifier>)] + // view [(<identifier>)] + // value [(<identifier>)] + // + + tt = l.next (tl, &tn); + + if (tt == CPP_OPEN_PAREN) + { + tt = l.next (tl, &tn); + + // Can be built-in type (e.g., bool). + // + if (tt == CPP_NAME || tt == CPP_KEYWORD || tt == CPP_SCOPE) + { + orig_decl = resolve_scoped_name ( + l, tt, tl, tn, current_scope (), decl_name, true, p); + + if (orig_decl == 0) + return; // Diagnostics has already been issued. + + // Get the actual type if this is a TYPE_DECL. Also get the main + // variant. + // + if (TREE_CODE (orig_decl) == TYPE_DECL) + orig_decl = TREE_TYPE (orig_decl); + + if (TYPE_P (orig_decl)) // Can be a template. + decl = declaration (TYPE_MAIN_VARIANT (orig_decl)); + else + decl = declaration (orig_decl); + + // Make sure we've got the correct declaration type. + // + if (!check_qual_decl_type (decl, decl_name, p, loc)) + return; + + if (tt != CPP_CLOSE_PAREN) + { + error (l) << "')' expected at the end of db pragma " << p << endl; + return; + } + + tt = l.next (tl, &tn); + } + else + { + error (l) << "type name expected in db pragma " << p << endl; + return; + } + } + } + else if (p == "member") + { + // member [(<identifier>)] + // + + tt = l.next (tl, &tn); + + if (tt == CPP_OPEN_PAREN) + { + tt = l.next (tl, &tn); + + if (tt != CPP_NAME && tt != CPP_SCOPE) + { + error (l) << "data member name expected in db pragma " << p << endl; + return; + } + + // We need to see if this is a virtual data member declaration. Also, + // if it is not, then the name can still refer to one so we need to + // take extra steps to handle that. But first, we save the name and + // look for the 'virtual' specifier. + // + cxx_tokens name_tokens; + for (; tt != CPP_CLOSE_PAREN && tt != CPP_EOF; tt = l.next (tl, &tn)) + name_tokens.push_back (cxx_token (l.location (), tt, tl, tn)); + + if (tt != CPP_CLOSE_PAREN) + { + error (l) << "')' expected at the end of db pragma " << p << endl; + return; + } + + // Now scan the remainder of the pragma looking for the 'virtual' + // keyword and saving the tokens in between for later. + // + bool virt (false); + size_t balance (0); + for (tt = l.next (tl, &tn); tt != CPP_EOF; tt = l.next (tl, &tn)) + { + switch (tt) + { + case CPP_OPEN_PAREN: + { + balance++; + break; + } + case CPP_CLOSE_PAREN: + { + if (balance > 0) + balance--; + else + { + error (l) << "unbalanced parenthesis in db pragma " << p << endl; + return; + } + break; + } + default: + { + if (balance == 0 && tt == CPP_KEYWORD && tl == "virtual") + virt = true; + break; + } + } + + if (virt) + break; + + saved_tokens.push_back (cxx_token (l.location (), tt, tl, tn)); + } + + if (balance != 0) + { + error (l) << "unbalanced parenthesis in db pragma " << p << endl; + return; + } + + // Regardless of whether this is a virtual member declaration or a + // reference, resolve its scope name (if one is specified), which + // should be a class. We will need it in both cases. + // + tree scope; + if (name_tokens.size () > 2) // scope::name + { + size_t n (name_tokens.size ()); + + if (name_tokens[n - 2].type != CPP_SCOPE || + name_tokens[n - 1].type != CPP_NAME) + { + error (l) << "invalid name in db pragma " << p << endl; + return; + } + + cxx_tokens scope_tokens (1, name_tokens.back ()); + name_tokens.pop_back (); // :: + name_tokens.pop_back (); // name + name_tokens.swap (scope_tokens); + + cxx_tokens_lexer l; + l.start (scope_tokens); + + tree tn; + string tl; + cpp_ttype tt (l.next (tl)); + + scope = resolve_scoped_name ( + l, tt, tl, tn, current_scope (), decl_name, true, p); + + if (scope == 0) + return; // Diagnostics has already been issued. + + scope = TYPE_MAIN_VARIANT (TREE_TYPE (scope)); + + if (tt != CPP_EOF) + { + error (l) << "invalid name in db pragma " << p << endl; + return; + } + + decl_name += "::"; + } + else + scope = current_scope (); + + if (virt) + { + // Should be a single name. + // + if (name_tokens.size () > 1 || name_tokens.back ().type != CPP_NAME) + { + location_t l (name_tokens.back ().loc); + error (l) << "invalid name in db pragma " << p << endl; + return; + } + string const& name (name_tokens.back ().literal); + + // Parse the remainder of the virtual specifier. + // + // virtual(<fq-name>) + // + tree type; + string type_name; + location_t ord (loc); + int ord_bias (0); + { + string p (tl); + location_t loc (l.location ()); + + if (l.next (tl, &tn) != CPP_OPEN_PAREN) + { + error (l) << "'(' expected after db pragma " << p << endl; + return; + } + + tt = l.next (tl, &tn); + + // Can be built-in type (e.g., bool). + // + if (tt == CPP_NAME || tt == CPP_KEYWORD || tt == CPP_SCOPE) + { + type = resolve_scoped_name ( + l, tt, tl, tn, current_scope (), type_name, true, p); + + if (type == 0) + return; // Diagnostics has already been issued. + + if (TREE_CODE (type) != TYPE_DECL) + { + error (loc) << "name '" << type_name << "' in db pragma " + << p << " does not refer to a type" << endl; + return; + } + + type = TREE_TYPE (type); + } + else + { + error (l) << "type name expected in db pragma " << p << endl; + return; + } + + if (tt != CPP_CLOSE_PAREN) + { + error (l) << "')' expected at the end of db pragma " << p << endl; + return; + } + + tt = l.next (tl, &tn); + + // See if we have before/after specifiers. + // + if (tt == CPP_NAME && tl == "before") + { + // before[(<member>)] + // + // Before without the member name means first. + // + tt = l.next (tl, &tn); + + if (tt == CPP_OPEN_PAREN) + { + if (l.next (tl, &tn) != CPP_NAME) + { + error (l) << "')' member name expected in db pragma before" + << endl; + } + + string dn; + cxx_tokens ts (1, cxx_token (l.location (), CPP_NAME, tl)); + declaration d (resolve_data_member (scope, ts, dn, "before")); + + if (!d) + return; // Diagnostics has already been issued. + + if (d.virt) + { + ord = d.decl.virt->ord; + ord_bias = d.decl.virt->ord_bias - 1; + } + else + { + ord = real_source_location (d.decl.real); + ord_bias = -1; + } + + if (l.next (tl, &tn) != CPP_CLOSE_PAREN) + { + error (l) << "')' expected at the end of db pragma before" + << endl; + return; + } + + tt = l.next (tl, &tn); + } + else + ord = 0; + } + + if (tt == CPP_NAME && tl == "after") + { + // after[(<member>)] + // + // Before without the member name means last. + // + tt = l.next (tl, &tn); + + if (tt == CPP_OPEN_PAREN) + { + if (l.next (tl, &tn) != CPP_NAME) + { + error (l) << "')' member name expected in db pragma after" + << endl; + } + + string dn; + cxx_tokens ts (1, cxx_token (l.location (), CPP_NAME, tl)); + declaration d (resolve_data_member (scope, ts, dn, "after")); + + if (!d) + return; // Diagnostics has already been issued. + + if (d.virt) + { + ord = d.decl.virt->ord; + ord_bias = d.decl.virt->ord_bias + 1; + } + else + { + ord = real_source_location (d.decl.real); + ord_bias = 1; + } + + if (l.next (tl, &tn) != CPP_CLOSE_PAREN) + { + error (l) << "')' expected at the end of db pragma after" + << endl; + return; + } + + tt = l.next (tl, &tn); + } + else + ord = ~location_t (0); + } + } + + pair<virt_declaration_set::const_iterator, bool> r ( + virt_declarations_[scope].insert ( + virt_declaration (loc, ord, ord_bias, name, FIELD_DECL, type))); + + if (!r.second) + { + error (loc) << "virtual data member declaration '" << name + << "' conflicts with a previous declaration" << endl; + + info (r.first->loc) << "'" << name << "' was previously " + << "declared here" << endl; + return; + } + + decl_name += name; + decl = declaration (*r.first); + + // Mark it as virtual using the standard pragma machinery. + // + add_pragma ( + pragma ("virtual", "virtual", true, loc, &check_spec_decl_type, 0), + decl, + false); + } + else + { + // Not a virtual member declaration. + // + decl = resolve_data_member (scope, name_tokens, decl_name, p); + + if (!decl) + return; // Diagnostics has already been issued. + + // Make sure we've got the correct declaration type. + // + if (!check_qual_decl_type (decl, decl_name, p, loc)) + return; + } + } + } + // + // The member qualifier can be omitted so we need to also handle all + // the member pragmas here. + // + else if (p == "id" || + p == "auto" || + p == "unique" || + p == "get" || + p == "set" || + p == "access" || + p == "column" || + p == "value_column" || + p == "index_column" || + p == "key_column" || + p == "id_column" || + p == "options" || + p == "value_options" || + p == "index_options" || + p == "key_options" || + p == "id_options" || + p == "type" || + p == "id_type" || + p == "value_type" || + p == "index_type" || + p == "key_type" || + p == "table" || + p == "null" || + p == "not_null" || + p == "key_null" || + p == "key_not_null" || + p == "value_null" || + p == "value_not_null" || + p == "default" || + p == "section" || + p == "load" || + p == "update" || + p == "inverse" || + p == "on_delete" || + p == "points_to" || + p == "unordered" || + p == "readonly" || + p == "transient" || + p == "added" || + p == "deleted" || + p == "version" || + p == "virtual") + { + handle_pragma (l, db, p, "member", val, declaration (), "", false); + return; + } + else + { + error (l) << "unknown db pragma " << p << endl; + return; + } + + // Record this pragma. Delay this until after we process the + // specifiers for value (see comment below for the reason). + // + if (adder == 0) + val = orig_decl; + + pragma prag (p, + name, // For now no need to translate '_' to '-'. + val, + loc, + &check_qual_decl_type, + adder != 0 ? adder : &add_qual_entry); + + tree scope; + if (!decl) + { + scope = current_scope (); + + if (!ns && !CLASS_TYPE_P (scope)) + scope = global_namespace; + } + + any* pval; + if (p != "value") + { + if (decl) + pval = &decl_pragmas_[decl].insert (prag).value; + else + { + if (!ns) + { + pragma_list& pl (loc_pragmas_[scope]); + pl.push_back (prag); + pval = &pl.back ().value; + } + else + { + ns_loc_pragmas_.push_back (ns_loc_pragma (scope, prag)); + pval = &ns_loc_pragmas_.back ().pragma.value; + } + } + } + else + pval = &val; + + // See if there are any saved tokens to replay. + // + if (!saved_tokens.empty ()) + { + cxx_tokens_lexer l; + l.start (saved_tokens); + + string tl; + cpp_ttype tt (l.next (tl)); + + if (tt == CPP_NAME || tt == CPP_KEYWORD) + { + handle_pragma (l, "", tl, p, *pval, decl, decl_name, ns); + + if (errorcount != 0) // Avoid parsing the rest if there was an error. + return; + } + else if (tt != CPP_EOF) + { + error (l) << "unexpected text after " << p << " in db pragma" << endl; + return; + } + } + + size_t count (0); + if (tt == CPP_NAME || tt == CPP_KEYWORD) + { + if (decl) + count = decl_pragmas_[decl].size (); + else + count = loc_pragmas_[scope].size (); + + handle_pragma (l, "", tl, p, *pval, decl, decl_name, ns); + } + else if (tt != CPP_EOF) + { + error (l) << "unexpected text after " << p << " in db pragma" << endl; + return; + } + + // Record the value pragma. Here things are complicated by the fact + // that we use the value pragma by itself to signify a composite value + // type declaration. Consider this example: + // + // #pragma db value pgsql:type("POINT") + // class point {...}; + // + // Should this class be considered composite value type in other + // databases (because that's what would happen by default)? Probably + // not. So to overcome this we are going to detect and ignore cases + // where (a) some specifiers followed the value qualifier but (b) + // none of them are for the database that we are compiling for. + // + if (p == "value") + { + if (decl) + { + pragma_set& ps (decl_pragmas_[decl]); + + if (tt == CPP_EOF || ps.size () > count) + ps.insert (prag); + } + else + { + pragma_list& pl (loc_pragmas_[scope]); + + if (tt == CPP_EOF || pl.size () > count) + pl.push_back (prag); + } + } +} + +/* +extern "C" void +handle_pragma_db_mysql (cpp_reader* r) +{ + handle_pragma_qualifier (r, "mysql"); +} + +extern "C" void +handle_pragma_db_sqlite (cpp_reader* r) +{ + handle_pragma_qualifier (r, "sqlite"); +} + +extern "C" void +handle_pragma_db_pgsql (cpp_reader* r) +{ + handle_pragma_qualifier (r, "pgsql"); +} + +extern "C" void +handle_pragma_db_oracle (cpp_reader* r) +{ + handle_pragma_qualifier (r, "oracle"); +} + +extern "C" void +handle_pragma_db_mssql (cpp_reader* r) +{ + handle_pragma_qualifier (r, "mssql"); +} + +extern "C" void +handle_pragma_db_model (cpp_reader* r) +{ + handle_pragma_qualifier (r, "model"); +} + +extern "C" void +handle_pragma_db_map (cpp_reader* r) +{ + handle_pragma_qualifier (r, "map"); +} + +extern "C" void +handle_pragma_db_namespace (cpp_reader* r) +{ + handle_pragma_qualifier (r, "namespace"); +} + +extern "C" void +handle_pragma_db_object (cpp_reader* r) +{ + handle_pragma_qualifier (r, "object"); +} + +extern "C" void +handle_pragma_db_view (cpp_reader* r) +{ + handle_pragma_qualifier (r, "view"); +} + +extern "C" void +handle_pragma_db_value (cpp_reader* r) +{ + handle_pragma_qualifier (r, "value"); +} + +extern "C" void +handle_pragma_db_member (cpp_reader* r) +{ + handle_pragma_qualifier (r, "member"); +} + +extern "C" void +handle_pragma_db_id (cpp_reader* r) +{ + handle_pragma_qualifier (r, "id"); +} + +extern "C" void +handle_pragma_db_auto (cpp_reader* r) +{ + handle_pragma_qualifier (r, "auto"); +} + +extern "C" void +handle_pragma_db_column (cpp_reader* r) +{ + handle_pragma_qualifier (r, "column"); +} + +extern "C" void +handle_pragma_db_vcolumn (cpp_reader* r) +{ + handle_pragma_qualifier (r, "value_column"); +} + +extern "C" void +handle_pragma_db_icolumn (cpp_reader* r) +{ + handle_pragma_qualifier (r, "index_column"); +} + +extern "C" void +handle_pragma_db_kcolumn (cpp_reader* r) +{ + handle_pragma_qualifier (r, "key_column"); +} + +extern "C" void +handle_pragma_db_idcolumn (cpp_reader* r) +{ + handle_pragma_qualifier (r, "id_column"); +} + +extern "C" void +handle_pragma_db_options (cpp_reader* r) +{ + handle_pragma_qualifier (r, "options"); +} + +extern "C" void +handle_pragma_db_voptions (cpp_reader* r) +{ + handle_pragma_qualifier (r, "value_options"); +} + +extern "C" void +handle_pragma_db_ioptions (cpp_reader* r) +{ + handle_pragma_qualifier (r, "index_options"); +} + +extern "C" void +handle_pragma_db_koptions (cpp_reader* r) +{ + handle_pragma_qualifier (r, "key_options"); +} + +extern "C" void +handle_pragma_db_idoptions (cpp_reader* r) +{ + handle_pragma_qualifier (r, "id_options"); +} + +extern "C" void +handle_pragma_db_type (cpp_reader* r) +{ + handle_pragma_qualifier (r, "type"); +} + +extern "C" void +handle_pragma_db_id_type (cpp_reader* r) +{ + handle_pragma_qualifier (r, "id_type"); +} + +extern "C" void +handle_pragma_db_vtype (cpp_reader* r) +{ + handle_pragma_qualifier (r, "value_type"); +} + +extern "C" void +handle_pragma_db_itype (cpp_reader* r) +{ + handle_pragma_qualifier (r, "index_type"); +} + +extern "C" void +handle_pragma_db_ktype (cpp_reader* r) +{ + handle_pragma_qualifier (r, "key_type"); +} + +extern "C" void +handle_pragma_db_table (cpp_reader* r) +{ + handle_pragma_qualifier (r, "table"); +} + +extern "C" void +handle_pragma_db_null (cpp_reader* r) +{ + handle_pragma_qualifier (r, "null"); +} + +extern "C" void +handle_pragma_db_not_null (cpp_reader* r) +{ + handle_pragma_qualifier (r, "not_null"); +} + +extern "C" void +handle_pragma_db_value_null (cpp_reader* r) +{ + handle_pragma_qualifier (r, "value_null"); +} + +extern "C" void +handle_pragma_db_value_not_null (cpp_reader* r) +{ + handle_pragma_qualifier (r, "value_not_null"); +} + +extern "C" void +handle_pragma_db_key_null (cpp_reader* r) +{ + handle_pragma_qualifier (r, "key_null"); +} + +extern "C" void +handle_pragma_db_key_not_null (cpp_reader* r) +{ + handle_pragma_qualifier (r, "key_not_null"); +} + +extern "C" void +handle_pragma_db_default (cpp_reader* r) +{ + handle_pragma_qualifier (r, "default"); +} + +extern "C" void +handle_pragma_db_section (cpp_reader* r) +{ + handle_pragma_qualifier (r, "section"); +} + +extern "C" void +handle_pragma_db_load (cpp_reader* r) +{ + handle_pragma_qualifier (r, "load"); +} + +extern "C" void +handle_pragma_db_update (cpp_reader* r) +{ + handle_pragma_qualifier (r, "update"); +} + +extern "C" void +handle_pragma_db_inverse (cpp_reader* r) +{ + handle_pragma_qualifier (r, "inverse"); +} + +extern "C" void +handle_pragma_db_on_delete (cpp_reader* r) +{ + handle_pragma_qualifier (r, "on_delete"); +} + +extern "C" void +handle_pragma_db_points_to (cpp_reader* r) +{ + handle_pragma_qualifier (r, "points_to"); +} + +extern "C" void +handle_pragma_db_unordered (cpp_reader* r) +{ + handle_pragma_qualifier (r, "unordered"); +} + +extern "C" void +handle_pragma_db_readonly (cpp_reader* r) +{ + handle_pragma_qualifier (r, "readonly"); +} + +extern "C" void +handle_pragma_db_transient (cpp_reader* r) +{ + handle_pragma_qualifier (r, "transient"); +} + +extern "C" void +handle_pragma_db_added (cpp_reader* r) +{ + handle_pragma_qualifier (r, "added"); +} + +extern "C" void +handle_pragma_db_deleted (cpp_reader* r) +{ + handle_pragma_qualifier (r, "deleted"); +} + +extern "C" void +handle_pragma_db_version (cpp_reader* r) +{ + handle_pragma_qualifier (r, "version"); +} + +extern "C" void +handle_pragma_db_virtual (cpp_reader* r) +{ + handle_pragma_qualifier (r, "virtual"); +} +*/ + +extern "C" void +handle_pragma_db (cpp_reader*) +{ + cxx_pragma_lexer l; + l.start (); + + string tl; + cpp_ttype tt (l.next (tl)); + + if (tt != CPP_NAME && tt != CPP_KEYWORD) + { + error (l) << "expected specifier after db pragma" << endl; + return; + } + + handle_pragma_qualifier (l, tl); +} + +extern "C" void +register_odb_pragmas (void*, void*) +{ + // GCC has a limited number of pragma slots and we have exhausted them. + // A workaround is to make 'db' a pragma rather than a namespace. This + // way we only have one pragma but the drawback of this approach is the + // fact that the specifier or qualifier name will now be macro-expanded + // (though this happens anyway if we have multiple specifiers in a single + // pragma). Once the GCC folks fix this, we can go back to the namespace + // approach. + // + c_register_pragma_with_expansion (0, "db", handle_pragma_db); + + /* + c_register_pragma_with_expansion ("db", "mysql", handle_pragma_db_mysql); + c_register_pragma_with_expansion ("db", "sqlite", handle_pragma_db_sqlite); + c_register_pragma_with_expansion ("db", "pgsql", handle_pragma_db_pgsql); + c_register_pragma_with_expansion ("db", "oracle", handle_pragma_db_oracle); + c_register_pragma_with_expansion ("db", "mssql", handle_pragma_db_mssql); + c_register_pragma_with_expansion ("db", "model", handle_pragma_db_model); + c_register_pragma_with_expansion ("db", "map", handle_pragma_db_map); + c_register_pragma_with_expansion ("db", "namespace", handle_pragma_db_namespace); + c_register_pragma_with_expansion ("db", "object", handle_pragma_db_object); + c_register_pragma_with_expansion ("db", "view", handle_pragma_db_view); + c_register_pragma_with_expansion ("db", "value", handle_pragma_db_value); + c_register_pragma_with_expansion ("db", "member", handle_pragma_db_member); + c_register_pragma_with_expansion ("db", "id", handle_pragma_db_id); + c_register_pragma_with_expansion ("db", "auto", handle_pragma_db_auto); + c_register_pragma_with_expansion ("db", "column", handle_pragma_db_column); + c_register_pragma_with_expansion ("db", "value_column", handle_pragma_db_vcolumn); + c_register_pragma_with_expansion ("db", "index_column", handle_pragma_db_icolumn); + c_register_pragma_with_expansion ("db", "key_column", handle_pragma_db_kcolumn); + c_register_pragma_with_expansion ("db", "id_column", handle_pragma_db_idcolumn); + c_register_pragma_with_expansion ("db", "options", handle_pragma_db_options); + c_register_pragma_with_expansion ("db", "value_options", handle_pragma_db_voptions); + c_register_pragma_with_expansion ("db", "index_options", handle_pragma_db_ioptions); + c_register_pragma_with_expansion ("db", "key_options", handle_pragma_db_koptions); + c_register_pragma_with_expansion ("db", "id_options", handle_pragma_db_idoptions); + c_register_pragma_with_expansion ("db", "type", handle_pragma_db_type); + c_register_pragma_with_expansion ("db", "id_type", handle_pragma_db_id_type); + c_register_pragma_with_expansion ("db", "value_type", handle_pragma_db_vtype); + c_register_pragma_with_expansion ("db", "index_type", handle_pragma_db_itype); + c_register_pragma_with_expansion ("db", "key_type", handle_pragma_db_ktype); + c_register_pragma_with_expansion ("db", "table", handle_pragma_db_table); + c_register_pragma_with_expansion ("db", "null", handle_pragma_db_null); + c_register_pragma_with_expansion ("db", "not_null", handle_pragma_db_not_null); + c_register_pragma_with_expansion ("db", "key_null", handle_pragma_db_key_null); + c_register_pragma_with_expansion ("db", "key_not_null", handle_pragma_db_key_not_null); + c_register_pragma_with_expansion ("db", "value_null", handle_pragma_db_value_null); + c_register_pragma_with_expansion ("db", "value_not_null", handle_pragma_db_value_not_null); + c_register_pragma_with_expansion ("db", "default", handle_pragma_db_default); + c_register_pragma_with_expansion ("db", "section", handle_pragma_db_section); + c_register_pragma_with_expansion ("db", "load", handle_pragma_db_load); + c_register_pragma_with_expansion ("db", "update", handle_pragma_db_update); + c_register_pragma_with_expansion ("db", "inverse", handle_pragma_db_inverse); + c_register_pragma_with_expansion ("db", "on_delete", handle_pragma_db_on_delete); + c_register_pragma_with_expansion ("db", "points_to", handle_pragma_db_points_to); + c_register_pragma_with_expansion ("db", "unordered", handle_pragma_db_unordered); + c_register_pragma_with_expansion ("db", "readonly", handle_pragma_db_readonly); + c_register_pragma_with_expansion ("db", "transient", handle_pragma_db_transient); + c_register_pragma_with_expansion ("db", "added", handle_pragma_db_added); + c_register_pragma_with_expansion ("db", "deleted", handle_pragma_db_deleted); + c_register_pragma_with_expansion ("db", "version", handle_pragma_db_version); + c_register_pragma_with_expansion ("db", "virtual", handle_pragma_db_virtual); + */ +} + +void +post_process_pragmas () +{ + // Make sure object, view, and composite class template instantiations + // are fully instantiated. + // + for (decl_pragmas::iterator i (decl_pragmas_.begin ()), + e (decl_pragmas_.end ()); i != e; ++i) + { + if (i->first.virt) + continue; + + tree type (i->first.decl.real); + + if (!(CLASS_TYPE_P (type) && CLASSTYPE_TEMPLATE_INSTANTIATION (type))) + continue; + + // Check whether this is an object, view, or composite value type. + // + pragma const* p (0); + string diag_name; + + for (pragma_set::iterator j (i->second.begin ()), e (i->second.end ()); + j != e; ++j) + { + string const& name (j->second.context_name); + + if (name == "object") + { + p = &j->second; + diag_name = "persistent object"; + break; + } + else if (name == "view") + { + p = &j->second; + diag_name = "view"; + break; + } + else if (name == "value") + { + p = &j->second; + diag_name = "composite value"; + break; + } + // We don't want to instantiate simple values since they may be + // incomplete. + // + else if (name == "simple" || name == "container") + { + p = 0; + break; + } + } + + if (p == 0) + continue; + + // Make sure it is instantiated. + // + tree decl (TYPE_NAME (p->value.value<tree> ())); + location_t loc (real_source_location (decl)); + + // Reset input location so that we get nice diagnostics in case + // of an error. + // + input_location = loc; + + if (instantiate_class_template (type) == error_mark_node || + errorcount != 0 || + !COMPLETE_TYPE_P (type)) + { + error (loc) << "unable to instantiate " << diag_name << " class template" + << endl; + throw pragmas_failed (); + } + } +} diff --git a/odb/odb/pragma.hxx b/odb/odb/pragma.hxx new file mode 100644 index 0000000..0d4d3f1 --- /dev/null +++ b/odb/odb/pragma.hxx @@ -0,0 +1,287 @@ +// file : odb/pragma.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_PRAGMA_HXX +#define ODB_PRAGMA_HXX + +#include <odb/gcc.hxx> + +#include <map> +#include <set> +#include <vector> +#include <string> + +#include <odb/option-types.hxx> // database + +#include <libcutl/container/any.hxx> +#include <libcutl/container/multi-index.hxx> +#include <libcutl/compiler/context.hxx> + +struct virt_declaration +{ + virt_declaration (location_t l, + location_t o, + int ob, + std::string const& n, + gcc_tree_code_type tc, + tree t) + : loc (l), ord (o), ord_bias (ob), name (n), tree_code (tc), type (t) {} + + location_t loc; + location_t ord; // Ordering location for before/after support. + int ord_bias; // Ordering bias for the same locations. + std::string name; + gcc_tree_code_type tree_code; + tree type; // Declaration's type. +}; + +// Note that we consider virtual declarations with the same name but +// different tree codes unequal. If that is too loose, then the +// inserting code must do additional checks. +// +struct virt_declaration_set +{ + typedef cutl::container::key<std::string, gcc_tree_code_type> key; + typedef std::map<key, virt_declaration> map; + typedef cutl::container::map_const_iterator<map> const_iterator; + + std::pair<const_iterator, bool> + insert (const virt_declaration& v) + { + std::pair<map::iterator, bool> r ( + map_.insert (map::value_type (key (v.name, v.tree_code), v))); + + const_iterator i (r.first); + + if (r.second) + r.first->first.assign (i->name, i->tree_code); + + return std::make_pair (i, r.second); + } + + const_iterator + find (std::string const& name, gcc_tree_code_type tree_code) const + { + return map_.find (key (name, tree_code)); + } + + const_iterator begin () const {return map_.begin ();} + const_iterator end () const {return map_.end ();} + +private: + map map_; +}; + +// Map of scopes (e.g., class, namespace) to sets of virtual declarations. +// +typedef std::map<tree, virt_declaration_set> virt_declarations; +extern virt_declarations virt_declarations_; + +// Real or virtual declaration. If it is real, then it is a pointer to +// the GCC tree node. Otherwise, it is a pointer to virt_declaration +// from virt_declarations_ above. +// +struct declaration +{ + declaration (): virt (false) {decl.real = 0;} + declaration (tree d): virt (false) {decl.real = d;} + declaration (virt_declaration const& d): virt (true) {decl.virt = &d;} + + bool virt; + + union + { + tree real; + virt_declaration const* virt; + } decl; + + gcc_tree_code_type + tree_code () const + { + return (virt ? decl.virt->tree_code : TREE_CODE (decl.real)); + } + + typedef bool declaration::*bool_convertible; + operator bool_convertible () const + { + return ptr () == 0 ? 0 : &declaration::virt; + } + +public: + bool + operator== (declaration const& x) const + { + return virt == x.virt && ptr () == x.ptr (); + } + + bool + operator!= (declaration const& x) const + { + return !(*this == x); + } + + bool + operator< (declaration const& x) const + { + return virt < x.virt || (virt == x.virt && ptr () < x.ptr ()); + } + +public: + void const* + ptr () const + { + return virt + ? static_cast<void const*> (decl.virt) + : static_cast<void const*> (decl.real); + } +}; + +inline bool +operator== (declaration const& x, tree y) +{ + return !x.virt && x.decl.real == y; +} + +inline bool +operator== (tree x, declaration const& y) +{ + return !y.virt && y.decl.real == x; +} + +struct pragma +{ + // Check that the pragma is applicable to the declaration. Return true + // on success, complain and return false otherwise. + // + typedef bool (*check_func) (declaration const& decl, + std::string const& decl_name, + std::string const& prag_name, + location_t); + + // Add the pragma value to the context. + // + typedef void (*add_func) (cutl::compiler::context&, + std::string const& key, + cutl::container::any const& value, + location_t); + + pragma (std::string const& pn, + std::string const& cn, + cutl::container::any const& v, + location_t l, + check_func c, + add_func a) + : pragma_name (pn), + context_name (cn), + value (v), + loc (l), + check (c), + add (a) + { + } + + std::string pragma_name; // Actual pragma name for diagnostics. + std::string context_name; // Context entry name. + cutl::container::any value; + location_t loc; + check_func check; + add_func add; +}; + +typedef std::vector<pragma> pragma_list; + +// A set of pragmas. Insertion of a pragma with the same name and no +// custom add function overrides the old value. +// +struct pragma_set: std::multimap<std::string, pragma> +{ + typedef std::multimap<std::string, pragma> base; + + pragma& + insert (pragma const& p) + { + std::string const& n (p.context_name); + std::pair<iterator, iterator> r (equal_range (n)); + + iterator i (end ()); + + if (p.add == 0) + { + if (r.first != r.second) + { + i = r.first; + assert (++r.first == r.second); + + i->second = p; + } + } + else if (r.first != r.second) + assert ((--r.second)->second.loc <= p.loc); + + if (i == end ()) + i = base::insert (base::value_type (n, p)); + + return i->second; + } + + void + insert (const_iterator begin, const_iterator end) + { + for (; begin != end; ++begin) + insert (begin->second); + } + + // Return the last pragma in the equal range which (by construction) has the + // location greater or equal to all the other pragmas in this range. + // + iterator + find (std::string const& n) + { + return equal_range (n).second; + } +}; + + +// Position pragmas inside a class or namespace. The key for the +// namespace case is the global_namespace node. +// +typedef std::map<tree, pragma_list> loc_pragmas; +extern loc_pragmas loc_pragmas_; + +// Position pragmas for namespaces. Because re-opened namespaces do +// not have any representation in the GCC tree, these are handled in +// a special way. They are stored as a list of pragmas and their outer +// namespaces. +// +struct ns_loc_pragma +{ + typedef ::pragma pragma_type; + ns_loc_pragma (tree n, pragma_type const& p): ns (n), pragma (p) {} + + tree ns; + pragma_type pragma; +}; + +typedef std::vector<ns_loc_pragma> ns_loc_pragmas; +extern ns_loc_pragmas ns_loc_pragmas_; + +// Pragmas associated with specific declarations (real or virtual). +// +typedef std::map<declaration, pragma_set> decl_pragmas; +extern decl_pragmas decl_pragmas_; + +// Database we are generating code for as well as multi-database support. +// Used to ignore database-specific pragmas. +// +extern database pragma_db_; +extern multi_database pragma_multi_; + +extern "C" void +register_odb_pragmas (void*, void*); + +struct pragmas_failed {}; + +void +post_process_pragmas (); + +#endif // ODB_PRAGMA_HXX diff --git a/odb/odb/pregenerated/odb/options.cxx b/odb/odb/pregenerated/odb/options.cxx new file mode 100644 index 0000000..da22570 --- /dev/null +++ b/odb/odb/pregenerated/odb/options.cxx @@ -0,0 +1,4034 @@ +// -*- C++ -*- +// +// This file was generated by CLI, a command line interface +// compiler for C++. +// + +// Begin prologue. +// +#include <odb/option-parsers.hxx> +// +// End prologue. + +#include <odb/options.hxx> + +#include <map> +#include <set> +#include <string> +#include <vector> +#include <utility> +#include <ostream> +#include <sstream> +#include <cstring> +#include <fstream> + +namespace cli +{ + // unknown_option + // + unknown_option:: + ~unknown_option () throw () + { + } + + void unknown_option:: + print (::std::ostream& os) const + { + os << "unknown option '" << option ().c_str () << "'"; + } + + const char* unknown_option:: + what () const throw () + { + return "unknown option"; + } + + // unknown_argument + // + unknown_argument:: + ~unknown_argument () throw () + { + } + + void unknown_argument:: + print (::std::ostream& os) const + { + os << "unknown argument '" << argument ().c_str () << "'"; + } + + const char* unknown_argument:: + what () const throw () + { + return "unknown argument"; + } + + // missing_value + // + missing_value:: + ~missing_value () throw () + { + } + + void missing_value:: + print (::std::ostream& os) const + { + os << "missing value for option '" << option ().c_str () << "'"; + } + + const char* missing_value:: + what () const throw () + { + return "missing option value"; + } + + // invalid_value + // + invalid_value:: + ~invalid_value () throw () + { + } + + void invalid_value:: + print (::std::ostream& os) const + { + os << "invalid value '" << value ().c_str () << "' for option '" + << option ().c_str () << "'"; + + if (!message ().empty ()) + os << ": " << message ().c_str (); + } + + const char* invalid_value:: + what () const throw () + { + return "invalid option value"; + } + + // eos_reached + // + void eos_reached:: + print (::std::ostream& os) const + { + os << what (); + } + + const char* eos_reached:: + what () const throw () + { + return "end of argument stream reached"; + } + + // file_io_failure + // + file_io_failure:: + ~file_io_failure () throw () + { + } + + void file_io_failure:: + print (::std::ostream& os) const + { + os << "unable to open file '" << file ().c_str () << "' or read failure"; + } + + const char* file_io_failure:: + what () const throw () + { + return "unable to open file or read failure"; + } + + // unmatched_quote + // + unmatched_quote:: + ~unmatched_quote () throw () + { + } + + void unmatched_quote:: + print (::std::ostream& os) const + { + os << "unmatched quote in argument '" << argument ().c_str () << "'"; + } + + const char* unmatched_quote:: + what () const throw () + { + return "unmatched quote"; + } + + // scanner + // + scanner:: + ~scanner () + { + } + + // argv_scanner + // + bool argv_scanner:: + more () + { + return i_ < argc_; + } + + const char* argv_scanner:: + peek () + { + if (i_ < argc_) + return argv_[i_]; + else + throw eos_reached (); + } + + const char* argv_scanner:: + next () + { + if (i_ < argc_) + { + const char* r (argv_[i_]); + + if (erase_) + { + for (int i (i_ + 1); i < argc_; ++i) + argv_[i - 1] = argv_[i]; + + --argc_; + argv_[argc_] = 0; + } + else + ++i_; + + ++start_position_; + return r; + } + else + throw eos_reached (); + } + + void argv_scanner:: + skip () + { + if (i_ < argc_) + { + ++i_; + ++start_position_; + } + else + throw eos_reached (); + } + + std::size_t argv_scanner:: + position () + { + return start_position_; + } + + // argv_file_scanner + // + int argv_file_scanner::zero_argc_ = 0; + std::string argv_file_scanner::empty_string_; + + bool argv_file_scanner:: + more () + { + if (!args_.empty ()) + return true; + + while (base::more ()) + { + // See if the next argument is the file option. + // + const char* a (base::peek ()); + const option_info* oi = 0; + const char* ov = 0; + + if (!skip_) + { + if ((oi = find (a)) != 0) + { + base::next (); + + if (!base::more ()) + throw missing_value (a); + + ov = base::next (); + } + else if (std::strncmp (a, "-", 1) == 0) + { + if ((ov = std::strchr (a, '=')) != 0) + { + std::string o (a, 0, ov - a); + if ((oi = find (o.c_str ())) != 0) + { + base::next (); + ++ov; + } + } + } + } + + if (oi != 0) + { + if (oi->search_func != 0) + { + std::string f (oi->search_func (ov, oi->arg)); + + if (!f.empty ()) + load (f); + } + else + load (ov); + + if (!args_.empty ()) + return true; + } + else + { + if (!skip_) + skip_ = (std::strcmp (a, "--") == 0); + + return true; + } + } + + return false; + } + + const char* argv_file_scanner:: + peek () + { + if (!more ()) + throw eos_reached (); + + return args_.empty () ? base::peek () : args_.front ().value.c_str (); + } + + const std::string& argv_file_scanner:: + peek_file () + { + if (!more ()) + throw eos_reached (); + + return args_.empty () ? empty_string_ : *args_.front ().file; + } + + std::size_t argv_file_scanner:: + peek_line () + { + if (!more ()) + throw eos_reached (); + + return args_.empty () ? 0 : args_.front ().line; + } + + const char* argv_file_scanner:: + next () + { + if (!more ()) + throw eos_reached (); + + if (args_.empty ()) + return base::next (); + else + { + hold_[i_ == 0 ? ++i_ : --i_].swap (args_.front ().value); + args_.pop_front (); + ++start_position_; + return hold_[i_].c_str (); + } + } + + void argv_file_scanner:: + skip () + { + if (!more ()) + throw eos_reached (); + + if (args_.empty ()) + return base::skip (); + else + { + args_.pop_front (); + ++start_position_; + } + } + + const argv_file_scanner::option_info* argv_file_scanner:: + find (const char* a) const + { + for (std::size_t i (0); i < options_count_; ++i) + if (std::strcmp (a, options_[i].option) == 0) + return &options_[i]; + + return 0; + } + + std::size_t argv_file_scanner:: + position () + { + return start_position_; + } + + void argv_file_scanner:: + load (const std::string& file) + { + using namespace std; + + ifstream is (file.c_str ()); + + if (!is.is_open ()) + throw file_io_failure (file); + + files_.push_back (file); + + arg a; + a.file = &*files_.rbegin (); + + for (a.line = 1; !is.eof (); ++a.line) + { + string line; + getline (is, line); + + if (is.fail () && !is.eof ()) + throw file_io_failure (file); + + string::size_type n (line.size ()); + + // Trim the line from leading and trailing whitespaces. + // + if (n != 0) + { + const char* f (line.c_str ()); + const char* l (f + n); + + const char* of (f); + while (f < l && (*f == ' ' || *f == '\t' || *f == '\r')) + ++f; + + --l; + + const char* ol (l); + while (l > f && (*l == ' ' || *l == '\t' || *l == '\r')) + --l; + + if (f != of || l != ol) + line = f <= l ? string (f, l - f + 1) : string (); + } + + // Ignore empty lines, those that start with #. + // + if (line.empty () || line[0] == '#') + continue; + + string::size_type p (string::npos); + if (line.compare (0, 1, "-") == 0) + { + p = line.find (' '); + + string::size_type q (line.find ('=')); + if (q != string::npos && q < p) + p = q; + } + + string s1; + if (p != string::npos) + { + s1.assign (line, 0, p); + + // Skip leading whitespaces in the argument. + // + if (line[p] == '=') + ++p; + else + { + n = line.size (); + for (++p; p < n; ++p) + { + char c (line[p]); + if (c != ' ' && c != '\t' && c != '\r') + break; + } + } + } + else if (!skip_) + skip_ = (line == "--"); + + string s2 (line, p != string::npos ? p : 0); + + // If the string (which is an option value or argument) is + // wrapped in quotes, remove them. + // + n = s2.size (); + char cf (s2[0]), cl (s2[n - 1]); + + if (cf == '"' || cf == '\'' || cl == '"' || cl == '\'') + { + if (n == 1 || cf != cl) + throw unmatched_quote (s2); + + s2 = string (s2, 1, n - 2); + } + + if (!s1.empty ()) + { + // See if this is another file option. + // + const option_info* oi; + if (!skip_ && (oi = find (s1.c_str ()))) + { + if (s2.empty ()) + throw missing_value (oi->option); + + if (oi->search_func != 0) + { + string f (oi->search_func (s2.c_str (), oi->arg)); + if (!f.empty ()) + load (f); + } + else + { + // If the path of the file being parsed is not simple and the + // path of the file that needs to be loaded is relative, then + // complete the latter using the former as a base. + // +#ifndef _WIN32 + string::size_type p (file.find_last_of ('/')); + bool c (p != string::npos && s2[0] != '/'); +#else + string::size_type p (file.find_last_of ("/\\")); + bool c (p != string::npos && s2[1] != ':'); +#endif + if (c) + s2.insert (0, file, 0, p + 1); + + load (s2); + } + + continue; + } + + a.value = s1; + args_.push_back (a); + } + + a.value = s2; + args_.push_back (a); + } + } + + void options:: + push_back (const option& o) + { + container_type::size_type n (size ()); + container_type::push_back (o); + map_[o.name ()] = n; + + for (option_names::const_iterator i (o.aliases ().begin ()); + i != o.aliases ().end (); ++i) + map_[*i] = n; + } + + template <typename X> + struct parser + { + static void + parse (X& x, bool& xs, scanner& s) + { + using namespace std; + + const char* o (s.next ()); + if (s.more ()) + { + string v (s.next ()); + istringstream is (v); + if (!(is >> x && is.peek () == istringstream::traits_type::eof ())) + throw invalid_value (o, v); + } + else + throw missing_value (o); + + xs = true; + } + }; + + template <> + struct parser<bool> + { + static void + parse (bool& x, bool& xs, scanner& s) + { + const char* o (s.next ()); + + if (s.more ()) + { + const char* v (s.next ()); + + if (std::strcmp (v, "1") == 0 || + std::strcmp (v, "true") == 0 || + std::strcmp (v, "TRUE") == 0 || + std::strcmp (v, "True") == 0) + x = true; + else if (std::strcmp (v, "0") == 0 || + std::strcmp (v, "false") == 0 || + std::strcmp (v, "FALSE") == 0 || + std::strcmp (v, "False") == 0) + x = false; + else + throw invalid_value (o, v); + } + else + throw missing_value (o); + + xs = true; + } + }; + + template <> + struct parser<std::string> + { + static void + parse (std::string& x, bool& xs, scanner& s) + { + const char* o (s.next ()); + + if (s.more ()) + x = s.next (); + else + throw missing_value (o); + + xs = true; + } + }; + + template <typename X> + struct parser<std::pair<X, std::size_t> > + { + static void + parse (std::pair<X, std::size_t>& x, bool& xs, scanner& s) + { + x.second = s.position (); + parser<X>::parse (x.first, xs, s); + } + }; + + template <typename X> + struct parser<std::vector<X> > + { + static void + parse (std::vector<X>& c, bool& xs, scanner& s) + { + X x; + bool dummy; + parser<X>::parse (x, dummy, s); + c.push_back (x); + xs = true; + } + }; + + template <typename X, typename C> + struct parser<std::set<X, C> > + { + static void + parse (std::set<X, C>& c, bool& xs, scanner& s) + { + X x; + bool dummy; + parser<X>::parse (x, dummy, s); + c.insert (x); + xs = true; + } + }; + + template <typename K, typename V, typename C> + struct parser<std::map<K, V, C> > + { + static void + parse (std::map<K, V, C>& m, bool& xs, scanner& s) + { + const char* o (s.next ()); + + if (s.more ()) + { + std::size_t pos (s.position ()); + std::string ov (s.next ()); + std::string::size_type p = ov.find ('='); + + K k = K (); + V v = V (); + std::string kstr (ov, 0, p); + std::string vstr (ov, (p != std::string::npos ? p + 1 : ov.size ())); + + int ac (2); + char* av[] = + { + const_cast<char*> (o), + 0 + }; + + bool dummy; + if (!kstr.empty ()) + { + av[1] = const_cast<char*> (kstr.c_str ()); + argv_scanner s (0, ac, av, false, pos); + parser<K>::parse (k, dummy, s); + } + + if (!vstr.empty ()) + { + av[1] = const_cast<char*> (vstr.c_str ()); + argv_scanner s (0, ac, av, false, pos); + parser<V>::parse (v, dummy, s); + } + + m[k] = v; + } + else + throw missing_value (o); + + xs = true; + } + }; + + template <typename K, typename V, typename C> + struct parser<std::multimap<K, V, C> > + { + static void + parse (std::multimap<K, V, C>& m, bool& xs, scanner& s) + { + const char* o (s.next ()); + + if (s.more ()) + { + std::size_t pos (s.position ()); + std::string ov (s.next ()); + std::string::size_type p = ov.find ('='); + + K k = K (); + V v = V (); + std::string kstr (ov, 0, p); + std::string vstr (ov, (p != std::string::npos ? p + 1 : ov.size ())); + + int ac (2); + char* av[] = + { + const_cast<char*> (o), + 0 + }; + + bool dummy; + if (!kstr.empty ()) + { + av[1] = const_cast<char*> (kstr.c_str ()); + argv_scanner s (0, ac, av, false, pos); + parser<K>::parse (k, dummy, s); + } + + if (!vstr.empty ()) + { + av[1] = const_cast<char*> (vstr.c_str ()); + argv_scanner s (0, ac, av, false, pos); + parser<V>::parse (v, dummy, s); + } + + m.insert (typename std::multimap<K, V, C>::value_type (k, v)); + } + else + throw missing_value (o); + + xs = true; + } + }; + + template <typename X, typename T, T X::*M> + void + thunk (X& x, scanner& s) + { + parser<T>::parse (x.*M, s); + } + + template <typename X, bool X::*M> + void + thunk (X& x, scanner& s) + { + s.next (); + x.*M = true; + } + + template <typename X, typename T, T X::*M, bool X::*S> + void + thunk (X& x, scanner& s) + { + parser<T>::parse (x.*M, x.*S, s); + } +} + +#include <map> + +// options +// + +options:: +options () +: build2_metadata_ (), + build2_metadata_specified_ (false), + help_ (), + version_ (), + I_ (), + I_specified_ (false), + D_ (), + D_specified_ (false), + U_ (), + U_specified_ (false), + database_ (), + database_specified_ (false), + multi_database_ (::multi_database::disabled), + multi_database_specified_ (false), + default_database_ (), + default_database_specified_ (false), + generate_query_ (), + generate_prepared_ (), + omit_unprepared_ (), + generate_session_ (), + generate_schema_ (), + generate_schema_only_ (), + suppress_migration_ (), + suppress_schema_version_ (), + schema_version_table_ (), + schema_version_table_specified_ (false), + schema_format_ (), + schema_format_specified_ (false), + omit_drop_ (), + omit_create_ (), + schema_name_ (), + schema_name_specified_ (false), + fkeys_deferrable_mode_ (), + fkeys_deferrable_mode_specified_ (false), + default_pointer_ ("*"), + default_pointer_specified_ (false), + session_type_ ("odb::session"), + session_type_specified_ (false), + profile_ (), + profile_specified_ (false), + at_once_ (), + schema_ (), + schema_specified_ (false), + export_symbol_ (), + export_symbol_specified_ (false), + extern_symbol_ (), + extern_symbol_specified_ (false), + std_ (cxx_version::cxx98), + std_specified_ (false), + warn_hard_add_ (), + warn_hard_delete_ (), + warn_hard_ (), + output_dir_ (), + output_dir_specified_ (false), + input_name_ (), + input_name_specified_ (false), + changelog_ (), + changelog_specified_ (false), + changelog_in_ (), + changelog_in_specified_ (false), + changelog_out_ (), + changelog_out_specified_ (false), + changelog_dir_ (), + changelog_dir_specified_ (false), + init_changelog_ (), + odb_file_suffix_ (), + odb_file_suffix_specified_ (false), + sql_file_suffix_ (), + sql_file_suffix_specified_ (false), + schema_file_suffix_ (), + schema_file_suffix_specified_ (false), + changelog_file_suffix_ (), + changelog_file_suffix_specified_ (false), + hxx_suffix_ (".hxx"), + hxx_suffix_specified_ (false), + ixx_suffix_ (".ixx"), + ixx_suffix_specified_ (false), + cxx_suffix_ (".cxx"), + cxx_suffix_specified_ (false), + sql_suffix_ (".sql"), + sql_suffix_specified_ (false), + changelog_suffix_ (".xml"), + changelog_suffix_specified_ (false), + hxx_prologue_ (), + hxx_prologue_specified_ (false), + ixx_prologue_ (), + ixx_prologue_specified_ (false), + cxx_prologue_ (), + cxx_prologue_specified_ (false), + schema_prologue_ (), + schema_prologue_specified_ (false), + sql_prologue_ (), + sql_prologue_specified_ (false), + migration_prologue_ (), + migration_prologue_specified_ (false), + sql_interlude_ (), + sql_interlude_specified_ (false), + hxx_epilogue_ (), + hxx_epilogue_specified_ (false), + ixx_epilogue_ (), + ixx_epilogue_specified_ (false), + cxx_epilogue_ (), + cxx_epilogue_specified_ (false), + schema_epilogue_ (), + schema_epilogue_specified_ (false), + sql_epilogue_ (), + sql_epilogue_specified_ (false), + migration_epilogue_ (), + migration_epilogue_specified_ (false), + hxx_prologue_file_ (), + hxx_prologue_file_specified_ (false), + ixx_prologue_file_ (), + ixx_prologue_file_specified_ (false), + cxx_prologue_file_ (), + cxx_prologue_file_specified_ (false), + schema_prologue_file_ (), + schema_prologue_file_specified_ (false), + sql_prologue_file_ (), + sql_prologue_file_specified_ (false), + migration_prologue_file_ (), + migration_prologue_file_specified_ (false), + sql_interlude_file_ (), + sql_interlude_file_specified_ (false), + hxx_epilogue_file_ (), + hxx_epilogue_file_specified_ (false), + ixx_epilogue_file_ (), + ixx_epilogue_file_specified_ (false), + cxx_epilogue_file_ (), + cxx_epilogue_file_specified_ (false), + schema_epilogue_file_ (), + schema_epilogue_file_specified_ (false), + sql_epilogue_file_ (), + sql_epilogue_file_specified_ (false), + migration_epilogue_file_ (), + migration_epilogue_file_specified_ (false), + odb_prologue_ (), + odb_prologue_specified_ (false), + odb_prologue_file_ (), + odb_prologue_file_specified_ (false), + odb_epilogue_ (), + odb_epilogue_specified_ (false), + odb_epilogue_file_ (), + odb_epilogue_file_specified_ (false), + table_prefix_ (), + table_prefix_specified_ (false), + index_suffix_ (), + index_suffix_specified_ (false), + fkey_suffix_ (), + fkey_suffix_specified_ (false), + sequence_suffix_ (), + sequence_suffix_specified_ (false), + sql_name_case_ (), + sql_name_case_specified_ (false), + table_regex_ (), + table_regex_specified_ (false), + column_regex_ (), + column_regex_specified_ (false), + index_regex_ (), + index_regex_specified_ (false), + fkey_regex_ (), + fkey_regex_specified_ (false), + sequence_regex_ (), + sequence_regex_specified_ (false), + statement_regex_ (), + statement_regex_specified_ (false), + sql_name_regex_ (), + sql_name_regex_specified_ (false), + sql_name_regex_trace_ (), + accessor_regex_ (), + accessor_regex_specified_ (false), + accessor_regex_trace_ (), + modifier_regex_ (), + modifier_regex_specified_ (false), + modifier_regex_trace_ (), + include_with_brackets_ (), + include_prefix_ (), + include_prefix_specified_ (false), + include_regex_ (), + include_regex_specified_ (false), + include_regex_trace_ (), + guard_prefix_ (), + guard_prefix_specified_ (false), + show_sloc_ (), + sloc_limit_ (), + sloc_limit_specified_ (false), + options_file_ (), + options_file_specified_ (false), + x_ (), + x_specified_ (false), + v_ (), + trace_ (), + mysql_engine_ ("InnoDB"), + mysql_engine_specified_ (false), + sqlite_override_null_ (), + sqlite_lax_auto_id_ (), + pgsql_server_version_ (7, 4), + pgsql_server_version_specified_ (false), + oracle_client_version_ (10, 1), + oracle_client_version_specified_ (false), + oracle_warn_truncation_ (), + mssql_server_version_ (10, 0), + mssql_server_version_specified_ (false), + mssql_short_limit_ (1024), + mssql_short_limit_specified_ (false) +{ +} + +options:: +options (int& argc, + char** argv, + bool erase, + ::cli::unknown_mode opt, + ::cli::unknown_mode arg) +: build2_metadata_ (), + build2_metadata_specified_ (false), + help_ (), + version_ (), + I_ (), + I_specified_ (false), + D_ (), + D_specified_ (false), + U_ (), + U_specified_ (false), + database_ (), + database_specified_ (false), + multi_database_ (::multi_database::disabled), + multi_database_specified_ (false), + default_database_ (), + default_database_specified_ (false), + generate_query_ (), + generate_prepared_ (), + omit_unprepared_ (), + generate_session_ (), + generate_schema_ (), + generate_schema_only_ (), + suppress_migration_ (), + suppress_schema_version_ (), + schema_version_table_ (), + schema_version_table_specified_ (false), + schema_format_ (), + schema_format_specified_ (false), + omit_drop_ (), + omit_create_ (), + schema_name_ (), + schema_name_specified_ (false), + fkeys_deferrable_mode_ (), + fkeys_deferrable_mode_specified_ (false), + default_pointer_ ("*"), + default_pointer_specified_ (false), + session_type_ ("odb::session"), + session_type_specified_ (false), + profile_ (), + profile_specified_ (false), + at_once_ (), + schema_ (), + schema_specified_ (false), + export_symbol_ (), + export_symbol_specified_ (false), + extern_symbol_ (), + extern_symbol_specified_ (false), + std_ (cxx_version::cxx98), + std_specified_ (false), + warn_hard_add_ (), + warn_hard_delete_ (), + warn_hard_ (), + output_dir_ (), + output_dir_specified_ (false), + input_name_ (), + input_name_specified_ (false), + changelog_ (), + changelog_specified_ (false), + changelog_in_ (), + changelog_in_specified_ (false), + changelog_out_ (), + changelog_out_specified_ (false), + changelog_dir_ (), + changelog_dir_specified_ (false), + init_changelog_ (), + odb_file_suffix_ (), + odb_file_suffix_specified_ (false), + sql_file_suffix_ (), + sql_file_suffix_specified_ (false), + schema_file_suffix_ (), + schema_file_suffix_specified_ (false), + changelog_file_suffix_ (), + changelog_file_suffix_specified_ (false), + hxx_suffix_ (".hxx"), + hxx_suffix_specified_ (false), + ixx_suffix_ (".ixx"), + ixx_suffix_specified_ (false), + cxx_suffix_ (".cxx"), + cxx_suffix_specified_ (false), + sql_suffix_ (".sql"), + sql_suffix_specified_ (false), + changelog_suffix_ (".xml"), + changelog_suffix_specified_ (false), + hxx_prologue_ (), + hxx_prologue_specified_ (false), + ixx_prologue_ (), + ixx_prologue_specified_ (false), + cxx_prologue_ (), + cxx_prologue_specified_ (false), + schema_prologue_ (), + schema_prologue_specified_ (false), + sql_prologue_ (), + sql_prologue_specified_ (false), + migration_prologue_ (), + migration_prologue_specified_ (false), + sql_interlude_ (), + sql_interlude_specified_ (false), + hxx_epilogue_ (), + hxx_epilogue_specified_ (false), + ixx_epilogue_ (), + ixx_epilogue_specified_ (false), + cxx_epilogue_ (), + cxx_epilogue_specified_ (false), + schema_epilogue_ (), + schema_epilogue_specified_ (false), + sql_epilogue_ (), + sql_epilogue_specified_ (false), + migration_epilogue_ (), + migration_epilogue_specified_ (false), + hxx_prologue_file_ (), + hxx_prologue_file_specified_ (false), + ixx_prologue_file_ (), + ixx_prologue_file_specified_ (false), + cxx_prologue_file_ (), + cxx_prologue_file_specified_ (false), + schema_prologue_file_ (), + schema_prologue_file_specified_ (false), + sql_prologue_file_ (), + sql_prologue_file_specified_ (false), + migration_prologue_file_ (), + migration_prologue_file_specified_ (false), + sql_interlude_file_ (), + sql_interlude_file_specified_ (false), + hxx_epilogue_file_ (), + hxx_epilogue_file_specified_ (false), + ixx_epilogue_file_ (), + ixx_epilogue_file_specified_ (false), + cxx_epilogue_file_ (), + cxx_epilogue_file_specified_ (false), + schema_epilogue_file_ (), + schema_epilogue_file_specified_ (false), + sql_epilogue_file_ (), + sql_epilogue_file_specified_ (false), + migration_epilogue_file_ (), + migration_epilogue_file_specified_ (false), + odb_prologue_ (), + odb_prologue_specified_ (false), + odb_prologue_file_ (), + odb_prologue_file_specified_ (false), + odb_epilogue_ (), + odb_epilogue_specified_ (false), + odb_epilogue_file_ (), + odb_epilogue_file_specified_ (false), + table_prefix_ (), + table_prefix_specified_ (false), + index_suffix_ (), + index_suffix_specified_ (false), + fkey_suffix_ (), + fkey_suffix_specified_ (false), + sequence_suffix_ (), + sequence_suffix_specified_ (false), + sql_name_case_ (), + sql_name_case_specified_ (false), + table_regex_ (), + table_regex_specified_ (false), + column_regex_ (), + column_regex_specified_ (false), + index_regex_ (), + index_regex_specified_ (false), + fkey_regex_ (), + fkey_regex_specified_ (false), + sequence_regex_ (), + sequence_regex_specified_ (false), + statement_regex_ (), + statement_regex_specified_ (false), + sql_name_regex_ (), + sql_name_regex_specified_ (false), + sql_name_regex_trace_ (), + accessor_regex_ (), + accessor_regex_specified_ (false), + accessor_regex_trace_ (), + modifier_regex_ (), + modifier_regex_specified_ (false), + modifier_regex_trace_ (), + include_with_brackets_ (), + include_prefix_ (), + include_prefix_specified_ (false), + include_regex_ (), + include_regex_specified_ (false), + include_regex_trace_ (), + guard_prefix_ (), + guard_prefix_specified_ (false), + show_sloc_ (), + sloc_limit_ (), + sloc_limit_specified_ (false), + options_file_ (), + options_file_specified_ (false), + x_ (), + x_specified_ (false), + v_ (), + trace_ (), + mysql_engine_ ("InnoDB"), + mysql_engine_specified_ (false), + sqlite_override_null_ (), + sqlite_lax_auto_id_ (), + pgsql_server_version_ (7, 4), + pgsql_server_version_specified_ (false), + oracle_client_version_ (10, 1), + oracle_client_version_specified_ (false), + oracle_warn_truncation_ (), + mssql_server_version_ (10, 0), + mssql_server_version_specified_ (false), + mssql_short_limit_ (1024), + mssql_short_limit_specified_ (false) +{ + ::cli::argv_scanner s (argc, argv, erase); + _parse (s, opt, arg); +} + +options:: +options (int start, + int& argc, + char** argv, + bool erase, + ::cli::unknown_mode opt, + ::cli::unknown_mode arg) +: build2_metadata_ (), + build2_metadata_specified_ (false), + help_ (), + version_ (), + I_ (), + I_specified_ (false), + D_ (), + D_specified_ (false), + U_ (), + U_specified_ (false), + database_ (), + database_specified_ (false), + multi_database_ (::multi_database::disabled), + multi_database_specified_ (false), + default_database_ (), + default_database_specified_ (false), + generate_query_ (), + generate_prepared_ (), + omit_unprepared_ (), + generate_session_ (), + generate_schema_ (), + generate_schema_only_ (), + suppress_migration_ (), + suppress_schema_version_ (), + schema_version_table_ (), + schema_version_table_specified_ (false), + schema_format_ (), + schema_format_specified_ (false), + omit_drop_ (), + omit_create_ (), + schema_name_ (), + schema_name_specified_ (false), + fkeys_deferrable_mode_ (), + fkeys_deferrable_mode_specified_ (false), + default_pointer_ ("*"), + default_pointer_specified_ (false), + session_type_ ("odb::session"), + session_type_specified_ (false), + profile_ (), + profile_specified_ (false), + at_once_ (), + schema_ (), + schema_specified_ (false), + export_symbol_ (), + export_symbol_specified_ (false), + extern_symbol_ (), + extern_symbol_specified_ (false), + std_ (cxx_version::cxx98), + std_specified_ (false), + warn_hard_add_ (), + warn_hard_delete_ (), + warn_hard_ (), + output_dir_ (), + output_dir_specified_ (false), + input_name_ (), + input_name_specified_ (false), + changelog_ (), + changelog_specified_ (false), + changelog_in_ (), + changelog_in_specified_ (false), + changelog_out_ (), + changelog_out_specified_ (false), + changelog_dir_ (), + changelog_dir_specified_ (false), + init_changelog_ (), + odb_file_suffix_ (), + odb_file_suffix_specified_ (false), + sql_file_suffix_ (), + sql_file_suffix_specified_ (false), + schema_file_suffix_ (), + schema_file_suffix_specified_ (false), + changelog_file_suffix_ (), + changelog_file_suffix_specified_ (false), + hxx_suffix_ (".hxx"), + hxx_suffix_specified_ (false), + ixx_suffix_ (".ixx"), + ixx_suffix_specified_ (false), + cxx_suffix_ (".cxx"), + cxx_suffix_specified_ (false), + sql_suffix_ (".sql"), + sql_suffix_specified_ (false), + changelog_suffix_ (".xml"), + changelog_suffix_specified_ (false), + hxx_prologue_ (), + hxx_prologue_specified_ (false), + ixx_prologue_ (), + ixx_prologue_specified_ (false), + cxx_prologue_ (), + cxx_prologue_specified_ (false), + schema_prologue_ (), + schema_prologue_specified_ (false), + sql_prologue_ (), + sql_prologue_specified_ (false), + migration_prologue_ (), + migration_prologue_specified_ (false), + sql_interlude_ (), + sql_interlude_specified_ (false), + hxx_epilogue_ (), + hxx_epilogue_specified_ (false), + ixx_epilogue_ (), + ixx_epilogue_specified_ (false), + cxx_epilogue_ (), + cxx_epilogue_specified_ (false), + schema_epilogue_ (), + schema_epilogue_specified_ (false), + sql_epilogue_ (), + sql_epilogue_specified_ (false), + migration_epilogue_ (), + migration_epilogue_specified_ (false), + hxx_prologue_file_ (), + hxx_prologue_file_specified_ (false), + ixx_prologue_file_ (), + ixx_prologue_file_specified_ (false), + cxx_prologue_file_ (), + cxx_prologue_file_specified_ (false), + schema_prologue_file_ (), + schema_prologue_file_specified_ (false), + sql_prologue_file_ (), + sql_prologue_file_specified_ (false), + migration_prologue_file_ (), + migration_prologue_file_specified_ (false), + sql_interlude_file_ (), + sql_interlude_file_specified_ (false), + hxx_epilogue_file_ (), + hxx_epilogue_file_specified_ (false), + ixx_epilogue_file_ (), + ixx_epilogue_file_specified_ (false), + cxx_epilogue_file_ (), + cxx_epilogue_file_specified_ (false), + schema_epilogue_file_ (), + schema_epilogue_file_specified_ (false), + sql_epilogue_file_ (), + sql_epilogue_file_specified_ (false), + migration_epilogue_file_ (), + migration_epilogue_file_specified_ (false), + odb_prologue_ (), + odb_prologue_specified_ (false), + odb_prologue_file_ (), + odb_prologue_file_specified_ (false), + odb_epilogue_ (), + odb_epilogue_specified_ (false), + odb_epilogue_file_ (), + odb_epilogue_file_specified_ (false), + table_prefix_ (), + table_prefix_specified_ (false), + index_suffix_ (), + index_suffix_specified_ (false), + fkey_suffix_ (), + fkey_suffix_specified_ (false), + sequence_suffix_ (), + sequence_suffix_specified_ (false), + sql_name_case_ (), + sql_name_case_specified_ (false), + table_regex_ (), + table_regex_specified_ (false), + column_regex_ (), + column_regex_specified_ (false), + index_regex_ (), + index_regex_specified_ (false), + fkey_regex_ (), + fkey_regex_specified_ (false), + sequence_regex_ (), + sequence_regex_specified_ (false), + statement_regex_ (), + statement_regex_specified_ (false), + sql_name_regex_ (), + sql_name_regex_specified_ (false), + sql_name_regex_trace_ (), + accessor_regex_ (), + accessor_regex_specified_ (false), + accessor_regex_trace_ (), + modifier_regex_ (), + modifier_regex_specified_ (false), + modifier_regex_trace_ (), + include_with_brackets_ (), + include_prefix_ (), + include_prefix_specified_ (false), + include_regex_ (), + include_regex_specified_ (false), + include_regex_trace_ (), + guard_prefix_ (), + guard_prefix_specified_ (false), + show_sloc_ (), + sloc_limit_ (), + sloc_limit_specified_ (false), + options_file_ (), + options_file_specified_ (false), + x_ (), + x_specified_ (false), + v_ (), + trace_ (), + mysql_engine_ ("InnoDB"), + mysql_engine_specified_ (false), + sqlite_override_null_ (), + sqlite_lax_auto_id_ (), + pgsql_server_version_ (7, 4), + pgsql_server_version_specified_ (false), + oracle_client_version_ (10, 1), + oracle_client_version_specified_ (false), + oracle_warn_truncation_ (), + mssql_server_version_ (10, 0), + mssql_server_version_specified_ (false), + mssql_short_limit_ (1024), + mssql_short_limit_specified_ (false) +{ + ::cli::argv_scanner s (start, argc, argv, erase); + _parse (s, opt, arg); +} + +options:: +options (int& argc, + char** argv, + int& end, + bool erase, + ::cli::unknown_mode opt, + ::cli::unknown_mode arg) +: build2_metadata_ (), + build2_metadata_specified_ (false), + help_ (), + version_ (), + I_ (), + I_specified_ (false), + D_ (), + D_specified_ (false), + U_ (), + U_specified_ (false), + database_ (), + database_specified_ (false), + multi_database_ (::multi_database::disabled), + multi_database_specified_ (false), + default_database_ (), + default_database_specified_ (false), + generate_query_ (), + generate_prepared_ (), + omit_unprepared_ (), + generate_session_ (), + generate_schema_ (), + generate_schema_only_ (), + suppress_migration_ (), + suppress_schema_version_ (), + schema_version_table_ (), + schema_version_table_specified_ (false), + schema_format_ (), + schema_format_specified_ (false), + omit_drop_ (), + omit_create_ (), + schema_name_ (), + schema_name_specified_ (false), + fkeys_deferrable_mode_ (), + fkeys_deferrable_mode_specified_ (false), + default_pointer_ ("*"), + default_pointer_specified_ (false), + session_type_ ("odb::session"), + session_type_specified_ (false), + profile_ (), + profile_specified_ (false), + at_once_ (), + schema_ (), + schema_specified_ (false), + export_symbol_ (), + export_symbol_specified_ (false), + extern_symbol_ (), + extern_symbol_specified_ (false), + std_ (cxx_version::cxx98), + std_specified_ (false), + warn_hard_add_ (), + warn_hard_delete_ (), + warn_hard_ (), + output_dir_ (), + output_dir_specified_ (false), + input_name_ (), + input_name_specified_ (false), + changelog_ (), + changelog_specified_ (false), + changelog_in_ (), + changelog_in_specified_ (false), + changelog_out_ (), + changelog_out_specified_ (false), + changelog_dir_ (), + changelog_dir_specified_ (false), + init_changelog_ (), + odb_file_suffix_ (), + odb_file_suffix_specified_ (false), + sql_file_suffix_ (), + sql_file_suffix_specified_ (false), + schema_file_suffix_ (), + schema_file_suffix_specified_ (false), + changelog_file_suffix_ (), + changelog_file_suffix_specified_ (false), + hxx_suffix_ (".hxx"), + hxx_suffix_specified_ (false), + ixx_suffix_ (".ixx"), + ixx_suffix_specified_ (false), + cxx_suffix_ (".cxx"), + cxx_suffix_specified_ (false), + sql_suffix_ (".sql"), + sql_suffix_specified_ (false), + changelog_suffix_ (".xml"), + changelog_suffix_specified_ (false), + hxx_prologue_ (), + hxx_prologue_specified_ (false), + ixx_prologue_ (), + ixx_prologue_specified_ (false), + cxx_prologue_ (), + cxx_prologue_specified_ (false), + schema_prologue_ (), + schema_prologue_specified_ (false), + sql_prologue_ (), + sql_prologue_specified_ (false), + migration_prologue_ (), + migration_prologue_specified_ (false), + sql_interlude_ (), + sql_interlude_specified_ (false), + hxx_epilogue_ (), + hxx_epilogue_specified_ (false), + ixx_epilogue_ (), + ixx_epilogue_specified_ (false), + cxx_epilogue_ (), + cxx_epilogue_specified_ (false), + schema_epilogue_ (), + schema_epilogue_specified_ (false), + sql_epilogue_ (), + sql_epilogue_specified_ (false), + migration_epilogue_ (), + migration_epilogue_specified_ (false), + hxx_prologue_file_ (), + hxx_prologue_file_specified_ (false), + ixx_prologue_file_ (), + ixx_prologue_file_specified_ (false), + cxx_prologue_file_ (), + cxx_prologue_file_specified_ (false), + schema_prologue_file_ (), + schema_prologue_file_specified_ (false), + sql_prologue_file_ (), + sql_prologue_file_specified_ (false), + migration_prologue_file_ (), + migration_prologue_file_specified_ (false), + sql_interlude_file_ (), + sql_interlude_file_specified_ (false), + hxx_epilogue_file_ (), + hxx_epilogue_file_specified_ (false), + ixx_epilogue_file_ (), + ixx_epilogue_file_specified_ (false), + cxx_epilogue_file_ (), + cxx_epilogue_file_specified_ (false), + schema_epilogue_file_ (), + schema_epilogue_file_specified_ (false), + sql_epilogue_file_ (), + sql_epilogue_file_specified_ (false), + migration_epilogue_file_ (), + migration_epilogue_file_specified_ (false), + odb_prologue_ (), + odb_prologue_specified_ (false), + odb_prologue_file_ (), + odb_prologue_file_specified_ (false), + odb_epilogue_ (), + odb_epilogue_specified_ (false), + odb_epilogue_file_ (), + odb_epilogue_file_specified_ (false), + table_prefix_ (), + table_prefix_specified_ (false), + index_suffix_ (), + index_suffix_specified_ (false), + fkey_suffix_ (), + fkey_suffix_specified_ (false), + sequence_suffix_ (), + sequence_suffix_specified_ (false), + sql_name_case_ (), + sql_name_case_specified_ (false), + table_regex_ (), + table_regex_specified_ (false), + column_regex_ (), + column_regex_specified_ (false), + index_regex_ (), + index_regex_specified_ (false), + fkey_regex_ (), + fkey_regex_specified_ (false), + sequence_regex_ (), + sequence_regex_specified_ (false), + statement_regex_ (), + statement_regex_specified_ (false), + sql_name_regex_ (), + sql_name_regex_specified_ (false), + sql_name_regex_trace_ (), + accessor_regex_ (), + accessor_regex_specified_ (false), + accessor_regex_trace_ (), + modifier_regex_ (), + modifier_regex_specified_ (false), + modifier_regex_trace_ (), + include_with_brackets_ (), + include_prefix_ (), + include_prefix_specified_ (false), + include_regex_ (), + include_regex_specified_ (false), + include_regex_trace_ (), + guard_prefix_ (), + guard_prefix_specified_ (false), + show_sloc_ (), + sloc_limit_ (), + sloc_limit_specified_ (false), + options_file_ (), + options_file_specified_ (false), + x_ (), + x_specified_ (false), + v_ (), + trace_ (), + mysql_engine_ ("InnoDB"), + mysql_engine_specified_ (false), + sqlite_override_null_ (), + sqlite_lax_auto_id_ (), + pgsql_server_version_ (7, 4), + pgsql_server_version_specified_ (false), + oracle_client_version_ (10, 1), + oracle_client_version_specified_ (false), + oracle_warn_truncation_ (), + mssql_server_version_ (10, 0), + mssql_server_version_specified_ (false), + mssql_short_limit_ (1024), + mssql_short_limit_specified_ (false) +{ + ::cli::argv_scanner s (argc, argv, erase); + _parse (s, opt, arg); + end = s.end (); +} + +options:: +options (int start, + int& argc, + char** argv, + int& end, + bool erase, + ::cli::unknown_mode opt, + ::cli::unknown_mode arg) +: build2_metadata_ (), + build2_metadata_specified_ (false), + help_ (), + version_ (), + I_ (), + I_specified_ (false), + D_ (), + D_specified_ (false), + U_ (), + U_specified_ (false), + database_ (), + database_specified_ (false), + multi_database_ (::multi_database::disabled), + multi_database_specified_ (false), + default_database_ (), + default_database_specified_ (false), + generate_query_ (), + generate_prepared_ (), + omit_unprepared_ (), + generate_session_ (), + generate_schema_ (), + generate_schema_only_ (), + suppress_migration_ (), + suppress_schema_version_ (), + schema_version_table_ (), + schema_version_table_specified_ (false), + schema_format_ (), + schema_format_specified_ (false), + omit_drop_ (), + omit_create_ (), + schema_name_ (), + schema_name_specified_ (false), + fkeys_deferrable_mode_ (), + fkeys_deferrable_mode_specified_ (false), + default_pointer_ ("*"), + default_pointer_specified_ (false), + session_type_ ("odb::session"), + session_type_specified_ (false), + profile_ (), + profile_specified_ (false), + at_once_ (), + schema_ (), + schema_specified_ (false), + export_symbol_ (), + export_symbol_specified_ (false), + extern_symbol_ (), + extern_symbol_specified_ (false), + std_ (cxx_version::cxx98), + std_specified_ (false), + warn_hard_add_ (), + warn_hard_delete_ (), + warn_hard_ (), + output_dir_ (), + output_dir_specified_ (false), + input_name_ (), + input_name_specified_ (false), + changelog_ (), + changelog_specified_ (false), + changelog_in_ (), + changelog_in_specified_ (false), + changelog_out_ (), + changelog_out_specified_ (false), + changelog_dir_ (), + changelog_dir_specified_ (false), + init_changelog_ (), + odb_file_suffix_ (), + odb_file_suffix_specified_ (false), + sql_file_suffix_ (), + sql_file_suffix_specified_ (false), + schema_file_suffix_ (), + schema_file_suffix_specified_ (false), + changelog_file_suffix_ (), + changelog_file_suffix_specified_ (false), + hxx_suffix_ (".hxx"), + hxx_suffix_specified_ (false), + ixx_suffix_ (".ixx"), + ixx_suffix_specified_ (false), + cxx_suffix_ (".cxx"), + cxx_suffix_specified_ (false), + sql_suffix_ (".sql"), + sql_suffix_specified_ (false), + changelog_suffix_ (".xml"), + changelog_suffix_specified_ (false), + hxx_prologue_ (), + hxx_prologue_specified_ (false), + ixx_prologue_ (), + ixx_prologue_specified_ (false), + cxx_prologue_ (), + cxx_prologue_specified_ (false), + schema_prologue_ (), + schema_prologue_specified_ (false), + sql_prologue_ (), + sql_prologue_specified_ (false), + migration_prologue_ (), + migration_prologue_specified_ (false), + sql_interlude_ (), + sql_interlude_specified_ (false), + hxx_epilogue_ (), + hxx_epilogue_specified_ (false), + ixx_epilogue_ (), + ixx_epilogue_specified_ (false), + cxx_epilogue_ (), + cxx_epilogue_specified_ (false), + schema_epilogue_ (), + schema_epilogue_specified_ (false), + sql_epilogue_ (), + sql_epilogue_specified_ (false), + migration_epilogue_ (), + migration_epilogue_specified_ (false), + hxx_prologue_file_ (), + hxx_prologue_file_specified_ (false), + ixx_prologue_file_ (), + ixx_prologue_file_specified_ (false), + cxx_prologue_file_ (), + cxx_prologue_file_specified_ (false), + schema_prologue_file_ (), + schema_prologue_file_specified_ (false), + sql_prologue_file_ (), + sql_prologue_file_specified_ (false), + migration_prologue_file_ (), + migration_prologue_file_specified_ (false), + sql_interlude_file_ (), + sql_interlude_file_specified_ (false), + hxx_epilogue_file_ (), + hxx_epilogue_file_specified_ (false), + ixx_epilogue_file_ (), + ixx_epilogue_file_specified_ (false), + cxx_epilogue_file_ (), + cxx_epilogue_file_specified_ (false), + schema_epilogue_file_ (), + schema_epilogue_file_specified_ (false), + sql_epilogue_file_ (), + sql_epilogue_file_specified_ (false), + migration_epilogue_file_ (), + migration_epilogue_file_specified_ (false), + odb_prologue_ (), + odb_prologue_specified_ (false), + odb_prologue_file_ (), + odb_prologue_file_specified_ (false), + odb_epilogue_ (), + odb_epilogue_specified_ (false), + odb_epilogue_file_ (), + odb_epilogue_file_specified_ (false), + table_prefix_ (), + table_prefix_specified_ (false), + index_suffix_ (), + index_suffix_specified_ (false), + fkey_suffix_ (), + fkey_suffix_specified_ (false), + sequence_suffix_ (), + sequence_suffix_specified_ (false), + sql_name_case_ (), + sql_name_case_specified_ (false), + table_regex_ (), + table_regex_specified_ (false), + column_regex_ (), + column_regex_specified_ (false), + index_regex_ (), + index_regex_specified_ (false), + fkey_regex_ (), + fkey_regex_specified_ (false), + sequence_regex_ (), + sequence_regex_specified_ (false), + statement_regex_ (), + statement_regex_specified_ (false), + sql_name_regex_ (), + sql_name_regex_specified_ (false), + sql_name_regex_trace_ (), + accessor_regex_ (), + accessor_regex_specified_ (false), + accessor_regex_trace_ (), + modifier_regex_ (), + modifier_regex_specified_ (false), + modifier_regex_trace_ (), + include_with_brackets_ (), + include_prefix_ (), + include_prefix_specified_ (false), + include_regex_ (), + include_regex_specified_ (false), + include_regex_trace_ (), + guard_prefix_ (), + guard_prefix_specified_ (false), + show_sloc_ (), + sloc_limit_ (), + sloc_limit_specified_ (false), + options_file_ (), + options_file_specified_ (false), + x_ (), + x_specified_ (false), + v_ (), + trace_ (), + mysql_engine_ ("InnoDB"), + mysql_engine_specified_ (false), + sqlite_override_null_ (), + sqlite_lax_auto_id_ (), + pgsql_server_version_ (7, 4), + pgsql_server_version_specified_ (false), + oracle_client_version_ (10, 1), + oracle_client_version_specified_ (false), + oracle_warn_truncation_ (), + mssql_server_version_ (10, 0), + mssql_server_version_specified_ (false), + mssql_short_limit_ (1024), + mssql_short_limit_specified_ (false) +{ + ::cli::argv_scanner s (start, argc, argv, erase); + _parse (s, opt, arg); + end = s.end (); +} + +options:: +options (::cli::scanner& s, + ::cli::unknown_mode opt, + ::cli::unknown_mode arg) +: build2_metadata_ (), + build2_metadata_specified_ (false), + help_ (), + version_ (), + I_ (), + I_specified_ (false), + D_ (), + D_specified_ (false), + U_ (), + U_specified_ (false), + database_ (), + database_specified_ (false), + multi_database_ (::multi_database::disabled), + multi_database_specified_ (false), + default_database_ (), + default_database_specified_ (false), + generate_query_ (), + generate_prepared_ (), + omit_unprepared_ (), + generate_session_ (), + generate_schema_ (), + generate_schema_only_ (), + suppress_migration_ (), + suppress_schema_version_ (), + schema_version_table_ (), + schema_version_table_specified_ (false), + schema_format_ (), + schema_format_specified_ (false), + omit_drop_ (), + omit_create_ (), + schema_name_ (), + schema_name_specified_ (false), + fkeys_deferrable_mode_ (), + fkeys_deferrable_mode_specified_ (false), + default_pointer_ ("*"), + default_pointer_specified_ (false), + session_type_ ("odb::session"), + session_type_specified_ (false), + profile_ (), + profile_specified_ (false), + at_once_ (), + schema_ (), + schema_specified_ (false), + export_symbol_ (), + export_symbol_specified_ (false), + extern_symbol_ (), + extern_symbol_specified_ (false), + std_ (cxx_version::cxx98), + std_specified_ (false), + warn_hard_add_ (), + warn_hard_delete_ (), + warn_hard_ (), + output_dir_ (), + output_dir_specified_ (false), + input_name_ (), + input_name_specified_ (false), + changelog_ (), + changelog_specified_ (false), + changelog_in_ (), + changelog_in_specified_ (false), + changelog_out_ (), + changelog_out_specified_ (false), + changelog_dir_ (), + changelog_dir_specified_ (false), + init_changelog_ (), + odb_file_suffix_ (), + odb_file_suffix_specified_ (false), + sql_file_suffix_ (), + sql_file_suffix_specified_ (false), + schema_file_suffix_ (), + schema_file_suffix_specified_ (false), + changelog_file_suffix_ (), + changelog_file_suffix_specified_ (false), + hxx_suffix_ (".hxx"), + hxx_suffix_specified_ (false), + ixx_suffix_ (".ixx"), + ixx_suffix_specified_ (false), + cxx_suffix_ (".cxx"), + cxx_suffix_specified_ (false), + sql_suffix_ (".sql"), + sql_suffix_specified_ (false), + changelog_suffix_ (".xml"), + changelog_suffix_specified_ (false), + hxx_prologue_ (), + hxx_prologue_specified_ (false), + ixx_prologue_ (), + ixx_prologue_specified_ (false), + cxx_prologue_ (), + cxx_prologue_specified_ (false), + schema_prologue_ (), + schema_prologue_specified_ (false), + sql_prologue_ (), + sql_prologue_specified_ (false), + migration_prologue_ (), + migration_prologue_specified_ (false), + sql_interlude_ (), + sql_interlude_specified_ (false), + hxx_epilogue_ (), + hxx_epilogue_specified_ (false), + ixx_epilogue_ (), + ixx_epilogue_specified_ (false), + cxx_epilogue_ (), + cxx_epilogue_specified_ (false), + schema_epilogue_ (), + schema_epilogue_specified_ (false), + sql_epilogue_ (), + sql_epilogue_specified_ (false), + migration_epilogue_ (), + migration_epilogue_specified_ (false), + hxx_prologue_file_ (), + hxx_prologue_file_specified_ (false), + ixx_prologue_file_ (), + ixx_prologue_file_specified_ (false), + cxx_prologue_file_ (), + cxx_prologue_file_specified_ (false), + schema_prologue_file_ (), + schema_prologue_file_specified_ (false), + sql_prologue_file_ (), + sql_prologue_file_specified_ (false), + migration_prologue_file_ (), + migration_prologue_file_specified_ (false), + sql_interlude_file_ (), + sql_interlude_file_specified_ (false), + hxx_epilogue_file_ (), + hxx_epilogue_file_specified_ (false), + ixx_epilogue_file_ (), + ixx_epilogue_file_specified_ (false), + cxx_epilogue_file_ (), + cxx_epilogue_file_specified_ (false), + schema_epilogue_file_ (), + schema_epilogue_file_specified_ (false), + sql_epilogue_file_ (), + sql_epilogue_file_specified_ (false), + migration_epilogue_file_ (), + migration_epilogue_file_specified_ (false), + odb_prologue_ (), + odb_prologue_specified_ (false), + odb_prologue_file_ (), + odb_prologue_file_specified_ (false), + odb_epilogue_ (), + odb_epilogue_specified_ (false), + odb_epilogue_file_ (), + odb_epilogue_file_specified_ (false), + table_prefix_ (), + table_prefix_specified_ (false), + index_suffix_ (), + index_suffix_specified_ (false), + fkey_suffix_ (), + fkey_suffix_specified_ (false), + sequence_suffix_ (), + sequence_suffix_specified_ (false), + sql_name_case_ (), + sql_name_case_specified_ (false), + table_regex_ (), + table_regex_specified_ (false), + column_regex_ (), + column_regex_specified_ (false), + index_regex_ (), + index_regex_specified_ (false), + fkey_regex_ (), + fkey_regex_specified_ (false), + sequence_regex_ (), + sequence_regex_specified_ (false), + statement_regex_ (), + statement_regex_specified_ (false), + sql_name_regex_ (), + sql_name_regex_specified_ (false), + sql_name_regex_trace_ (), + accessor_regex_ (), + accessor_regex_specified_ (false), + accessor_regex_trace_ (), + modifier_regex_ (), + modifier_regex_specified_ (false), + modifier_regex_trace_ (), + include_with_brackets_ (), + include_prefix_ (), + include_prefix_specified_ (false), + include_regex_ (), + include_regex_specified_ (false), + include_regex_trace_ (), + guard_prefix_ (), + guard_prefix_specified_ (false), + show_sloc_ (), + sloc_limit_ (), + sloc_limit_specified_ (false), + options_file_ (), + options_file_specified_ (false), + x_ (), + x_specified_ (false), + v_ (), + trace_ (), + mysql_engine_ ("InnoDB"), + mysql_engine_specified_ (false), + sqlite_override_null_ (), + sqlite_lax_auto_id_ (), + pgsql_server_version_ (7, 4), + pgsql_server_version_specified_ (false), + oracle_client_version_ (10, 1), + oracle_client_version_specified_ (false), + oracle_warn_truncation_ (), + mssql_server_version_ (10, 0), + mssql_server_version_specified_ (false), + mssql_short_limit_ (1024), + mssql_short_limit_specified_ (false) +{ + _parse (s, opt, arg); +} + +::cli::usage_para options:: +print_usage (::std::ostream& os, ::cli::usage_para p) +{ + CLI_POTENTIALLY_UNUSED (os); + + if (p == ::cli::usage_para::text) + os << ::std::endl; + + os << "--help Print usage information and exit." << ::std::endl; + + os << "--version Print version and exit." << ::std::endl; + + os << "-I <dir> Add <dir> to the beginning of the list of" << ::std::endl + << " directories to be searched for included header" << ::std::endl + << " files." << ::std::endl; + + os << "-D <name>[=<def>] Define macro <name> with definition <def>." << ::std::endl; + + os << "-U <name> Cancel any previous definitions of macro <name>," << ::std::endl + << " either built-in or provided with the -D option." << ::std::endl; + + os << "--database|-d <db> Generate code for the <db> database." << ::std::endl; + + os << "--multi-database|-m <type> Enable multi-database support and specify its" << ::std::endl + << " type." << ::std::endl; + + os << "--default-database <db> When static multi-database support is used," << ::std::endl + << " specify the database that should be made the" << ::std::endl + << " default." << ::std::endl; + + os << "--generate-query|-q Generate query support code." << ::std::endl; + + os << "--generate-prepared Generate prepared query execution support code." << ::std::endl; + + os << "--omit-unprepared Omit un-prepared (once-off) query execution" << ::std::endl + << " support code." << ::std::endl; + + os << "--generate-session|-e Generate session support code." << ::std::endl; + + os << "--generate-schema|-s Generate the database schema." << ::std::endl; + + os << "--generate-schema-only Generate only the database schema." << ::std::endl; + + os << "--suppress-migration Suppress the generation of database schema" << ::std::endl + << " migration statements." << ::std::endl; + + os << "--suppress-schema-version Suppress the generation of schema version table." << ::std::endl; + + os << "--schema-version-table <name> Specify the alternative schema version table name" << ::std::endl + << " instead of the default schema_version." << ::std::endl; + + os << "--schema-format <format> Generate the database schema in the specified" << ::std::endl + << " format." << ::std::endl; + + os << "--omit-drop Omit DROP statements from the generated database" << ::std::endl + << " schema." << ::std::endl; + + os << "--omit-create Omit CREATE statements from the generated" << ::std::endl + << " database schema." << ::std::endl; + + os << "--schema-name <name> Use <name> as the database schema name." << ::std::endl; + + os << "--fkeys-deferrable-mode <m> Use constraint checking mode <m> in foreign keys" << ::std::endl + << " generated for object relationships." << ::std::endl; + + os << "--default-pointer <ptr> Use <ptr> as the default pointer for persistent" << ::std::endl + << " objects and views." << ::std::endl; + + os << "--session-type <type> Use <type> as the alternative session type" << ::std::endl + << " instead of the default odb::session." << ::std::endl; + + os << "--profile|-p <name> Specify a profile that should be used during" << ::std::endl + << " compilation." << ::std::endl; + + os << "--at-once Generate code for all the input files as well as" << ::std::endl + << " for all the files that they include at once." << ::std::endl; + + os << "--schema <schema> Specify a database schema (database namespace)" << ::std::endl + << " that should be assigned to the persistent classes" << ::std::endl + << " in the file being compiled." << ::std::endl; + + os << "--export-symbol <symbol> Insert <symbol> in places where DLL export/import" << ::std::endl + << " control statements" << ::std::endl + << " (__declspec(dllexport/dllimport)) are necessary." << ::std::endl; + + os << "--extern-symbol <symbol> If <symbol> is defined, insert it in places where" << ::std::endl + << " a template instantiation must be declared extern." << ::std::endl; + + os << "--std <version> Specify the C++ standard that should be used" << ::std::endl + << " during compilation." << ::std::endl; + + os << "--warn-hard-add Warn about hard-added data members." << ::std::endl; + + os << "--warn-hard-delete Warn about hard-deleted data members and" << ::std::endl + << " persistent classes." << ::std::endl; + + os << "--warn-hard Warn about both hard-added and hard-deleted data" << ::std::endl + << " members and persistent classes." << ::std::endl; + + os << "--output-dir|-o <dir> Write the generated files to <dir> instead of the" << ::std::endl + << " current directory." << ::std::endl; + + os << "--input-name <name> Use <name> instead of the input file to derive" << ::std::endl + << " the names of the generated files." << ::std::endl; + + os << "--changelog <file> Read/write changelog from/to <file> instead of" << ::std::endl + << " the default changelog file." << ::std::endl; + + os << "--changelog-in <file> Read changelog from <file> instead of the default" << ::std::endl + << " changelog file." << ::std::endl; + + os << "--changelog-out <file> Write changelog to <file> instead of the default" << ::std::endl + << " changelog file." << ::std::endl; + + os << "--changelog-dir <dir> Use <dir> instead of the input file directory as" << ::std::endl + << " the changelog file directory." << ::std::endl; + + os << "--init-changelog Force re-initialization of the changelog even if" << ::std::endl + << " one exists (all the existing change history will" << ::std::endl + << " be lost)." << ::std::endl; + + os << "--odb-file-suffix <suffix> Use <suffix> to construct the names of the" << ::std::endl + << " generated C++ files." << ::std::endl; + + os << "--sql-file-suffix <suffix> Use <suffix> to construct the name of the" << ::std::endl + << " generated schema SQL file." << ::std::endl; + + os << "--schema-file-suffix <suffix> Use <suffix> to construct the name of the" << ::std::endl + << " generated schema C++ source file." << ::std::endl; + + os << "--changelog-file-suffix <sfx> Use <sfx> to construct the name of the changelog" << ::std::endl + << " file." << ::std::endl; + + os << "--hxx-suffix <suffix> Use <suffix> instead of the default .hxx to" << ::std::endl + << " construct the name of the generated C++ header" << ::std::endl + << " file." << ::std::endl; + + os << "--ixx-suffix <suffix> Use <suffix> instead of the default .ixx to" << ::std::endl + << " construct the name of the generated C++ inline" << ::std::endl + << " file." << ::std::endl; + + os << "--cxx-suffix <suffix> Use <suffix> instead of the default .cxx to" << ::std::endl + << " construct the name of the generated C++ source" << ::std::endl + << " file." << ::std::endl; + + os << "--sql-suffix <suffix> Use <suffix> instead of the default .sql to" << ::std::endl + << " construct the name of the generated database" << ::std::endl + << " schema file." << ::std::endl; + + os << "--changelog-suffix <suffix> Use <suffix> instead of the default .xml to" << ::std::endl + << " construct the name of the changelog file." << ::std::endl; + + os << "--hxx-prologue <text> Insert <text> at the beginning of the generated" << ::std::endl + << " C++ header file." << ::std::endl; + + os << "--ixx-prologue <text> Insert <text> at the beginning of the generated" << ::std::endl + << " C++ inline file." << ::std::endl; + + os << "--cxx-prologue <text> Insert <text> at the beginning of the generated" << ::std::endl + << " C++ source file." << ::std::endl; + + os << "--schema-prologue <text> Insert <text> at the beginning of the generated" << ::std::endl + << " schema C++ source file." << ::std::endl; + + os << "--sql-prologue <text> Insert <text> at the beginning of the generated" << ::std::endl + << " database schema file." << ::std::endl; + + os << "--migration-prologue <text> Insert <text> at the beginning of the generated" << ::std::endl + << " database migration file." << ::std::endl; + + os << "--sql-interlude <text> Insert <text> after all the DROP and before any" << ::std::endl + << " CREATE statements in the generated database" << ::std::endl + << " schema file." << ::std::endl; + + os << "--hxx-epilogue <text> Insert <text> at the end of the generated C++" << ::std::endl + << " header file." << ::std::endl; + + os << "--ixx-epilogue <text> Insert <text> at the end of the generated C++" << ::std::endl + << " inline file." << ::std::endl; + + os << "--cxx-epilogue <text> Insert <text> at the end of the generated C++" << ::std::endl + << " source file." << ::std::endl; + + os << "--schema-epilogue <text> Insert <text> at the end of the generated schema" << ::std::endl + << " C++ source file." << ::std::endl; + + os << "--sql-epilogue <text> Insert <text> at the end of the generated" << ::std::endl + << " database schema file." << ::std::endl; + + os << "--migration-epilogue <text> Insert <text> at the end of the generated" << ::std::endl + << " database migration file." << ::std::endl; + + os << "--hxx-prologue-file <file> Insert the content of <file> at the beginning of" << ::std::endl + << " the generated C++ header file." << ::std::endl; + + os << "--ixx-prologue-file <file> Insert the content of <file> at the beginning of" << ::std::endl + << " the generated C++ inline file." << ::std::endl; + + os << "--cxx-prologue-file <file> Insert the content of <file> at the beginning of" << ::std::endl + << " the generated C++ source file." << ::std::endl; + + os << "--schema-prologue-file <file> Insert the content of <file> at the beginning of" << ::std::endl + << " the generated schema C++ source file." << ::std::endl; + + os << "--sql-prologue-file <file> Insert the content of <file> at the beginning of" << ::std::endl + << " the generated database schema file." << ::std::endl; + + os << "--migration-prologue-file <f> Insert the content of file <f> at the beginning" << ::std::endl + << " of the generated database migration file." << ::std::endl; + + os << "--sql-interlude-file <file> Insert the content of <file> after all the DROP" << ::std::endl + << " and before any CREATE statements in the generated" << ::std::endl + << " database schema file." << ::std::endl; + + os << "--hxx-epilogue-file <file> Insert the content of <file> at the end of the" << ::std::endl + << " generated C++ header file." << ::std::endl; + + os << "--ixx-epilogue-file <file> Insert the content of <file> at the end of the" << ::std::endl + << " generated C++ inline file." << ::std::endl; + + os << "--cxx-epilogue-file <file> Insert the content of <file> at the end of the" << ::std::endl + << " generated C++ source file." << ::std::endl; + + os << "--schema-epilogue-file <file> Insert the content of <file> at the end of the" << ::std::endl + << " generated schema C++ source file." << ::std::endl; + + os << "--sql-epilogue-file <file> Insert the content of <file> at the end of the" << ::std::endl + << " generated database schema file." << ::std::endl; + + os << "--migration-epilogue-file <f> Insert the content of file <f> at the end of the" << ::std::endl + << " generated database migration file." << ::std::endl; + + os << "--odb-prologue <text> Compile <text> before the input header file." << ::std::endl; + + os << "--odb-prologue-file <file> Compile <file> contents before the input header" << ::std::endl + << " file." << ::std::endl; + + os << "--odb-epilogue <text> Compile <text> after the input header file." << ::std::endl; + + os << "--odb-epilogue-file <file> Compile <file> contents after the input header" << ::std::endl + << " file." << ::std::endl; + + os << "--table-prefix <prefix> Add <prefix> to table names and, for databases" << ::std::endl + << " that have global index and/or foreign key names," << ::std::endl + << " to those names as well." << ::std::endl; + + os << "--index-suffix <suffix> Use <suffix> instead of the default _i to" << ::std::endl + << " construct index names." << ::std::endl; + + os << "--fkey-suffix <suffix> Use <suffix> instead of the default _fk to" << ::std::endl + << " construct foreign key names." << ::std::endl; + + os << "--sequence-suffix <suffix> Use <suffix> instead of the default _seq to" << ::std::endl + << " construct sequence names." << ::std::endl; + + os << "--sql-name-case <case> Convert all automatically-derived SQL names to" << ::std::endl + << " upper or lower case." << ::std::endl; + + os << "--table-regex <regex> Add <regex> to the list of regular expressions" << ::std::endl + << " that is used to transform automatically-derived" << ::std::endl + << " table names." << ::std::endl; + + os << "--column-regex <regex> Add <regex> to the list of regular expressions" << ::std::endl + << " that is used to transform automatically-derived" << ::std::endl + << " column names." << ::std::endl; + + os << "--index-regex <regex> Add <regex> to the list of regular expressions" << ::std::endl + << " that is used to transform automatically-derived" << ::std::endl + << " index names." << ::std::endl; + + os << "--fkey-regex <regex> Add <regex> to the list of regular expressions" << ::std::endl + << " that is used to transform automatically-derived" << ::std::endl + << " foreign key names." << ::std::endl; + + os << "--sequence-regex <regex> Add <regex> to the list of regular expressions" << ::std::endl + << " that is used to transform automatically-derived" << ::std::endl + << " sequence names." << ::std::endl; + + os << "--statement-regex <regex> Add <regex> to the list of regular expressions" << ::std::endl + << " that is used to transform automatically-derived" << ::std::endl + << " prepared statement names." << ::std::endl; + + os << "--sql-name-regex <regex> Add <regex> to the list of regular expressions" << ::std::endl + << " that is used to transform all" << ::std::endl + << " automatically-derived SQL names." << ::std::endl; + + os << "--sql-name-regex-trace Trace the process of applying regular expressions" << ::std::endl + << " specified with the SQL name --*-regex options." << ::std::endl; + + os << "--accessor-regex <regex> Add <regex> to the list of regular expressions" << ::std::endl + << " used to transform data member names to function" << ::std::endl + << " names when searching for a suitable accessor" << ::std::endl + << " function." << ::std::endl; + + os << "--accessor-regex-trace Trace the process of applying regular expressions" << ::std::endl + << " specified with the --accessor-regex option." << ::std::endl; + + os << "--modifier-regex <regex> Add <regex> to the list of regular expressions" << ::std::endl + << " used to transform data member names to function" << ::std::endl + << " names when searching for a suitable modifier" << ::std::endl + << " function." << ::std::endl; + + os << "--modifier-regex-trace Trace the process of applying regular expressions" << ::std::endl + << " specified with the --modifier-regex option." << ::std::endl; + + os << "--include-with-brackets Use angle brackets (<>) instead of quotes (\"\") in" << ::std::endl + << " the generated #include directives." << ::std::endl; + + os << "--include-prefix <prefix> Add <prefix> to the generated #include directive" << ::std::endl + << " paths." << ::std::endl; + + os << "--include-regex <regex> Add <regex> to the list of regular expressions" << ::std::endl + << " used to transform generated #include directive" << ::std::endl + << " paths." << ::std::endl; + + os << "--include-regex-trace Trace the process of applying regular expressions" << ::std::endl + << " specified with the --include-regex option." << ::std::endl; + + os << "--guard-prefix <prefix> Add <prefix> to the generated header inclusion" << ::std::endl + << " guards." << ::std::endl; + + os << "--show-sloc Print the number of generated physical source" << ::std::endl + << " lines of code (SLOC)." << ::std::endl; + + os << "--sloc-limit <num> Check that the number of generated physical" << ::std::endl + << " source lines of code (SLOC) does not exceed" << ::std::endl + << " <num>." << ::std::endl; + + os << "--options-file <file> Read additional options from <file>." << ::std::endl; + + os << "-x <option> Pass <option> to the underlying C++ compiler" << ::std::endl + << " (g++)." << ::std::endl; + + os << "-v Print the commands executed to run the stages of" << ::std::endl + << " compilation." << ::std::endl; + + os << "--trace Trace the compilation process." << ::std::endl; + + os << "--mysql-engine <engine> Use <engine> instead of the default InnoDB in the" << ::std::endl + << " generated database schema file." << ::std::endl; + + os << "--sqlite-override-null Make all columns in the generated database schema" << ::std::endl + << " allow NULL values." << ::std::endl; + + os << "--sqlite-lax-auto-id Do not force monotonically increasing" << ::std::endl + << " automatically-assigned object ids." << ::std::endl; + + os << "--pgsql-server-version <ver> Specify the minimum PostgreSQL server version" << ::std::endl + << " with which the generated C++ code and schema will" << ::std::endl + << " be used." << ::std::endl; + + os << "--oracle-client-version <ver> Specify the minimum Oracle client library (OCI)" << ::std::endl + << " version with which the generated C++ code will be" << ::std::endl + << " linked." << ::std::endl; + + os << "--oracle-warn-truncation Warn about SQL names that are longer than 30" << ::std::endl + << " characters and are therefore truncated." << ::std::endl; + + os << "--mssql-server-version <ver> Specify the minimum SQL Server server version" << ::std::endl + << " with which the generated C++ code and schema will" << ::std::endl + << " be used." << ::std::endl; + + os << "--mssql-short-limit <size> Specify the short data size limit." << ::std::endl; + + p = ::cli::usage_para::option; + + return p; +} + +struct _cli_options_desc_type: ::cli::options +{ + _cli_options_desc_type () + { + ::options::fill (*this); + } +}; + +static _cli_options_desc_type _cli_options_desc_; + +void options:: +fill (::cli::options& os) +{ + // --build2-metadata + // + { + ::cli::option_names a; + std::string dv; + ::cli::option o ("--build2-metadata", a, false, dv); + os.push_back (o); + } + + // --help + // + { + ::cli::option_names a; + std::string dv; + ::cli::option o ("--help", a, true, dv); + os.push_back (o); + } + + // --version + // + { + ::cli::option_names a; + std::string dv; + ::cli::option o ("--version", a, true, dv); + os.push_back (o); + } + + // -I + // + { + ::cli::option_names a; + std::string dv; + ::cli::option o ("-I", a, false, dv); + os.push_back (o); + } + + // -D + // + { + ::cli::option_names a; + std::string dv; + ::cli::option o ("-D", a, false, dv); + os.push_back (o); + } + + // -U + // + { + ::cli::option_names a; + std::string dv; + ::cli::option o ("-U", a, false, dv); + os.push_back (o); + } + + // --database + // + { + ::cli::option_names a; + a.push_back ("-d"); + std::string dv; + ::cli::option o ("--database", a, false, dv); + os.push_back (o); + } + + // --multi-database + // + { + ::cli::option_names a; + a.push_back ("-m"); + std::string dv; + ::cli::option o ("--multi-database", a, false, dv); + os.push_back (o); + } + + // --default-database + // + { + ::cli::option_names a; + std::string dv; + ::cli::option o ("--default-database", a, false, dv); + os.push_back (o); + } + + // --generate-query + // + { + ::cli::option_names a; + a.push_back ("-q"); + std::string dv; + ::cli::option o ("--generate-query", a, true, dv); + os.push_back (o); + } + + // --generate-prepared + // + { + ::cli::option_names a; + std::string dv; + ::cli::option o ("--generate-prepared", a, true, dv); + os.push_back (o); + } + + // --omit-unprepared + // + { + ::cli::option_names a; + std::string dv; + ::cli::option o ("--omit-unprepared", a, true, dv); + os.push_back (o); + } + + // --generate-session + // + { + ::cli::option_names a; + a.push_back ("-e"); + std::string dv; + ::cli::option o ("--generate-session", a, true, dv); + os.push_back (o); + } + + // --generate-schema + // + { + ::cli::option_names a; + a.push_back ("-s"); + std::string dv; + ::cli::option o ("--generate-schema", a, true, dv); + os.push_back (o); + } + + // --generate-schema-only + // + { + ::cli::option_names a; + std::string dv; + ::cli::option o ("--generate-schema-only", a, true, dv); + os.push_back (o); + } + + // --suppress-migration + // + { + ::cli::option_names a; + std::string dv; + ::cli::option o ("--suppress-migration", a, true, dv); + os.push_back (o); + } + + // --suppress-schema-version + // + { + ::cli::option_names a; + std::string dv; + ::cli::option o ("--suppress-schema-version", a, true, dv); + os.push_back (o); + } + + // --schema-version-table + // + { + ::cli::option_names a; + std::string dv; + ::cli::option o ("--schema-version-table", a, false, dv); + os.push_back (o); + } + + // --schema-format + // + { + ::cli::option_names a; + std::string dv; + ::cli::option o ("--schema-format", a, false, dv); + os.push_back (o); + } + + // --omit-drop + // + { + ::cli::option_names a; + std::string dv; + ::cli::option o ("--omit-drop", a, true, dv); + os.push_back (o); + } + + // --omit-create + // + { + ::cli::option_names a; + std::string dv; + ::cli::option o ("--omit-create", a, true, dv); + os.push_back (o); + } + + // --schema-name + // + { + ::cli::option_names a; + std::string dv; + ::cli::option o ("--schema-name", a, false, dv); + os.push_back (o); + } + + // --fkeys-deferrable-mode + // + { + ::cli::option_names a; + std::string dv; + ::cli::option o ("--fkeys-deferrable-mode", a, false, dv); + os.push_back (o); + } + + // --default-pointer + // + { + ::cli::option_names a; + std::string dv ("*"); + ::cli::option o ("--default-pointer", a, false, dv); + os.push_back (o); + } + + // --session-type + // + { + ::cli::option_names a; + std::string dv ("odb::session"); + ::cli::option o ("--session-type", a, false, dv); + os.push_back (o); + } + + // --profile + // + { + ::cli::option_names a; + a.push_back ("-p"); + std::string dv; + ::cli::option o ("--profile", a, false, dv); + os.push_back (o); + } + + // --at-once + // + { + ::cli::option_names a; + std::string dv; + ::cli::option o ("--at-once", a, true, dv); + os.push_back (o); + } + + // --schema + // + { + ::cli::option_names a; + std::string dv; + ::cli::option o ("--schema", a, false, dv); + os.push_back (o); + } + + // --export-symbol + // + { + ::cli::option_names a; + std::string dv; + ::cli::option o ("--export-symbol", a, false, dv); + os.push_back (o); + } + + // --extern-symbol + // + { + ::cli::option_names a; + std::string dv; + ::cli::option o ("--extern-symbol", a, false, dv); + os.push_back (o); + } + + // --std + // + { + ::cli::option_names a; + std::string dv; + ::cli::option o ("--std", a, false, dv); + os.push_back (o); + } + + // --warn-hard-add + // + { + ::cli::option_names a; + std::string dv; + ::cli::option o ("--warn-hard-add", a, true, dv); + os.push_back (o); + } + + // --warn-hard-delete + // + { + ::cli::option_names a; + std::string dv; + ::cli::option o ("--warn-hard-delete", a, true, dv); + os.push_back (o); + } + + // --warn-hard + // + { + ::cli::option_names a; + std::string dv; + ::cli::option o ("--warn-hard", a, true, dv); + os.push_back (o); + } + + // --output-dir + // + { + ::cli::option_names a; + a.push_back ("-o"); + std::string dv; + ::cli::option o ("--output-dir", a, false, dv); + os.push_back (o); + } + + // --input-name + // + { + ::cli::option_names a; + std::string dv; + ::cli::option o ("--input-name", a, false, dv); + os.push_back (o); + } + + // --changelog + // + { + ::cli::option_names a; + std::string dv; + ::cli::option o ("--changelog", a, false, dv); + os.push_back (o); + } + + // --changelog-in + // + { + ::cli::option_names a; + std::string dv; + ::cli::option o ("--changelog-in", a, false, dv); + os.push_back (o); + } + + // --changelog-out + // + { + ::cli::option_names a; + std::string dv; + ::cli::option o ("--changelog-out", a, false, dv); + os.push_back (o); + } + + // --changelog-dir + // + { + ::cli::option_names a; + std::string dv; + ::cli::option o ("--changelog-dir", a, false, dv); + os.push_back (o); + } + + // --init-changelog + // + { + ::cli::option_names a; + std::string dv; + ::cli::option o ("--init-changelog", a, true, dv); + os.push_back (o); + } + + // --odb-file-suffix + // + { + ::cli::option_names a; + std::string dv; + ::cli::option o ("--odb-file-suffix", a, false, dv); + os.push_back (o); + } + + // --sql-file-suffix + // + { + ::cli::option_names a; + std::string dv; + ::cli::option o ("--sql-file-suffix", a, false, dv); + os.push_back (o); + } + + // --schema-file-suffix + // + { + ::cli::option_names a; + std::string dv; + ::cli::option o ("--schema-file-suffix", a, false, dv); + os.push_back (o); + } + + // --changelog-file-suffix + // + { + ::cli::option_names a; + std::string dv; + ::cli::option o ("--changelog-file-suffix", a, false, dv); + os.push_back (o); + } + + // --hxx-suffix + // + { + ::cli::option_names a; + std::string dv (".hxx"); + ::cli::option o ("--hxx-suffix", a, false, dv); + os.push_back (o); + } + + // --ixx-suffix + // + { + ::cli::option_names a; + std::string dv (".ixx"); + ::cli::option o ("--ixx-suffix", a, false, dv); + os.push_back (o); + } + + // --cxx-suffix + // + { + ::cli::option_names a; + std::string dv (".cxx"); + ::cli::option o ("--cxx-suffix", a, false, dv); + os.push_back (o); + } + + // --sql-suffix + // + { + ::cli::option_names a; + std::string dv (".sql"); + ::cli::option o ("--sql-suffix", a, false, dv); + os.push_back (o); + } + + // --changelog-suffix + // + { + ::cli::option_names a; + std::string dv (".xml"); + ::cli::option o ("--changelog-suffix", a, false, dv); + os.push_back (o); + } + + // --hxx-prologue + // + { + ::cli::option_names a; + std::string dv; + ::cli::option o ("--hxx-prologue", a, false, dv); + os.push_back (o); + } + + // --ixx-prologue + // + { + ::cli::option_names a; + std::string dv; + ::cli::option o ("--ixx-prologue", a, false, dv); + os.push_back (o); + } + + // --cxx-prologue + // + { + ::cli::option_names a; + std::string dv; + ::cli::option o ("--cxx-prologue", a, false, dv); + os.push_back (o); + } + + // --schema-prologue + // + { + ::cli::option_names a; + std::string dv; + ::cli::option o ("--schema-prologue", a, false, dv); + os.push_back (o); + } + + // --sql-prologue + // + { + ::cli::option_names a; + std::string dv; + ::cli::option o ("--sql-prologue", a, false, dv); + os.push_back (o); + } + + // --migration-prologue + // + { + ::cli::option_names a; + std::string dv; + ::cli::option o ("--migration-prologue", a, false, dv); + os.push_back (o); + } + + // --sql-interlude + // + { + ::cli::option_names a; + std::string dv; + ::cli::option o ("--sql-interlude", a, false, dv); + os.push_back (o); + } + + // --hxx-epilogue + // + { + ::cli::option_names a; + std::string dv; + ::cli::option o ("--hxx-epilogue", a, false, dv); + os.push_back (o); + } + + // --ixx-epilogue + // + { + ::cli::option_names a; + std::string dv; + ::cli::option o ("--ixx-epilogue", a, false, dv); + os.push_back (o); + } + + // --cxx-epilogue + // + { + ::cli::option_names a; + std::string dv; + ::cli::option o ("--cxx-epilogue", a, false, dv); + os.push_back (o); + } + + // --schema-epilogue + // + { + ::cli::option_names a; + std::string dv; + ::cli::option o ("--schema-epilogue", a, false, dv); + os.push_back (o); + } + + // --sql-epilogue + // + { + ::cli::option_names a; + std::string dv; + ::cli::option o ("--sql-epilogue", a, false, dv); + os.push_back (o); + } + + // --migration-epilogue + // + { + ::cli::option_names a; + std::string dv; + ::cli::option o ("--migration-epilogue", a, false, dv); + os.push_back (o); + } + + // --hxx-prologue-file + // + { + ::cli::option_names a; + std::string dv; + ::cli::option o ("--hxx-prologue-file", a, false, dv); + os.push_back (o); + } + + // --ixx-prologue-file + // + { + ::cli::option_names a; + std::string dv; + ::cli::option o ("--ixx-prologue-file", a, false, dv); + os.push_back (o); + } + + // --cxx-prologue-file + // + { + ::cli::option_names a; + std::string dv; + ::cli::option o ("--cxx-prologue-file", a, false, dv); + os.push_back (o); + } + + // --schema-prologue-file + // + { + ::cli::option_names a; + std::string dv; + ::cli::option o ("--schema-prologue-file", a, false, dv); + os.push_back (o); + } + + // --sql-prologue-file + // + { + ::cli::option_names a; + std::string dv; + ::cli::option o ("--sql-prologue-file", a, false, dv); + os.push_back (o); + } + + // --migration-prologue-file + // + { + ::cli::option_names a; + std::string dv; + ::cli::option o ("--migration-prologue-file", a, false, dv); + os.push_back (o); + } + + // --sql-interlude-file + // + { + ::cli::option_names a; + std::string dv; + ::cli::option o ("--sql-interlude-file", a, false, dv); + os.push_back (o); + } + + // --hxx-epilogue-file + // + { + ::cli::option_names a; + std::string dv; + ::cli::option o ("--hxx-epilogue-file", a, false, dv); + os.push_back (o); + } + + // --ixx-epilogue-file + // + { + ::cli::option_names a; + std::string dv; + ::cli::option o ("--ixx-epilogue-file", a, false, dv); + os.push_back (o); + } + + // --cxx-epilogue-file + // + { + ::cli::option_names a; + std::string dv; + ::cli::option o ("--cxx-epilogue-file", a, false, dv); + os.push_back (o); + } + + // --schema-epilogue-file + // + { + ::cli::option_names a; + std::string dv; + ::cli::option o ("--schema-epilogue-file", a, false, dv); + os.push_back (o); + } + + // --sql-epilogue-file + // + { + ::cli::option_names a; + std::string dv; + ::cli::option o ("--sql-epilogue-file", a, false, dv); + os.push_back (o); + } + + // --migration-epilogue-file + // + { + ::cli::option_names a; + std::string dv; + ::cli::option o ("--migration-epilogue-file", a, false, dv); + os.push_back (o); + } + + // --odb-prologue + // + { + ::cli::option_names a; + std::string dv; + ::cli::option o ("--odb-prologue", a, false, dv); + os.push_back (o); + } + + // --odb-prologue-file + // + { + ::cli::option_names a; + std::string dv; + ::cli::option o ("--odb-prologue-file", a, false, dv); + os.push_back (o); + } + + // --odb-epilogue + // + { + ::cli::option_names a; + std::string dv; + ::cli::option o ("--odb-epilogue", a, false, dv); + os.push_back (o); + } + + // --odb-epilogue-file + // + { + ::cli::option_names a; + std::string dv; + ::cli::option o ("--odb-epilogue-file", a, false, dv); + os.push_back (o); + } + + // --table-prefix + // + { + ::cli::option_names a; + std::string dv; + ::cli::option o ("--table-prefix", a, false, dv); + os.push_back (o); + } + + // --index-suffix + // + { + ::cli::option_names a; + std::string dv; + ::cli::option o ("--index-suffix", a, false, dv); + os.push_back (o); + } + + // --fkey-suffix + // + { + ::cli::option_names a; + std::string dv; + ::cli::option o ("--fkey-suffix", a, false, dv); + os.push_back (o); + } + + // --sequence-suffix + // + { + ::cli::option_names a; + std::string dv; + ::cli::option o ("--sequence-suffix", a, false, dv); + os.push_back (o); + } + + // --sql-name-case + // + { + ::cli::option_names a; + std::string dv; + ::cli::option o ("--sql-name-case", a, false, dv); + os.push_back (o); + } + + // --table-regex + // + { + ::cli::option_names a; + std::string dv; + ::cli::option o ("--table-regex", a, false, dv); + os.push_back (o); + } + + // --column-regex + // + { + ::cli::option_names a; + std::string dv; + ::cli::option o ("--column-regex", a, false, dv); + os.push_back (o); + } + + // --index-regex + // + { + ::cli::option_names a; + std::string dv; + ::cli::option o ("--index-regex", a, false, dv); + os.push_back (o); + } + + // --fkey-regex + // + { + ::cli::option_names a; + std::string dv; + ::cli::option o ("--fkey-regex", a, false, dv); + os.push_back (o); + } + + // --sequence-regex + // + { + ::cli::option_names a; + std::string dv; + ::cli::option o ("--sequence-regex", a, false, dv); + os.push_back (o); + } + + // --statement-regex + // + { + ::cli::option_names a; + std::string dv; + ::cli::option o ("--statement-regex", a, false, dv); + os.push_back (o); + } + + // --sql-name-regex + // + { + ::cli::option_names a; + std::string dv; + ::cli::option o ("--sql-name-regex", a, false, dv); + os.push_back (o); + } + + // --sql-name-regex-trace + // + { + ::cli::option_names a; + std::string dv; + ::cli::option o ("--sql-name-regex-trace", a, true, dv); + os.push_back (o); + } + + // --accessor-regex + // + { + ::cli::option_names a; + std::string dv; + ::cli::option o ("--accessor-regex", a, false, dv); + os.push_back (o); + } + + // --accessor-regex-trace + // + { + ::cli::option_names a; + std::string dv; + ::cli::option o ("--accessor-regex-trace", a, true, dv); + os.push_back (o); + } + + // --modifier-regex + // + { + ::cli::option_names a; + std::string dv; + ::cli::option o ("--modifier-regex", a, false, dv); + os.push_back (o); + } + + // --modifier-regex-trace + // + { + ::cli::option_names a; + std::string dv; + ::cli::option o ("--modifier-regex-trace", a, true, dv); + os.push_back (o); + } + + // --include-with-brackets + // + { + ::cli::option_names a; + std::string dv; + ::cli::option o ("--include-with-brackets", a, true, dv); + os.push_back (o); + } + + // --include-prefix + // + { + ::cli::option_names a; + std::string dv; + ::cli::option o ("--include-prefix", a, false, dv); + os.push_back (o); + } + + // --include-regex + // + { + ::cli::option_names a; + std::string dv; + ::cli::option o ("--include-regex", a, false, dv); + os.push_back (o); + } + + // --include-regex-trace + // + { + ::cli::option_names a; + std::string dv; + ::cli::option o ("--include-regex-trace", a, true, dv); + os.push_back (o); + } + + // --guard-prefix + // + { + ::cli::option_names a; + std::string dv; + ::cli::option o ("--guard-prefix", a, false, dv); + os.push_back (o); + } + + // --show-sloc + // + { + ::cli::option_names a; + std::string dv; + ::cli::option o ("--show-sloc", a, true, dv); + os.push_back (o); + } + + // --sloc-limit + // + { + ::cli::option_names a; + std::string dv; + ::cli::option o ("--sloc-limit", a, false, dv); + os.push_back (o); + } + + // --options-file + // + { + ::cli::option_names a; + std::string dv; + ::cli::option o ("--options-file", a, false, dv); + os.push_back (o); + } + + // -x + // + { + ::cli::option_names a; + std::string dv; + ::cli::option o ("-x", a, false, dv); + os.push_back (o); + } + + // -v + // + { + ::cli::option_names a; + std::string dv; + ::cli::option o ("-v", a, true, dv); + os.push_back (o); + } + + // --trace + // + { + ::cli::option_names a; + std::string dv; + ::cli::option o ("--trace", a, true, dv); + os.push_back (o); + } + + // --mysql-engine + // + { + ::cli::option_names a; + std::string dv ("InnoDB"); + ::cli::option o ("--mysql-engine", a, false, dv); + os.push_back (o); + } + + // --sqlite-override-null + // + { + ::cli::option_names a; + std::string dv; + ::cli::option o ("--sqlite-override-null", a, true, dv); + os.push_back (o); + } + + // --sqlite-lax-auto-id + // + { + ::cli::option_names a; + std::string dv; + ::cli::option o ("--sqlite-lax-auto-id", a, true, dv); + os.push_back (o); + } + + // --pgsql-server-version + // + { + ::cli::option_names a; + std::string dv; + ::cli::option o ("--pgsql-server-version", a, false, dv); + os.push_back (o); + } + + // --oracle-client-version + // + { + ::cli::option_names a; + std::string dv; + ::cli::option o ("--oracle-client-version", a, false, dv); + os.push_back (o); + } + + // --oracle-warn-truncation + // + { + ::cli::option_names a; + std::string dv; + ::cli::option o ("--oracle-warn-truncation", a, true, dv); + os.push_back (o); + } + + // --mssql-server-version + // + { + ::cli::option_names a; + std::string dv; + ::cli::option o ("--mssql-server-version", a, false, dv); + os.push_back (o); + } + + // --mssql-short-limit + // + { + ::cli::option_names a; + std::string dv ("1024"); + ::cli::option o ("--mssql-short-limit", a, false, dv); + os.push_back (o); + } +} + +const ::cli::options& options:: +description () +{ + return _cli_options_desc_; +} + +typedef +std::map<std::string, void (*) (options&, ::cli::scanner&)> +_cli_options_map; + +static _cli_options_map _cli_options_map_; + +struct _cli_options_map_init +{ + _cli_options_map_init () + { + _cli_options_map_["--build2-metadata"] = + &::cli::thunk< options, std::uint64_t, &options::build2_metadata_, + &options::build2_metadata_specified_ >; + _cli_options_map_["--help"] = + &::cli::thunk< options, &options::help_ >; + _cli_options_map_["--version"] = + &::cli::thunk< options, &options::version_ >; + _cli_options_map_["-I"] = + &::cli::thunk< options, std::vector<std::string>, &options::I_, + &options::I_specified_ >; + _cli_options_map_["-D"] = + &::cli::thunk< options, std::vector<std::string>, &options::D_, + &options::D_specified_ >; + _cli_options_map_["-U"] = + &::cli::thunk< options, std::vector<std::string>, &options::U_, + &options::U_specified_ >; + _cli_options_map_["--database"] = + &::cli::thunk< options, std::vector< ::database>, &options::database_, + &options::database_specified_ >; + _cli_options_map_["-d"] = + &::cli::thunk< options, std::vector< ::database>, &options::database_, + &options::database_specified_ >; + _cli_options_map_["--multi-database"] = + &::cli::thunk< options, ::multi_database, &options::multi_database_, + &options::multi_database_specified_ >; + _cli_options_map_["-m"] = + &::cli::thunk< options, ::multi_database, &options::multi_database_, + &options::multi_database_specified_ >; + _cli_options_map_["--default-database"] = + &::cli::thunk< options, ::database, &options::default_database_, + &options::default_database_specified_ >; + _cli_options_map_["--generate-query"] = + &::cli::thunk< options, &options::generate_query_ >; + _cli_options_map_["-q"] = + &::cli::thunk< options, &options::generate_query_ >; + _cli_options_map_["--generate-prepared"] = + &::cli::thunk< options, &options::generate_prepared_ >; + _cli_options_map_["--omit-unprepared"] = + &::cli::thunk< options, &options::omit_unprepared_ >; + _cli_options_map_["--generate-session"] = + &::cli::thunk< options, &options::generate_session_ >; + _cli_options_map_["-e"] = + &::cli::thunk< options, &options::generate_session_ >; + _cli_options_map_["--generate-schema"] = + &::cli::thunk< options, &options::generate_schema_ >; + _cli_options_map_["-s"] = + &::cli::thunk< options, &options::generate_schema_ >; + _cli_options_map_["--generate-schema-only"] = + &::cli::thunk< options, &options::generate_schema_only_ >; + _cli_options_map_["--suppress-migration"] = + &::cli::thunk< options, &options::suppress_migration_ >; + _cli_options_map_["--suppress-schema-version"] = + &::cli::thunk< options, &options::suppress_schema_version_ >; + _cli_options_map_["--schema-version-table"] = + &::cli::thunk< options, database_map<qname>, &options::schema_version_table_, + &options::schema_version_table_specified_ >; + _cli_options_map_["--schema-format"] = + &::cli::thunk< options, database_map<std::set< ::schema_format> >, &options::schema_format_, + &options::schema_format_specified_ >; + _cli_options_map_["--omit-drop"] = + &::cli::thunk< options, &options::omit_drop_ >; + _cli_options_map_["--omit-create"] = + &::cli::thunk< options, &options::omit_create_ >; + _cli_options_map_["--schema-name"] = + &::cli::thunk< options, database_map<std::string>, &options::schema_name_, + &options::schema_name_specified_ >; + _cli_options_map_["--fkeys-deferrable-mode"] = + &::cli::thunk< options, database_map<deferrable>, &options::fkeys_deferrable_mode_, + &options::fkeys_deferrable_mode_specified_ >; + _cli_options_map_["--default-pointer"] = + &::cli::thunk< options, std::string, &options::default_pointer_, + &options::default_pointer_specified_ >; + _cli_options_map_["--session-type"] = + &::cli::thunk< options, std::string, &options::session_type_, + &options::session_type_specified_ >; + _cli_options_map_["--profile"] = + &::cli::thunk< options, std::string, &options::profile_, + &options::profile_specified_ >; + _cli_options_map_["-p"] = + &::cli::thunk< options, std::string, &options::profile_, + &options::profile_specified_ >; + _cli_options_map_["--at-once"] = + &::cli::thunk< options, &options::at_once_ >; + _cli_options_map_["--schema"] = + &::cli::thunk< options, database_map<qname>, &options::schema_, + &options::schema_specified_ >; + _cli_options_map_["--export-symbol"] = + &::cli::thunk< options, database_map<std::string>, &options::export_symbol_, + &options::export_symbol_specified_ >; + _cli_options_map_["--extern-symbol"] = + &::cli::thunk< options, database_map<std::string>, &options::extern_symbol_, + &options::extern_symbol_specified_ >; + _cli_options_map_["--std"] = + &::cli::thunk< options, cxx_version, &options::std_, + &options::std_specified_ >; + _cli_options_map_["--warn-hard-add"] = + &::cli::thunk< options, &options::warn_hard_add_ >; + _cli_options_map_["--warn-hard-delete"] = + &::cli::thunk< options, &options::warn_hard_delete_ >; + _cli_options_map_["--warn-hard"] = + &::cli::thunk< options, &options::warn_hard_ >; + _cli_options_map_["--output-dir"] = + &::cli::thunk< options, std::string, &options::output_dir_, + &options::output_dir_specified_ >; + _cli_options_map_["-o"] = + &::cli::thunk< options, std::string, &options::output_dir_, + &options::output_dir_specified_ >; + _cli_options_map_["--input-name"] = + &::cli::thunk< options, std::string, &options::input_name_, + &options::input_name_specified_ >; + _cli_options_map_["--changelog"] = + &::cli::thunk< options, database_map<std::string>, &options::changelog_, + &options::changelog_specified_ >; + _cli_options_map_["--changelog-in"] = + &::cli::thunk< options, database_map<std::string>, &options::changelog_in_, + &options::changelog_in_specified_ >; + _cli_options_map_["--changelog-out"] = + &::cli::thunk< options, database_map<std::string>, &options::changelog_out_, + &options::changelog_out_specified_ >; + _cli_options_map_["--changelog-dir"] = + &::cli::thunk< options, database_map<std::string>, &options::changelog_dir_, + &options::changelog_dir_specified_ >; + _cli_options_map_["--init-changelog"] = + &::cli::thunk< options, &options::init_changelog_ >; + _cli_options_map_["--odb-file-suffix"] = + &::cli::thunk< options, database_map<std::string>, &options::odb_file_suffix_, + &options::odb_file_suffix_specified_ >; + _cli_options_map_["--sql-file-suffix"] = + &::cli::thunk< options, database_map<std::string>, &options::sql_file_suffix_, + &options::sql_file_suffix_specified_ >; + _cli_options_map_["--schema-file-suffix"] = + &::cli::thunk< options, database_map<std::string>, &options::schema_file_suffix_, + &options::schema_file_suffix_specified_ >; + _cli_options_map_["--changelog-file-suffix"] = + &::cli::thunk< options, database_map<std::string>, &options::changelog_file_suffix_, + &options::changelog_file_suffix_specified_ >; + _cli_options_map_["--hxx-suffix"] = + &::cli::thunk< options, std::string, &options::hxx_suffix_, + &options::hxx_suffix_specified_ >; + _cli_options_map_["--ixx-suffix"] = + &::cli::thunk< options, std::string, &options::ixx_suffix_, + &options::ixx_suffix_specified_ >; + _cli_options_map_["--cxx-suffix"] = + &::cli::thunk< options, std::string, &options::cxx_suffix_, + &options::cxx_suffix_specified_ >; + _cli_options_map_["--sql-suffix"] = + &::cli::thunk< options, std::string, &options::sql_suffix_, + &options::sql_suffix_specified_ >; + _cli_options_map_["--changelog-suffix"] = + &::cli::thunk< options, std::string, &options::changelog_suffix_, + &options::changelog_suffix_specified_ >; + _cli_options_map_["--hxx-prologue"] = + &::cli::thunk< options, database_map<std::vector<std::string> >, &options::hxx_prologue_, + &options::hxx_prologue_specified_ >; + _cli_options_map_["--ixx-prologue"] = + &::cli::thunk< options, database_map<std::vector<std::string> >, &options::ixx_prologue_, + &options::ixx_prologue_specified_ >; + _cli_options_map_["--cxx-prologue"] = + &::cli::thunk< options, database_map<std::vector<std::string> >, &options::cxx_prologue_, + &options::cxx_prologue_specified_ >; + _cli_options_map_["--schema-prologue"] = + &::cli::thunk< options, database_map<std::vector<std::string> >, &options::schema_prologue_, + &options::schema_prologue_specified_ >; + _cli_options_map_["--sql-prologue"] = + &::cli::thunk< options, database_map<std::vector<std::string> >, &options::sql_prologue_, + &options::sql_prologue_specified_ >; + _cli_options_map_["--migration-prologue"] = + &::cli::thunk< options, database_map<std::vector<std::string> >, &options::migration_prologue_, + &options::migration_prologue_specified_ >; + _cli_options_map_["--sql-interlude"] = + &::cli::thunk< options, database_map<std::vector<std::string> >, &options::sql_interlude_, + &options::sql_interlude_specified_ >; + _cli_options_map_["--hxx-epilogue"] = + &::cli::thunk< options, database_map<std::vector<std::string> >, &options::hxx_epilogue_, + &options::hxx_epilogue_specified_ >; + _cli_options_map_["--ixx-epilogue"] = + &::cli::thunk< options, database_map<std::vector<std::string> >, &options::ixx_epilogue_, + &options::ixx_epilogue_specified_ >; + _cli_options_map_["--cxx-epilogue"] = + &::cli::thunk< options, database_map<std::vector<std::string> >, &options::cxx_epilogue_, + &options::cxx_epilogue_specified_ >; + _cli_options_map_["--schema-epilogue"] = + &::cli::thunk< options, database_map<std::vector<std::string> >, &options::schema_epilogue_, + &options::schema_epilogue_specified_ >; + _cli_options_map_["--sql-epilogue"] = + &::cli::thunk< options, database_map<std::vector<std::string> >, &options::sql_epilogue_, + &options::sql_epilogue_specified_ >; + _cli_options_map_["--migration-epilogue"] = + &::cli::thunk< options, database_map<std::vector<std::string> >, &options::migration_epilogue_, + &options::migration_epilogue_specified_ >; + _cli_options_map_["--hxx-prologue-file"] = + &::cli::thunk< options, database_map<std::vector<std::string> >, &options::hxx_prologue_file_, + &options::hxx_prologue_file_specified_ >; + _cli_options_map_["--ixx-prologue-file"] = + &::cli::thunk< options, database_map<std::vector<std::string> >, &options::ixx_prologue_file_, + &options::ixx_prologue_file_specified_ >; + _cli_options_map_["--cxx-prologue-file"] = + &::cli::thunk< options, database_map<std::vector<std::string> >, &options::cxx_prologue_file_, + &options::cxx_prologue_file_specified_ >; + _cli_options_map_["--schema-prologue-file"] = + &::cli::thunk< options, database_map<std::vector<std::string> >, &options::schema_prologue_file_, + &options::schema_prologue_file_specified_ >; + _cli_options_map_["--sql-prologue-file"] = + &::cli::thunk< options, database_map<std::vector<std::string> >, &options::sql_prologue_file_, + &options::sql_prologue_file_specified_ >; + _cli_options_map_["--migration-prologue-file"] = + &::cli::thunk< options, database_map<std::vector<std::string> >, &options::migration_prologue_file_, + &options::migration_prologue_file_specified_ >; + _cli_options_map_["--sql-interlude-file"] = + &::cli::thunk< options, database_map<std::vector<std::string> >, &options::sql_interlude_file_, + &options::sql_interlude_file_specified_ >; + _cli_options_map_["--hxx-epilogue-file"] = + &::cli::thunk< options, database_map<std::vector<std::string> >, &options::hxx_epilogue_file_, + &options::hxx_epilogue_file_specified_ >; + _cli_options_map_["--ixx-epilogue-file"] = + &::cli::thunk< options, database_map<std::vector<std::string> >, &options::ixx_epilogue_file_, + &options::ixx_epilogue_file_specified_ >; + _cli_options_map_["--cxx-epilogue-file"] = + &::cli::thunk< options, database_map<std::vector<std::string> >, &options::cxx_epilogue_file_, + &options::cxx_epilogue_file_specified_ >; + _cli_options_map_["--schema-epilogue-file"] = + &::cli::thunk< options, database_map<std::vector<std::string> >, &options::schema_epilogue_file_, + &options::schema_epilogue_file_specified_ >; + _cli_options_map_["--sql-epilogue-file"] = + &::cli::thunk< options, database_map<std::vector<std::string> >, &options::sql_epilogue_file_, + &options::sql_epilogue_file_specified_ >; + _cli_options_map_["--migration-epilogue-file"] = + &::cli::thunk< options, database_map<std::vector<std::string> >, &options::migration_epilogue_file_, + &options::migration_epilogue_file_specified_ >; + _cli_options_map_["--odb-prologue"] = + &::cli::thunk< options, database_map<std::vector<std::string> >, &options::odb_prologue_, + &options::odb_prologue_specified_ >; + _cli_options_map_["--odb-prologue-file"] = + &::cli::thunk< options, database_map<std::vector<std::string> >, &options::odb_prologue_file_, + &options::odb_prologue_file_specified_ >; + _cli_options_map_["--odb-epilogue"] = + &::cli::thunk< options, database_map<std::vector<std::string> >, &options::odb_epilogue_, + &options::odb_epilogue_specified_ >; + _cli_options_map_["--odb-epilogue-file"] = + &::cli::thunk< options, database_map<std::vector<std::string> >, &options::odb_epilogue_file_, + &options::odb_epilogue_file_specified_ >; + _cli_options_map_["--table-prefix"] = + &::cli::thunk< options, database_map<std::string>, &options::table_prefix_, + &options::table_prefix_specified_ >; + _cli_options_map_["--index-suffix"] = + &::cli::thunk< options, database_map<std::string>, &options::index_suffix_, + &options::index_suffix_specified_ >; + _cli_options_map_["--fkey-suffix"] = + &::cli::thunk< options, database_map<std::string>, &options::fkey_suffix_, + &options::fkey_suffix_specified_ >; + _cli_options_map_["--sequence-suffix"] = + &::cli::thunk< options, database_map<std::string>, &options::sequence_suffix_, + &options::sequence_suffix_specified_ >; + _cli_options_map_["--sql-name-case"] = + &::cli::thunk< options, database_map<name_case>, &options::sql_name_case_, + &options::sql_name_case_specified_ >; + _cli_options_map_["--table-regex"] = + &::cli::thunk< options, database_map<std::vector<std::string> >, &options::table_regex_, + &options::table_regex_specified_ >; + _cli_options_map_["--column-regex"] = + &::cli::thunk< options, database_map<std::vector<std::string> >, &options::column_regex_, + &options::column_regex_specified_ >; + _cli_options_map_["--index-regex"] = + &::cli::thunk< options, database_map<std::vector<std::string> >, &options::index_regex_, + &options::index_regex_specified_ >; + _cli_options_map_["--fkey-regex"] = + &::cli::thunk< options, database_map<std::vector<std::string> >, &options::fkey_regex_, + &options::fkey_regex_specified_ >; + _cli_options_map_["--sequence-regex"] = + &::cli::thunk< options, database_map<std::vector<std::string> >, &options::sequence_regex_, + &options::sequence_regex_specified_ >; + _cli_options_map_["--statement-regex"] = + &::cli::thunk< options, database_map<std::vector<std::string> >, &options::statement_regex_, + &options::statement_regex_specified_ >; + _cli_options_map_["--sql-name-regex"] = + &::cli::thunk< options, database_map<std::vector<std::string> >, &options::sql_name_regex_, + &options::sql_name_regex_specified_ >; + _cli_options_map_["--sql-name-regex-trace"] = + &::cli::thunk< options, &options::sql_name_regex_trace_ >; + _cli_options_map_["--accessor-regex"] = + &::cli::thunk< options, std::vector<std::string>, &options::accessor_regex_, + &options::accessor_regex_specified_ >; + _cli_options_map_["--accessor-regex-trace"] = + &::cli::thunk< options, &options::accessor_regex_trace_ >; + _cli_options_map_["--modifier-regex"] = + &::cli::thunk< options, std::vector<std::string>, &options::modifier_regex_, + &options::modifier_regex_specified_ >; + _cli_options_map_["--modifier-regex-trace"] = + &::cli::thunk< options, &options::modifier_regex_trace_ >; + _cli_options_map_["--include-with-brackets"] = + &::cli::thunk< options, &options::include_with_brackets_ >; + _cli_options_map_["--include-prefix"] = + &::cli::thunk< options, std::string, &options::include_prefix_, + &options::include_prefix_specified_ >; + _cli_options_map_["--include-regex"] = + &::cli::thunk< options, std::vector<std::string>, &options::include_regex_, + &options::include_regex_specified_ >; + _cli_options_map_["--include-regex-trace"] = + &::cli::thunk< options, &options::include_regex_trace_ >; + _cli_options_map_["--guard-prefix"] = + &::cli::thunk< options, std::string, &options::guard_prefix_, + &options::guard_prefix_specified_ >; + _cli_options_map_["--show-sloc"] = + &::cli::thunk< options, &options::show_sloc_ >; + _cli_options_map_["--sloc-limit"] = + &::cli::thunk< options, std::size_t, &options::sloc_limit_, + &options::sloc_limit_specified_ >; + _cli_options_map_["--options-file"] = + &::cli::thunk< options, std::string, &options::options_file_, + &options::options_file_specified_ >; + _cli_options_map_["-x"] = + &::cli::thunk< options, std::vector<std::string>, &options::x_, + &options::x_specified_ >; + _cli_options_map_["-v"] = + &::cli::thunk< options, &options::v_ >; + _cli_options_map_["--trace"] = + &::cli::thunk< options, &options::trace_ >; + _cli_options_map_["--mysql-engine"] = + &::cli::thunk< options, std::string, &options::mysql_engine_, + &options::mysql_engine_specified_ >; + _cli_options_map_["--sqlite-override-null"] = + &::cli::thunk< options, &options::sqlite_override_null_ >; + _cli_options_map_["--sqlite-lax-auto-id"] = + &::cli::thunk< options, &options::sqlite_lax_auto_id_ >; + _cli_options_map_["--pgsql-server-version"] = + &::cli::thunk< options, ::pgsql_version, &options::pgsql_server_version_, + &options::pgsql_server_version_specified_ >; + _cli_options_map_["--oracle-client-version"] = + &::cli::thunk< options, ::oracle_version, &options::oracle_client_version_, + &options::oracle_client_version_specified_ >; + _cli_options_map_["--oracle-warn-truncation"] = + &::cli::thunk< options, &options::oracle_warn_truncation_ >; + _cli_options_map_["--mssql-server-version"] = + &::cli::thunk< options, ::mssql_version, &options::mssql_server_version_, + &options::mssql_server_version_specified_ >; + _cli_options_map_["--mssql-short-limit"] = + &::cli::thunk< options, unsigned int, &options::mssql_short_limit_, + &options::mssql_short_limit_specified_ >; + } +}; + +static _cli_options_map_init _cli_options_map_init_; + +bool options:: +_parse (const char* o, ::cli::scanner& s) +{ + _cli_options_map::const_iterator i (_cli_options_map_.find (o)); + + if (i != _cli_options_map_.end ()) + { + (*(i->second)) (*this, s); + return true; + } + + return false; +} + +bool options:: +_parse (::cli::scanner& s, + ::cli::unknown_mode opt_mode, + ::cli::unknown_mode arg_mode) +{ + // Can't skip combined flags (--no-combined-flags). + // + assert (opt_mode != ::cli::unknown_mode::skip); + + bool r = false; + bool opt = true; + + while (s.more ()) + { + const char* o = s.peek (); + + if (std::strcmp (o, "--") == 0) + { + opt = false; + s.skip (); + r = true; + continue; + } + + if (opt) + { + if (_parse (o, s)) + { + r = true; + continue; + } + + if (std::strncmp (o, "-", 1) == 0 && o[1] != '\0') + { + // Handle combined option values. + // + std::string co; + if (const char* v = std::strchr (o, '=')) + { + co.assign (o, 0, v - o); + ++v; + + int ac (2); + char* av[] = + { + const_cast<char*> (co.c_str ()), + const_cast<char*> (v) + }; + + ::cli::argv_scanner ns (0, ac, av); + + if (_parse (co.c_str (), ns)) + { + // Parsed the option but not its value? + // + if (ns.end () != 2) + throw ::cli::invalid_value (co, v); + + s.next (); + r = true; + continue; + } + else + { + // Set the unknown option and fall through. + // + o = co.c_str (); + } + } + + // Handle combined flags. + // + char cf[3]; + { + const char* p = o + 1; + for (; *p != '\0'; ++p) + { + if (!((*p >= 'a' && *p <= 'z') || + (*p >= 'A' && *p <= 'Z') || + (*p >= '0' && *p <= '9'))) + break; + } + + if (*p == '\0') + { + for (p = o + 1; *p != '\0'; ++p) + { + std::strcpy (cf, "-"); + cf[1] = *p; + cf[2] = '\0'; + + int ac (1); + char* av[] = + { + cf + }; + + ::cli::argv_scanner ns (0, ac, av); + + if (!_parse (cf, ns)) + break; + } + + if (*p == '\0') + { + // All handled. + // + s.next (); + r = true; + continue; + } + else + { + // Set the unknown option and fall through. + // + o = cf; + } + } + } + + switch (opt_mode) + { + case ::cli::unknown_mode::skip: + { + s.skip (); + r = true; + continue; + } + case ::cli::unknown_mode::stop: + { + break; + } + case ::cli::unknown_mode::fail: + { + throw ::cli::unknown_option (o); + } + } + + break; + } + } + + switch (arg_mode) + { + case ::cli::unknown_mode::skip: + { + s.skip (); + r = true; + continue; + } + case ::cli::unknown_mode::stop: + { + break; + } + case ::cli::unknown_mode::fail: + { + throw ::cli::unknown_argument (o); + } + } + + break; + } + + return r; +} + +// Begin epilogue. +// +// +// End epilogue. + diff --git a/odb/odb/pregenerated/odb/options.hxx b/odb/odb/pregenerated/odb/options.hxx new file mode 100644 index 0000000..74406a0 --- /dev/null +++ b/odb/odb/pregenerated/odb/options.hxx @@ -0,0 +1,2340 @@ +// -*- C++ -*- +// +// This file was generated by CLI, a command line interface +// compiler for C++. +// + +#ifndef ODB_OPTIONS_HXX +#define ODB_OPTIONS_HXX + +// Begin prologue. +// +// +// End prologue. + +#include <list> +#include <deque> +#include <map> +#include <vector> +#include <iosfwd> +#include <string> +#include <cstddef> +#include <exception> + +#ifndef CLI_POTENTIALLY_UNUSED +# if defined(_MSC_VER) || defined(__xlC__) +# define CLI_POTENTIALLY_UNUSED(x) (void*)&x +# else +# define CLI_POTENTIALLY_UNUSED(x) (void)x +# endif +#endif + +namespace cli +{ + class usage_para + { + public: + enum value + { + none, + text, + option + }; + + usage_para (value); + + operator value () const + { + return v_; + } + + private: + value v_; + }; + + class unknown_mode + { + public: + enum value + { + skip, + stop, + fail + }; + + unknown_mode (value); + + operator value () const + { + return v_; + } + + private: + value v_; + }; + + // Exceptions. + // + + class exception: public std::exception + { + public: + virtual void + print (::std::ostream&) const = 0; + }; + + ::std::ostream& + operator<< (::std::ostream&, const exception&); + + class unknown_option: public exception + { + public: + virtual + ~unknown_option () throw (); + + unknown_option (const std::string& option); + + const std::string& + option () const; + + virtual void + print (::std::ostream&) const; + + virtual const char* + what () const throw (); + + private: + std::string option_; + }; + + class unknown_argument: public exception + { + public: + virtual + ~unknown_argument () throw (); + + unknown_argument (const std::string& argument); + + const std::string& + argument () const; + + virtual void + print (::std::ostream&) const; + + virtual const char* + what () const throw (); + + private: + std::string argument_; + }; + + class missing_value: public exception + { + public: + virtual + ~missing_value () throw (); + + missing_value (const std::string& option); + + const std::string& + option () const; + + virtual void + print (::std::ostream&) const; + + virtual const char* + what () const throw (); + + private: + std::string option_; + }; + + class invalid_value: public exception + { + public: + virtual + ~invalid_value () throw (); + + invalid_value (const std::string& option, + const std::string& value, + const std::string& message = std::string ()); + + const std::string& + option () const; + + const std::string& + value () const; + + const std::string& + message () const; + + virtual void + print (::std::ostream&) const; + + virtual const char* + what () const throw (); + + private: + std::string option_; + std::string value_; + std::string message_; + }; + + class eos_reached: public exception + { + public: + virtual void + print (::std::ostream&) const; + + virtual const char* + what () const throw (); + }; + + class file_io_failure: public exception + { + public: + virtual + ~file_io_failure () throw (); + + file_io_failure (const std::string& file); + + const std::string& + file () const; + + virtual void + print (::std::ostream&) const; + + virtual const char* + what () const throw (); + + private: + std::string file_; + }; + + class unmatched_quote: public exception + { + public: + virtual + ~unmatched_quote () throw (); + + unmatched_quote (const std::string& argument); + + const std::string& + argument () const; + + virtual void + print (::std::ostream&) const; + + virtual const char* + what () const throw (); + + private: + std::string argument_; + }; + + // Command line argument scanner interface. + // + // The values returned by next() are guaranteed to be valid + // for the two previous arguments up until a call to a third + // peek() or next(). + // + // The position() function returns a monotonically-increasing + // number which, if stored, can later be used to determine the + // relative position of the argument returned by the following + // call to next(). Note that if multiple scanners are used to + // extract arguments from multiple sources, then the end + // position of the previous scanner should be used as the + // start position of the next. + // + class scanner + { + public: + virtual + ~scanner (); + + virtual bool + more () = 0; + + virtual const char* + peek () = 0; + + virtual const char* + next () = 0; + + virtual void + skip () = 0; + + virtual std::size_t + position () = 0; + }; + + class argv_scanner: public scanner + { + public: + argv_scanner (int& argc, + char** argv, + bool erase = false, + std::size_t start_position = 0); + + argv_scanner (int start, + int& argc, + char** argv, + bool erase = false, + std::size_t start_position = 0); + + int + end () const; + + virtual bool + more (); + + virtual const char* + peek (); + + virtual const char* + next (); + + virtual void + skip (); + + virtual std::size_t + position (); + + protected: + std::size_t start_position_; + int i_; + int& argc_; + char** argv_; + bool erase_; + }; + + class argv_file_scanner: public argv_scanner + { + public: + argv_file_scanner (int& argc, + char** argv, + const std::string& option, + bool erase = false, + std::size_t start_position = 0); + + argv_file_scanner (int start, + int& argc, + char** argv, + const std::string& option, + bool erase = false, + std::size_t start_position = 0); + + argv_file_scanner (const std::string& file, + const std::string& option, + std::size_t start_position = 0); + + struct option_info + { + // If search_func is not NULL, it is called, with the arg + // value as the second argument, to locate the options file. + // If it returns an empty string, then the file is ignored. + // + const char* option; + std::string (*search_func) (const char*, void* arg); + void* arg; + }; + + argv_file_scanner (int& argc, + char** argv, + const option_info* options, + std::size_t options_count, + bool erase = false, + std::size_t start_position = 0); + + argv_file_scanner (int start, + int& argc, + char** argv, + const option_info* options, + std::size_t options_count, + bool erase = false, + std::size_t start_position = 0); + + argv_file_scanner (const std::string& file, + const option_info* options = 0, + std::size_t options_count = 0, + std::size_t start_position = 0); + + virtual bool + more (); + + virtual const char* + peek (); + + virtual const char* + next (); + + virtual void + skip (); + + virtual std::size_t + position (); + + // Return the file path if the peeked at argument came from a file and + // the empty string otherwise. The reference is guaranteed to be valid + // till the end of the scanner lifetime. + // + const std::string& + peek_file (); + + // Return the 1-based line number if the peeked at argument came from + // a file and zero otherwise. + // + std::size_t + peek_line (); + + private: + const option_info* + find (const char*) const; + + void + load (const std::string& file); + + typedef argv_scanner base; + + const std::string option_; + option_info option_info_; + const option_info* options_; + std::size_t options_count_; + + struct arg + { + std::string value; + const std::string* file; + std::size_t line; + }; + + std::deque<arg> args_; + std::list<std::string> files_; + + // Circular buffer of two arguments. + // + std::string hold_[2]; + std::size_t i_; + + bool skip_; + + static int zero_argc_; + static std::string empty_string_; + }; + + typedef std::vector<std::string> option_names; + + class option + { + public: + + const std::string& + name () const; + + const option_names& + aliases () const; + + bool + flag () const; + + const std::string& + default_value () const; + + public:option (); + option (const std::string& name, + const option_names& aliases, + bool flag, + const std::string& default_value); + + private: + std::string name_; + option_names aliases_; + bool flag_; + std::string default_value_; + }; + + class options: public std::vector<option> + { + public: + typedef std::vector<option> container_type; + + container_type::const_iterator + find (const std::string& name) const; + + void + push_back (const option&); + private: + typedef std::map<std::string, container_type::size_type> map_type; + map_type map_; + }; + + template <typename X> + struct parser; +} + +#include <set> + +#include <vector> + +#include <string> + +#include <cstddef> + +#include <cstdint> + +#include <odb/option-types.hxx> + +class options +{ + public: + options (); + + options (int& argc, + char** argv, + bool erase = false, + ::cli::unknown_mode option = ::cli::unknown_mode::fail, + ::cli::unknown_mode argument = ::cli::unknown_mode::stop); + + options (int start, + int& argc, + char** argv, + bool erase = false, + ::cli::unknown_mode option = ::cli::unknown_mode::fail, + ::cli::unknown_mode argument = ::cli::unknown_mode::stop); + + options (int& argc, + char** argv, + int& end, + bool erase = false, + ::cli::unknown_mode option = ::cli::unknown_mode::fail, + ::cli::unknown_mode argument = ::cli::unknown_mode::stop); + + options (int start, + int& argc, + char** argv, + int& end, + bool erase = false, + ::cli::unknown_mode option = ::cli::unknown_mode::fail, + ::cli::unknown_mode argument = ::cli::unknown_mode::stop); + + options (::cli::scanner&, + ::cli::unknown_mode option = ::cli::unknown_mode::fail, + ::cli::unknown_mode argument = ::cli::unknown_mode::stop); + + // Option accessors and modifiers. + // + const std::uint64_t& + build2_metadata () const; + + std::uint64_t& + build2_metadata (); + + void + build2_metadata (const std::uint64_t&); + + bool + build2_metadata_specified () const; + + void + build2_metadata_specified (bool); + + const bool& + help () const; + + bool& + help (); + + void + help (const bool&); + + const bool& + version () const; + + bool& + version (); + + void + version (const bool&); + + const std::vector<std::string>& + I () const; + + std::vector<std::string>& + I (); + + void + I (const std::vector<std::string>&); + + bool + I_specified () const; + + void + I_specified (bool); + + const std::vector<std::string>& + D () const; + + std::vector<std::string>& + D (); + + void + D (const std::vector<std::string>&); + + bool + D_specified () const; + + void + D_specified (bool); + + const std::vector<std::string>& + U () const; + + std::vector<std::string>& + U (); + + void + U (const std::vector<std::string>&); + + bool + U_specified () const; + + void + U_specified (bool); + + const std::vector< ::database>& + database () const; + + std::vector< ::database>& + database (); + + void + database (const std::vector< ::database>&); + + bool + database_specified () const; + + void + database_specified (bool); + + const ::multi_database& + multi_database () const; + + ::multi_database& + multi_database (); + + void + multi_database (const ::multi_database&); + + bool + multi_database_specified () const; + + void + multi_database_specified (bool); + + const ::database& + default_database () const; + + ::database& + default_database (); + + void + default_database (const ::database&); + + bool + default_database_specified () const; + + void + default_database_specified (bool); + + const bool& + generate_query () const; + + bool& + generate_query (); + + void + generate_query (const bool&); + + const bool& + generate_prepared () const; + + bool& + generate_prepared (); + + void + generate_prepared (const bool&); + + const bool& + omit_unprepared () const; + + bool& + omit_unprepared (); + + void + omit_unprepared (const bool&); + + const bool& + generate_session () const; + + bool& + generate_session (); + + void + generate_session (const bool&); + + const bool& + generate_schema () const; + + bool& + generate_schema (); + + void + generate_schema (const bool&); + + const bool& + generate_schema_only () const; + + bool& + generate_schema_only (); + + void + generate_schema_only (const bool&); + + const bool& + suppress_migration () const; + + bool& + suppress_migration (); + + void + suppress_migration (const bool&); + + const bool& + suppress_schema_version () const; + + bool& + suppress_schema_version (); + + void + suppress_schema_version (const bool&); + + const database_map<qname>& + schema_version_table () const; + + database_map<qname>& + schema_version_table (); + + void + schema_version_table (const database_map<qname>&); + + bool + schema_version_table_specified () const; + + void + schema_version_table_specified (bool); + + const database_map<std::set< ::schema_format> >& + schema_format () const; + + database_map<std::set< ::schema_format> >& + schema_format (); + + void + schema_format (const database_map<std::set< ::schema_format> >&); + + bool + schema_format_specified () const; + + void + schema_format_specified (bool); + + const bool& + omit_drop () const; + + bool& + omit_drop (); + + void + omit_drop (const bool&); + + const bool& + omit_create () const; + + bool& + omit_create (); + + void + omit_create (const bool&); + + const database_map<std::string>& + schema_name () const; + + database_map<std::string>& + schema_name (); + + void + schema_name (const database_map<std::string>&); + + bool + schema_name_specified () const; + + void + schema_name_specified (bool); + + const database_map<deferrable>& + fkeys_deferrable_mode () const; + + database_map<deferrable>& + fkeys_deferrable_mode (); + + void + fkeys_deferrable_mode (const database_map<deferrable>&); + + bool + fkeys_deferrable_mode_specified () const; + + void + fkeys_deferrable_mode_specified (bool); + + const std::string& + default_pointer () const; + + std::string& + default_pointer (); + + void + default_pointer (const std::string&); + + bool + default_pointer_specified () const; + + void + default_pointer_specified (bool); + + const std::string& + session_type () const; + + std::string& + session_type (); + + void + session_type (const std::string&); + + bool + session_type_specified () const; + + void + session_type_specified (bool); + + const std::string& + profile () const; + + std::string& + profile (); + + void + profile (const std::string&); + + bool + profile_specified () const; + + void + profile_specified (bool); + + const bool& + at_once () const; + + bool& + at_once (); + + void + at_once (const bool&); + + const database_map<qname>& + schema () const; + + database_map<qname>& + schema (); + + void + schema (const database_map<qname>&); + + bool + schema_specified () const; + + void + schema_specified (bool); + + const database_map<std::string>& + export_symbol () const; + + database_map<std::string>& + export_symbol (); + + void + export_symbol (const database_map<std::string>&); + + bool + export_symbol_specified () const; + + void + export_symbol_specified (bool); + + const database_map<std::string>& + extern_symbol () const; + + database_map<std::string>& + extern_symbol (); + + void + extern_symbol (const database_map<std::string>&); + + bool + extern_symbol_specified () const; + + void + extern_symbol_specified (bool); + + const cxx_version& + std () const; + + cxx_version& + std (); + + void + std (const cxx_version&); + + bool + std_specified () const; + + void + std_specified (bool); + + const bool& + warn_hard_add () const; + + bool& + warn_hard_add (); + + void + warn_hard_add (const bool&); + + const bool& + warn_hard_delete () const; + + bool& + warn_hard_delete (); + + void + warn_hard_delete (const bool&); + + const bool& + warn_hard () const; + + bool& + warn_hard (); + + void + warn_hard (const bool&); + + const std::string& + output_dir () const; + + std::string& + output_dir (); + + void + output_dir (const std::string&); + + bool + output_dir_specified () const; + + void + output_dir_specified (bool); + + const std::string& + input_name () const; + + std::string& + input_name (); + + void + input_name (const std::string&); + + bool + input_name_specified () const; + + void + input_name_specified (bool); + + const database_map<std::string>& + changelog () const; + + database_map<std::string>& + changelog (); + + void + changelog (const database_map<std::string>&); + + bool + changelog_specified () const; + + void + changelog_specified (bool); + + const database_map<std::string>& + changelog_in () const; + + database_map<std::string>& + changelog_in (); + + void + changelog_in (const database_map<std::string>&); + + bool + changelog_in_specified () const; + + void + changelog_in_specified (bool); + + const database_map<std::string>& + changelog_out () const; + + database_map<std::string>& + changelog_out (); + + void + changelog_out (const database_map<std::string>&); + + bool + changelog_out_specified () const; + + void + changelog_out_specified (bool); + + const database_map<std::string>& + changelog_dir () const; + + database_map<std::string>& + changelog_dir (); + + void + changelog_dir (const database_map<std::string>&); + + bool + changelog_dir_specified () const; + + void + changelog_dir_specified (bool); + + const bool& + init_changelog () const; + + bool& + init_changelog (); + + void + init_changelog (const bool&); + + const database_map<std::string>& + odb_file_suffix () const; + + database_map<std::string>& + odb_file_suffix (); + + void + odb_file_suffix (const database_map<std::string>&); + + bool + odb_file_suffix_specified () const; + + void + odb_file_suffix_specified (bool); + + const database_map<std::string>& + sql_file_suffix () const; + + database_map<std::string>& + sql_file_suffix (); + + void + sql_file_suffix (const database_map<std::string>&); + + bool + sql_file_suffix_specified () const; + + void + sql_file_suffix_specified (bool); + + const database_map<std::string>& + schema_file_suffix () const; + + database_map<std::string>& + schema_file_suffix (); + + void + schema_file_suffix (const database_map<std::string>&); + + bool + schema_file_suffix_specified () const; + + void + schema_file_suffix_specified (bool); + + const database_map<std::string>& + changelog_file_suffix () const; + + database_map<std::string>& + changelog_file_suffix (); + + void + changelog_file_suffix (const database_map<std::string>&); + + bool + changelog_file_suffix_specified () const; + + void + changelog_file_suffix_specified (bool); + + const std::string& + hxx_suffix () const; + + std::string& + hxx_suffix (); + + void + hxx_suffix (const std::string&); + + bool + hxx_suffix_specified () const; + + void + hxx_suffix_specified (bool); + + const std::string& + ixx_suffix () const; + + std::string& + ixx_suffix (); + + void + ixx_suffix (const std::string&); + + bool + ixx_suffix_specified () const; + + void + ixx_suffix_specified (bool); + + const std::string& + cxx_suffix () const; + + std::string& + cxx_suffix (); + + void + cxx_suffix (const std::string&); + + bool + cxx_suffix_specified () const; + + void + cxx_suffix_specified (bool); + + const std::string& + sql_suffix () const; + + std::string& + sql_suffix (); + + void + sql_suffix (const std::string&); + + bool + sql_suffix_specified () const; + + void + sql_suffix_specified (bool); + + const std::string& + changelog_suffix () const; + + std::string& + changelog_suffix (); + + void + changelog_suffix (const std::string&); + + bool + changelog_suffix_specified () const; + + void + changelog_suffix_specified (bool); + + const database_map<std::vector<std::string> >& + hxx_prologue () const; + + database_map<std::vector<std::string> >& + hxx_prologue (); + + void + hxx_prologue (const database_map<std::vector<std::string> >&); + + bool + hxx_prologue_specified () const; + + void + hxx_prologue_specified (bool); + + const database_map<std::vector<std::string> >& + ixx_prologue () const; + + database_map<std::vector<std::string> >& + ixx_prologue (); + + void + ixx_prologue (const database_map<std::vector<std::string> >&); + + bool + ixx_prologue_specified () const; + + void + ixx_prologue_specified (bool); + + const database_map<std::vector<std::string> >& + cxx_prologue () const; + + database_map<std::vector<std::string> >& + cxx_prologue (); + + void + cxx_prologue (const database_map<std::vector<std::string> >&); + + bool + cxx_prologue_specified () const; + + void + cxx_prologue_specified (bool); + + const database_map<std::vector<std::string> >& + schema_prologue () const; + + database_map<std::vector<std::string> >& + schema_prologue (); + + void + schema_prologue (const database_map<std::vector<std::string> >&); + + bool + schema_prologue_specified () const; + + void + schema_prologue_specified (bool); + + const database_map<std::vector<std::string> >& + sql_prologue () const; + + database_map<std::vector<std::string> >& + sql_prologue (); + + void + sql_prologue (const database_map<std::vector<std::string> >&); + + bool + sql_prologue_specified () const; + + void + sql_prologue_specified (bool); + + const database_map<std::vector<std::string> >& + migration_prologue () const; + + database_map<std::vector<std::string> >& + migration_prologue (); + + void + migration_prologue (const database_map<std::vector<std::string> >&); + + bool + migration_prologue_specified () const; + + void + migration_prologue_specified (bool); + + const database_map<std::vector<std::string> >& + sql_interlude () const; + + database_map<std::vector<std::string> >& + sql_interlude (); + + void + sql_interlude (const database_map<std::vector<std::string> >&); + + bool + sql_interlude_specified () const; + + void + sql_interlude_specified (bool); + + const database_map<std::vector<std::string> >& + hxx_epilogue () const; + + database_map<std::vector<std::string> >& + hxx_epilogue (); + + void + hxx_epilogue (const database_map<std::vector<std::string> >&); + + bool + hxx_epilogue_specified () const; + + void + hxx_epilogue_specified (bool); + + const database_map<std::vector<std::string> >& + ixx_epilogue () const; + + database_map<std::vector<std::string> >& + ixx_epilogue (); + + void + ixx_epilogue (const database_map<std::vector<std::string> >&); + + bool + ixx_epilogue_specified () const; + + void + ixx_epilogue_specified (bool); + + const database_map<std::vector<std::string> >& + cxx_epilogue () const; + + database_map<std::vector<std::string> >& + cxx_epilogue (); + + void + cxx_epilogue (const database_map<std::vector<std::string> >&); + + bool + cxx_epilogue_specified () const; + + void + cxx_epilogue_specified (bool); + + const database_map<std::vector<std::string> >& + schema_epilogue () const; + + database_map<std::vector<std::string> >& + schema_epilogue (); + + void + schema_epilogue (const database_map<std::vector<std::string> >&); + + bool + schema_epilogue_specified () const; + + void + schema_epilogue_specified (bool); + + const database_map<std::vector<std::string> >& + sql_epilogue () const; + + database_map<std::vector<std::string> >& + sql_epilogue (); + + void + sql_epilogue (const database_map<std::vector<std::string> >&); + + bool + sql_epilogue_specified () const; + + void + sql_epilogue_specified (bool); + + const database_map<std::vector<std::string> >& + migration_epilogue () const; + + database_map<std::vector<std::string> >& + migration_epilogue (); + + void + migration_epilogue (const database_map<std::vector<std::string> >&); + + bool + migration_epilogue_specified () const; + + void + migration_epilogue_specified (bool); + + const database_map<std::vector<std::string> >& + hxx_prologue_file () const; + + database_map<std::vector<std::string> >& + hxx_prologue_file (); + + void + hxx_prologue_file (const database_map<std::vector<std::string> >&); + + bool + hxx_prologue_file_specified () const; + + void + hxx_prologue_file_specified (bool); + + const database_map<std::vector<std::string> >& + ixx_prologue_file () const; + + database_map<std::vector<std::string> >& + ixx_prologue_file (); + + void + ixx_prologue_file (const database_map<std::vector<std::string> >&); + + bool + ixx_prologue_file_specified () const; + + void + ixx_prologue_file_specified (bool); + + const database_map<std::vector<std::string> >& + cxx_prologue_file () const; + + database_map<std::vector<std::string> >& + cxx_prologue_file (); + + void + cxx_prologue_file (const database_map<std::vector<std::string> >&); + + bool + cxx_prologue_file_specified () const; + + void + cxx_prologue_file_specified (bool); + + const database_map<std::vector<std::string> >& + schema_prologue_file () const; + + database_map<std::vector<std::string> >& + schema_prologue_file (); + + void + schema_prologue_file (const database_map<std::vector<std::string> >&); + + bool + schema_prologue_file_specified () const; + + void + schema_prologue_file_specified (bool); + + const database_map<std::vector<std::string> >& + sql_prologue_file () const; + + database_map<std::vector<std::string> >& + sql_prologue_file (); + + void + sql_prologue_file (const database_map<std::vector<std::string> >&); + + bool + sql_prologue_file_specified () const; + + void + sql_prologue_file_specified (bool); + + const database_map<std::vector<std::string> >& + migration_prologue_file () const; + + database_map<std::vector<std::string> >& + migration_prologue_file (); + + void + migration_prologue_file (const database_map<std::vector<std::string> >&); + + bool + migration_prologue_file_specified () const; + + void + migration_prologue_file_specified (bool); + + const database_map<std::vector<std::string> >& + sql_interlude_file () const; + + database_map<std::vector<std::string> >& + sql_interlude_file (); + + void + sql_interlude_file (const database_map<std::vector<std::string> >&); + + bool + sql_interlude_file_specified () const; + + void + sql_interlude_file_specified (bool); + + const database_map<std::vector<std::string> >& + hxx_epilogue_file () const; + + database_map<std::vector<std::string> >& + hxx_epilogue_file (); + + void + hxx_epilogue_file (const database_map<std::vector<std::string> >&); + + bool + hxx_epilogue_file_specified () const; + + void + hxx_epilogue_file_specified (bool); + + const database_map<std::vector<std::string> >& + ixx_epilogue_file () const; + + database_map<std::vector<std::string> >& + ixx_epilogue_file (); + + void + ixx_epilogue_file (const database_map<std::vector<std::string> >&); + + bool + ixx_epilogue_file_specified () const; + + void + ixx_epilogue_file_specified (bool); + + const database_map<std::vector<std::string> >& + cxx_epilogue_file () const; + + database_map<std::vector<std::string> >& + cxx_epilogue_file (); + + void + cxx_epilogue_file (const database_map<std::vector<std::string> >&); + + bool + cxx_epilogue_file_specified () const; + + void + cxx_epilogue_file_specified (bool); + + const database_map<std::vector<std::string> >& + schema_epilogue_file () const; + + database_map<std::vector<std::string> >& + schema_epilogue_file (); + + void + schema_epilogue_file (const database_map<std::vector<std::string> >&); + + bool + schema_epilogue_file_specified () const; + + void + schema_epilogue_file_specified (bool); + + const database_map<std::vector<std::string> >& + sql_epilogue_file () const; + + database_map<std::vector<std::string> >& + sql_epilogue_file (); + + void + sql_epilogue_file (const database_map<std::vector<std::string> >&); + + bool + sql_epilogue_file_specified () const; + + void + sql_epilogue_file_specified (bool); + + const database_map<std::vector<std::string> >& + migration_epilogue_file () const; + + database_map<std::vector<std::string> >& + migration_epilogue_file (); + + void + migration_epilogue_file (const database_map<std::vector<std::string> >&); + + bool + migration_epilogue_file_specified () const; + + void + migration_epilogue_file_specified (bool); + + const database_map<std::vector<std::string> >& + odb_prologue () const; + + database_map<std::vector<std::string> >& + odb_prologue (); + + void + odb_prologue (const database_map<std::vector<std::string> >&); + + bool + odb_prologue_specified () const; + + void + odb_prologue_specified (bool); + + const database_map<std::vector<std::string> >& + odb_prologue_file () const; + + database_map<std::vector<std::string> >& + odb_prologue_file (); + + void + odb_prologue_file (const database_map<std::vector<std::string> >&); + + bool + odb_prologue_file_specified () const; + + void + odb_prologue_file_specified (bool); + + const database_map<std::vector<std::string> >& + odb_epilogue () const; + + database_map<std::vector<std::string> >& + odb_epilogue (); + + void + odb_epilogue (const database_map<std::vector<std::string> >&); + + bool + odb_epilogue_specified () const; + + void + odb_epilogue_specified (bool); + + const database_map<std::vector<std::string> >& + odb_epilogue_file () const; + + database_map<std::vector<std::string> >& + odb_epilogue_file (); + + void + odb_epilogue_file (const database_map<std::vector<std::string> >&); + + bool + odb_epilogue_file_specified () const; + + void + odb_epilogue_file_specified (bool); + + const database_map<std::string>& + table_prefix () const; + + database_map<std::string>& + table_prefix (); + + void + table_prefix (const database_map<std::string>&); + + bool + table_prefix_specified () const; + + void + table_prefix_specified (bool); + + const database_map<std::string>& + index_suffix () const; + + database_map<std::string>& + index_suffix (); + + void + index_suffix (const database_map<std::string>&); + + bool + index_suffix_specified () const; + + void + index_suffix_specified (bool); + + const database_map<std::string>& + fkey_suffix () const; + + database_map<std::string>& + fkey_suffix (); + + void + fkey_suffix (const database_map<std::string>&); + + bool + fkey_suffix_specified () const; + + void + fkey_suffix_specified (bool); + + const database_map<std::string>& + sequence_suffix () const; + + database_map<std::string>& + sequence_suffix (); + + void + sequence_suffix (const database_map<std::string>&); + + bool + sequence_suffix_specified () const; + + void + sequence_suffix_specified (bool); + + const database_map<name_case>& + sql_name_case () const; + + database_map<name_case>& + sql_name_case (); + + void + sql_name_case (const database_map<name_case>&); + + bool + sql_name_case_specified () const; + + void + sql_name_case_specified (bool); + + const database_map<std::vector<std::string> >& + table_regex () const; + + database_map<std::vector<std::string> >& + table_regex (); + + void + table_regex (const database_map<std::vector<std::string> >&); + + bool + table_regex_specified () const; + + void + table_regex_specified (bool); + + const database_map<std::vector<std::string> >& + column_regex () const; + + database_map<std::vector<std::string> >& + column_regex (); + + void + column_regex (const database_map<std::vector<std::string> >&); + + bool + column_regex_specified () const; + + void + column_regex_specified (bool); + + const database_map<std::vector<std::string> >& + index_regex () const; + + database_map<std::vector<std::string> >& + index_regex (); + + void + index_regex (const database_map<std::vector<std::string> >&); + + bool + index_regex_specified () const; + + void + index_regex_specified (bool); + + const database_map<std::vector<std::string> >& + fkey_regex () const; + + database_map<std::vector<std::string> >& + fkey_regex (); + + void + fkey_regex (const database_map<std::vector<std::string> >&); + + bool + fkey_regex_specified () const; + + void + fkey_regex_specified (bool); + + const database_map<std::vector<std::string> >& + sequence_regex () const; + + database_map<std::vector<std::string> >& + sequence_regex (); + + void + sequence_regex (const database_map<std::vector<std::string> >&); + + bool + sequence_regex_specified () const; + + void + sequence_regex_specified (bool); + + const database_map<std::vector<std::string> >& + statement_regex () const; + + database_map<std::vector<std::string> >& + statement_regex (); + + void + statement_regex (const database_map<std::vector<std::string> >&); + + bool + statement_regex_specified () const; + + void + statement_regex_specified (bool); + + const database_map<std::vector<std::string> >& + sql_name_regex () const; + + database_map<std::vector<std::string> >& + sql_name_regex (); + + void + sql_name_regex (const database_map<std::vector<std::string> >&); + + bool + sql_name_regex_specified () const; + + void + sql_name_regex_specified (bool); + + const bool& + sql_name_regex_trace () const; + + bool& + sql_name_regex_trace (); + + void + sql_name_regex_trace (const bool&); + + const std::vector<std::string>& + accessor_regex () const; + + std::vector<std::string>& + accessor_regex (); + + void + accessor_regex (const std::vector<std::string>&); + + bool + accessor_regex_specified () const; + + void + accessor_regex_specified (bool); + + const bool& + accessor_regex_trace () const; + + bool& + accessor_regex_trace (); + + void + accessor_regex_trace (const bool&); + + const std::vector<std::string>& + modifier_regex () const; + + std::vector<std::string>& + modifier_regex (); + + void + modifier_regex (const std::vector<std::string>&); + + bool + modifier_regex_specified () const; + + void + modifier_regex_specified (bool); + + const bool& + modifier_regex_trace () const; + + bool& + modifier_regex_trace (); + + void + modifier_regex_trace (const bool&); + + const bool& + include_with_brackets () const; + + bool& + include_with_brackets (); + + void + include_with_brackets (const bool&); + + const std::string& + include_prefix () const; + + std::string& + include_prefix (); + + void + include_prefix (const std::string&); + + bool + include_prefix_specified () const; + + void + include_prefix_specified (bool); + + const std::vector<std::string>& + include_regex () const; + + std::vector<std::string>& + include_regex (); + + void + include_regex (const std::vector<std::string>&); + + bool + include_regex_specified () const; + + void + include_regex_specified (bool); + + const bool& + include_regex_trace () const; + + bool& + include_regex_trace (); + + void + include_regex_trace (const bool&); + + const std::string& + guard_prefix () const; + + std::string& + guard_prefix (); + + void + guard_prefix (const std::string&); + + bool + guard_prefix_specified () const; + + void + guard_prefix_specified (bool); + + const bool& + show_sloc () const; + + bool& + show_sloc (); + + void + show_sloc (const bool&); + + const std::size_t& + sloc_limit () const; + + std::size_t& + sloc_limit (); + + void + sloc_limit (const std::size_t&); + + bool + sloc_limit_specified () const; + + void + sloc_limit_specified (bool); + + const std::string& + options_file () const; + + std::string& + options_file (); + + void + options_file (const std::string&); + + bool + options_file_specified () const; + + void + options_file_specified (bool); + + const std::vector<std::string>& + x () const; + + std::vector<std::string>& + x (); + + void + x (const std::vector<std::string>&); + + bool + x_specified () const; + + void + x_specified (bool); + + const bool& + v () const; + + bool& + v (); + + void + v (const bool&); + + const bool& + trace () const; + + bool& + trace (); + + void + trace (const bool&); + + const std::string& + mysql_engine () const; + + std::string& + mysql_engine (); + + void + mysql_engine (const std::string&); + + bool + mysql_engine_specified () const; + + void + mysql_engine_specified (bool); + + const bool& + sqlite_override_null () const; + + bool& + sqlite_override_null (); + + void + sqlite_override_null (const bool&); + + const bool& + sqlite_lax_auto_id () const; + + bool& + sqlite_lax_auto_id (); + + void + sqlite_lax_auto_id (const bool&); + + const ::pgsql_version& + pgsql_server_version () const; + + ::pgsql_version& + pgsql_server_version (); + + void + pgsql_server_version (const ::pgsql_version&); + + bool + pgsql_server_version_specified () const; + + void + pgsql_server_version_specified (bool); + + const ::oracle_version& + oracle_client_version () const; + + ::oracle_version& + oracle_client_version (); + + void + oracle_client_version (const ::oracle_version&); + + bool + oracle_client_version_specified () const; + + void + oracle_client_version_specified (bool); + + const bool& + oracle_warn_truncation () const; + + bool& + oracle_warn_truncation (); + + void + oracle_warn_truncation (const bool&); + + const ::mssql_version& + mssql_server_version () const; + + ::mssql_version& + mssql_server_version (); + + void + mssql_server_version (const ::mssql_version&); + + bool + mssql_server_version_specified () const; + + void + mssql_server_version_specified (bool); + + const unsigned int& + mssql_short_limit () const; + + unsigned int& + mssql_short_limit (); + + void + mssql_short_limit (const unsigned int&); + + bool + mssql_short_limit_specified () const; + + void + mssql_short_limit_specified (bool); + + // Print usage information. + // + static ::cli::usage_para + print_usage (::std::ostream&, + ::cli::usage_para = ::cli::usage_para::none); + + // Option description. + // + static const ::cli::options& + description (); + + // Implementation details. + // + protected: + friend struct _cli_options_desc_type; + + static void + fill (::cli::options&); + + bool + _parse (const char*, ::cli::scanner&); + + private: + bool + _parse (::cli::scanner&, + ::cli::unknown_mode option, + ::cli::unknown_mode argument); + + public: + std::uint64_t build2_metadata_; + bool build2_metadata_specified_; + bool help_; + bool version_; + std::vector<std::string> I_; + bool I_specified_; + std::vector<std::string> D_; + bool D_specified_; + std::vector<std::string> U_; + bool U_specified_; + std::vector< ::database> database_; + bool database_specified_; + ::multi_database multi_database_; + bool multi_database_specified_; + ::database default_database_; + bool default_database_specified_; + bool generate_query_; + bool generate_prepared_; + bool omit_unprepared_; + bool generate_session_; + bool generate_schema_; + bool generate_schema_only_; + bool suppress_migration_; + bool suppress_schema_version_; + database_map<qname> schema_version_table_; + bool schema_version_table_specified_; + database_map<std::set< ::schema_format> > schema_format_; + bool schema_format_specified_; + bool omit_drop_; + bool omit_create_; + database_map<std::string> schema_name_; + bool schema_name_specified_; + database_map<deferrable> fkeys_deferrable_mode_; + bool fkeys_deferrable_mode_specified_; + std::string default_pointer_; + bool default_pointer_specified_; + std::string session_type_; + bool session_type_specified_; + std::string profile_; + bool profile_specified_; + bool at_once_; + database_map<qname> schema_; + bool schema_specified_; + database_map<std::string> export_symbol_; + bool export_symbol_specified_; + database_map<std::string> extern_symbol_; + bool extern_symbol_specified_; + cxx_version std_; + bool std_specified_; + bool warn_hard_add_; + bool warn_hard_delete_; + bool warn_hard_; + std::string output_dir_; + bool output_dir_specified_; + std::string input_name_; + bool input_name_specified_; + database_map<std::string> changelog_; + bool changelog_specified_; + database_map<std::string> changelog_in_; + bool changelog_in_specified_; + database_map<std::string> changelog_out_; + bool changelog_out_specified_; + database_map<std::string> changelog_dir_; + bool changelog_dir_specified_; + bool init_changelog_; + database_map<std::string> odb_file_suffix_; + bool odb_file_suffix_specified_; + database_map<std::string> sql_file_suffix_; + bool sql_file_suffix_specified_; + database_map<std::string> schema_file_suffix_; + bool schema_file_suffix_specified_; + database_map<std::string> changelog_file_suffix_; + bool changelog_file_suffix_specified_; + std::string hxx_suffix_; + bool hxx_suffix_specified_; + std::string ixx_suffix_; + bool ixx_suffix_specified_; + std::string cxx_suffix_; + bool cxx_suffix_specified_; + std::string sql_suffix_; + bool sql_suffix_specified_; + std::string changelog_suffix_; + bool changelog_suffix_specified_; + database_map<std::vector<std::string> > hxx_prologue_; + bool hxx_prologue_specified_; + database_map<std::vector<std::string> > ixx_prologue_; + bool ixx_prologue_specified_; + database_map<std::vector<std::string> > cxx_prologue_; + bool cxx_prologue_specified_; + database_map<std::vector<std::string> > schema_prologue_; + bool schema_prologue_specified_; + database_map<std::vector<std::string> > sql_prologue_; + bool sql_prologue_specified_; + database_map<std::vector<std::string> > migration_prologue_; + bool migration_prologue_specified_; + database_map<std::vector<std::string> > sql_interlude_; + bool sql_interlude_specified_; + database_map<std::vector<std::string> > hxx_epilogue_; + bool hxx_epilogue_specified_; + database_map<std::vector<std::string> > ixx_epilogue_; + bool ixx_epilogue_specified_; + database_map<std::vector<std::string> > cxx_epilogue_; + bool cxx_epilogue_specified_; + database_map<std::vector<std::string> > schema_epilogue_; + bool schema_epilogue_specified_; + database_map<std::vector<std::string> > sql_epilogue_; + bool sql_epilogue_specified_; + database_map<std::vector<std::string> > migration_epilogue_; + bool migration_epilogue_specified_; + database_map<std::vector<std::string> > hxx_prologue_file_; + bool hxx_prologue_file_specified_; + database_map<std::vector<std::string> > ixx_prologue_file_; + bool ixx_prologue_file_specified_; + database_map<std::vector<std::string> > cxx_prologue_file_; + bool cxx_prologue_file_specified_; + database_map<std::vector<std::string> > schema_prologue_file_; + bool schema_prologue_file_specified_; + database_map<std::vector<std::string> > sql_prologue_file_; + bool sql_prologue_file_specified_; + database_map<std::vector<std::string> > migration_prologue_file_; + bool migration_prologue_file_specified_; + database_map<std::vector<std::string> > sql_interlude_file_; + bool sql_interlude_file_specified_; + database_map<std::vector<std::string> > hxx_epilogue_file_; + bool hxx_epilogue_file_specified_; + database_map<std::vector<std::string> > ixx_epilogue_file_; + bool ixx_epilogue_file_specified_; + database_map<std::vector<std::string> > cxx_epilogue_file_; + bool cxx_epilogue_file_specified_; + database_map<std::vector<std::string> > schema_epilogue_file_; + bool schema_epilogue_file_specified_; + database_map<std::vector<std::string> > sql_epilogue_file_; + bool sql_epilogue_file_specified_; + database_map<std::vector<std::string> > migration_epilogue_file_; + bool migration_epilogue_file_specified_; + database_map<std::vector<std::string> > odb_prologue_; + bool odb_prologue_specified_; + database_map<std::vector<std::string> > odb_prologue_file_; + bool odb_prologue_file_specified_; + database_map<std::vector<std::string> > odb_epilogue_; + bool odb_epilogue_specified_; + database_map<std::vector<std::string> > odb_epilogue_file_; + bool odb_epilogue_file_specified_; + database_map<std::string> table_prefix_; + bool table_prefix_specified_; + database_map<std::string> index_suffix_; + bool index_suffix_specified_; + database_map<std::string> fkey_suffix_; + bool fkey_suffix_specified_; + database_map<std::string> sequence_suffix_; + bool sequence_suffix_specified_; + database_map<name_case> sql_name_case_; + bool sql_name_case_specified_; + database_map<std::vector<std::string> > table_regex_; + bool table_regex_specified_; + database_map<std::vector<std::string> > column_regex_; + bool column_regex_specified_; + database_map<std::vector<std::string> > index_regex_; + bool index_regex_specified_; + database_map<std::vector<std::string> > fkey_regex_; + bool fkey_regex_specified_; + database_map<std::vector<std::string> > sequence_regex_; + bool sequence_regex_specified_; + database_map<std::vector<std::string> > statement_regex_; + bool statement_regex_specified_; + database_map<std::vector<std::string> > sql_name_regex_; + bool sql_name_regex_specified_; + bool sql_name_regex_trace_; + std::vector<std::string> accessor_regex_; + bool accessor_regex_specified_; + bool accessor_regex_trace_; + std::vector<std::string> modifier_regex_; + bool modifier_regex_specified_; + bool modifier_regex_trace_; + bool include_with_brackets_; + std::string include_prefix_; + bool include_prefix_specified_; + std::vector<std::string> include_regex_; + bool include_regex_specified_; + bool include_regex_trace_; + std::string guard_prefix_; + bool guard_prefix_specified_; + bool show_sloc_; + std::size_t sloc_limit_; + bool sloc_limit_specified_; + std::string options_file_; + bool options_file_specified_; + std::vector<std::string> x_; + bool x_specified_; + bool v_; + bool trace_; + std::string mysql_engine_; + bool mysql_engine_specified_; + bool sqlite_override_null_; + bool sqlite_lax_auto_id_; + ::pgsql_version pgsql_server_version_; + bool pgsql_server_version_specified_; + ::oracle_version oracle_client_version_; + bool oracle_client_version_specified_; + bool oracle_warn_truncation_; + ::mssql_version mssql_server_version_; + bool mssql_server_version_specified_; + unsigned int mssql_short_limit_; + bool mssql_short_limit_specified_; +}; + +#include <odb/options.ixx> + +// Begin epilogue. +// +// +// End epilogue. + +#endif // ODB_OPTIONS_HXX diff --git a/odb/odb/pregenerated/odb/options.ixx b/odb/odb/pregenerated/odb/options.ixx new file mode 100644 index 0000000..9a78a2e --- /dev/null +++ b/odb/odb/pregenerated/odb/options.ixx @@ -0,0 +1,3471 @@ +// -*- C++ -*- +// +// This file was generated by CLI, a command line interface +// compiler for C++. +// + +// Begin prologue. +// +// +// End prologue. + +#include <cassert> + +namespace cli +{ + // usage_para + // + inline usage_para:: + usage_para (value v) + : v_ (v) + { + } + + // unknown_mode + // + inline unknown_mode:: + unknown_mode (value v) + : v_ (v) + { + } + + // exception + // + inline ::std::ostream& + operator<< (::std::ostream& os, const exception& e) + { + e.print (os); + return os; + } + + // unknown_option + // + inline unknown_option:: + unknown_option (const std::string& option) + : option_ (option) + { + } + + inline const std::string& unknown_option:: + option () const + { + return option_; + } + + // unknown_argument + // + inline unknown_argument:: + unknown_argument (const std::string& argument) + : argument_ (argument) + { + } + + inline const std::string& unknown_argument:: + argument () const + { + return argument_; + } + + // missing_value + // + inline missing_value:: + missing_value (const std::string& option) + : option_ (option) + { + } + + inline const std::string& missing_value:: + option () const + { + return option_; + } + + // invalid_value + // + inline invalid_value:: + invalid_value (const std::string& option, + const std::string& value, + const std::string& message) + : option_ (option), + value_ (value), + message_ (message) + { + } + + inline const std::string& invalid_value:: + option () const + { + return option_; + } + + inline const std::string& invalid_value:: + value () const + { + return value_; + } + + inline const std::string& invalid_value:: + message () const + { + return message_; + } + + // file_io_failure + // + inline file_io_failure:: + file_io_failure (const std::string& file) + : file_ (file) + { + } + + inline const std::string& file_io_failure:: + file () const + { + return file_; + } + + // unmatched_quote + // + inline unmatched_quote:: + unmatched_quote (const std::string& argument) + : argument_ (argument) + { + } + + inline const std::string& unmatched_quote:: + argument () const + { + return argument_; + } + + // argv_scanner + // + inline argv_scanner:: + argv_scanner (int& argc, + char** argv, + bool erase, + std::size_t sp) + : start_position_ (sp + 1), + i_ (1), + argc_ (argc), + argv_ (argv), + erase_ (erase) + { + } + + inline argv_scanner:: + argv_scanner (int start, + int& argc, + char** argv, + bool erase, + std::size_t sp) + : start_position_ (sp + static_cast<std::size_t> (start)), + i_ (start), + argc_ (argc), + argv_ (argv), + erase_ (erase) + { + } + + inline int argv_scanner:: + end () const + { + return i_; + } + + // argv_file_scanner + // + inline argv_file_scanner:: + argv_file_scanner (int& argc, + char** argv, + const std::string& option, + bool erase, + std::size_t sp) + : argv_scanner (argc, argv, erase, sp), + option_ (option), + options_ (&option_info_), + options_count_ (1), + i_ (1), + skip_ (false) + { + option_info_.option = option_.c_str (); + option_info_.search_func = 0; + } + + inline argv_file_scanner:: + argv_file_scanner (int start, + int& argc, + char** argv, + const std::string& option, + bool erase, + std::size_t sp) + : argv_scanner (start, argc, argv, erase, sp), + option_ (option), + options_ (&option_info_), + options_count_ (1), + i_ (1), + skip_ (false) + { + option_info_.option = option_.c_str (); + option_info_.search_func = 0; + } + + inline argv_file_scanner:: + argv_file_scanner (const std::string& file, + const std::string& option, + std::size_t sp) + : argv_scanner (0, zero_argc_, 0, sp), + option_ (option), + options_ (&option_info_), + options_count_ (1), + i_ (1), + skip_ (false) + { + option_info_.option = option_.c_str (); + option_info_.search_func = 0; + + load (file); + } + + inline argv_file_scanner:: + argv_file_scanner (int& argc, + char** argv, + const option_info* options, + std::size_t options_count, + bool erase, + std::size_t sp) + : argv_scanner (argc, argv, erase, sp), + options_ (options), + options_count_ (options_count), + i_ (1), + skip_ (false) + { + } + + inline argv_file_scanner:: + argv_file_scanner (int start, + int& argc, + char** argv, + const option_info* options, + std::size_t options_count, + bool erase, + std::size_t sp) + : argv_scanner (start, argc, argv, erase, sp), + options_ (options), + options_count_ (options_count), + i_ (1), + skip_ (false) + { + } + + inline argv_file_scanner:: + argv_file_scanner (const std::string& file, + const option_info* options, + std::size_t options_count, + std::size_t sp) + : argv_scanner (0, zero_argc_, 0, sp), + options_ (options), + options_count_ (options_count), + i_ (1), + skip_ (false) + { + load (file); + } + + inline const std::string& option:: + name () const + { + return name_; + } + + inline const option_names& option:: + aliases () const + { + return aliases_; + } + + inline bool option:: + flag () const + { + return flag_; + } + + inline const std::string& option:: + default_value () const + { + return default_value_; + } + + inline option:: + option () + { + } + + inline option:: + option (const std::string& n, + const option_names& a, + bool f, + const std::string& dv) + : name_ (n), aliases_ (a), flag_ (f), default_value_ (dv) + { + } + + inline options::container_type::const_iterator options:: + find (const std::string& name) const + { + map_type::const_iterator i (map_.find (name)); + return i != map_.end () ? begin () + i->second : end (); + } +} + +// options +// + +inline const std::uint64_t& options:: +build2_metadata () const +{ + return this->build2_metadata_; +} + +inline std::uint64_t& options:: +build2_metadata () +{ + return this->build2_metadata_; +} + +inline void options:: +build2_metadata (const std::uint64_t& x) +{ + this->build2_metadata_ = x; +} + +inline bool options:: +build2_metadata_specified () const +{ + return this->build2_metadata_specified_; +} + +inline void options:: +build2_metadata_specified (bool x) +{ + this->build2_metadata_specified_ = x; +} + +inline const bool& options:: +help () const +{ + return this->help_; +} + +inline bool& options:: +help () +{ + return this->help_; +} + +inline void options:: +help (const bool& x) +{ + this->help_ = x; +} + +inline const bool& options:: +version () const +{ + return this->version_; +} + +inline bool& options:: +version () +{ + return this->version_; +} + +inline void options:: +version (const bool& x) +{ + this->version_ = x; +} + +inline const std::vector<std::string>& options:: +I () const +{ + return this->I_; +} + +inline std::vector<std::string>& options:: +I () +{ + return this->I_; +} + +inline void options:: +I (const std::vector<std::string>& x) +{ + this->I_ = x; +} + +inline bool options:: +I_specified () const +{ + return this->I_specified_; +} + +inline void options:: +I_specified (bool x) +{ + this->I_specified_ = x; +} + +inline const std::vector<std::string>& options:: +D () const +{ + return this->D_; +} + +inline std::vector<std::string>& options:: +D () +{ + return this->D_; +} + +inline void options:: +D (const std::vector<std::string>& x) +{ + this->D_ = x; +} + +inline bool options:: +D_specified () const +{ + return this->D_specified_; +} + +inline void options:: +D_specified (bool x) +{ + this->D_specified_ = x; +} + +inline const std::vector<std::string>& options:: +U () const +{ + return this->U_; +} + +inline std::vector<std::string>& options:: +U () +{ + return this->U_; +} + +inline void options:: +U (const std::vector<std::string>& x) +{ + this->U_ = x; +} + +inline bool options:: +U_specified () const +{ + return this->U_specified_; +} + +inline void options:: +U_specified (bool x) +{ + this->U_specified_ = x; +} + +inline const std::vector< ::database>& options:: +database () const +{ + return this->database_; +} + +inline std::vector< ::database>& options:: +database () +{ + return this->database_; +} + +inline void options:: +database (const std::vector< ::database>& x) +{ + this->database_ = x; +} + +inline bool options:: +database_specified () const +{ + return this->database_specified_; +} + +inline void options:: +database_specified (bool x) +{ + this->database_specified_ = x; +} + +inline const ::multi_database& options:: +multi_database () const +{ + return this->multi_database_; +} + +inline ::multi_database& options:: +multi_database () +{ + return this->multi_database_; +} + +inline void options:: +multi_database (const ::multi_database& x) +{ + this->multi_database_ = x; +} + +inline bool options:: +multi_database_specified () const +{ + return this->multi_database_specified_; +} + +inline void options:: +multi_database_specified (bool x) +{ + this->multi_database_specified_ = x; +} + +inline const ::database& options:: +default_database () const +{ + return this->default_database_; +} + +inline ::database& options:: +default_database () +{ + return this->default_database_; +} + +inline void options:: +default_database (const ::database& x) +{ + this->default_database_ = x; +} + +inline bool options:: +default_database_specified () const +{ + return this->default_database_specified_; +} + +inline void options:: +default_database_specified (bool x) +{ + this->default_database_specified_ = x; +} + +inline const bool& options:: +generate_query () const +{ + return this->generate_query_; +} + +inline bool& options:: +generate_query () +{ + return this->generate_query_; +} + +inline void options:: +generate_query (const bool& x) +{ + this->generate_query_ = x; +} + +inline const bool& options:: +generate_prepared () const +{ + return this->generate_prepared_; +} + +inline bool& options:: +generate_prepared () +{ + return this->generate_prepared_; +} + +inline void options:: +generate_prepared (const bool& x) +{ + this->generate_prepared_ = x; +} + +inline const bool& options:: +omit_unprepared () const +{ + return this->omit_unprepared_; +} + +inline bool& options:: +omit_unprepared () +{ + return this->omit_unprepared_; +} + +inline void options:: +omit_unprepared (const bool& x) +{ + this->omit_unprepared_ = x; +} + +inline const bool& options:: +generate_session () const +{ + return this->generate_session_; +} + +inline bool& options:: +generate_session () +{ + return this->generate_session_; +} + +inline void options:: +generate_session (const bool& x) +{ + this->generate_session_ = x; +} + +inline const bool& options:: +generate_schema () const +{ + return this->generate_schema_; +} + +inline bool& options:: +generate_schema () +{ + return this->generate_schema_; +} + +inline void options:: +generate_schema (const bool& x) +{ + this->generate_schema_ = x; +} + +inline const bool& options:: +generate_schema_only () const +{ + return this->generate_schema_only_; +} + +inline bool& options:: +generate_schema_only () +{ + return this->generate_schema_only_; +} + +inline void options:: +generate_schema_only (const bool& x) +{ + this->generate_schema_only_ = x; +} + +inline const bool& options:: +suppress_migration () const +{ + return this->suppress_migration_; +} + +inline bool& options:: +suppress_migration () +{ + return this->suppress_migration_; +} + +inline void options:: +suppress_migration (const bool& x) +{ + this->suppress_migration_ = x; +} + +inline const bool& options:: +suppress_schema_version () const +{ + return this->suppress_schema_version_; +} + +inline bool& options:: +suppress_schema_version () +{ + return this->suppress_schema_version_; +} + +inline void options:: +suppress_schema_version (const bool& x) +{ + this->suppress_schema_version_ = x; +} + +inline const database_map<qname>& options:: +schema_version_table () const +{ + return this->schema_version_table_; +} + +inline database_map<qname>& options:: +schema_version_table () +{ + return this->schema_version_table_; +} + +inline void options:: +schema_version_table (const database_map<qname>& x) +{ + this->schema_version_table_ = x; +} + +inline bool options:: +schema_version_table_specified () const +{ + return this->schema_version_table_specified_; +} + +inline void options:: +schema_version_table_specified (bool x) +{ + this->schema_version_table_specified_ = x; +} + +inline const database_map<std::set< ::schema_format> >& options:: +schema_format () const +{ + return this->schema_format_; +} + +inline database_map<std::set< ::schema_format> >& options:: +schema_format () +{ + return this->schema_format_; +} + +inline void options:: +schema_format (const database_map<std::set< ::schema_format> >& x) +{ + this->schema_format_ = x; +} + +inline bool options:: +schema_format_specified () const +{ + return this->schema_format_specified_; +} + +inline void options:: +schema_format_specified (bool x) +{ + this->schema_format_specified_ = x; +} + +inline const bool& options:: +omit_drop () const +{ + return this->omit_drop_; +} + +inline bool& options:: +omit_drop () +{ + return this->omit_drop_; +} + +inline void options:: +omit_drop (const bool& x) +{ + this->omit_drop_ = x; +} + +inline const bool& options:: +omit_create () const +{ + return this->omit_create_; +} + +inline bool& options:: +omit_create () +{ + return this->omit_create_; +} + +inline void options:: +omit_create (const bool& x) +{ + this->omit_create_ = x; +} + +inline const database_map<std::string>& options:: +schema_name () const +{ + return this->schema_name_; +} + +inline database_map<std::string>& options:: +schema_name () +{ + return this->schema_name_; +} + +inline void options:: +schema_name (const database_map<std::string>& x) +{ + this->schema_name_ = x; +} + +inline bool options:: +schema_name_specified () const +{ + return this->schema_name_specified_; +} + +inline void options:: +schema_name_specified (bool x) +{ + this->schema_name_specified_ = x; +} + +inline const database_map<deferrable>& options:: +fkeys_deferrable_mode () const +{ + return this->fkeys_deferrable_mode_; +} + +inline database_map<deferrable>& options:: +fkeys_deferrable_mode () +{ + return this->fkeys_deferrable_mode_; +} + +inline void options:: +fkeys_deferrable_mode (const database_map<deferrable>& x) +{ + this->fkeys_deferrable_mode_ = x; +} + +inline bool options:: +fkeys_deferrable_mode_specified () const +{ + return this->fkeys_deferrable_mode_specified_; +} + +inline void options:: +fkeys_deferrable_mode_specified (bool x) +{ + this->fkeys_deferrable_mode_specified_ = x; +} + +inline const std::string& options:: +default_pointer () const +{ + return this->default_pointer_; +} + +inline std::string& options:: +default_pointer () +{ + return this->default_pointer_; +} + +inline void options:: +default_pointer (const std::string& x) +{ + this->default_pointer_ = x; +} + +inline bool options:: +default_pointer_specified () const +{ + return this->default_pointer_specified_; +} + +inline void options:: +default_pointer_specified (bool x) +{ + this->default_pointer_specified_ = x; +} + +inline const std::string& options:: +session_type () const +{ + return this->session_type_; +} + +inline std::string& options:: +session_type () +{ + return this->session_type_; +} + +inline void options:: +session_type (const std::string& x) +{ + this->session_type_ = x; +} + +inline bool options:: +session_type_specified () const +{ + return this->session_type_specified_; +} + +inline void options:: +session_type_specified (bool x) +{ + this->session_type_specified_ = x; +} + +inline const std::string& options:: +profile () const +{ + return this->profile_; +} + +inline std::string& options:: +profile () +{ + return this->profile_; +} + +inline void options:: +profile (const std::string& x) +{ + this->profile_ = x; +} + +inline bool options:: +profile_specified () const +{ + return this->profile_specified_; +} + +inline void options:: +profile_specified (bool x) +{ + this->profile_specified_ = x; +} + +inline const bool& options:: +at_once () const +{ + return this->at_once_; +} + +inline bool& options:: +at_once () +{ + return this->at_once_; +} + +inline void options:: +at_once (const bool& x) +{ + this->at_once_ = x; +} + +inline const database_map<qname>& options:: +schema () const +{ + return this->schema_; +} + +inline database_map<qname>& options:: +schema () +{ + return this->schema_; +} + +inline void options:: +schema (const database_map<qname>& x) +{ + this->schema_ = x; +} + +inline bool options:: +schema_specified () const +{ + return this->schema_specified_; +} + +inline void options:: +schema_specified (bool x) +{ + this->schema_specified_ = x; +} + +inline const database_map<std::string>& options:: +export_symbol () const +{ + return this->export_symbol_; +} + +inline database_map<std::string>& options:: +export_symbol () +{ + return this->export_symbol_; +} + +inline void options:: +export_symbol (const database_map<std::string>& x) +{ + this->export_symbol_ = x; +} + +inline bool options:: +export_symbol_specified () const +{ + return this->export_symbol_specified_; +} + +inline void options:: +export_symbol_specified (bool x) +{ + this->export_symbol_specified_ = x; +} + +inline const database_map<std::string>& options:: +extern_symbol () const +{ + return this->extern_symbol_; +} + +inline database_map<std::string>& options:: +extern_symbol () +{ + return this->extern_symbol_; +} + +inline void options:: +extern_symbol (const database_map<std::string>& x) +{ + this->extern_symbol_ = x; +} + +inline bool options:: +extern_symbol_specified () const +{ + return this->extern_symbol_specified_; +} + +inline void options:: +extern_symbol_specified (bool x) +{ + this->extern_symbol_specified_ = x; +} + +inline const cxx_version& options:: +std () const +{ + return this->std_; +} + +inline cxx_version& options:: +std () +{ + return this->std_; +} + +inline void options:: +std (const cxx_version& x) +{ + this->std_ = x; +} + +inline bool options:: +std_specified () const +{ + return this->std_specified_; +} + +inline void options:: +std_specified (bool x) +{ + this->std_specified_ = x; +} + +inline const bool& options:: +warn_hard_add () const +{ + return this->warn_hard_add_; +} + +inline bool& options:: +warn_hard_add () +{ + return this->warn_hard_add_; +} + +inline void options:: +warn_hard_add (const bool& x) +{ + this->warn_hard_add_ = x; +} + +inline const bool& options:: +warn_hard_delete () const +{ + return this->warn_hard_delete_; +} + +inline bool& options:: +warn_hard_delete () +{ + return this->warn_hard_delete_; +} + +inline void options:: +warn_hard_delete (const bool& x) +{ + this->warn_hard_delete_ = x; +} + +inline const bool& options:: +warn_hard () const +{ + return this->warn_hard_; +} + +inline bool& options:: +warn_hard () +{ + return this->warn_hard_; +} + +inline void options:: +warn_hard (const bool& x) +{ + this->warn_hard_ = x; +} + +inline const std::string& options:: +output_dir () const +{ + return this->output_dir_; +} + +inline std::string& options:: +output_dir () +{ + return this->output_dir_; +} + +inline void options:: +output_dir (const std::string& x) +{ + this->output_dir_ = x; +} + +inline bool options:: +output_dir_specified () const +{ + return this->output_dir_specified_; +} + +inline void options:: +output_dir_specified (bool x) +{ + this->output_dir_specified_ = x; +} + +inline const std::string& options:: +input_name () const +{ + return this->input_name_; +} + +inline std::string& options:: +input_name () +{ + return this->input_name_; +} + +inline void options:: +input_name (const std::string& x) +{ + this->input_name_ = x; +} + +inline bool options:: +input_name_specified () const +{ + return this->input_name_specified_; +} + +inline void options:: +input_name_specified (bool x) +{ + this->input_name_specified_ = x; +} + +inline const database_map<std::string>& options:: +changelog () const +{ + return this->changelog_; +} + +inline database_map<std::string>& options:: +changelog () +{ + return this->changelog_; +} + +inline void options:: +changelog (const database_map<std::string>& x) +{ + this->changelog_ = x; +} + +inline bool options:: +changelog_specified () const +{ + return this->changelog_specified_; +} + +inline void options:: +changelog_specified (bool x) +{ + this->changelog_specified_ = x; +} + +inline const database_map<std::string>& options:: +changelog_in () const +{ + return this->changelog_in_; +} + +inline database_map<std::string>& options:: +changelog_in () +{ + return this->changelog_in_; +} + +inline void options:: +changelog_in (const database_map<std::string>& x) +{ + this->changelog_in_ = x; +} + +inline bool options:: +changelog_in_specified () const +{ + return this->changelog_in_specified_; +} + +inline void options:: +changelog_in_specified (bool x) +{ + this->changelog_in_specified_ = x; +} + +inline const database_map<std::string>& options:: +changelog_out () const +{ + return this->changelog_out_; +} + +inline database_map<std::string>& options:: +changelog_out () +{ + return this->changelog_out_; +} + +inline void options:: +changelog_out (const database_map<std::string>& x) +{ + this->changelog_out_ = x; +} + +inline bool options:: +changelog_out_specified () const +{ + return this->changelog_out_specified_; +} + +inline void options:: +changelog_out_specified (bool x) +{ + this->changelog_out_specified_ = x; +} + +inline const database_map<std::string>& options:: +changelog_dir () const +{ + return this->changelog_dir_; +} + +inline database_map<std::string>& options:: +changelog_dir () +{ + return this->changelog_dir_; +} + +inline void options:: +changelog_dir (const database_map<std::string>& x) +{ + this->changelog_dir_ = x; +} + +inline bool options:: +changelog_dir_specified () const +{ + return this->changelog_dir_specified_; +} + +inline void options:: +changelog_dir_specified (bool x) +{ + this->changelog_dir_specified_ = x; +} + +inline const bool& options:: +init_changelog () const +{ + return this->init_changelog_; +} + +inline bool& options:: +init_changelog () +{ + return this->init_changelog_; +} + +inline void options:: +init_changelog (const bool& x) +{ + this->init_changelog_ = x; +} + +inline const database_map<std::string>& options:: +odb_file_suffix () const +{ + return this->odb_file_suffix_; +} + +inline database_map<std::string>& options:: +odb_file_suffix () +{ + return this->odb_file_suffix_; +} + +inline void options:: +odb_file_suffix (const database_map<std::string>& x) +{ + this->odb_file_suffix_ = x; +} + +inline bool options:: +odb_file_suffix_specified () const +{ + return this->odb_file_suffix_specified_; +} + +inline void options:: +odb_file_suffix_specified (bool x) +{ + this->odb_file_suffix_specified_ = x; +} + +inline const database_map<std::string>& options:: +sql_file_suffix () const +{ + return this->sql_file_suffix_; +} + +inline database_map<std::string>& options:: +sql_file_suffix () +{ + return this->sql_file_suffix_; +} + +inline void options:: +sql_file_suffix (const database_map<std::string>& x) +{ + this->sql_file_suffix_ = x; +} + +inline bool options:: +sql_file_suffix_specified () const +{ + return this->sql_file_suffix_specified_; +} + +inline void options:: +sql_file_suffix_specified (bool x) +{ + this->sql_file_suffix_specified_ = x; +} + +inline const database_map<std::string>& options:: +schema_file_suffix () const +{ + return this->schema_file_suffix_; +} + +inline database_map<std::string>& options:: +schema_file_suffix () +{ + return this->schema_file_suffix_; +} + +inline void options:: +schema_file_suffix (const database_map<std::string>& x) +{ + this->schema_file_suffix_ = x; +} + +inline bool options:: +schema_file_suffix_specified () const +{ + return this->schema_file_suffix_specified_; +} + +inline void options:: +schema_file_suffix_specified (bool x) +{ + this->schema_file_suffix_specified_ = x; +} + +inline const database_map<std::string>& options:: +changelog_file_suffix () const +{ + return this->changelog_file_suffix_; +} + +inline database_map<std::string>& options:: +changelog_file_suffix () +{ + return this->changelog_file_suffix_; +} + +inline void options:: +changelog_file_suffix (const database_map<std::string>& x) +{ + this->changelog_file_suffix_ = x; +} + +inline bool options:: +changelog_file_suffix_specified () const +{ + return this->changelog_file_suffix_specified_; +} + +inline void options:: +changelog_file_suffix_specified (bool x) +{ + this->changelog_file_suffix_specified_ = x; +} + +inline const std::string& options:: +hxx_suffix () const +{ + return this->hxx_suffix_; +} + +inline std::string& options:: +hxx_suffix () +{ + return this->hxx_suffix_; +} + +inline void options:: +hxx_suffix (const std::string& x) +{ + this->hxx_suffix_ = x; +} + +inline bool options:: +hxx_suffix_specified () const +{ + return this->hxx_suffix_specified_; +} + +inline void options:: +hxx_suffix_specified (bool x) +{ + this->hxx_suffix_specified_ = x; +} + +inline const std::string& options:: +ixx_suffix () const +{ + return this->ixx_suffix_; +} + +inline std::string& options:: +ixx_suffix () +{ + return this->ixx_suffix_; +} + +inline void options:: +ixx_suffix (const std::string& x) +{ + this->ixx_suffix_ = x; +} + +inline bool options:: +ixx_suffix_specified () const +{ + return this->ixx_suffix_specified_; +} + +inline void options:: +ixx_suffix_specified (bool x) +{ + this->ixx_suffix_specified_ = x; +} + +inline const std::string& options:: +cxx_suffix () const +{ + return this->cxx_suffix_; +} + +inline std::string& options:: +cxx_suffix () +{ + return this->cxx_suffix_; +} + +inline void options:: +cxx_suffix (const std::string& x) +{ + this->cxx_suffix_ = x; +} + +inline bool options:: +cxx_suffix_specified () const +{ + return this->cxx_suffix_specified_; +} + +inline void options:: +cxx_suffix_specified (bool x) +{ + this->cxx_suffix_specified_ = x; +} + +inline const std::string& options:: +sql_suffix () const +{ + return this->sql_suffix_; +} + +inline std::string& options:: +sql_suffix () +{ + return this->sql_suffix_; +} + +inline void options:: +sql_suffix (const std::string& x) +{ + this->sql_suffix_ = x; +} + +inline bool options:: +sql_suffix_specified () const +{ + return this->sql_suffix_specified_; +} + +inline void options:: +sql_suffix_specified (bool x) +{ + this->sql_suffix_specified_ = x; +} + +inline const std::string& options:: +changelog_suffix () const +{ + return this->changelog_suffix_; +} + +inline std::string& options:: +changelog_suffix () +{ + return this->changelog_suffix_; +} + +inline void options:: +changelog_suffix (const std::string& x) +{ + this->changelog_suffix_ = x; +} + +inline bool options:: +changelog_suffix_specified () const +{ + return this->changelog_suffix_specified_; +} + +inline void options:: +changelog_suffix_specified (bool x) +{ + this->changelog_suffix_specified_ = x; +} + +inline const database_map<std::vector<std::string> >& options:: +hxx_prologue () const +{ + return this->hxx_prologue_; +} + +inline database_map<std::vector<std::string> >& options:: +hxx_prologue () +{ + return this->hxx_prologue_; +} + +inline void options:: +hxx_prologue (const database_map<std::vector<std::string> >& x) +{ + this->hxx_prologue_ = x; +} + +inline bool options:: +hxx_prologue_specified () const +{ + return this->hxx_prologue_specified_; +} + +inline void options:: +hxx_prologue_specified (bool x) +{ + this->hxx_prologue_specified_ = x; +} + +inline const database_map<std::vector<std::string> >& options:: +ixx_prologue () const +{ + return this->ixx_prologue_; +} + +inline database_map<std::vector<std::string> >& options:: +ixx_prologue () +{ + return this->ixx_prologue_; +} + +inline void options:: +ixx_prologue (const database_map<std::vector<std::string> >& x) +{ + this->ixx_prologue_ = x; +} + +inline bool options:: +ixx_prologue_specified () const +{ + return this->ixx_prologue_specified_; +} + +inline void options:: +ixx_prologue_specified (bool x) +{ + this->ixx_prologue_specified_ = x; +} + +inline const database_map<std::vector<std::string> >& options:: +cxx_prologue () const +{ + return this->cxx_prologue_; +} + +inline database_map<std::vector<std::string> >& options:: +cxx_prologue () +{ + return this->cxx_prologue_; +} + +inline void options:: +cxx_prologue (const database_map<std::vector<std::string> >& x) +{ + this->cxx_prologue_ = x; +} + +inline bool options:: +cxx_prologue_specified () const +{ + return this->cxx_prologue_specified_; +} + +inline void options:: +cxx_prologue_specified (bool x) +{ + this->cxx_prologue_specified_ = x; +} + +inline const database_map<std::vector<std::string> >& options:: +schema_prologue () const +{ + return this->schema_prologue_; +} + +inline database_map<std::vector<std::string> >& options:: +schema_prologue () +{ + return this->schema_prologue_; +} + +inline void options:: +schema_prologue (const database_map<std::vector<std::string> >& x) +{ + this->schema_prologue_ = x; +} + +inline bool options:: +schema_prologue_specified () const +{ + return this->schema_prologue_specified_; +} + +inline void options:: +schema_prologue_specified (bool x) +{ + this->schema_prologue_specified_ = x; +} + +inline const database_map<std::vector<std::string> >& options:: +sql_prologue () const +{ + return this->sql_prologue_; +} + +inline database_map<std::vector<std::string> >& options:: +sql_prologue () +{ + return this->sql_prologue_; +} + +inline void options:: +sql_prologue (const database_map<std::vector<std::string> >& x) +{ + this->sql_prologue_ = x; +} + +inline bool options:: +sql_prologue_specified () const +{ + return this->sql_prologue_specified_; +} + +inline void options:: +sql_prologue_specified (bool x) +{ + this->sql_prologue_specified_ = x; +} + +inline const database_map<std::vector<std::string> >& options:: +migration_prologue () const +{ + return this->migration_prologue_; +} + +inline database_map<std::vector<std::string> >& options:: +migration_prologue () +{ + return this->migration_prologue_; +} + +inline void options:: +migration_prologue (const database_map<std::vector<std::string> >& x) +{ + this->migration_prologue_ = x; +} + +inline bool options:: +migration_prologue_specified () const +{ + return this->migration_prologue_specified_; +} + +inline void options:: +migration_prologue_specified (bool x) +{ + this->migration_prologue_specified_ = x; +} + +inline const database_map<std::vector<std::string> >& options:: +sql_interlude () const +{ + return this->sql_interlude_; +} + +inline database_map<std::vector<std::string> >& options:: +sql_interlude () +{ + return this->sql_interlude_; +} + +inline void options:: +sql_interlude (const database_map<std::vector<std::string> >& x) +{ + this->sql_interlude_ = x; +} + +inline bool options:: +sql_interlude_specified () const +{ + return this->sql_interlude_specified_; +} + +inline void options:: +sql_interlude_specified (bool x) +{ + this->sql_interlude_specified_ = x; +} + +inline const database_map<std::vector<std::string> >& options:: +hxx_epilogue () const +{ + return this->hxx_epilogue_; +} + +inline database_map<std::vector<std::string> >& options:: +hxx_epilogue () +{ + return this->hxx_epilogue_; +} + +inline void options:: +hxx_epilogue (const database_map<std::vector<std::string> >& x) +{ + this->hxx_epilogue_ = x; +} + +inline bool options:: +hxx_epilogue_specified () const +{ + return this->hxx_epilogue_specified_; +} + +inline void options:: +hxx_epilogue_specified (bool x) +{ + this->hxx_epilogue_specified_ = x; +} + +inline const database_map<std::vector<std::string> >& options:: +ixx_epilogue () const +{ + return this->ixx_epilogue_; +} + +inline database_map<std::vector<std::string> >& options:: +ixx_epilogue () +{ + return this->ixx_epilogue_; +} + +inline void options:: +ixx_epilogue (const database_map<std::vector<std::string> >& x) +{ + this->ixx_epilogue_ = x; +} + +inline bool options:: +ixx_epilogue_specified () const +{ + return this->ixx_epilogue_specified_; +} + +inline void options:: +ixx_epilogue_specified (bool x) +{ + this->ixx_epilogue_specified_ = x; +} + +inline const database_map<std::vector<std::string> >& options:: +cxx_epilogue () const +{ + return this->cxx_epilogue_; +} + +inline database_map<std::vector<std::string> >& options:: +cxx_epilogue () +{ + return this->cxx_epilogue_; +} + +inline void options:: +cxx_epilogue (const database_map<std::vector<std::string> >& x) +{ + this->cxx_epilogue_ = x; +} + +inline bool options:: +cxx_epilogue_specified () const +{ + return this->cxx_epilogue_specified_; +} + +inline void options:: +cxx_epilogue_specified (bool x) +{ + this->cxx_epilogue_specified_ = x; +} + +inline const database_map<std::vector<std::string> >& options:: +schema_epilogue () const +{ + return this->schema_epilogue_; +} + +inline database_map<std::vector<std::string> >& options:: +schema_epilogue () +{ + return this->schema_epilogue_; +} + +inline void options:: +schema_epilogue (const database_map<std::vector<std::string> >& x) +{ + this->schema_epilogue_ = x; +} + +inline bool options:: +schema_epilogue_specified () const +{ + return this->schema_epilogue_specified_; +} + +inline void options:: +schema_epilogue_specified (bool x) +{ + this->schema_epilogue_specified_ = x; +} + +inline const database_map<std::vector<std::string> >& options:: +sql_epilogue () const +{ + return this->sql_epilogue_; +} + +inline database_map<std::vector<std::string> >& options:: +sql_epilogue () +{ + return this->sql_epilogue_; +} + +inline void options:: +sql_epilogue (const database_map<std::vector<std::string> >& x) +{ + this->sql_epilogue_ = x; +} + +inline bool options:: +sql_epilogue_specified () const +{ + return this->sql_epilogue_specified_; +} + +inline void options:: +sql_epilogue_specified (bool x) +{ + this->sql_epilogue_specified_ = x; +} + +inline const database_map<std::vector<std::string> >& options:: +migration_epilogue () const +{ + return this->migration_epilogue_; +} + +inline database_map<std::vector<std::string> >& options:: +migration_epilogue () +{ + return this->migration_epilogue_; +} + +inline void options:: +migration_epilogue (const database_map<std::vector<std::string> >& x) +{ + this->migration_epilogue_ = x; +} + +inline bool options:: +migration_epilogue_specified () const +{ + return this->migration_epilogue_specified_; +} + +inline void options:: +migration_epilogue_specified (bool x) +{ + this->migration_epilogue_specified_ = x; +} + +inline const database_map<std::vector<std::string> >& options:: +hxx_prologue_file () const +{ + return this->hxx_prologue_file_; +} + +inline database_map<std::vector<std::string> >& options:: +hxx_prologue_file () +{ + return this->hxx_prologue_file_; +} + +inline void options:: +hxx_prologue_file (const database_map<std::vector<std::string> >& x) +{ + this->hxx_prologue_file_ = x; +} + +inline bool options:: +hxx_prologue_file_specified () const +{ + return this->hxx_prologue_file_specified_; +} + +inline void options:: +hxx_prologue_file_specified (bool x) +{ + this->hxx_prologue_file_specified_ = x; +} + +inline const database_map<std::vector<std::string> >& options:: +ixx_prologue_file () const +{ + return this->ixx_prologue_file_; +} + +inline database_map<std::vector<std::string> >& options:: +ixx_prologue_file () +{ + return this->ixx_prologue_file_; +} + +inline void options:: +ixx_prologue_file (const database_map<std::vector<std::string> >& x) +{ + this->ixx_prologue_file_ = x; +} + +inline bool options:: +ixx_prologue_file_specified () const +{ + return this->ixx_prologue_file_specified_; +} + +inline void options:: +ixx_prologue_file_specified (bool x) +{ + this->ixx_prologue_file_specified_ = x; +} + +inline const database_map<std::vector<std::string> >& options:: +cxx_prologue_file () const +{ + return this->cxx_prologue_file_; +} + +inline database_map<std::vector<std::string> >& options:: +cxx_prologue_file () +{ + return this->cxx_prologue_file_; +} + +inline void options:: +cxx_prologue_file (const database_map<std::vector<std::string> >& x) +{ + this->cxx_prologue_file_ = x; +} + +inline bool options:: +cxx_prologue_file_specified () const +{ + return this->cxx_prologue_file_specified_; +} + +inline void options:: +cxx_prologue_file_specified (bool x) +{ + this->cxx_prologue_file_specified_ = x; +} + +inline const database_map<std::vector<std::string> >& options:: +schema_prologue_file () const +{ + return this->schema_prologue_file_; +} + +inline database_map<std::vector<std::string> >& options:: +schema_prologue_file () +{ + return this->schema_prologue_file_; +} + +inline void options:: +schema_prologue_file (const database_map<std::vector<std::string> >& x) +{ + this->schema_prologue_file_ = x; +} + +inline bool options:: +schema_prologue_file_specified () const +{ + return this->schema_prologue_file_specified_; +} + +inline void options:: +schema_prologue_file_specified (bool x) +{ + this->schema_prologue_file_specified_ = x; +} + +inline const database_map<std::vector<std::string> >& options:: +sql_prologue_file () const +{ + return this->sql_prologue_file_; +} + +inline database_map<std::vector<std::string> >& options:: +sql_prologue_file () +{ + return this->sql_prologue_file_; +} + +inline void options:: +sql_prologue_file (const database_map<std::vector<std::string> >& x) +{ + this->sql_prologue_file_ = x; +} + +inline bool options:: +sql_prologue_file_specified () const +{ + return this->sql_prologue_file_specified_; +} + +inline void options:: +sql_prologue_file_specified (bool x) +{ + this->sql_prologue_file_specified_ = x; +} + +inline const database_map<std::vector<std::string> >& options:: +migration_prologue_file () const +{ + return this->migration_prologue_file_; +} + +inline database_map<std::vector<std::string> >& options:: +migration_prologue_file () +{ + return this->migration_prologue_file_; +} + +inline void options:: +migration_prologue_file (const database_map<std::vector<std::string> >& x) +{ + this->migration_prologue_file_ = x; +} + +inline bool options:: +migration_prologue_file_specified () const +{ + return this->migration_prologue_file_specified_; +} + +inline void options:: +migration_prologue_file_specified (bool x) +{ + this->migration_prologue_file_specified_ = x; +} + +inline const database_map<std::vector<std::string> >& options:: +sql_interlude_file () const +{ + return this->sql_interlude_file_; +} + +inline database_map<std::vector<std::string> >& options:: +sql_interlude_file () +{ + return this->sql_interlude_file_; +} + +inline void options:: +sql_interlude_file (const database_map<std::vector<std::string> >& x) +{ + this->sql_interlude_file_ = x; +} + +inline bool options:: +sql_interlude_file_specified () const +{ + return this->sql_interlude_file_specified_; +} + +inline void options:: +sql_interlude_file_specified (bool x) +{ + this->sql_interlude_file_specified_ = x; +} + +inline const database_map<std::vector<std::string> >& options:: +hxx_epilogue_file () const +{ + return this->hxx_epilogue_file_; +} + +inline database_map<std::vector<std::string> >& options:: +hxx_epilogue_file () +{ + return this->hxx_epilogue_file_; +} + +inline void options:: +hxx_epilogue_file (const database_map<std::vector<std::string> >& x) +{ + this->hxx_epilogue_file_ = x; +} + +inline bool options:: +hxx_epilogue_file_specified () const +{ + return this->hxx_epilogue_file_specified_; +} + +inline void options:: +hxx_epilogue_file_specified (bool x) +{ + this->hxx_epilogue_file_specified_ = x; +} + +inline const database_map<std::vector<std::string> >& options:: +ixx_epilogue_file () const +{ + return this->ixx_epilogue_file_; +} + +inline database_map<std::vector<std::string> >& options:: +ixx_epilogue_file () +{ + return this->ixx_epilogue_file_; +} + +inline void options:: +ixx_epilogue_file (const database_map<std::vector<std::string> >& x) +{ + this->ixx_epilogue_file_ = x; +} + +inline bool options:: +ixx_epilogue_file_specified () const +{ + return this->ixx_epilogue_file_specified_; +} + +inline void options:: +ixx_epilogue_file_specified (bool x) +{ + this->ixx_epilogue_file_specified_ = x; +} + +inline const database_map<std::vector<std::string> >& options:: +cxx_epilogue_file () const +{ + return this->cxx_epilogue_file_; +} + +inline database_map<std::vector<std::string> >& options:: +cxx_epilogue_file () +{ + return this->cxx_epilogue_file_; +} + +inline void options:: +cxx_epilogue_file (const database_map<std::vector<std::string> >& x) +{ + this->cxx_epilogue_file_ = x; +} + +inline bool options:: +cxx_epilogue_file_specified () const +{ + return this->cxx_epilogue_file_specified_; +} + +inline void options:: +cxx_epilogue_file_specified (bool x) +{ + this->cxx_epilogue_file_specified_ = x; +} + +inline const database_map<std::vector<std::string> >& options:: +schema_epilogue_file () const +{ + return this->schema_epilogue_file_; +} + +inline database_map<std::vector<std::string> >& options:: +schema_epilogue_file () +{ + return this->schema_epilogue_file_; +} + +inline void options:: +schema_epilogue_file (const database_map<std::vector<std::string> >& x) +{ + this->schema_epilogue_file_ = x; +} + +inline bool options:: +schema_epilogue_file_specified () const +{ + return this->schema_epilogue_file_specified_; +} + +inline void options:: +schema_epilogue_file_specified (bool x) +{ + this->schema_epilogue_file_specified_ = x; +} + +inline const database_map<std::vector<std::string> >& options:: +sql_epilogue_file () const +{ + return this->sql_epilogue_file_; +} + +inline database_map<std::vector<std::string> >& options:: +sql_epilogue_file () +{ + return this->sql_epilogue_file_; +} + +inline void options:: +sql_epilogue_file (const database_map<std::vector<std::string> >& x) +{ + this->sql_epilogue_file_ = x; +} + +inline bool options:: +sql_epilogue_file_specified () const +{ + return this->sql_epilogue_file_specified_; +} + +inline void options:: +sql_epilogue_file_specified (bool x) +{ + this->sql_epilogue_file_specified_ = x; +} + +inline const database_map<std::vector<std::string> >& options:: +migration_epilogue_file () const +{ + return this->migration_epilogue_file_; +} + +inline database_map<std::vector<std::string> >& options:: +migration_epilogue_file () +{ + return this->migration_epilogue_file_; +} + +inline void options:: +migration_epilogue_file (const database_map<std::vector<std::string> >& x) +{ + this->migration_epilogue_file_ = x; +} + +inline bool options:: +migration_epilogue_file_specified () const +{ + return this->migration_epilogue_file_specified_; +} + +inline void options:: +migration_epilogue_file_specified (bool x) +{ + this->migration_epilogue_file_specified_ = x; +} + +inline const database_map<std::vector<std::string> >& options:: +odb_prologue () const +{ + return this->odb_prologue_; +} + +inline database_map<std::vector<std::string> >& options:: +odb_prologue () +{ + return this->odb_prologue_; +} + +inline void options:: +odb_prologue (const database_map<std::vector<std::string> >& x) +{ + this->odb_prologue_ = x; +} + +inline bool options:: +odb_prologue_specified () const +{ + return this->odb_prologue_specified_; +} + +inline void options:: +odb_prologue_specified (bool x) +{ + this->odb_prologue_specified_ = x; +} + +inline const database_map<std::vector<std::string> >& options:: +odb_prologue_file () const +{ + return this->odb_prologue_file_; +} + +inline database_map<std::vector<std::string> >& options:: +odb_prologue_file () +{ + return this->odb_prologue_file_; +} + +inline void options:: +odb_prologue_file (const database_map<std::vector<std::string> >& x) +{ + this->odb_prologue_file_ = x; +} + +inline bool options:: +odb_prologue_file_specified () const +{ + return this->odb_prologue_file_specified_; +} + +inline void options:: +odb_prologue_file_specified (bool x) +{ + this->odb_prologue_file_specified_ = x; +} + +inline const database_map<std::vector<std::string> >& options:: +odb_epilogue () const +{ + return this->odb_epilogue_; +} + +inline database_map<std::vector<std::string> >& options:: +odb_epilogue () +{ + return this->odb_epilogue_; +} + +inline void options:: +odb_epilogue (const database_map<std::vector<std::string> >& x) +{ + this->odb_epilogue_ = x; +} + +inline bool options:: +odb_epilogue_specified () const +{ + return this->odb_epilogue_specified_; +} + +inline void options:: +odb_epilogue_specified (bool x) +{ + this->odb_epilogue_specified_ = x; +} + +inline const database_map<std::vector<std::string> >& options:: +odb_epilogue_file () const +{ + return this->odb_epilogue_file_; +} + +inline database_map<std::vector<std::string> >& options:: +odb_epilogue_file () +{ + return this->odb_epilogue_file_; +} + +inline void options:: +odb_epilogue_file (const database_map<std::vector<std::string> >& x) +{ + this->odb_epilogue_file_ = x; +} + +inline bool options:: +odb_epilogue_file_specified () const +{ + return this->odb_epilogue_file_specified_; +} + +inline void options:: +odb_epilogue_file_specified (bool x) +{ + this->odb_epilogue_file_specified_ = x; +} + +inline const database_map<std::string>& options:: +table_prefix () const +{ + return this->table_prefix_; +} + +inline database_map<std::string>& options:: +table_prefix () +{ + return this->table_prefix_; +} + +inline void options:: +table_prefix (const database_map<std::string>& x) +{ + this->table_prefix_ = x; +} + +inline bool options:: +table_prefix_specified () const +{ + return this->table_prefix_specified_; +} + +inline void options:: +table_prefix_specified (bool x) +{ + this->table_prefix_specified_ = x; +} + +inline const database_map<std::string>& options:: +index_suffix () const +{ + return this->index_suffix_; +} + +inline database_map<std::string>& options:: +index_suffix () +{ + return this->index_suffix_; +} + +inline void options:: +index_suffix (const database_map<std::string>& x) +{ + this->index_suffix_ = x; +} + +inline bool options:: +index_suffix_specified () const +{ + return this->index_suffix_specified_; +} + +inline void options:: +index_suffix_specified (bool x) +{ + this->index_suffix_specified_ = x; +} + +inline const database_map<std::string>& options:: +fkey_suffix () const +{ + return this->fkey_suffix_; +} + +inline database_map<std::string>& options:: +fkey_suffix () +{ + return this->fkey_suffix_; +} + +inline void options:: +fkey_suffix (const database_map<std::string>& x) +{ + this->fkey_suffix_ = x; +} + +inline bool options:: +fkey_suffix_specified () const +{ + return this->fkey_suffix_specified_; +} + +inline void options:: +fkey_suffix_specified (bool x) +{ + this->fkey_suffix_specified_ = x; +} + +inline const database_map<std::string>& options:: +sequence_suffix () const +{ + return this->sequence_suffix_; +} + +inline database_map<std::string>& options:: +sequence_suffix () +{ + return this->sequence_suffix_; +} + +inline void options:: +sequence_suffix (const database_map<std::string>& x) +{ + this->sequence_suffix_ = x; +} + +inline bool options:: +sequence_suffix_specified () const +{ + return this->sequence_suffix_specified_; +} + +inline void options:: +sequence_suffix_specified (bool x) +{ + this->sequence_suffix_specified_ = x; +} + +inline const database_map<name_case>& options:: +sql_name_case () const +{ + return this->sql_name_case_; +} + +inline database_map<name_case>& options:: +sql_name_case () +{ + return this->sql_name_case_; +} + +inline void options:: +sql_name_case (const database_map<name_case>& x) +{ + this->sql_name_case_ = x; +} + +inline bool options:: +sql_name_case_specified () const +{ + return this->sql_name_case_specified_; +} + +inline void options:: +sql_name_case_specified (bool x) +{ + this->sql_name_case_specified_ = x; +} + +inline const database_map<std::vector<std::string> >& options:: +table_regex () const +{ + return this->table_regex_; +} + +inline database_map<std::vector<std::string> >& options:: +table_regex () +{ + return this->table_regex_; +} + +inline void options:: +table_regex (const database_map<std::vector<std::string> >& x) +{ + this->table_regex_ = x; +} + +inline bool options:: +table_regex_specified () const +{ + return this->table_regex_specified_; +} + +inline void options:: +table_regex_specified (bool x) +{ + this->table_regex_specified_ = x; +} + +inline const database_map<std::vector<std::string> >& options:: +column_regex () const +{ + return this->column_regex_; +} + +inline database_map<std::vector<std::string> >& options:: +column_regex () +{ + return this->column_regex_; +} + +inline void options:: +column_regex (const database_map<std::vector<std::string> >& x) +{ + this->column_regex_ = x; +} + +inline bool options:: +column_regex_specified () const +{ + return this->column_regex_specified_; +} + +inline void options:: +column_regex_specified (bool x) +{ + this->column_regex_specified_ = x; +} + +inline const database_map<std::vector<std::string> >& options:: +index_regex () const +{ + return this->index_regex_; +} + +inline database_map<std::vector<std::string> >& options:: +index_regex () +{ + return this->index_regex_; +} + +inline void options:: +index_regex (const database_map<std::vector<std::string> >& x) +{ + this->index_regex_ = x; +} + +inline bool options:: +index_regex_specified () const +{ + return this->index_regex_specified_; +} + +inline void options:: +index_regex_specified (bool x) +{ + this->index_regex_specified_ = x; +} + +inline const database_map<std::vector<std::string> >& options:: +fkey_regex () const +{ + return this->fkey_regex_; +} + +inline database_map<std::vector<std::string> >& options:: +fkey_regex () +{ + return this->fkey_regex_; +} + +inline void options:: +fkey_regex (const database_map<std::vector<std::string> >& x) +{ + this->fkey_regex_ = x; +} + +inline bool options:: +fkey_regex_specified () const +{ + return this->fkey_regex_specified_; +} + +inline void options:: +fkey_regex_specified (bool x) +{ + this->fkey_regex_specified_ = x; +} + +inline const database_map<std::vector<std::string> >& options:: +sequence_regex () const +{ + return this->sequence_regex_; +} + +inline database_map<std::vector<std::string> >& options:: +sequence_regex () +{ + return this->sequence_regex_; +} + +inline void options:: +sequence_regex (const database_map<std::vector<std::string> >& x) +{ + this->sequence_regex_ = x; +} + +inline bool options:: +sequence_regex_specified () const +{ + return this->sequence_regex_specified_; +} + +inline void options:: +sequence_regex_specified (bool x) +{ + this->sequence_regex_specified_ = x; +} + +inline const database_map<std::vector<std::string> >& options:: +statement_regex () const +{ + return this->statement_regex_; +} + +inline database_map<std::vector<std::string> >& options:: +statement_regex () +{ + return this->statement_regex_; +} + +inline void options:: +statement_regex (const database_map<std::vector<std::string> >& x) +{ + this->statement_regex_ = x; +} + +inline bool options:: +statement_regex_specified () const +{ + return this->statement_regex_specified_; +} + +inline void options:: +statement_regex_specified (bool x) +{ + this->statement_regex_specified_ = x; +} + +inline const database_map<std::vector<std::string> >& options:: +sql_name_regex () const +{ + return this->sql_name_regex_; +} + +inline database_map<std::vector<std::string> >& options:: +sql_name_regex () +{ + return this->sql_name_regex_; +} + +inline void options:: +sql_name_regex (const database_map<std::vector<std::string> >& x) +{ + this->sql_name_regex_ = x; +} + +inline bool options:: +sql_name_regex_specified () const +{ + return this->sql_name_regex_specified_; +} + +inline void options:: +sql_name_regex_specified (bool x) +{ + this->sql_name_regex_specified_ = x; +} + +inline const bool& options:: +sql_name_regex_trace () const +{ + return this->sql_name_regex_trace_; +} + +inline bool& options:: +sql_name_regex_trace () +{ + return this->sql_name_regex_trace_; +} + +inline void options:: +sql_name_regex_trace (const bool& x) +{ + this->sql_name_regex_trace_ = x; +} + +inline const std::vector<std::string>& options:: +accessor_regex () const +{ + return this->accessor_regex_; +} + +inline std::vector<std::string>& options:: +accessor_regex () +{ + return this->accessor_regex_; +} + +inline void options:: +accessor_regex (const std::vector<std::string>& x) +{ + this->accessor_regex_ = x; +} + +inline bool options:: +accessor_regex_specified () const +{ + return this->accessor_regex_specified_; +} + +inline void options:: +accessor_regex_specified (bool x) +{ + this->accessor_regex_specified_ = x; +} + +inline const bool& options:: +accessor_regex_trace () const +{ + return this->accessor_regex_trace_; +} + +inline bool& options:: +accessor_regex_trace () +{ + return this->accessor_regex_trace_; +} + +inline void options:: +accessor_regex_trace (const bool& x) +{ + this->accessor_regex_trace_ = x; +} + +inline const std::vector<std::string>& options:: +modifier_regex () const +{ + return this->modifier_regex_; +} + +inline std::vector<std::string>& options:: +modifier_regex () +{ + return this->modifier_regex_; +} + +inline void options:: +modifier_regex (const std::vector<std::string>& x) +{ + this->modifier_regex_ = x; +} + +inline bool options:: +modifier_regex_specified () const +{ + return this->modifier_regex_specified_; +} + +inline void options:: +modifier_regex_specified (bool x) +{ + this->modifier_regex_specified_ = x; +} + +inline const bool& options:: +modifier_regex_trace () const +{ + return this->modifier_regex_trace_; +} + +inline bool& options:: +modifier_regex_trace () +{ + return this->modifier_regex_trace_; +} + +inline void options:: +modifier_regex_trace (const bool& x) +{ + this->modifier_regex_trace_ = x; +} + +inline const bool& options:: +include_with_brackets () const +{ + return this->include_with_brackets_; +} + +inline bool& options:: +include_with_brackets () +{ + return this->include_with_brackets_; +} + +inline void options:: +include_with_brackets (const bool& x) +{ + this->include_with_brackets_ = x; +} + +inline const std::string& options:: +include_prefix () const +{ + return this->include_prefix_; +} + +inline std::string& options:: +include_prefix () +{ + return this->include_prefix_; +} + +inline void options:: +include_prefix (const std::string& x) +{ + this->include_prefix_ = x; +} + +inline bool options:: +include_prefix_specified () const +{ + return this->include_prefix_specified_; +} + +inline void options:: +include_prefix_specified (bool x) +{ + this->include_prefix_specified_ = x; +} + +inline const std::vector<std::string>& options:: +include_regex () const +{ + return this->include_regex_; +} + +inline std::vector<std::string>& options:: +include_regex () +{ + return this->include_regex_; +} + +inline void options:: +include_regex (const std::vector<std::string>& x) +{ + this->include_regex_ = x; +} + +inline bool options:: +include_regex_specified () const +{ + return this->include_regex_specified_; +} + +inline void options:: +include_regex_specified (bool x) +{ + this->include_regex_specified_ = x; +} + +inline const bool& options:: +include_regex_trace () const +{ + return this->include_regex_trace_; +} + +inline bool& options:: +include_regex_trace () +{ + return this->include_regex_trace_; +} + +inline void options:: +include_regex_trace (const bool& x) +{ + this->include_regex_trace_ = x; +} + +inline const std::string& options:: +guard_prefix () const +{ + return this->guard_prefix_; +} + +inline std::string& options:: +guard_prefix () +{ + return this->guard_prefix_; +} + +inline void options:: +guard_prefix (const std::string& x) +{ + this->guard_prefix_ = x; +} + +inline bool options:: +guard_prefix_specified () const +{ + return this->guard_prefix_specified_; +} + +inline void options:: +guard_prefix_specified (bool x) +{ + this->guard_prefix_specified_ = x; +} + +inline const bool& options:: +show_sloc () const +{ + return this->show_sloc_; +} + +inline bool& options:: +show_sloc () +{ + return this->show_sloc_; +} + +inline void options:: +show_sloc (const bool& x) +{ + this->show_sloc_ = x; +} + +inline const std::size_t& options:: +sloc_limit () const +{ + return this->sloc_limit_; +} + +inline std::size_t& options:: +sloc_limit () +{ + return this->sloc_limit_; +} + +inline void options:: +sloc_limit (const std::size_t& x) +{ + this->sloc_limit_ = x; +} + +inline bool options:: +sloc_limit_specified () const +{ + return this->sloc_limit_specified_; +} + +inline void options:: +sloc_limit_specified (bool x) +{ + this->sloc_limit_specified_ = x; +} + +inline const std::string& options:: +options_file () const +{ + return this->options_file_; +} + +inline std::string& options:: +options_file () +{ + return this->options_file_; +} + +inline void options:: +options_file (const std::string& x) +{ + this->options_file_ = x; +} + +inline bool options:: +options_file_specified () const +{ + return this->options_file_specified_; +} + +inline void options:: +options_file_specified (bool x) +{ + this->options_file_specified_ = x; +} + +inline const std::vector<std::string>& options:: +x () const +{ + return this->x_; +} + +inline std::vector<std::string>& options:: +x () +{ + return this->x_; +} + +inline void options:: +x (const std::vector<std::string>& x) +{ + this->x_ = x; +} + +inline bool options:: +x_specified () const +{ + return this->x_specified_; +} + +inline void options:: +x_specified (bool x) +{ + this->x_specified_ = x; +} + +inline const bool& options:: +v () const +{ + return this->v_; +} + +inline bool& options:: +v () +{ + return this->v_; +} + +inline void options:: +v (const bool& x) +{ + this->v_ = x; +} + +inline const bool& options:: +trace () const +{ + return this->trace_; +} + +inline bool& options:: +trace () +{ + return this->trace_; +} + +inline void options:: +trace (const bool& x) +{ + this->trace_ = x; +} + +inline const std::string& options:: +mysql_engine () const +{ + return this->mysql_engine_; +} + +inline std::string& options:: +mysql_engine () +{ + return this->mysql_engine_; +} + +inline void options:: +mysql_engine (const std::string& x) +{ + this->mysql_engine_ = x; +} + +inline bool options:: +mysql_engine_specified () const +{ + return this->mysql_engine_specified_; +} + +inline void options:: +mysql_engine_specified (bool x) +{ + this->mysql_engine_specified_ = x; +} + +inline const bool& options:: +sqlite_override_null () const +{ + return this->sqlite_override_null_; +} + +inline bool& options:: +sqlite_override_null () +{ + return this->sqlite_override_null_; +} + +inline void options:: +sqlite_override_null (const bool& x) +{ + this->sqlite_override_null_ = x; +} + +inline const bool& options:: +sqlite_lax_auto_id () const +{ + return this->sqlite_lax_auto_id_; +} + +inline bool& options:: +sqlite_lax_auto_id () +{ + return this->sqlite_lax_auto_id_; +} + +inline void options:: +sqlite_lax_auto_id (const bool& x) +{ + this->sqlite_lax_auto_id_ = x; +} + +inline const ::pgsql_version& options:: +pgsql_server_version () const +{ + return this->pgsql_server_version_; +} + +inline ::pgsql_version& options:: +pgsql_server_version () +{ + return this->pgsql_server_version_; +} + +inline void options:: +pgsql_server_version (const ::pgsql_version& x) +{ + this->pgsql_server_version_ = x; +} + +inline bool options:: +pgsql_server_version_specified () const +{ + return this->pgsql_server_version_specified_; +} + +inline void options:: +pgsql_server_version_specified (bool x) +{ + this->pgsql_server_version_specified_ = x; +} + +inline const ::oracle_version& options:: +oracle_client_version () const +{ + return this->oracle_client_version_; +} + +inline ::oracle_version& options:: +oracle_client_version () +{ + return this->oracle_client_version_; +} + +inline void options:: +oracle_client_version (const ::oracle_version& x) +{ + this->oracle_client_version_ = x; +} + +inline bool options:: +oracle_client_version_specified () const +{ + return this->oracle_client_version_specified_; +} + +inline void options:: +oracle_client_version_specified (bool x) +{ + this->oracle_client_version_specified_ = x; +} + +inline const bool& options:: +oracle_warn_truncation () const +{ + return this->oracle_warn_truncation_; +} + +inline bool& options:: +oracle_warn_truncation () +{ + return this->oracle_warn_truncation_; +} + +inline void options:: +oracle_warn_truncation (const bool& x) +{ + this->oracle_warn_truncation_ = x; +} + +inline const ::mssql_version& options:: +mssql_server_version () const +{ + return this->mssql_server_version_; +} + +inline ::mssql_version& options:: +mssql_server_version () +{ + return this->mssql_server_version_; +} + +inline void options:: +mssql_server_version (const ::mssql_version& x) +{ + this->mssql_server_version_ = x; +} + +inline bool options:: +mssql_server_version_specified () const +{ + return this->mssql_server_version_specified_; +} + +inline void options:: +mssql_server_version_specified (bool x) +{ + this->mssql_server_version_specified_ = x; +} + +inline const unsigned int& options:: +mssql_short_limit () const +{ + return this->mssql_short_limit_; +} + +inline unsigned int& options:: +mssql_short_limit () +{ + return this->mssql_short_limit_; +} + +inline void options:: +mssql_short_limit (const unsigned int& x) +{ + this->mssql_short_limit_ = x; +} + +inline bool options:: +mssql_short_limit_specified () const +{ + return this->mssql_short_limit_specified_; +} + +inline void options:: +mssql_short_limit_specified (bool x) +{ + this->mssql_short_limit_specified_ = x; +} + +// Begin epilogue. +// +// +// End epilogue. diff --git a/odb/odb/processor.cxx b/odb/odb/processor.cxx new file mode 100644 index 0000000..d48baa7 --- /dev/null +++ b/odb/odb/processor.cxx @@ -0,0 +1,3255 @@ +// file : odb/processor.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <odb/gcc.hxx> + +#include <iostream> +#include <algorithm> // std::find + +#include <odb/common.hxx> +#include <odb/lookup.hxx> +#include <odb/context.hxx> +#include <odb/cxx-lexer.hxx> +#include <odb/processor.hxx> +#include <odb/diagnostics.hxx> + +#include <odb/relational/processor.hxx> + +using namespace std; + +namespace +{ + // Find name hint for this type decl. + // + static semantics::names* + find_hint (semantics::unit& u, tree decl) + { + semantics::names* r (0); + + for (tree ot (DECL_ORIGINAL_TYPE (decl)); + ot != 0; + ot = decl ? DECL_ORIGINAL_TYPE (decl) : 0) + { + if ((r = u.find_hint (ot))) + break; + + decl = TYPE_NAME (ot); + } + + return r; + } + + // Indirect (dynamic) context values. + // + static semantics::type* + id_tree_type (semantics::names*& hint) + { + context& c (context::current ()); + data_member_path& id (*context::id_member (*c.top_object)); + return &c.utype (id, hint); + } + + struct data_member: traversal::data_member, context + { + virtual void + traverse (semantics::data_member& m) + { + if (transient (m)) + return; + + semantics::names* hint; + semantics::type& t (utype (m, hint)); + + // See if this member is a section. + // + if (t.fq_name () == "::odb::section") + { + using semantics::class_; + + class_& c (dynamic_cast<class_&> (m.scope ())); + class_* poly_root (polymorphic (c)); + semantics::data_member* opt (optimistic (c)); + + // If we have sections in a polymorphic optimistic hierarchy, + // then the version member should be in the root. + // + if (poly_root == &c && opt != 0 && &opt->scope () != &c) + { + error (m.location ()) << "version must be a direct data member " << + "of a class that contains sections" << endl; + info (opt->location ()) << "version member is declared here" << endl; + throw operation_failed (); + } + + process_user_section (m, c); + + // We don't need a modifier but the accessor should be by-reference. + // + process_access (m, "get"); + + member_access& ma (m.get<member_access> ("get")); + if (ma.by_value) + { + error (ma.loc) << "accessor returning a value cannot be used " + << "for a section" << endl; + info (ma.loc) << "accessor returning a const reference is required" + << endl; + info (m.location ()) << "data member is defined here" << endl; + throw operation_failed (); + } + + // Mark this member as transient since we don't store it in the + // database. + // + m.set ("transient", true); + + features.section = true; + return; + } + else + { + process_access (m, "get"); + process_access (m, "set"); + } + + // See if this member belongs to a section. + // + if (m.count ("section-member") != 0) + process_section_member (m); + + // We don't need to do any further processing for common if we + // are generating static multi-database code. + // + if (multi_static && options.database ()[0] == database::common) + return; + + // Handle wrappers. + // + semantics::type* wt (0), *qwt (0); + semantics::names* whint (0); + if (process_wrapper (t)) + { + qwt = t.get<semantics::type*> ("wrapper-type"); + whint = t.get<semantics::names*> ("wrapper-hint"); + wt = &utype (*qwt, whint); + } + + // If the type is const and the member is not id, version, or + // inverse, then mark it as readonly. In case of a wrapper, + // both the wrapper type and the wrapped type must be const. + // To see why, consider these possibilities: + // + // unique_ptr<const T> - can modify by setting a new pointer + // const unique_ptr<T> - can modify by changing the pointed-to value + // + if (const_member (m) && !(id (m) || version (m) || m.count ("inverse"))) + { + if (qwt == 0 || const_type (*qwt)) + m.set ("readonly", true); + } + + process_points_to (m); + + if (composite_wrapper (t)) + return; + + // Process object pointer. The resulting column will be a simple + // or composite value. + // + if (process_object_pointer (m, t)) + return; + + // Before checking if this is a container, check if this member + // or its type were deduced to be a simple value based on the + // pragmas. This is necessary because a container member (e.g., + // vector<char>) can be "overridden" into a simple value (e.g., + // BLOB) with a pragma. + // + if (m.count ("simple") || + t.count ("simple") || + (wt != 0 && wt->count ("simple"))) + return; + + process_container (m, (wt != 0 ? *wt : t)); + } + + // + // Process member access expressions. + // + + enum found_type + { + found_none, + found_some, // Found something but keep looking for a better one. + found_best + }; + + // Check if a function is a suitable accessor for this member. + // + found_type + check_accessor (semantics::data_member& m, + tree f, + string const& n, + member_access& ma, + bool strict) + { + // Must be const. + // + if (!DECL_CONST_MEMFUNC_P (f)) + return found_none; + + // Accessor is a function with no arguments (other than 'this'). + // + if (FUNCTION_FIRST_USER_PARMTYPE (f) != void_list_node) + return found_none; + + // Note that to get the return type we have to use + // TREE_TYPE(TREE_TYPE()) and not DECL_RESULT, as + // suggested in the documentation. + // + tree r (TYPE_MAIN_VARIANT (TREE_TYPE (TREE_TYPE (f)))); + gcc_tree_code_type tc (TREE_CODE (r)); + + // In the strict mode make sure the function returns for non-array + // types a value or a (const) reference to the member type and for + // array types a (const) pointer to element type. In the lax mode + // we just check that the return value is not void. + // + if (strict) + { + semantics::type& t (utype (m)); + semantics::array* ar (dynamic_cast<semantics::array*> (&t)); + + if (ar != 0 && tc != POINTER_TYPE) + return found_none; + + tree bt (ar != 0 || tc == REFERENCE_TYPE ? TREE_TYPE (r) : r); + tree bt_mv (TYPE_MAIN_VARIANT (bt)); + + if ((ar != 0 ? ar->base_type () : t).tree_node () != bt_mv) + return found_none; + } + else if (r == void_type_node) + return found_none; + + cxx_tokens& e (ma.expr); + e.push_back (cxx_token (0, CPP_KEYWORD, "this")); + e.push_back (cxx_token (0, CPP_DOT)); + e.push_back (cxx_token (0, CPP_NAME, n)); + e.push_back (cxx_token (0, CPP_OPEN_PAREN)); + e.push_back (cxx_token (0, CPP_CLOSE_PAREN)); + + // See if it returns by value. + // + ma.by_value = (tc != REFERENCE_TYPE && tc != POINTER_TYPE); + + return found_best; + } + + // Check if a function is a suitable modifier for this member. + // + found_type + check_modifier (semantics::data_member& m, + tree f, + string const& n, + member_access& ma, + bool strict) + { + tree a (FUNCTION_FIRST_USER_PARMTYPE (f)); + + // For a modifier, it can either be a function that returns a non- + // const reference (or non-const pointer, in case the member is an + // array) or a by-value modifier that sets a new value. If both are + // available, we prefer the former for efficiency. + // + cxx_tokens& e (ma.expr); + semantics::type& t (utype (m)); + semantics::array* ar (dynamic_cast<semantics::array*> (&t)); + + if (a == void_list_node) + { + // Note that to get the return type we have to use + // TREE_TYPE(TREE_TYPE()) and not DECL_RESULT, as + // suggested in the documentation. + // + tree r (TYPE_MAIN_VARIANT (TREE_TYPE (TREE_TYPE (f)))); + gcc_tree_code_type tc (TREE_CODE (r)); + + // By-reference modifier. Should return a reference or a pointer. + // + if (tc != (ar != 0 ? POINTER_TYPE : REFERENCE_TYPE)) + return found_none; + + // The base type should not be const and, in strict mode, should + // match the member type. + // + tree bt (TREE_TYPE (r)); + + if (CP_TYPE_CONST_P (bt)) + return found_none; + + tree bt_mv (TYPE_MAIN_VARIANT (bt)); + + if (strict && (ar != 0 ? ar->base_type () : t).tree_node () != bt_mv) + return found_none; + + e.clear (); // Could contain by value modifier. + e.push_back (cxx_token (0, CPP_KEYWORD, "this")); + e.push_back (cxx_token (0, CPP_DOT)); + e.push_back (cxx_token (0, CPP_NAME, n)); + e.push_back (cxx_token (0, CPP_OPEN_PAREN)); + e.push_back (cxx_token (0, CPP_CLOSE_PAREN)); + + return found_best; + } + // Otherwise look for a by value modifier, which is a function + // with a single argument. + // + else if (TREE_CHAIN (a) == void_list_node) + { + // In the lax mode any function with a single argument works + // for us. And we don't care what it returns. + // + if (strict) + { + // In the strict mode make sure the argument matches the + // member. This is exactly the same logic as in accessor + // with regards to arrays, references, etc. + // + tree at (TREE_VALUE (a)); + gcc_tree_code_type tc (TREE_CODE (at)); + + if (ar != 0 && tc != POINTER_TYPE) + return found_none; + + tree bt (ar != 0 || tc == REFERENCE_TYPE ? TREE_TYPE (at) : at); + tree bt_mv (TYPE_MAIN_VARIANT (bt)); + + if ((ar != 0 ? ar->base_type () : t).tree_node () != bt_mv) + return found_none; + } + + if (e.empty ()) + { + e.push_back (cxx_token (0, CPP_KEYWORD, "this")); + e.push_back (cxx_token (0, CPP_DOT)); + e.push_back (cxx_token (0, CPP_NAME, n)); + e.push_back (cxx_token (0, CPP_OPEN_PAREN)); + e.push_back (cxx_token (0, CPP_QUERY)); + e.push_back (cxx_token (0, CPP_CLOSE_PAREN)); + + // Continue searching in case there is version that returns a + // non-const reference which we prefer for efficiency. + // + return found_some; + } + else + return found_none; // We didn't find anything better. + } + + return found_none; + } + + void + process_access (semantics::data_member& m, std::string const& k) + { + bool virt (m.count ("virtual")); + + // Ignore certain special virtual members. + // + if (virt && (m.count ("polymorphic-ref") || m.count ("discriminator"))) + return; + + char const* kind (k == "get" ? "accessor" : "modifier"); + semantics::class_& c (dynamic_cast<semantics::class_&> (m.scope ())); + + // If we don't have an access expression, try to come up with + // one. + // + if (!m.count (k)) + { + found_type found (found_none); + semantics::access const& a (m.named ().access ()); + member_access& ma ( + m.set ( + k, member_access (m.location (), kind, true))); + + // If this member is not virtual and is either public or if we + // are a friend of this class, then go for the member directly. + // + if (!virt && (a == semantics::access::public_ || + c.get<bool> ("friend"))) + { + ma.expr.push_back (cxx_token (0, CPP_KEYWORD, "this")); + ma.expr.push_back (cxx_token (0, CPP_DOT)); + ma.expr.push_back (cxx_token (0, CPP_NAME, m.name ())); + found = found_best; + } + + // Otherwise, try to find a suitable accessor/modifier. + // + + // First try the original name. If that doesn't produce anything, + // then try the public name. + // + bool t (k == "get" + ? options.accessor_regex_trace () + : options.modifier_regex_trace ()); + regex_mapping const& re ( + k == "get" ? accessor_regex : modifier_regex); + + for (unsigned short j (0); found != found_best && j != 2; ++j) + { + string b (j == 0 ? m.name () : public_name (m, false)); + + // Skip the second pass if original and public names are the same. + // + if (j == 1 && b == m.name ()) + continue; + + if (t) + cerr << kind << (j == 0 ? " original" : " public") + << " name '" << b << "'" << endl; + + for (regex_mapping::const_iterator i (re.begin ()); + found != found_best && i != re.end (); + ++i) + { + if (t) + cerr << "try: '" << i->regex () << "' : "; + + if (!i->match (b)) + { + if (t) + cerr << '-' << endl; + continue; + } + + string n (i->replace (b)); + + if (t) + cerr << "'" << n << "' : "; + + tree decl ( + lookup_qualified_name ( + c.tree_node (), get_identifier (n.c_str ()), false, false)); + + if (decl == error_mark_node || TREE_CODE (decl) != BASELINK) + { + if (t) + cerr << '-' << endl; + continue; + } + + // OVL_* macros work for both FUNCTION_DECL and OVERLOAD. + // +#if BUILDING_GCC_MAJOR >= 8 + for (ovl_iterator i (BASELINK_FUNCTIONS (decl)); i; ++i) +#else + for (tree o (BASELINK_FUNCTIONS (decl)); o != 0; o = OVL_NEXT (o)) +#endif + { +#if BUILDING_GCC_MAJOR >= 8 + tree f (*i); +#else + tree f (OVL_CURRENT (o)); +#endif + // We are only interested in public non-static member + // functions. Note that TREE_PUBLIC() returns something + // other than what we need. + // + if (!DECL_NONSTATIC_MEMBER_FUNCTION_P (f) || + TREE_PRIVATE (f) || TREE_PROTECTED (f)) + continue; + + found_type r (k == "get" + ? check_accessor (m, f, n, ma, true) + : check_modifier (m, f, n, ma, true)); + + if (r != found_none) + { + // Update the location of the access expression to point + // to this function. + // + ma.loc = location (real_source_location (f)); + found = r; + } + } + + if (t) + cerr << (found != found_none ? '+' : '-') << endl; + } + } + + // If that didn't work then the generated code won't be able + // to access this member. + // + if (found == found_none) + { + location const& l (m.location ()); + + if (virt) + { + error (l) << "no suitable " << kind << " function could be " + << "automatically found for virtual data member '" + << m.name () << "'" << endl; + + info (l) << "use '#pragma db " << k << "' to explicitly " + << "specify the " << kind << " function or " + << "expression" << endl; + } + else + { + error (l) << "data member '" << m.name () << "' is " + << a.string () << " and no suitable " << kind + << " function could be automatically found" << endl; + + info (l) << "consider making class 'odb::access' a friend of " + << "class '" << class_name (c) << "'" << endl; + + info (l) << "or use '#pragma db " << k << "' to explicitly " + << "specify the " << kind << " function or " + << "expression" << endl; + } + + throw operation_failed (); + } + } + + member_access& ma (m.get<member_access> (k)); + + if (ma.empty ()) + return; + + cxx_tokens& e (ma.expr); + + // If it is just a name, resolve it and convert to an appropriate + // expression. + // + if (e.size () == 1 && e.back ().type == CPP_NAME) + { + string n (e.back ().literal); + e.clear (); + + tree decl ( + lookup_qualified_name ( + c.tree_node (), get_identifier (n.c_str ()), false, false)); + + if (decl == error_mark_node) + { + error (ma.loc) << "unable to resolve data member or function " + << "name '" << n << "'" << endl; + throw operation_failed (); + } + + switch (TREE_CODE (decl)) + { + case FIELD_DECL: + { + e.push_back (cxx_token (0, CPP_KEYWORD, "this")); + e.push_back (cxx_token (0, CPP_DOT)); + e.push_back (cxx_token (0, CPP_NAME, n)); + break; + } + case BASELINK: + { + // OVL_* macros work for both FUNCTION_DECL and OVERLOAD. + // +#if BUILDING_GCC_MAJOR >= 8 + for (ovl_iterator i (BASELINK_FUNCTIONS (decl)); i; ++i) +#else + for (tree o (BASELINK_FUNCTIONS (decl)); o != 0; o = OVL_NEXT (o)) +#endif + { +#if BUILDING_GCC_MAJOR >= 8 + tree f (*i); +#else + tree f (OVL_CURRENT (o)); +#endif + // We are only interested in non-static member functions. + // + if (!DECL_NONSTATIC_MEMBER_FUNCTION_P (f)) + continue; + + if ((k == "get" + ? check_accessor (m, f, n, ma, false) + : check_modifier (m, f, n, ma, false)) == found_best) + break; + } + + if (e.empty ()) + { + error (ma.loc) << "unable to find suitable " << kind + << " function '" << n << "'" << endl; + throw operation_failed (); + } + break; + } + default: + { + error (ma.loc) << "name '" << n << "' does not refer to a data " + << "member or function" << endl; + throw operation_failed (); + } + } + } + + // If there is no 'this' keyword, then add it as a prefix. + // + { + bool t (false); + for (cxx_tokens::iterator i (e.begin ()); i != e.end (); ++i) + { + if (i->type == CPP_KEYWORD && i->literal == "this") + { + t = true; + break; + } + } + + if (!t) + { + e.insert (e.begin (), cxx_token (0, CPP_DOT)); + e.insert (e.begin (), cxx_token (0, CPP_KEYWORD, "this")); + } + } + + // Check that there is no placeholder in the accessor expression. + // + if (k == "get" && ma.placeholder ()) + { + error (ma.loc) << "(?) placeholder in the accessor expression" + << endl; + throw operation_failed (); + } + + // Check that the member type is default-constructible if we + // have a by value modifier. + // + if (k == "set" && ma.placeholder ()) + { + semantics::class_* c (dynamic_cast<semantics::class_*> (&utype (m))); + + // Assume all other types are default-constructible. + // + if (c != 0) + { + // If this type is a class template instantiation, then make + // sure it is instantiated. While types used in real members + // will be instantiated, this is not necessarily the case for + // virtual members. Without the instantiation we won't be able + // to detect whether the type has the default ctor. + // + // It would have been cleaner to do it in post_process_pragmas() + // but there we don't yet know whether we need the default ctor. + // And it is a good idea not to require instantiability unless + // we really need it. + // + tree type (c->tree_node ()); + + if (!COMPLETE_TYPE_P (type) && + CLASSTYPE_TEMPLATE_INSTANTIATION (type)) + { + // Reset input location so that we get nice diagnostics in + // case of an error. Use the location of the virtual pragma. + // + location_t loc (m.get<location_t> ("virtual-location")); + input_location = loc; + + if (instantiate_class_template (type) == error_mark_node || + errorcount != 0 || + !COMPLETE_TYPE_P (type)) + { + error (loc) << "unable to instantiate virtual data member " << + "type" << endl; + throw operation_failed (); + } + } + + if (!c->default_ctor ()) + { + error (ma.loc) << "modifier expression requires member type " << + "to be default-constructible" << endl; + throw operation_failed (); + } + } + } + } + + // + // Process section. + // + + user_section& + process_user_section (semantics::data_member& m, semantics::class_& c) + { + user_sections& uss (c.get<user_sections> ("user-sections")); + + user_section::load_type l ( + m.get ("section-load", user_section::load_eager)); + + user_section::update_type u ( + m.get ("section-update", user_section::update_always)); + + if (l == user_section::load_eager && u == user_section::update_always) + { + location const& l (m.location ()); + + error (l) << "eager-loaded, always-updated section is pointless" + << endl; + + info (l) << "use '#pragma db load' and/or '#pragma db update' to " + "specify an alternative loading and/or updating strategy" << endl; + + info (l) << "or remove the section altogether" << endl; + + throw operation_failed (); + } + + size_t n (uss.count (user_sections::count_total | + user_sections::count_all | + user_sections::count_special_version)); + user_section us (m, c, n, l, u); + + // We may already have seen this section (e.g., forward reference + // from a member of this section). + // + user_sections::iterator i (find (uss.begin (), uss.end (), us)); + + if (i != uss.end ()) + return *i; + + // If we are adding a new section to an optimistic class with + // version in a base, make sure the base is sectionable. + // + semantics::data_member* opt (optimistic (c)); + if (opt != 0 && &opt->scope () != &c) + { + semantics::class_* poly_root (polymorphic (c)); + semantics::node* base (poly_root ? poly_root : &opt->scope ()); + + if (!base->count ("sectionable")) + { + error (m.location ()) << "adding new section to a derived class " << + "in an optimistic hierarchy requires sectionable base class" << + endl; + + info (base->location ()) << "use '#pragma db object sectionable' " << + "to make the base class of this hierarchy sectionable" << endl; + + throw operation_failed (); + } + } + + uss.push_back (us); + return uss.back (); + } + + void + process_section_member (semantics::data_member& m) + { + using semantics::class_; + using semantics::data_member; + + string name (m.get<string> ("section-member")); + location_t loc (m.get<location_t> ("section-member-location")); + class_& c (dynamic_cast<class_&> (m.scope ())); + + class_* poly_root (polymorphic (c)); + bool poly_derived (poly_root != 0 && poly_root != &c); + + try + { + data_member& us (c.lookup<data_member> (name, class_::include_hidden)); + + // Make sure we are referencing a section. + // + if (utype (us).fq_name () != "::odb::section") + { + error (loc) << "data member '" << name << "' in '#pragma db " << + "section' is not of the odb::section type" << endl; + throw operation_failed (); + } + + // If the section is in the base, handle polymorphic inheritance. + // + class_& b (dynamic_cast<class_&> (us.scope ())); + user_section* s (0); + + if (&c != &b && poly_derived) + { + user_sections& uss (c.get<user_sections> ("user-sections")); + + // This is a section override. See if we have already handled + // this section. + // + for (user_sections::iterator i (uss.begin ()); + s == 0 && i != uss.end (); + ++i) + { + if (i->member == &us) + s = &*i; + } + + // Otherwise, find and copy the nearest override in the base. + // The result should be a chain of overrides leading all the + // way to the original section. + // + if (s == 0) + { + for (class_* b (&polymorphic_base (c));; + b = &polymorphic_base (*b)) + { + user_sections& buss (b->get<user_sections> ("user-sections")); + + for (user_sections::iterator i (buss.begin ()); + s == 0 && i != buss.end (); + ++i) + { + if (i->member == &us) + { + uss.push_back (*i); + uss.back ().object = &c; + uss.back ().base = &*i; + s = &uss.back (); + } + } + + if (s != 0) + break; + + assert (b != poly_root); // We should have found it by now. + } + } + } + else + s = &process_user_section (us, c); + + // Mark the member as added/deleted if the section is added/deleted. + // Also check that the version ordering is correct. + // + if (unsigned long long sav = added (*s->member)) + { + location_t sl (s->member->get<location_t> ("added-location")); + + if (unsigned long long mav = added (m)) + { + location_t ml (m.get<location_t> ("added-location")); + + if (mav < sav) + { + error (ml) << "member addition version is less than the " << + "section addition version" << endl; + info (sl) << "section addition version is specified here" << + endl; + throw operation_failed (); + } + + if (mav == sav) + { + error (ml) << "member addition version is the same as " << + "section addition version" << endl; + info (sl) << "section addition version is specified here" << + endl; + info (ml) << "delete this pragma" << endl; + throw operation_failed (); + } + } + else + { + m.set ("added", sav); + m.set ("added-location", sl); + } + } + + if (unsigned long long sdv = deleted (*s->member)) + { + location_t sl (s->member->get<location_t> ("deleted-location")); + + if (unsigned long long mdv = deleted (m)) + { + location_t ml (m.get<location_t> ("deleted-location")); + + if (mdv > sdv) + { + error (ml) << "member deletion version is greater than the " << + "section deletion version" << endl; + info (sl) << "section deletion version is specified here" << + endl; + throw operation_failed (); + } + + if (mdv == sdv) + { + error (ml) << "member deletion version is the same as " << + "section deletion version" << endl; + info (sl) << "section deletion version is specified here" << + endl; + info (ml) << "delete this pragma" << endl; + throw operation_failed (); + } + } + else + { + m.set ("deleted", sdv); + m.set ("deleted-location", sl); + } + } + + // Insert as object_section. + // + m.set ("section", static_cast<object_section*> (s)); + } + catch (semantics::unresolved const& e) + { + if (e.type_mismatch) + error (loc) << "name '" << name << "' in '#pragma db section' " << + "does not refer to a data member" << endl; + else + error (loc) << "unable to resolve data member '" << name << "' " << + "specified with '#pragma db section'" << endl; + + throw operation_failed (); + } + catch (semantics::ambiguous const& e) + { + error (loc) << "data member name '" << name << "' specified " << + "with '#pragma db section' is ambiguous" << endl; + + info (e.first.named ().location ()) << "could resolve to this " << + "data member" << endl; + + info (e.second.named ().location ()) << "or could resolve to " << + "this data member" << endl; + + throw operation_failed (); + } + } + + // + // Process wrapper. + // + + bool + process_wrapper (semantics::type& t) + { + if (t.count ("wrapper")) + return t.get<bool> ("wrapper"); + + // Check this type with wrapper_traits. + // + tree inst (instantiate_template (wrapper_traits_, t.tree_node ())); + + if (inst == 0) + { + t.set ("wrapper", false); + return false; + } + + // @@ This points to the primary template, not the specialization. + // + tree decl (TYPE_NAME (inst)); + + string f (DECL_SOURCE_FILE (decl)); + size_t l (DECL_SOURCE_LINE (decl)); + size_t c (DECL_SOURCE_COLUMN (decl)); + + // Get the wrapped type. + // + try + { + tree decl ( + lookup_qualified_name ( + inst, get_identifier ("wrapped_type"), true, false)); + + if (decl == error_mark_node || TREE_CODE (decl) != TYPE_DECL) + throw operation_failed (); + + // The wrapped_type alias is a typedef in an instantiation + // that we just instantiated dynamically. As a result there + // is no semantic graph edges corresponding to this typedef + // since we haven't parsed it yet (unless it was instantiated + // explicitly by the user; see below). So to get the tree node + // that can actually be resolved to the graph node, we use + // the source type of this typedef. + // + tree type (DECL_ORIGINAL_TYPE (decl)); + + bool qc (CP_TYPE_CONST_P (type)); + bool qv (CP_TYPE_VOLATILE_P (type)); + bool qr (CP_TYPE_RESTRICT_P (type)); + + type = TYPE_MAIN_VARIANT (type); + semantics::type* wt ( + dynamic_cast<semantics::type*> (unit.find (type))); + + // Object pointers and wrappers often use the same smart + // pointers so check if the wrapped type is an object. + // + if (object (*wt)) + { + t.set ("wrapper", false); + return false; + } + + if (qc || qv || qr) + { + for (semantics::type::qualified_iterator i (wt->qualified_begin ()); + i != wt->qualified_end (); ++i) + { + semantics::qualifier& q (i->qualifier ()); + + if (q.const_ () == qc && + q.volatile_ () == qv && + q.restrict_ () == qr) + { + wt = &q; + break; + } + } + } + + // Find the hint. + // + // If we can't find any, then try to fallback to the wrapped_type + // alias inside wrapper_traits. This requires an explicit + // wrapper_traits instantiation (see above). + // + semantics::names* wh (find_hint (unit, decl)); + + if (wh == nullptr) + wh = unit.find_hint (TREE_TYPE (decl)); + + t.set ("wrapper-type", wt); + t.set ("wrapper-hint", wh); + } + catch (operation_failed const&) + { + os << f << ":" << l << ":" << c << ": error: " + << "wrapper_traits specialization does not define the " + << "wrapped_type type" << endl; + throw; + } + + // Get the null_handler flag. + // + bool null_handler (false); + + try + { + tree nh ( + lookup_qualified_name ( + inst, get_identifier ("null_handler"), false, false)); + + if (nh == error_mark_node || TREE_CODE (nh) != VAR_DECL) + throw operation_failed (); + + // Instantiate this decalaration so that we can get its value. + // + if (DECL_TEMPLATE_INSTANTIATION (nh) && + !DECL_TEMPLATE_INSTANTIATED (nh) && + !DECL_EXPLICIT_INSTANTIATION (nh)) + instantiate_decl (nh, false, false); + + tree init (DECL_INITIAL (nh)); + + if (init == error_mark_node || TREE_CODE (init) != INTEGER_CST) + throw operation_failed (); + + null_handler = static_cast<bool> (integer_value (init)); + t.set ("wrapper-null-handler", null_handler); + } + catch (operation_failed const&) + { + os << f << ":" << l << ":" << c << ": error: " + << "wrapper_traits specialization does not define the " + << "null_handler constant" << endl; + throw; + } + + // Get the null_default flag. + // + if (null_handler) + { + try + { + tree nh ( + lookup_qualified_name ( + inst, get_identifier ("null_default"), false, false)); + + if (nh == error_mark_node || TREE_CODE (nh) != VAR_DECL) + throw operation_failed (); + + // Instantiate this decalaration so that we can get its value. + // + if (DECL_TEMPLATE_INSTANTIATION (nh) && + !DECL_TEMPLATE_INSTANTIATED (nh) && + !DECL_EXPLICIT_INSTANTIATION (nh)) + instantiate_decl (nh, false, false); + + tree init (DECL_INITIAL (nh)); + + if (init == error_mark_node || TREE_CODE (init) != INTEGER_CST) + throw operation_failed (); + + t.set ("wrapper-null-default", + static_cast<bool> (integer_value (init))); + } + catch (operation_failed const&) + { + os << f << ":" << l << ":" << c << ": error: " + << "wrapper_traits specialization does not define the " + << "null_default constant" << endl; + throw; + } + } + + // Check if the wrapper is a TR1 template instantiation. + // + if (tree ti = TYPE_TEMPLATE_INFO (t.tree_node ())) + { + tree decl (TI_TEMPLATE (ti)); // DECL_TEMPLATE + + // Get to the most general template declaration. + // + while (DECL_TEMPLATE_INFO (decl)) + decl = DECL_TI_TEMPLATE (decl); + + bool& tr1 (features.tr1_pointer); + bool& boost (features.boost_pointer); + + string n (decl_as_string (decl, TFF_PLAIN_IDENTIFIER)); + + // In case of a boost TR1 implementation, we cannot distinguish + // between the boost:: and std::tr1:: usage since the latter is + // just a using-declaration for the former. + // + tr1 = tr1 + || n.compare (0, 8, "std::tr1") == 0 + || n.compare (0, 10, "::std::tr1") == 0; + + boost = boost + || n.compare (0, 17, "boost::shared_ptr") == 0 + || n.compare (0, 19, "::boost::shared_ptr") == 0; + } + + t.set ("wrapper", true); + return true; + } + + // + // Process object pointer. + // + + semantics::class_* + process_object_pointer (semantics::data_member& m, + semantics::type& t, + string const& kp = string ()) + { + using semantics::class_; + using semantics::data_member; + + class_* c (0); + + // The overall idea is as follows: try to instantiate the pointer + // traits class template. If we are successeful, then get the + // element type and see if it is an object. + // + if (t.count ("element-type")) + c = t.get<class_*> ("element-type"); + else + { + tree inst (instantiate_template (pointer_traits_, t.tree_node ())); + + if (inst == 0) + return 0; + + // @@ This points to the primary template, not the specialization. + // + tree decl (TYPE_NAME (inst)); + + string fl (DECL_SOURCE_FILE (decl)); + size_t ln (DECL_SOURCE_LINE (decl)); + size_t cl (DECL_SOURCE_COLUMN (decl)); + + // Get the element type. + // + tree tn (0); + try + { + tree decl ( + lookup_qualified_name ( + inst, get_identifier ("element_type"), true, false)); + + if (decl == error_mark_node || TREE_CODE (decl) != TYPE_DECL) + throw operation_failed (); + + tn = TYPE_MAIN_VARIANT (TREE_TYPE (decl)); + + // Check if the pointer is a TR1 template instantiation. + // + if (tree ti = TYPE_TEMPLATE_INFO (t.tree_node ())) + { + decl = TI_TEMPLATE (ti); // DECL_TEMPLATE + + // Get to the most general template declaration. + // + while (DECL_TEMPLATE_INFO (decl)) + decl = DECL_TI_TEMPLATE (decl); + + bool& tr1 (features.tr1_pointer); + bool& boost (features.boost_pointer); + + string n (decl_as_string (decl, TFF_PLAIN_IDENTIFIER)); + + // In case of a boost TR1 implementation, we cannot distinguish + // between the boost:: and std::tr1:: usage since the latter is + // just a using-declaration for the former. + // + tr1 = tr1 + || n.compare (0, 8, "std::tr1") == 0 + || n.compare (0, 10, "::std::tr1") == 0; + + boost = boost + || n.compare (0, 17, "boost::shared_ptr") == 0 + || n.compare (0, 19, "::boost::shared_ptr") == 0; + } + } + catch (operation_failed const&) + { + os << fl << ":" << ln << ":" << cl << ": error: pointer_traits " + << "specialization does not define the 'element_type' type" + << endl; + throw; + } + + c = dynamic_cast<class_*> (unit.find (tn)); + + if (c == 0 || !object (*c)) + return 0; + + t.set ("element-type", c); + + // Determine the pointer kind. + // + try + { + tree kind ( + lookup_qualified_name ( + inst, get_identifier ("kind"), false, false)); + + if (kind == error_mark_node || TREE_CODE (kind) != VAR_DECL) + throw operation_failed (); + + // Instantiate this decalaration so that we can get its value. + // + if (DECL_TEMPLATE_INSTANTIATION (kind) && + !DECL_TEMPLATE_INSTANTIATED (kind) && + !DECL_EXPLICIT_INSTANTIATION (kind)) + instantiate_decl (kind, false, false); + + tree init (DECL_INITIAL (kind)); + + if (init == error_mark_node || TREE_CODE (init) != INTEGER_CST) + throw operation_failed (); + + pointer_kind_type pk = static_cast<pointer_kind_type> ( + integer_value (init)); + t.set ("pointer-kind", pk); + } + catch (operation_failed const&) + { + os << fl << ":" << ln << ":" << cl << ": error: pointer_traits " + << "specialization does not define the 'kind' constant" << endl; + throw; + } + + // Get the lazy flag. + // + try + { + tree lazy ( + lookup_qualified_name ( + inst, get_identifier ("lazy"), false, false)); + + if (lazy == error_mark_node || TREE_CODE (lazy) != VAR_DECL) + throw operation_failed (); + + // Instantiate this decalaration so that we can get its value. + // + if (DECL_TEMPLATE_INSTANTIATION (lazy) && + !DECL_TEMPLATE_INSTANTIATED (lazy) && + !DECL_EXPLICIT_INSTANTIATION (lazy)) + instantiate_decl (lazy, false, false); + + tree init (DECL_INITIAL (lazy)); + + if (init == error_mark_node || TREE_CODE (init) != INTEGER_CST) + throw operation_failed (); + + t.set ("pointer-lazy", static_cast<bool> (integer_value (init))); + } + catch (operation_failed const&) + { + os << fl << ":" << ln << ":" << cl << ": error: pointer_traits " + << "specialization does not define the 'kind' constant" << endl; + throw; + } + } + + // See if this is the inverse side of a bidirectional relationship. + // If so, then resolve the member path and cache it in the context. + // + if (m.count ("inverse")) + { + string name (m.get<string> ("inverse")); + location_t l (m.get<location_t> ("inverse-location")); + data_member_path mp (resolve_data_members (*c, name, l, lex_)); + + { + string tl; + data_member& m (*mp.back ()); + + if (container (m) && lex_.next (tl) != CPP_EOF) + { + error (l) << "unexpect name after container member " << + m.name () << " in '#pragma db inverse'" << endl; + throw operation_failed (); + } + } + + // Validate each member. + // + for (data_member_path::iterator i (mp.begin ()); i != mp.end (); ++i) + { + data_member& im (**i); + const string& n (im.name ()); + + if (im.count ("transient")) + { + error (l) << "data member '" << n << "' specified with " << + "'#pragma db inverse' is transient" << endl; + info (im.location ()) << "data member '" << n << "' is " << + "defined here" << endl; + throw operation_failed (); + } + + if (im.count ("inverse") || im.count ("value-inverse")) + { + error (l) << "data member '" << n << "' specified with " << + "'#pragma db inverse' is itself inverse" << endl; + info (im.location ()) << "data member '" << n << "' is " << + "defined here" << endl; + throw operation_failed (); + } + } + + // @@ Would be good to check that the other end is actually + // an object pointer/points_to and points to the correct + // object. But the other class may not have been processed + // yet. Need to do in validator, pass 2. + // + m.remove ("inverse"); + m.set (kp + (kp.empty () ? "": "-") + "inverse", mp); + } + + return c; + } + + // + // Process points-to pragma. + // + + void + process_points_to (semantics::data_member& m, + string const& /*kp*/ = string ()) + { + if (!m.count ("points-to")) + return; + + using semantics::class_; + + tree t (m.get<tree> ("points-to")); + location_t l (m.get<location_t> ("points-to-location")); + + class_* c (dynamic_cast<class_*> (unit.find (t))); + + if (c == 0 || !object (*c)) + { + error (l) << "name specified with '#pragma db points_to' does " + << "not refer to an object" << endl; + throw operation_failed (); + } + + m.remove ("points-to"); + m.set (/*kp + (kp.empty () ? "": "-") + */"points-to", c); + } + + // + // Process container. + // + + void + process_container_value (semantics::type& t, + semantics::data_member& m, + string const& prefix, + bool obj_ptr) + { + if (composite_wrapper (t)) + return; + + if (obj_ptr) + process_object_pointer (m, t, prefix); + } + + bool + process_container (semantics::data_member& m, semantics::type& t) + { + // The overall idea is as follows: try to instantiate the container + // traits class template. If we are successeful, then this is a + // container type and we can extract the various information from + // the instantiation. Otherwise, this is not a container. + // + location ml (m.location ()); + + container_kind_type ck; + bool smart; + semantics::type* vt (0); + semantics::type* it (0); + semantics::type* kt (0); + + semantics::names* vh (0); + semantics::names* ih (0); + semantics::names* kh (0); + + if (t.count ("container-kind")) + { + ck = t.get<container_kind_type> ("container-kind"); + smart = t.get<bool> ("container-smart"); + + vt = &utype (m, vh, "value"); + + if (ck == ck_ordered) + it = &utype (m, ih, "index"); + + if (ck == ck_map || ck == ck_multimap) + kt = &utype (m, kh, "key"); + } + else + { + tree inst (instantiate_template (container_traits_, t.tree_node ())); + + if (inst == 0) + return false; + + // @@ This points to the primary template, not the specialization. + // + tree decl (TYPE_NAME (inst)); + + string f (DECL_SOURCE_FILE (decl)); + size_t l (DECL_SOURCE_LINE (decl)); + size_t c (DECL_SOURCE_COLUMN (decl)); + + // Determine the container kind. + // + try + { + tree decl ( + lookup_qualified_name ( + inst, get_identifier ("kind"), false, false)); + + if (decl == error_mark_node || TREE_CODE (decl) != VAR_DECL) + throw operation_failed (); + + // Instantiate this decalaration so that we can get its value. + // + if (DECL_TEMPLATE_INSTANTIATION (decl) && + !DECL_TEMPLATE_INSTANTIATED (decl) && + !DECL_EXPLICIT_INSTANTIATION (decl)) + instantiate_decl (decl, false, false); + + tree init (DECL_INITIAL (decl)); + + if (init == error_mark_node || TREE_CODE (init) != INTEGER_CST) + throw operation_failed (); + + ck = static_cast<container_kind_type> (integer_value (init)); + } + catch (operation_failed const&) + { + os << f << ":" << l << ":" << c << ": error: " + << "container_traits specialization does not define the " + << "container kind constant" << endl; + + throw; + } + + t.set ("container-kind", ck); + + // See if it is a smart container. + // + try + { + tree decl ( + lookup_qualified_name ( + inst, get_identifier ("smart"), false, false)); + + if (decl == error_mark_node || TREE_CODE (decl) != VAR_DECL) + throw operation_failed (); + + // Instantiate this decalaration so that we can get its value. + // + if (DECL_TEMPLATE_INSTANTIATION (decl) && + !DECL_TEMPLATE_INSTANTIATED (decl) && + !DECL_EXPLICIT_INSTANTIATION (decl)) + instantiate_decl (decl, false, false); + + tree init (DECL_INITIAL (decl)); + + if (init == error_mark_node || TREE_CODE (init) != INTEGER_CST) + throw operation_failed (); + + smart = static_cast<bool> (integer_value (init)); + } + catch (operation_failed const&) + { + os << f << ":" << l << ":" << c << ": error: " + << "container_traits specialization does not define the " + << "'smart' constant" << endl; + throw; + } + + // For now we only support ordered smart containers. + // + if (smart && ck != ck_ordered) + { + os << f << ":" << l << ":" << c << ": error: only ordered smart " << + "containers are currently supported" << endl; + throw operation_failed (); + } + + t.set ("container-smart", smart); + + // Mark id column as not null. + // + t.set ("id-not-null", true); + + // Get the value type. + // + { + tree decl (lookup_qualified_name ( + inst, get_identifier ("value_type"), true, false)); + + if (decl == error_mark_node || TREE_CODE (decl) != TYPE_DECL) + { + os << f << ":" << l << ":" << c << ": error: " + << "container_traits specialization does not define the " + << "value_type type" << endl; + + throw operation_failed (); + } + + tree type (TYPE_MAIN_VARIANT (TREE_TYPE (decl))); + + if (semantics::node* n = unit.find (type)) + vt = &dynamic_cast<semantics::type&> (*n); + else + { + error (ml) << "container value type is not instantiated" << endl; + info (ml) << "use typedef/using to instantiate" << endl; + throw operation_failed (); + } + + // Find the hint. + // + vh = find_hint (unit, decl); + } + + + t.set ("value-tree-type", vt); + t.set ("value-tree-hint", vh); + vt = &utype (m, vh, "value"); // Map. + + // Issue a warning if we are relaxing null-ness in the container + // type. + // + if (t.count ("value-null") && vt->count ("not-null")) + { + os << t.file () << ":" << t.line () << ":" << t.column () << ":" + << " warning: container value declared null while its type " + << "is declared not null" << endl; + } + + // Get the index type for ordered containers. + // + if (ck == ck_ordered) + { + tree decl ( + lookup_qualified_name ( + inst, get_identifier ("index_type"), true, false)); + + if (decl == error_mark_node || TREE_CODE (decl) != TYPE_DECL) + { + os << f << ":" << l << ":" << c << ": error: " + << "container_traits specialization does not define the " + << "index_type type" << endl; + throw operation_failed (); + } + + tree type (TYPE_MAIN_VARIANT (TREE_TYPE (decl))); + + if (semantics::node* n = unit.find (type)) + it = &dynamic_cast<semantics::type&> (*n); + else + { + error (ml) << "container index type is not instantiated" << endl; + info (ml) << "use typedef/using to instantiate" << endl; + throw operation_failed (); + } + + // Find the hint. + // + ih = find_hint (unit, decl); + + t.set ("index-not-null", true); + t.set ("index-tree-type", it); + t.set ("index-tree-hint", ih); + it = &utype (m, ih, "index"); // Map. + } + + // Get the key type for maps. + // + if (ck == ck_map || ck == ck_multimap) + { + tree decl ( + lookup_qualified_name ( + inst, get_identifier ("key_type"), true, false)); + + if (decl == error_mark_node || TREE_CODE (decl) != TYPE_DECL) + { + os << f << ":" << l << ":" << c << ": error: " + << "container_traits specialization does not define the " + << "key_type type" << endl; + throw operation_failed (); + } + + tree type (TYPE_MAIN_VARIANT (TREE_TYPE (decl))); + + if (semantics::node* n = unit.find (type)) + kt = &dynamic_cast<semantics::type&> (*n); + else + { + error (ml) << "container key type is not instantiated" << endl; + info (ml) << "use typedef/using to instantiate" << endl; + throw operation_failed (); + } + + // Find the hint. + // + kh = find_hint (unit, decl); + + t.set ("key-tree-type", kt); + t.set ("key-tree-hint", kh); + kt = &utype (m, kh, "key"); // Map. + + // Issue a warning if we are relaxing null-ness in the container + // type. + // + if (t.count ("key-null") && kt->count ("not-null")) + { + os << t.file () << ":" << t.line () << ":" << t.column () << ":" + << " warning: container key declared null while its type " + << "is declared not null" << endl; + } + } + + // Determine if container value/index/key types are wrappers. + // + process_wrapper (*vt); + + if (it != 0) + process_wrapper (*it); + + if (kt != 0) + process_wrapper (*kt); + + // Check if we are versioned. For now we are not allowing for + // soft-add/delete in container keys (might be used in WHERE, + // primary key). + // + { + semantics::class_* comp (0); + switch (ck) + { + case ck_ordered: + { + comp = composite_wrapper (*vt); + break; + } + case ck_map: + case ck_multimap: + { + comp = composite_wrapper (*kt); + if (comp == 0 || column_count (*comp).soft == 0) + { + comp = composite_wrapper (*vt); + break; + } + + error (ml) << "map key type cannot have soft-added/deleted " << + "data members" << endl; + info (kt->location ()) << "key type is defined here" << endl; + throw operation_failed (); + } + case ck_set: + case ck_multiset: + { + comp = composite_wrapper (*vt); + if (comp == 0 || column_count (*comp).soft == 0) + { + comp = 0; + break; + } + + error (ml) << "set value type cannot have soft-added/deleted " << + "data members" << endl; + info (vt->location ()) << "value type is defined here" << endl; + throw operation_failed (); + } + } + + if (force_versioned || (comp != 0 && column_count (*comp).soft != 0)) + t.set ("versioned", true); + } + } + + // Process member data. + // + m.set ("id-tree-type", &id_tree_type); + + // Has to be first to handle inverse. + // + process_container_value (*vt, m, "value", true); + + if (it != 0) + process_container_value (*it, m, "index", false); + + if (kt != 0) + process_container_value (*kt, m, "key", true); + + // A map cannot be an inverse container. + // + if (m.count ("value-inverse") && (ck == ck_map || ck == ck_multimap)) + { + os << m.file () << ":" << m.line () << ":" << m.column () << ":" + << " error: inverse container cannot be a map" << endl; + throw operation_failed (); + } + + // If this is an inverse side of a bidirectional object relationship + // and it is an ordered container, mark it as unordred since there is + // no concept of order in this construct. + // + if (ck == ck_ordered && m.count ("value-inverse")) + m.set ("unordered", true); + + // Issue an error if we have a non-inverse smart unordered container. + // + if (smart && ck == ck_ordered && unordered (m) && + !m.count ("value-inverse")) + { + error (ml) << "smart ordered container cannot be unordered" << endl; + throw operation_failed (); + } + + // Issue a warning if we are relaxing null-ness in the member. + // + if (m.count ("value-null") && + (t.count ("value-not-null") || vt->count ("not-null"))) + { + warn (ml) << "container value declared null while the container " + << "type or value type declares it as not null" << endl; + } + + if (ck == ck_map || ck == ck_multimap) + { + if (m.count ("key-null") && + (t.count ("key-not-null") || kt->count ("not-null"))) + { + warn (ml) << "container key declared null while the container " + << "type or key type declares it as not null" << endl; + } + } + + return true; + } + + // + // Implementation details (c-tor, helpers). + // + + data_member () + { + // Find the odb namespace. + // + tree odb = lookup_qualified_name ( + global_namespace, get_identifier ("odb"), false, false); + + if (odb == error_mark_node) + { + os << unit.file () << ": error: unable to resolve odb namespace" + << endl; + + throw operation_failed (); + } + + // Find wrapper traits. + // + wrapper_traits_ = lookup_qualified_name ( + odb, get_identifier ("wrapper_traits"), true, false); + + if (wrapper_traits_ == error_mark_node || + !DECL_CLASS_TEMPLATE_P (wrapper_traits_)) + { + os << unit.file () << ": error: unable to resolve wrapper_traits " + << "in the odb namespace" << endl; + + throw operation_failed (); + } + + // Find pointer traits. + // + pointer_traits_ = lookup_qualified_name ( + odb, get_identifier ("pointer_traits"), true, false); + + if (pointer_traits_ == error_mark_node || + !DECL_CLASS_TEMPLATE_P (pointer_traits_)) + { + os << unit.file () << ": error: unable to resolve pointer_traits " + << "in the odb namespace" << endl; + + throw operation_failed (); + } + + // Find the access class. + // + tree access = lookup_qualified_name ( + odb, get_identifier ("access"), true, false); + + if (access == error_mark_node) + { + os << unit.file () << ": error: unable to resolve access class" + << "in the odb namespace" << endl; + + throw operation_failed (); + } + + access = TREE_TYPE (access); + + // Find container_traits. + // + container_traits_ = lookup_qualified_name ( + access, get_identifier ("container_traits"), true, false); + + if (container_traits_ == error_mark_node || + !DECL_CLASS_TEMPLATE_P (container_traits_)) + { + os << unit.file () << ": error: unable to resolve container_traits " + << "in the odb namespace" << endl; + + throw operation_failed (); + } + } + + static tree + instantiate_template (tree t, tree arg) + { + tree args (make_tree_vec (1)); + TREE_VEC_ELT (args, 0) = arg; + + // This step should succeed regardles of whether there is a + // specialization for this type. + // + tree inst ( + lookup_template_class (t, args, 0, 0, 0, tf_warning_or_error)); + + if (inst == error_mark_node) + { + // Diagnostics has already been issued by lookup_template_class. + // + throw operation_failed (); + } + + inst = TYPE_MAIN_VARIANT (inst); + + // The instantiation may already be complete if it matches a + // (complete) specialization or was used before. + // + if (!COMPLETE_TYPE_P (inst)) + inst = instantiate_class_template (inst); + + // If we cannot instantiate this type, assume there is no suitable + // specialization for it. + // + if (inst == error_mark_node || !COMPLETE_TYPE_P (inst)) + return 0; + + return inst; + } + + private: + tree wrapper_traits_; + tree pointer_traits_; + tree container_traits_; + + cxx_string_lexer lex_; + }; + + struct view_data_member: traversal::data_member, context + { + view_data_member (semantics::class_& c) + : view_ (c), + amap_ (c.get<view_alias_map> ("alias-map")), + omap_ (c.get<view_object_map> ("object-map")) {} + + virtual void + traverse (semantics::data_member& m) + { + using semantics::data_member; + + if (transient (m)) + return; + + semantics::type& t (utype (m)); + + if (semantics::class_* c = object_pointer (t)) + { + location const& l (m.location ()); + + if (lazy_pointer (t)) + { + error (l) << "lazy object pointer in view" << endl; + throw operation_failed (); + } + + // Find the corresponding associated object. First see if this + // data member name matches any aliases. + // + view_alias_map::iterator i (amap_.find (m.name ())); + + if (i == amap_.end ()) + i = amap_.find (public_name (m, false)); + + view_object* vo (0); + + if (i != amap_.end ()) + { + vo = i->second; + + if (vo->obj != c) // @@ Poly base/derived difference? + { + error (l) << "different pointed-to and associated objects" << endl; + info (vo->loc) << "associated object is defined here" << endl; + throw operation_failed (); + } + } + else + { + // If there is no alias match, try the object type. + // + view_object_map::iterator i (omap_.find (c)); + + if (i == omap_.end ()) + { + error (l) << "unable to find associated object for object " + << "pointer" << endl; + info (l) << "use associated object alias as this data member " + << "name" << endl; + throw operation_failed (); + } + + vo = i->second; + } + + if (vo->ptr != 0) + { + location const& l2 (vo->ptr->location ()); + + error (l) << "associated object is already loaded via another " + << "object pointer" << endl; + info (l2) << "the other data member is defined here" << endl; + info (l2) << "use associated object alias as this data member " + << "name to load a different object" << endl; + + throw operation_failed (); + } + + vo->ptr = &m; + m.set ("view-object", vo); + } + } + + private: + semantics::class_& view_; + view_alias_map& amap_; + view_object_map& omap_; + }; + + // Figure out the "summary" added/deleted version for a composite + // value type. + // + struct summary_version: object_members_base + { + summary_version (): av (0), dv (0), a_ (true), d_ (true) {} + + virtual void + traverse_simple (semantics::data_member&) + { + if (a_) + { + if (unsigned long long v = added (member_path_)) + { + if (av == 0 || av < v) + av = v; + } + else + { + av = 0; + a_ = false; + } + } + + if (d_) + { + if (unsigned long long v = deleted (member_path_)) + { + if (dv == 0 || dv > v) + dv = v; + } + else + { + dv = 0; + d_ = false; + } + } + } + + public: + unsigned long long av; + unsigned long long dv; + + bool a_; + bool d_; + }; + + struct class_: traversal::class_, context + { + class_ () + : typedefs_ (true), + std_string_ (0), + std_string_hint_ (0), + access_ (0) + { + *this >> defines_ >> *this; + *this >> typedefs_ >> *this; + + member_names_ >> member_; + + // Resolve the std::string type node. + // + using semantics::scope; + + for (scope::names_iterator_pair ip (unit.find ("std")); + ip.first != ip.second; ++ip.first) + { + if (scope* ns = dynamic_cast<scope*> (&ip.first->named ())) + { + scope::names_iterator_pair jp (ns->find ("string")); + + if (jp.first != jp.second) + { + std_string_ = dynamic_cast<semantics::type*> ( + &jp.first->named ()); + std_string_hint_ = &*jp.first; + break; + } + } + } + + assert (std_string_ != 0); // No std::string? + + // Resolve odb::access, if any. + // + tree odb = lookup_qualified_name ( + global_namespace, get_identifier ("odb"), false, false); + + if (odb != error_mark_node) + { + access_ = lookup_qualified_name ( + odb, get_identifier ("access"), true, false); + + access_ = (access_ != error_mark_node ? TREE_TYPE (access_) : 0); + } + } + + virtual void + traverse (type& c) + { + class_kind_type k (class_kind (c)); + + if (k == class_other) + return; + + names (c); // Process nested classes. + + // Check if odb::access is a friend of this class. + // + c.set ("friend", access_ != 0 && is_friend (c.tree_node (), access_)); + + // Assign pointer. + // + if (k == class_object || k == class_view) + assign_pointer (c); + + if (k == class_object) + traverse_object_pre (c); + else if (k == class_view) + traverse_view_pre (c); + + names (c, member_names_); + + if (k == class_object) + traverse_object_post (c); + else if (k == class_view) + traverse_view_post (c); + else if (k == class_composite) + traverse_composite_post (c); + } + + // + // Object. + // + + virtual void + traverse_object_pre (type& c) + { + using semantics::class_; + using semantics::data_member; + + class_* poly_root (polymorphic (c)); + + // Sections. + // + user_sections& uss (c.set ("user-sections", user_sections (c))); + + // Copy sections from reuse bases. For polymorphic classes, sections + // are overridden. + // + if (poly_root == 0 || poly_root == &c) + { + for (type::inherits_iterator i (c.inherits_begin ()); + i != c.inherits_end (); ++i) + { + type& b (i->base ()); + + if (object (b)) + { + user_sections& buss (b.get<user_sections> ("user-sections")); + + for (user_sections::iterator j (buss.begin ()); + j != buss.end (); + ++j) + { + // Don't copy the special version update section. + // + if (j->special == user_section::special_version) + continue; + + uss.push_back (*j); + uss.back ().object = &c; + uss.back ().base = &*j; + } + } + } + } + + // Determine whether it is a session object. + // + if (!c.count ("session")) + { + // If this is a derived class in a polymorphic hierarchy, + // then it should have the same session value as the root. + // + if (poly_root != 0 && poly_root != &c) + c.set ("session", session (*poly_root)); + else + { + // See if any of the namespaces containing this class specify + // the session value. + // + bool found (false); + for (semantics::scope* s (&class_scope (c));; s = &s->scope_ ()) + { + using semantics::namespace_; + + namespace_* ns (dynamic_cast<namespace_*> (s)); + + if (ns == 0) // Some other scope. + { + if (!s->named_p ()) + break; + + continue; + } + + if (ns->extension ()) + ns = &ns->original (); + + if (ns->count ("session")) + { + c.set ("session", ns->get<bool> ("session")); + found = true; + break; + } + + if (ns->global_scope ()) // Note: namespaces always named. + break; + } + + // If still not found, then use the default value. + // + if (!found) + c.set ("session", options.generate_session ()); + } + } + + if (session (c)) + features.session_object = true; + + if (poly_root != 0) + { + using namespace semantics; + using semantics::data_member; + + data_member_path& id (*id_member (*poly_root)); + data_member* idm (id.front ()); + + if (poly_root != &c) + { + // If we are a derived class in the polymorphic persistent + // class hierarchy, then add a synthesized virtual pointer + // member that points back to the root. + // + semantics::class_& base (polymorphic_base (c)); + + if (&base != poly_root) + idm = &dynamic_cast<data_member&> (base.names_begin ()->named ()); + + path const& f (idm->file ()); + size_t l (idm->line ()), col (idm->column ()); + + semantics::data_member& m ( + unit.new_node<semantics::data_member> (f, l, col, tree (0))); + m.set ("virtual", true); + + // Make it the first member in the class. This is important: + // we rely on the corrensponding foreign key to be first. + // + node_position<type, scope::names_iterator> np (c, c.names_end ()); + unit.new_edge<semantics::names> ( + np, m, idm->name (), access::public_); + + // Use the raw pointer as this member's type. + // + if (!base.pointed_p ()) + { + // Create the pointer type in the graph. The pointer node + // in GCC seems to always be present, even if not explicitly + // used in the translation unit. + // + tree t (base.tree_node ()); + tree ptr (TYPE_POINTER_TO (t)); + assert (ptr != 0); + ptr = TYPE_MAIN_VARIANT (ptr); + pointer& p (unit.new_node<pointer> (f, l, col, ptr)); + unit.insert (ptr, p); + unit.new_edge<points> (p, base); + assert (base.pointed_p ()); + } + + unit.new_edge<belongs> (m, base.pointed ().pointer ()); + + // Mark it as a special kind of id. + // + m.set ("id", string ()); + m.set ("polymorphic-ref", true); + + // Make sure we also use the same column name as the root. + // + if (composite_wrapper (utype (id))) + m.set ("column", table_column (column_prefix (id, true).prefix)); + else + m.set ("column", table_column (column_name (id))); + } + else + { + // If we are a root of the polymorphic persistent class hierarchy, + // then add a synthesized virtual member for the discriminator. + // Use the location of the polymorphic pragma as the location of + // this member. + // + location_t loc (c.get<location_t> ("polymorphic-location")); + semantics::data_member& m ( + unit.new_node<semantics::data_member> ( + path (LOCATION_FILE (loc)), + LOCATION_LINE (loc), + LOCATION_COLUMN (loc), + tree (0))); + m.set ("virtual", true); + + // Insert it after the id member (or first if this id comes + // from reuse-base). + // + node_position<type, scope::names_iterator> np ( + c, c.find (idm->named ())); + unit.new_edge<semantics::names> ( + np, m, "typeid_", access::public_); + + belongs& edge (unit.new_edge<belongs> (m, *std_string_)); + edge.hint (*std_string_hint_); + + m.set ("readonly", true); + m.set ("discriminator", true); + + c.set ("discriminator", &m); + } + } + } + + virtual void + traverse_object_post (type& c) + { + semantics::class_* poly_root (polymorphic (c)); + bool poly_derived (poly_root != 0 && poly_root != &c); + + semantics::data_member* opt (optimistic (c)); + + // Figure out if we are versioned. We are versioned if we have + // soft-added/deleted columns ourselves or our poly-base is + // versioned. + // + if (force_versioned || + column_count (c).soft != 0 || + (poly_derived && polymorphic_base (c).count ("versioned"))) + c.set ("versioned", true); + + // Sections. + // + user_sections& uss (c.get<user_sections> ("user-sections")); + + // See if we need to add a special fake section for version update. + // + if (c.count ("sectionable")) + { + uss.push_back ( + user_section (*opt, + c, + uss.count (user_sections::count_total | + user_sections::count_all | + user_sections::count_special_version), + user_section::load_lazy, + user_section::update_manual, + user_section::special_version)); + + // If we are a root of a polymorphic hierarchy and the version is in + // a reuse-base, then we need to make sure that base is sectionable + // and derive from its special version update section. + // + semantics::node& opt_base (opt->scope ()); + if (poly_root == &c && &opt_base != &c) + { + if (!opt_base.count ("sectionable")) + { + location_t l (c.get<location_t> ("sectionable-location")); + + error (l) << "reuse base class of a sectionable polymorphic " << + "root class must be sectionable" << endl; + + info (opt_base.location ()) << "use '#pragma db object " << + "sectionable' to make the base class of this hierarchy " << + "sectionable" << endl; + + throw operation_failed (); + } + + uss.back ().base = + &opt_base.get<user_sections> ("user-sections").back (); + } + } + + // Calculate column counts for sections. + // + for (user_sections::iterator i (uss.begin ()); i != uss.end (); ++i) + { + column_count_type cc (column_count (c, &*i)); + i->total = cc.total; + i->inverse = cc.inverse; + i->readonly = cc.readonly; + + // Figure out if we are versioned. We are versioned if we have + // soft-added/deleted columns ourselves or our poly-base is + // versioned. + // + if (force_versioned || cc.soft != 0 || + (poly_derived && i->base != 0 && i->base->versioned)) + i->versioned = true; + + if (size_t n = has_a (c, test_container, &*i)) + { + i->containers = true; + i->versioned_containers = + n != has_a (c, + test_container | + exclude_deleted | exclude_added | exclude_versioned, + &*i); + + if ((n = has_a (c, test_readwrite_container, &*i))) + { + i->readwrite_containers = true; + i->readwrite_versioned_containers = + n != has_a (c, + test_readwrite_container | + exclude_deleted | exclude_added | exclude_versioned, + &*i); + } + } + } + } + + // + // View. + // + + virtual void + traverse_view_pre (type& c) + { + // Resolve referenced objects from tree nodes to semantic graph + // nodes. Also populate maps and compute counts. + // + view_alias_map& amap (c.set ("alias-map", view_alias_map ())); + view_object_map& omap (c.set ("object-map", view_object_map ())); + + size_t& obj_count (c.set ("object-count", size_t (0))); + size_t& tbl_count (c.set ("table-count", size_t (0))); + + if (c.count ("objects")) + { + using semantics::class_; + + view_objects& objs (c.get<view_objects> ("objects")); + + for (view_objects::iterator i (objs.begin ()); i != objs.end (); ++i) + { + if (i->kind != view_object::object) + { + tbl_count++; + continue; + } + else + obj_count++; + + tree n (TYPE_MAIN_VARIANT (i->obj_node)); + + if (TREE_CODE (n) != RECORD_TYPE) + { + error (i->loc) << "name '" << i->obj_name << "' in db pragma " << + "object does not name a class" << endl; + + throw operation_failed (); + } + + class_& o (dynamic_cast<class_&> (*unit.find (n))); + + if (!object (o)) + { + error (i->loc) << "name '" << i->obj_name << "' in db pragma " << + "object does not name a persistent class" << endl; + + info (o.location ()) << "class '" << i->obj_name << "' is " << + "defined here" << endl; + + throw operation_failed (); + } + + i->obj = &o; + i->ptr = 0; // Nothing yet. + + if (i->alias.empty ()) + { + if (!omap.insert (view_object_map::value_type (&o, &*i)).second) + { + error (i->loc) << "persistent class '" << i->obj_name << + "' is used in the view more than once" << endl; + + error (omap[&o]->loc) << "previously used here" << endl; + + info (i->loc) << "use the alias clause to assign it a " << + "different name" << endl; + + throw operation_failed (); + } + + // Also add the bases of a polymorphic object. + // + class_* poly_root (polymorphic (o)); + + if (poly_root != 0 && poly_root != &o) + { + for (class_* b (&polymorphic_base (o));; + b = &polymorphic_base (*b)) + { + if (!omap.insert (view_object_map::value_type (b, &*i)).second) + { + error (i->loc) << "base class '" << class_name (*b) << + "' is used in the view more than once" << endl; + + error (omap[b]->loc) << "previously used here" << endl; + + info (i->loc) << "use the alias clause to assign it a " << + "different name" << endl; + + throw operation_failed (); + } + + if (b == poly_root) + break; + } + } + } + else + { + if (!amap.insert ( + view_alias_map::value_type (i->alias, &*i)).second) + { + error (i->loc) << "alias '" << i->alias << "' is used in " << + "the view more than once" << endl; + + throw operation_failed (); + } + } + } + } + } + + virtual void + traverse_view_post (type& c) + { + // Handle data members. + // + { + view_data_member t (c); + traversal::names n (t); + names (c, n); + } + + // Figure out if we are versioned. Forced versioning is handled + // in relational/processing. + // + if (column_count (c).soft != 0) + c.set ("versioned", true); + } + + // + // Composite. + // + + virtual void + traverse_composite_post (type& c) + { + // Figure out if we are versioned. + // + if (force_versioned || column_count (c).soft != 0) + { + c.set ("versioned", true); + + // See if we are "summarily" added/deleted, that is, all the + // columns are added/deleted. Note: this does not include + // containers. + // + summary_version sv; + sv.traverse (c); + + // Note: there are no locations. + // + if (sv.av != 0) + c.set ("added", sv.av); + + if (sv.dv != 0) + c.set ("deleted", sv.dv); + } + } + + // + // Assign object/view pointer. + // + + void + assign_pointer (type& c) + { + location_t loc (0); // Pragma location, or 0 if not used. + + try + { + bool raw; + string ptr; + string const& type (class_fq_name (c)); + + tree decl (0); // Resolved template node. + string decl_name; // User-provided template name. + tree resolve_scope (0); // Scope in which we resolve names. + + class_pointer const* cp (0); + bool cp_template (false); + + if (c.count ("pointer")) + { + cp = &c.get<class_pointer> ("pointer"); + } + // If we are a derived type in polymorphic hierarchy, then use + // our root's pointer type by default. + // + else if (semantics::class_* r = polymorphic (c)) + { + if (&c != r && r->count ("pointer-template")) + cp = r->get<class_pointer const*> ("pointer-template"); + } + + if (cp != 0) + { + string const& p (cp->name); + + if (p == "*") + { + raw = true; + ptr = type + "*"; + cp_template = true; + } + else if (p[p.size () - 1] == '*') + { + raw = true; + ptr = p; + } + else if (p.find ('<') != string::npos) + { + // Template-id. + // + raw = false; // Fair to assume not raw, though technically can be. + ptr = p; + decl_name.assign (p, 0, p.find ('<')); + } + else + { + // This is not a template-id. Resolve it and see if it is a + // template or a type. + // + decl = resolve_name (p, cp->scope, true); + gcc_tree_code_type tc (TREE_CODE (decl)); + + if (tc == TYPE_DECL) + { + raw = (TREE_CODE (TREE_TYPE (decl)) == POINTER_TYPE); + ptr = p; + + // This can be a typedef'ed alias for a TR1 template-id. + // + if (tree ti = TYPE_TEMPLATE_INFO (TREE_TYPE (decl))) + { + decl = TI_TEMPLATE (ti); // DECL_TEMPLATE + + // Get to the most general template declaration. + // + while (DECL_TEMPLATE_INFO (decl)) + decl = DECL_TI_TEMPLATE (decl); + } + else + decl = 0; // Not a template. + } + else if (tc == TEMPLATE_DECL && DECL_CLASS_TEMPLATE_P (decl)) + { + raw = false; + ptr = p + "< " + type + " >"; + decl_name = p; + cp_template = true; + } + else + { + error (cp->loc) + << "name '" << p << "' specified with db pragma pointer " + << "does not name a type or a template" << endl; + + throw operation_failed (); + } + } + + // Resolve scope is the scope of the pragma. + // + resolve_scope = cp->scope; + loc = cp->loc; + } + else + { + // See if any of the namespaces containing this class specify + // a pointer. + // + for (semantics::scope* s (&class_scope (c));; s = &s->scope_ ()) + { + using semantics::namespace_; + + namespace_* ns (dynamic_cast<namespace_*> (s)); + + if (ns == 0) // Some other scope. + { + if (!s->named_p ()) + break; + + continue; + } + + if (ns->extension ()) + ns = &ns->original (); + + if (!ns->count ("pointer")) + { + if (ns->global_scope ()) // Note: namespace always named. + break; + else + continue; + } + + cp = &ns->get<class_pointer> ("pointer"); + string const& p (cp->name); + + // Namespace-specified pointer can only be '*' or are template. + // + if (p == "*") + { + raw = true; + ptr = type + "*"; + } + else if (p[p.size () - 1] == '*') + { + error (cp->loc) + << "name '" << p << "' specified with db pragma pointer " + << "at namespace level cannot be a raw pointer" << endl; + } + else if (p.find ('<') != string::npos) + { + error (cp->loc) + << "name '" << p << "' specified with db pragma pointer " + << "at namespace level cannot be a template-id" << endl; + } + else + { + // Resolve this name and make sure it is a template. + // + decl = resolve_name (p, cp->scope, true); + gcc_tree_code_type tc (TREE_CODE (decl)); + + if (tc == TEMPLATE_DECL && DECL_CLASS_TEMPLATE_P (decl)) + { + raw = false; + ptr = p + "< " + type + " >"; + decl_name = p; + } + else + { + error (cp->loc) + << "name '" << p << "' specified with db pragma pointer " + << "does not name a template" << endl; + } + } + + if (ptr.empty ()) + throw operation_failed (); + + cp_template = true; + + // Resolve scope is the scope of the pragma. + // + resolve_scope = cp->scope; + loc = cp->loc; + break; + } + + // Use the default pointer. + // + if (ptr.empty ()) + { + string const& p (options.default_pointer ()); + + if (p == "*") + { + raw = true; + ptr = type + "*"; + } + else + { + raw = false; + ptr = p + "< " + type + " >"; + decl_name = p; + } + + // Resolve scope is the scope of the class. + // + resolve_scope = class_scope (c).tree_node (); + } + } + + // If this class is a root of a polymorphic hierarchy, then cache + // the pointer template so that we can use it for derived classes. + // + if (cp != 0 && cp_template && polymorphic (c) == &c) + c.set ("pointer-template", cp); + + // Check if we are using TR1. + // + if (decl != 0 || !decl_name.empty ()) + { + bool& tr1 (features.tr1_pointer); + bool& boost (features.boost_pointer); + + // First check the user-supplied name. + // + tr1 = tr1 + || decl_name.compare (0, 8, "std::tr1") == 0 + || decl_name.compare (0, 10, "::std::tr1") == 0; + + // If there was no match, also resolve the name since it can be + // a using-declaration for a TR1 template. + // + if (!tr1) + { + if (decl == 0) + decl = resolve_name (decl_name, resolve_scope, false); + + if (TREE_CODE (decl) != TEMPLATE_DECL || ! + DECL_CLASS_TEMPLATE_P (decl)) + { + // This is only checked for the --default-pointer option. + // + error (c.file (), c.line (), c.column ()) + << "name '" << decl_name << "' specified with the " + << "--default-pointer option does not name a class " + << "template" << endl; + + throw operation_failed (); + } + + string n (decl_as_string (decl, TFF_PLAIN_IDENTIFIER)); + + // In case of a boost TR1 implementation, we cannot distinguish + // between the boost:: and std::tr1:: usage since the latter is + // just a using-declaration for the former. + // + tr1 = tr1 + || n.compare (0, 8, "std::tr1") == 0 + || n.compare (0, 10, "::std::tr1") == 0; + + boost = boost + || n.compare (0, 17, "boost::shared_ptr") == 0 + || n.compare (0, 19, "::boost::shared_ptr") == 0; + } + } + + // Fully-qualify all the unqualified components of the name. + // + try + { + lex_.start (ptr); + ptr.clear (); + + string t; + bool punc (false); + bool scoped (false); + + for (cpp_ttype tt (lex_.next (t)); + tt != CPP_EOF; + tt = lex_.next (t)) + { + if (punc && tt > CPP_LAST_PUNCTUATOR) + ptr += ' '; + + punc = false; + + switch (static_cast<unsigned> (tt)) + { + case CPP_LESS: + { + ptr += "< "; + break; + } + case CPP_GREATER: + { + ptr += " >"; + break; + } + case CPP_COMMA: + { + ptr += ", "; + break; + } + case CPP_NAME: + { + // If the name was not preceeded with '::', look it + // up in the pragmas's scope and add the qualifer. + // + if (!scoped) + { + tree decl (resolve_name (t, resolve_scope, false)); + tree scope (CP_DECL_CONTEXT (decl)); + + // If this is an inline namespace, skip it until we get + // to the non-inline one. + // + while (scope != global_namespace) + { + tree prev (CP_DECL_CONTEXT (scope)); + +#if BUILDING_GCC_MAJOR >= 8 + if (!is_nested_namespace (prev, scope, true)) +#else + if (!is_associated_namespace (prev, scope)) +#endif + break; + + scope = prev; + } + + if (scope != global_namespace) + { + ptr += "::"; + ptr += decl_as_string (scope, TFF_PLAIN_IDENTIFIER); + } + + ptr += "::"; + } + + ptr += t; + punc = true; + break; + } + case CPP_KEYWORD: + case CPP_NUMBER: + { + ptr += t; + punc = true; + break; + } + default: + { + ptr += t; + break; + } + } + + scoped = (tt == CPP_SCOPE); + } + } + catch (cxx_lexer::invalid_input const&) + { + throw operation_failed (); + } + + c.set ("object-pointer", ptr); + c.set ("object-pointer-raw", raw); + } + catch (invalid_name const& ex) + { + if (loc != 0) + error (loc) + << "name '" << ex.name () << "' specified with db pragma " + << "pointer is invalid" << endl; + else + error (c.file (), c.line (), c.column ()) + << "name '" << ex.name () << "' specified with the " + << "--default-pointer option is invalid" << endl; + + + throw operation_failed (); + } + catch (unable_to_resolve const& ex) + { + if (loc != 0) + error (loc) + << "unable to resolve name '" << ex.name () << "' specified " + << "with db pragma pointer" << endl; + else + error (c.file (), c.line (), c.column ()) + << "unable to resolve name '" << ex.name () << "' specified " + << "with the --default-pointer option" << endl; + + throw operation_failed (); + } + } + + private: + struct invalid_name + { + invalid_name (string const& n): name_ (n) {} + + string const& + name () const {return name_;} + + private: + string name_; + }; + + typedef lookup::unable_to_resolve unable_to_resolve; + + tree + resolve_name (string const& qn, tree scope, bool is_type) + { + try + { + string tl; + tree tn; + cpp_ttype tt, ptt; + + nlex_.start (qn); + tt = nlex_.next (tl, &tn); + + string name; + return lookup::resolve_scoped_name ( + nlex_, tt, tl, tn, ptt, scope, name, is_type); + } + catch (cxx_lexer::invalid_input const&) + { + throw invalid_name (qn); + } + catch (lookup::invalid_name const&) + { + throw invalid_name (qn); + } + } + + private: + traversal::defines defines_; + typedefs typedefs_; + + data_member member_; + traversal::names member_names_; + + cxx_string_lexer lex_; + cxx_string_lexer nlex_; // Nested lexer. + + semantics::type* std_string_; + semantics::names* std_string_hint_; + + tree access_; // odb::access node. + }; + + static bool + check_to_from (const cxx_tokens& ex, const char* c, location_t l) + { + // Make sure we have one and only one placeholder (?). + // + bool r (false), m (true); + + for (cxx_tokens::const_iterator i (ex.begin ()), e (ex.end ()); i != e;) + { + if (i->type == CPP_OPEN_PAREN) + { + if (++i != e && i->type == CPP_QUERY) + { + if (++i != e && i->type == CPP_CLOSE_PAREN) + { + if (r) + m = false; // Multiple (?), can't move. + else + r = true; + } + } + } + else + ++i; + } + + if (!r) + { + error (l) << "no '(?)' expression in the '" << c << "' clause " + << "of db pragma map" << endl; + + throw operation_failed (); + } + + return m; + } +} + +static void +process1 (semantics::unit& u) +{ + // Process custom C++ type mapping. + // + + // Create an empty list if we don't have one. This makes the + // rest of the code simpler. + // + if (!u.count ("custom-cxx-types")) + u.set ("custom-cxx-types", custom_cxx_types ()); + + custom_cxx_types & cts (u.get<custom_cxx_types> ("custom-cxx-types")); + + for (custom_cxx_types::iterator i (cts.begin ()); i != cts.end (); ++i) + { + custom_cxx_type& ct (*i); + + // type + // + if (ct.type_node == 0) + { + error (ct.loc) << "'type' clause expected in db pragma map" << endl; + throw operation_failed (); + } + + ct.type = dynamic_cast<semantics::type*> ( + u.find (TYPE_MAIN_VARIANT (ct.type_node))); + ct.type_hint = u.find_hint (ct.type_node); + + // as + // + if (ct.as_node == 0) + { + error (ct.loc) << "'as' clause expected in db pragma map" << endl; + throw operation_failed (); + } + + ct.as = dynamic_cast<semantics::type*> ( + u.find (TYPE_MAIN_VARIANT (ct.as_node))); + ct.as_hint = u.find_hint (ct.as_node); + + // to + // + { + cxx_tokens& e (ct.to); + + if (e.empty ()) + { + e.push_back (cxx_token (0, CPP_OPEN_PAREN)); + e.push_back (cxx_token (0, CPP_QUERY)); + e.push_back (cxx_token (0, CPP_CLOSE_PAREN)); + ct.to_move = true; + } + else + ct.to_move = check_to_from (e, "to", ct.loc); + } + + // from + // + { + cxx_tokens& e (ct.from); + + if (e.empty ()) + { + e.push_back (cxx_token (0, CPP_OPEN_PAREN)); + e.push_back (cxx_token (0, CPP_QUERY)); + e.push_back (cxx_token (0, CPP_CLOSE_PAREN)); + ct.from_move = true; + } + else + ct.from_move = check_to_from (e, "from", ct.loc); + } + + // Resolve mapping scope. + // + semantics::scope* s (dynamic_cast<semantics::scope*> (u.find (ct.scope))); + if (s == 0) + { + error (ct.loc) << "unable to resolve db pragma map scope" << endl; + throw operation_failed (); + } + + if (semantics::namespace_* ns = dynamic_cast<semantics::namespace_*> (s)) + { + if (ns->extension ()) + s = &ns->original (); + } + + // Enter into the map. + // + if (!s->count ("custom-cxx-type-map")) + s->set ("custom-cxx-type-map", custom_cxx_type_map ()); + + s->get<custom_cxx_type_map> ("custom-cxx-type-map")[ct.type] = &ct; + } +} + +static void +process2 (options const& ops, features& f, semantics::unit& u) +{ + unique_ptr<context> ctx (create_context (cerr, u, ops, f, 0)); + + // Common processing. + // + { + traversal::unit unit; + traversal::defines unit_defines; + typedefs unit_typedefs (true); + traversal::namespace_ ns; + class_ c; + + unit >> unit_defines >> ns; + unit_defines >> c; + unit >> unit_typedefs >> c; + + traversal::defines ns_defines; + typedefs ns_typedefs (true); + + ns >> ns_defines >> ns; + ns_defines >> c; + ns >> ns_typedefs >> c; + + unit.dispatch (ctx->unit); + } + + // Database-specific processing. + // + switch (ops.database ()[0]) + { + case database::common: + { + break; + } + case database::mssql: + case database::mysql: + case database::oracle: + case database::pgsql: + case database::sqlite: + { + relational::process (); + break; + } + } +} + +void +process (options const& ops, + features& f, + semantics::unit& u, + semantics::path const&, + unsigned short pass) +{ + try + { + if (pass == 1) + process1 (u); + else if (pass == 2) + process2 (ops, f, u); + } + catch (operation_failed const&) + { + // Processing failed. Diagnostics has already been issued. + // + throw processor_failed (); + } +} diff --git a/odb/odb/processor.hxx b/odb/odb/processor.hxx new file mode 100644 index 0000000..1e70cab --- /dev/null +++ b/odb/odb/processor.hxx @@ -0,0 +1,23 @@ +// file : odb/processor.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_PROCESSOR_HXX +#define ODB_PROCESSOR_HXX + +#include <odb/options.hxx> +#include <odb/features.hxx> +#include <odb/semantics/unit.hxx> + +class processor_failed {}; + +// Pass one is very early processing before any validation has been +// done. +// +void +process (options const&, + features&, + semantics::unit&, + semantics::path const&, + unsigned short pass); + +#endif // ODB_PROCESSOR_HXX diff --git a/odb/odb/profile.cxx b/odb/odb/profile.cxx new file mode 100644 index 0000000..1a10bfb --- /dev/null +++ b/odb/odb/profile.cxx @@ -0,0 +1,86 @@ +// file : odb/profile.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <unistd.h> // stat +#include <sys/types.h> // stat +#include <sys/stat.h> // stat + +#include <iostream> + +#include <odb/profile.hxx> + +using namespace std; + +static bool +exist (profile_data::path const& p) +{ + struct stat info; + + // Just check that the file exist without checking for permissions, etc. + // + return stat (p.string ().c_str (), &info) == 0 && S_ISREG (info.st_mode); +} + +string +profile_search (char const* prof, void* arg) +{ + typedef profile_data::path path; + typedef profile_data::paths paths; + + profile_data* pd (static_cast<profile_data*> (arg)); + paths const& ps (pd->search_paths); + + path p (prof), odb ("odb"), r; + p.normalize (); // Convert '/' to the canonical path separator form. + path p_db (p); + p_db += "-"; + p_db += pd->db.string (); + p += ".options"; + p_db += ".options"; + + paths::const_iterator i (ps.begin ()), end (ps.end ()); + for (; i != end; ++i) + { + // First check for the database-specific version in the search directory + // itself and then try the odb/ subdirectory. + // + if (exist (r = *i / p_db)) + break; + + if (exist (r = *i / odb / p_db)) + break; + + // Then try the same with the database-independent version. + // + if (exist (r = *i / p)) + break; + + if (exist (r = *i / odb / p)) + break; + } + + if (i == end) + { + // Ignore the case where we didn't find the profile and this is the + // common database. + // + if (pd->db == database::common) + return string (); + + cerr << pd->name << ": error: unable to locate options file for profile '" + << prof << "'" << endl; + throw profile_failure (); + } + + if (pd->loaded.find (r) != pd->loaded.end ()) + return string (); + + pd->loaded.insert (r); + return r.string (); +} + +string +profile_search_ignore (char const*, void*) +{ + return string (); +} diff --git a/odb/odb/profile.hxx b/odb/odb/profile.hxx new file mode 100644 index 0000000..b6e8e53 --- /dev/null +++ b/odb/odb/profile.hxx @@ -0,0 +1,39 @@ +// file : odb/profile.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_PROFILE_HXX +#define ODB_PROFILE_HXX + +#include <set> +#include <vector> +#include <string> + +#include <libcutl/fs/path.hxx> + +#include <odb/option-types.hxx> + +struct profile_data +{ + typedef cutl::fs::path path; + typedef std::vector<path> paths; + + profile_data (paths const& p, database d, char const* n) + : search_paths (p), db (d), name (n) + { + } + + paths const& search_paths; + database db; + char const* name; + std::set<path> loaded; +}; + +struct profile_failure {}; + +std::string +profile_search (char const* profile, void* arg); + +std::string +profile_search_ignore (char const* profile, void* arg); + +#endif // ODB_PROFILE_HXX diff --git a/odb/odb/relational/changelog.cxx b/odb/odb/relational/changelog.cxx new file mode 100644 index 0000000..99f72da --- /dev/null +++ b/odb/odb/relational/changelog.cxx @@ -0,0 +1,1239 @@ +// file : odb/relational/changelog.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <map> + +#include <odb/diagnostics.hxx> + +#include <odb/semantics/relational.hxx> +#include <odb/traversal/relational.hxx> + +#include <odb/relational/context.hxx> +#include <odb/relational/generate.hxx> + +using namespace std; + +namespace relational +{ + namespace changelog + { + using namespace sema_rel; + using sema_rel::model; + using sema_rel::changelog; + + typedef map<qname, semantics::node*> deleted_table_map; + typedef map<uname, semantics::data_member*> deleted_column_map; + + namespace + { + // + // diff + // + + struct diff_table: trav_rel::column, + trav_rel::primary_key, + trav_rel::foreign_key, + trav_rel::index + { + enum mode_type {mode_add, mode_drop}; + + diff_table (table& o, + mode_type m, + alter_table& a, + graph& gr, + options const& op, + model_version const* v) + : other (o), mode (m), at (a), g (gr), ops (op), version (v) {} + + virtual void + traverse (sema_rel::column& c) + { + using sema_rel::column; + + if (mode == mode_add) + { + if (column* oc = other.find<column> (c.name ())) + { + if (c.type () != oc->type ()) + diagnose_column (c, "type", oc->type (), c.type ()); + + if (c.null () != oc->null ()) + { + alter_column& ac (g.new_node<alter_column> (c.id ())); + + // Set the alters edge. + // + column* b (at.lookup<column, drop_column> (c.name ())); + assert (b != 0); + g.new_edge<alters> (ac, *b); + + ac.null (c.null ()); + g.new_edge<unames> (at, ac, c.name ()); + } + + if (c.default_ () != oc->default_ ()) + diagnose_column ( + c, "default value", oc->default_ (), c.default_ ()); + + if (c.options () != oc->options ()) + diagnose_column (c, "options", oc->options (), c.options ()); + } + else + { + if (version != 0) + { + data_member_path const& mp ( + c.get<data_member_path> ("member-path")); + + semantics::data_member* m (context::added_member (mp)); + if (m != 0) + { + // Make sure the addition version is the current version. + // + if (context::added (*m) != version->current) + { + location l (m->get<location_t> ("added-location")); + error (l) << "member addition version is not the same " << + "as the current model version" << endl; + throw operation_failed (); + } + } + // Warn about hard additions. If the version is closed, then + // we have already seen these warnings so no need to repeat + // them. + // + else if (ops.warn_hard_add () && version->open) + { + // Issue nicer diagnostics for the direct data member case. + // + if (mp.size () == 1) + { + location l (mp.back ()->location ()); + warn (l) << "data member is hard-added" << endl; + } + else + { + semantics::class_& s ( + dynamic_cast<semantics::class_&> ( + mp.front ()->scope ())); + + warn (s.location ()) << "column '" << c.name () << "' " << + "in class '" << s.name () << "' is hard-added" << endl; + + for (data_member_path::const_iterator i (mp.begin ()); + i != mp.end (); ++i) + { + info ((*i)->location ()) << "corresponding hard-" << + "added data member could be '" << (*i)->name () << + "'" << endl; + } + } + } + } + + add_column& ac (g.new_node<add_column> (c, at, g)); + g.new_edge<unames> (at, ac, c.name ()); + } + } + else + { + if (other.find<column> (c.name ()) == 0) + { + if (version != 0) + { + // See if we have an entry for this column in the soft- + // deleted map. + // + deleted_column_map const& dm ( + other.get<deleted_column_map> ("deleted-map")); + deleted_column_map::const_iterator i (dm.find (c.name ())); + + if (i != dm.end ()) + { + if (context::deleted (*i->second) != version->current) + { + location l (i->second->get<location_t> ("deleted-location")); + error (l) << "member deletion version is not the same " << + "as the current model version" << endl; + throw operation_failed (); + } + } + // Warn about hard deletions. If the version is closed, then + // we have already seen these warnings so no need to repeat + // them. + // + else if (ops.warn_hard_delete () && version->open) + { + // Here all we have is a column name and a class (object) + // or data member (container) corresponding to the table. + // + if (semantics::class_* s = + other.get<semantics::class_*> ("class", 0)) + { + warn (s->location ()) << "column '" << c.name () << "' " << + "in class '" << s->name () << "' is hard-deleted" << endl; + } + else + { + data_member_path const& mp ( + other.get<data_member_path> ("member-path")); + + warn (mp.back ()->location ()) << "column '" << + c.name () << "' in container '" << + mp.back ()->name () << "' is hard-deleted" << endl; + } + } + } + + drop_column& dc (g.new_node<drop_column> (c.id ())); + g.new_edge<unames> (at, dc, c.name ()); + } + } + } + + virtual void + traverse (sema_rel::primary_key& pk) + { + using sema_rel::primary_key; + + if (mode == mode_add) + { + if (primary_key* opk = other.find<primary_key> (pk.name ())) + { + if (pk.auto_ () != opk->auto_ ()) + diagnose_primary_key (pk, "auto kind"); + + // Database-specific information. + // + for (primary_key::extra_map::const_iterator i ( + pk.extra ().begin ()); i != pk.extra ().end (); ++i) + { + if (opk->extra ().count (i->first) == 0 || + opk->extra ()[i->first] != i->second) + diagnose_primary_key (pk, i->first.c_str ()); + } + + for (primary_key::extra_map::const_iterator i ( + opk->extra ().begin ()); i != opk->extra ().end (); ++i) + { + if (pk.extra ().count (i->first) == 0 || + pk.extra ()[i->first] != i->second) + diagnose_primary_key (pk, i->first.c_str ()); + } + + if (pk.contains_size () != opk->contains_size ()) + diagnose_primary_key (pk, "member set"); + + for (primary_key::contains_size_type i (0); + i != pk.contains_size (); ++i) + { + sema_rel::contains& c (pk.contains_at (i)); + sema_rel::contains& oc (opk->contains_at (i)); + + if (c.column ().name () != oc.column ().name ()) + diagnose_primary_key (pk, "member set"); + } + } + else + { + location const& l (pk.get<location> ("cxx-location")); + error (l) << "adding object id to an existing class is " << + "not supported" << endl; + info (l) << "consider re-implementing this change by adding " << + "a new class with the object id, migrating the data, and " << + "deleteing the old class" << endl; + throw operation_failed (); + } + } + else + { + if (other.find<primary_key> (pk.name ()) == 0) + { + location const& l (other.get<location> ("cxx-location")); + error (l) << "deleting object id from an existing class is " << + "not supported" << endl; + info (l) << "consider re-implementing this change by adding " << + "a new class without the object id, migrating the data, " << + "and deleteing the old class" << endl; + throw operation_failed (); + } + } + } + + virtual void + traverse (sema_rel::foreign_key& fk) + { + using sema_rel::foreign_key; + + if (mode == mode_add) + { + if (foreign_key* ofk = other.find<foreign_key> (fk.name ())) + { + if (fk.deferrable () != ofk->deferrable ()) + diagnose_foreign_key (fk, "deferrable mode"); + + if (fk.on_delete () != ofk->on_delete ()) + diagnose_foreign_key (fk, "on delete action"); + + if (fk.referenced_table () != ofk->referenced_table ()) + // See diagnose_foreign_key() if changing this name. + // + diagnose_foreign_key (fk, "pointed-to class"); + + if (fk.referenced_columns () != ofk->referenced_columns ()) + diagnose_foreign_key (fk, "id member set"); + + if (fk.contains_size () != ofk->contains_size ()) + diagnose_foreign_key (fk, "id member set"); + + for (foreign_key::contains_size_type i (0); + i != fk.contains_size (); ++i) + { + sema_rel::contains& c (fk.contains_at (i)); + sema_rel::contains& oc (ofk->contains_at (i)); + + if (c.column ().name () != oc.column ().name ()) + diagnose_foreign_key (fk, "id member set"); + } + } + else + { + add_foreign_key& afk (g.new_node<add_foreign_key> (fk, at, g)); + g.new_edge<unames> (at, afk, fk.name ()); + } + } + else + { + if (other.find<foreign_key> (fk.name ()) == 0) + { + drop_foreign_key& dfk (g.new_node<drop_foreign_key> (fk.id ())); + g.new_edge<unames> (at, dfk, fk.name ()); + } + } + } + + virtual void + traverse (sema_rel::index& i) + { + using sema_rel::index; + + if (mode == mode_add) + { + if (index* oi = other.find<index> (i.name ())) + { + if (i.type () != oi->type ()) + diagnose_index (i, "type", oi->type (), i.type ()); + + if (i.method () != oi->method ()) + diagnose_index (i, "method", oi->method (), i.method ()); + + if (i.options () != oi->options ()) + diagnose_index (i, "options", oi->options (), i.options ()); + + if (i.contains_size () != oi->contains_size ()) + diagnose_index (i, "member set", "", ""); + + for (index::contains_size_type j (0); + j != i.contains_size (); ++j) + { + sema_rel::contains& c (i.contains_at (j)); + sema_rel::contains& oc (oi->contains_at (j)); + + if (c.column ().name () != oc.column ().name ()) + diagnose_index (i, "member set", "", ""); + + if (c.options () != oc.options ()) + diagnose_index ( + i, "member options", oc.options (), c.options ()); + } + } + else + { + add_index& ai (g.new_node<add_index> (i, at, g)); + g.new_edge<unames> (at, ai, i.name ()); + } + } + else + { + if (other.find<index> (i.name ()) == 0) + { + drop_index& di (g.new_node<drop_index> (i.id ())); + g.new_edge<unames> (at, di, i.name ()); + } + } + } + + void + diagnose_column (sema_rel::column& c, + char const* name, + string const& ov, + string const& nv) + { + table& t (c.table ()); + location const& tl (t.get<location> ("cxx-location")); + location const& cl (c.get<location> ("cxx-location")); + + error (cl) << "change to data member results in the change of " << + "the corresponding column " << name; + + if (!ov.empty () || !nv.empty ()) + cerr << " (old: '" << ov << "', new: '" << nv << "')"; + + cerr << endl; + + error (cl) << "this change is not yet handled automatically" << endl; + info (cl) << "corresponding column '" << c.name () << "' " << + "originates here" << endl; + info (tl) << "corresponding table '" << t.name () << "' " << + "originates here" << endl; + info (cl) << "consider re-implementing this change by adding " << + "a new data member with the desired " << name << ", migrating " << + "the data, and deleting the old data member" << endl; + + throw operation_failed (); + } + + void + diagnose_primary_key (sema_rel::primary_key& pk, char const* name) + { + location const& l (pk.get<location> ("cxx-location")); + + error (l) << "changing object id " << name << " in an existing " << + "class is not supported" << endl; + info (l) << "consider re-implementing this change by adding " << + "a new class with the desired object id " << name << ", " << + "migrating the data, and deleteing the old class" << endl; + + throw operation_failed (); + } + + void + diagnose_foreign_key (sema_rel::foreign_key& fk, char const* name) + { + // This can be an object pointer or a polymorphic base link. + // The latter will trigger this call if we change one base + // to another. + // + using sema_rel::table; + using sema_rel::foreign_key; + + // Polymorphic base link is the first foreign key. + // + table& t (fk.table ()); + table::names_iterator p (t.find (fk.name ())); + + if (t.extra ()["kind"] == "polymorphic derived object" && + (p == t.names_begin () || !(--p)->is_a<foreign_key> ())) + { + location const& l (t.get<location> ("cxx-location")); + + if (name == string ("pointed-to class")) + { + error (l) << "changing polymorphic base is not " << + "supported" << endl; + info (l) << "consider re-implementing this change by adding " << + "a new derived class with the desired base, migrating the " << + "data, and deleteing the old class" << endl; + } + else + { + error (l) << "changing polymorphic base " << name << + " is not supported" << endl; + info (l) << "consider re-implementing this change by adding " << + "a new derived class with the desired " << name << ", " << + "migrating the data, and deleteing the old class" << endl; + } + } + else + { + location const& l (fk.get<location> ("cxx-location")); + + error (l) << "changing object pointer " << name << " is not " << + "supported" << endl; + info (l) << "consider re-implementing this change by adding " << + "a new object pointer with the desired " << name << ", " << + "migrating the data, and deleteing the old pointer" << endl; + } + + throw operation_failed (); + } + + void + diagnose_index (sema_rel::index& i, + char const* name, + string const& ov, + string const& nv) + { + table& t (i.table ()); + location const& tl (t.get<location> ("cxx-location")); + location const& il (i.get<location> ("cxx-location")); + + error (il) << "change to index " << name; + + if (!ov.empty () || !nv.empty ()) + cerr << " (old: '" << ov << "', new: '" << nv << "')"; + + cerr << " is not yet handled automatically" << endl; + + info (il) << "corresponding index '" << i.name () << "' " << + "originates here" << endl; + info (tl) << "corresponding table '" << t.name () << "' " << + "originates here" << endl; + info (il) << "consider re-implementing this change by adding " << + "a new index with the desired " << name << " and deleting the " << + "old one" << endl; + + throw operation_failed (); + } + + protected: + table& other; + mode_type mode; + alter_table& at; + graph& g; + options const& ops; + model_version const* version; + }; + + struct diff_model: trav_rel::table + { + enum mode_type {mode_add, mode_drop}; + + diff_model (model& o, + mode_type m, + changeset& s, + graph& gr, + string const& in, + options const& op, + model_version const* v) + : other (o), + mode (m), + cs (s), + g (gr), + in_name (in), + ops (op), + version (v) {} + + virtual void + traverse (sema_rel::table& t) + { + using sema_rel::table; + + if (mode == mode_add) + { + if (table* ot = other.find<table> (t.name ())) + { + // See if there are any changes to the table. + // + alter_table& at (g.new_node<alter_table> (t.id ())); + + // Set the alters edge for lookup. + // + table* bt (cs.lookup<table, drop_table> (t.name ())); + assert (bt != 0); + alters& ae (g.new_edge<alters> (at, *bt)); + + if (t.options () != ot->options ()) + diagnose_table (t, "options", ot->options (), t.options ()); + + if (ot->extra ()["kind"] != t.extra ()["kind"]) + diagnose_table (t, "kind", + ot->extra ()["kind"], t.extra ()["kind"]); + + { + trav_rel::table table; + trav_rel::unames names; + diff_table dtable ( + *ot, diff_table::mode_add, at, g, ops, version); + table >> names >> dtable; + table.traverse (t); + } + + { + trav_rel::table table; + trav_rel::unames names; + diff_table dtable ( + t, diff_table::mode_drop, at, g, ops, version); + table >> names >> dtable; + table.traverse (*ot); + } + + if (!at.names_empty ()) + g.new_edge<qnames> (cs, at, t.name ()); + else + { + g.delete_edge (at, *bt, ae); + g.delete_node (at); + } + } + else + { + // Soft-add is only applicable to containers. + // + if (version != 0 && t.count ("member-path")) + { + data_member_path const& mp ( + t.get<data_member_path> ("member-path")); + + // Make sure the addition version is the current version. + // + semantics::data_member* m (context::added_member (mp)); + if (m != 0) + { + if (context::added (*m) != version->current) + { + location l (m->get<location_t> ("added-location")); + error (l) << "member addition version is not the same " << + "as the current model version" << endl; + throw operation_failed (); + } + } + // Warn about hard additions. If the version is closed, then + // we have already seen these warnings so no need to repeat + // them. + // + else if (ops.warn_hard_add () && version->open) + { + // Issue nicer diagnostics for the direct data member case. + // + if (mp.size () == 1) + { + location l (mp.back ()->location ()); + warn (l) << "data member is hard-added" << endl; + } + else + { + semantics::class_& s ( + dynamic_cast<semantics::class_&> ( + mp.front ()->scope ())); + + warn (s.location ()) << "container table '" << + t.name () << "' in class '" << s.name () << "' is " << + "hard-added" << endl; + + for (data_member_path::const_iterator i (mp.begin ()); + i != mp.end (); ++i) + { + info ((*i)->location ()) << "corresponding hard-" << + "added data member could be '" << (*i)->name () << + "'" << endl; + } + } + } + } + + add_table& at (g.new_node<add_table> (t, cs, g)); + g.new_edge<qnames> (cs, at, t.name ()); + } + } + else + { + if (other.find<table> (t.name ()) == 0) + { + if (version != 0) + { + // See if we have an entry for this table in the soft- + // deleted map. + // + deleted_table_map const& dm ( + other.get<deleted_table_map> ("deleted-map")); + deleted_table_map::const_iterator i (dm.find (t.name ())); + + if (i != dm.end ()) + { + // This table could be derived either from a class (object) + // or data member (container). + // + semantics::class_* c ( + dynamic_cast<semantics::class_*> (i->second)); + if (c != 0 && context::deleted (*c) != version->current) + { + location l (c->get<location_t> ("deleted-location")); + error (l) << "class deletion version is not the same " << + "as the current model version" << endl; + throw operation_failed (); + } + + semantics::data_member* m ( + dynamic_cast<semantics::data_member*> (i->second)); + if (m != 0 && context::deleted (*m) != version->current) + { + location l (m->get<location_t> ("deleted-location")); + error (l) << "member deletion version is not the same " << + "as the current model version" << endl; + throw operation_failed (); + } + } + // Warn about hard deletions. If the version is closed, then + // we have already seen these warnings so no need to repeat + // them. + // + // At first, it may seem like a good idea not to warn about + // class deletions since it is not possible to do anything + // useful with a class without referencing it from the code + // (in C++ sense). However, things get tricky once we consider + // polymorphism since the migration code could be "working" + // with the hard-deleted derived class via its base. So we + // are going to warn about polymorphic derived tables. + // + else if (ops.warn_hard_delete () && version->open) + { + string k (t.extra ()["kind"]); + + // We don't have any sensible location. + // + if (k == "container") + { + // We will issue a useless warning if the object table + // that this container references is also deleted. + // While we could detect this, it is going to require + // some effort since we would have to delay the warning + // and check later once we know all the deleted tables. + // + cerr << in_name << ": warning: container table '" << + t.name () << "' is hard-deleted" << endl; + } + else if (k == "polymorphic derived object") + { + // The same as above: the warning will be useless if + // we are dropping the whole hierarchy. + // + cerr << in_name << ": warning: polymorphic derived " << + "object table '" << t.name () << "' is hard-deleted" << + endl; + } + } + } + + drop_table& dt (g.new_node<drop_table> (t.id ())); + g.new_edge<qnames> (cs, dt, t.name ()); + } + } + } + + void + diagnose_table (sema_rel::table& t, + char const* name, + string const& ov, + string const& nv) + { + location const& tl (t.get<location> ("cxx-location")); + + error (tl) << "change to object or container member results in " + "the change of the corresponding table " << name; + + if (!ov.empty () || !nv.empty ()) + cerr << " (old: '" << ov << "', new: '" << nv << "')"; + + cerr << endl; + + error (tl) << "this change is not yet handled automatically" << endl; + info (tl) << "consider re-implementing this change by adding a " << + "new object or container member with the desired " << name << + ", migrating the data, and deleting the old object or member" << + endl; + + throw operation_failed (); + } + + protected: + model& other; + mode_type mode; + changeset& cs; + graph& g; + string in_name; + options const& ops; + model_version const* version; + }; + + // Assumes the new model has cxx-location set. If version is not 0, + // then assume it is the current model version and the new model is + // the current model which has member paths and deleted maps set. + // + changeset& + diff (model& o, + model& n, + changelog& l, + string const& in_name, + options const& ops, + model_version const* version) + { + changeset& r (l.new_node<changeset> (n.version ())); + + // Set the alters edge for lookup. If we are diff'ing two models of + // the same version, then use the old model as a base. Otherwise use + // the tip of changelog (it should correspond to the old model). + // + if (o.version () == n.version ()) + l.new_edge<alters> (r, o); + else + { + if (l.contains_changeset_empty ()) + { + model& m (l.model ()); + + // The changelog model version may also be equal to the new model + // version if the new base model version is greater than the + // latest changeset. + // + assert (m.version () == o.version () || + m.version () == n.version ()); + + l.new_edge<alters> (r, m); + } + else + { + changeset& c (l.contains_changeset_back ().changeset ()); + assert (o.version () == c.version ()); + l.new_edge<alters> (r, c); + } + } + + { + trav_rel::model model; + trav_rel::qnames names; + diff_model dmodel ( + o, diff_model::mode_add, r, l, in_name, ops, version); + model >> names >> dmodel; + model.traverse (n); + } + + { + trav_rel::model model; + trav_rel::qnames names; + diff_model dmodel ( + n, diff_model::mode_drop, r, l, in_name, ops, version); + model >> names >> dmodel; + model.traverse (o); + } + + return r; + } + + // + // patch + // + + struct patch_table: trav_rel::add_column, + trav_rel::drop_column, + trav_rel::alter_column, + trav_rel::add_index, + trav_rel::drop_index, + trav_rel::add_foreign_key, + trav_rel::drop_foreign_key + { + patch_table (table& tl, graph& gr): t (tl), g (gr) {} + + virtual void + traverse (sema_rel::add_column& ac) + { + try + { + column& c (g.new_node<column> (ac, t, g)); + g.new_edge<unames> (t, c, ac.name ()); + } + catch (duplicate_name const&) + { + cerr << "error: invalid changelog: column '" << ac.name () << + "' already exists in table '" << t.name () << "'" << endl; + throw operation_failed (); + } + } + + virtual void + traverse (sema_rel::drop_column& dc) + { + table::names_iterator i (t.find (dc.name ())); + + if (i == t.names_end () || !i->nameable ().is_a<column> ()) + { + cerr << "error: invalid changelog: column '" << dc.name () << + "' does not exist in table '" << t.name () << "'" << endl; + throw operation_failed (); + } + + g.delete_edge (t, i->nameable (), *i); + } + + virtual void + traverse (sema_rel::alter_column& ac) + { + if (column* c = t.find<column> (ac.name ())) + { + if (ac.null_altered ()) + c->null (ac.null ()); + } + else + { + cerr << "error: invalid changelog: column '" << ac.name () << + "' does not exist in table '" << t.name () << "'" << endl; + throw operation_failed (); + } + } + + virtual void + traverse (sema_rel::add_index& ai) + { + using sema_rel::index; + + try + { + index& i (g.new_node<index> (ai, t, g)); + g.new_edge<unames> (t, i, ai.name ()); + } + catch (duplicate_name const&) + { + cerr << "error: invalid changelog: index '" << ai.name () << + "' already exists in table '" << t.name () << "'" << endl; + throw operation_failed (); + } + } + + virtual void + traverse (sema_rel::drop_index& di) + { + using sema_rel::index; + table::names_iterator i (t.find (di.name ())); + + if (i == t.names_end () || !i->nameable ().is_a<index> ()) + { + cerr << "error: invalid changelog: index '" << di.name () << + "' does not exist in table '" << t.name () << "'" << endl; + throw operation_failed (); + } + + g.delete_edge (t, i->nameable (), *i); + } + + virtual void + traverse (sema_rel::add_foreign_key& afk) + { + using sema_rel::foreign_key; + + try + { + foreign_key& fk (g.new_node<foreign_key> (afk, t, g)); + g.new_edge<unames> (t, fk, afk.name ()); + } + catch (duplicate_name const&) + { + cerr << "error: invalid changelog: foreign key '" << afk.name () << + "' already exists in table '" << t.name () << "'" << endl; + throw operation_failed (); + } + } + + virtual void + traverse (sema_rel::drop_foreign_key& dfk) + { + using sema_rel::foreign_key; + table::names_iterator i (t.find (dfk.name ())); + + if (i == t.names_end () || !i->nameable ().is_a<foreign_key> ()) + { + cerr << "error: invalid changelog: foreign key '" << dfk.name () << + "' does not exist in table '" << t.name () << "'" << endl; + throw operation_failed (); + } + + g.delete_edge (t, i->nameable (), *i); + } + + protected: + table& t; + graph& g; + }; + + struct patch_model: trav_rel::add_table, + trav_rel::drop_table, + trav_rel::alter_table + { + patch_model (model& ml, graph& gr): m (ml), g (gr) {} + + virtual void + traverse (sema_rel::add_table& at) + { + try + { + table& t (g.new_node<table> (at, m, g)); + g.new_edge<qnames> (m, t, at.name ()); + } + catch (duplicate_name const&) + { + cerr << "error: invalid changelog: table '" << at.name () << + "' already exists in model version " << m.version () << endl; + throw operation_failed (); + } + } + + virtual void + traverse (sema_rel::drop_table& dt) + { + model::names_iterator i (m.find (dt.name ())); + + if (i == m.names_end () || !i->nameable ().is_a<table> ()) + { + cerr << "error: invalid changelog: table '" << dt.name () << + "' does not exist in model version " << m.version () << endl; + throw operation_failed (); + } + + g.delete_edge (m, i->nameable (), *i); + } + + virtual void + traverse (sema_rel::alter_table& at) + { + if (table* t = m.find<table> (at.name ())) + { + trav_rel::alter_table atable; + trav_rel::unames names; + patch_table ptable (*t, g); + atable >> names >> ptable; + atable.traverse (at); + } + else + { + cerr << "error: invalid changelog: table '" << at.name () << + "' does not exist in model version " << m.version () << endl; + throw operation_failed (); + } + } + + protected: + model& m; + graph& g; + }; + + model& + patch (model& m, changeset& c, graph& g) + { + model& r (g.new_node<model> (m, g)); + + trav_rel::changeset changeset; + trav_rel::qnames names; + patch_model pmodel (r, g); + changeset >> names >> pmodel; + changeset.traverse (c); + + r.version (c.version ()); + return r; + } + } + + cutl::shared_ptr<changelog> + generate (model& m, + model_version const& mv, + changelog* old, + string const& in_name, + string const& out_name, + options const& ops) + { + database db (ops.database ()[0]); + cutl::shared_ptr<changelog> cl ( + new (shared) changelog (db.string (), ops.schema_name ()[db])); + graph& g (*cl); + + if (old == 0) + { + // Don't allow changelog initialization if the version is closed. + // This will prevent adding new files to an existing object model + // with a closed version. + // + if (!mv.open) + { + cerr << out_name << ": error: unable to initialize changelog " << + "because current version is closed" << endl; + throw operation_failed (); + } + + if (!ops.init_changelog ()) + cerr << out_name << ": info: initializing changelog with base " << + "version " << mv.base << endl; + + if (mv.base == mv.current) + g.new_edge<contains_model> (*cl, g.new_node<model> (m, g)); + else + { + // In this case we have to create an empty model at the base + // version and a changeset. We do it this way instead of putting + // everything into the base model in order to support adding new + // header files to the project. + // + cerr << out_name << ": warning: base and current versions " << + "differ; assuming base model is empty" << endl; + + model& nm (g.new_node<model> (mv.base)); + g.new_edge<contains_model> (*cl, nm); + changeset& c (diff (nm, m, *cl, in_name, ops, &mv)); + + if (!c.names_empty ()) + { + g.new_edge<alters_model> (c, nm); + g.new_edge<contains_changeset> (*cl, c); + } + } + + return cl; + } + + // Get the changelog base and current versions and do some sanity + // checks. + // + version bver (old->model ().version ()); + version cver ( + old->contains_changeset_empty () + ? bver + : old->contains_changeset_back ().changeset ().version ()); + + if (mv.base < bver) + { + cerr << in_name << ": error: latest changelog base version is " << + "greater than model base version" << endl; + throw operation_failed (); + } + + if (mv.current < cver) + { + cerr << in_name << ": error: latest changelog current version is " << + "greater than model current version" << endl; + throw operation_failed (); + } + + // Build the new changelog. + // + model& oldm (old->model ()); + + // Now we have a case with a "real" old model (i.e., non-empty + // and with version older than current) as well as zero or more + // changeset. + // + // + model* last (&g.new_node<model> (oldm, g)); + model* base (bver == mv.base && mv.base != mv.current ? last : 0); + if (base != 0) + g.new_edge<contains_model> (*cl, *base); + + for (changelog::contains_changeset_iterator i ( + old->contains_changeset_begin ()); + i != old->contains_changeset_end (); ++i) + { + changeset& cs (i->changeset ()); + + // Don't copy the changeset for the current version. Instead, we + // will re-create it from scratch. + // + if (cs.version () == mv.current) + break; + + model& prev (*last); + last = &patch (prev, cs, g); + + if (base == 0) + { + if (last->version () == mv.base) + { + base = last; + g.new_edge<contains_model> (*cl, *base); + } + else if (last->version () > mv.base) + { + // We have a gap. Plug it with an empty base model. We will + // also need to create a new changeset for this step. + // + base = &g.new_node<model> (mv.base); + g.new_edge<contains_model> (*cl, *base); + + changeset& c (diff (*base, *last, *cl, in_name, ops, 0)); + if (!c.names_empty ()) + { + g.new_edge<alters_model> (c, *base); + g.new_edge<contains_changeset> (*cl, c); + } + + continue; + } + } + + // Copy the changeset unless it is below or at our base version. + // + if (last->version () <= mv.base) + continue; + + changeset& c ( + g.new_node<changeset> ( + cs, + cl->contains_changeset_empty () + ? static_cast<qscope&> (*base) // Cannot be NULL. + : cl->contains_changeset_back ().changeset (), + g)); + + g.new_edge<alters_model> (c, prev); + g.new_edge<contains_changeset> (*cl, c); + } + + // If we still haven't found the new base model, then it means it + // has version greater than any changeset we have seen. + // + if (base == 0) + { + if (mv.base == mv.current) + base = &g.new_node<model> (m, g); + else + { + // Fast-forward the latest model to the new base. + // + base = last; + base->version (mv.base); + } + + g.new_edge<contains_model> (*cl, *base); + } + + // If the current version is closed, make sure the model hasn't + // changed. + // + if (!mv.open) + { + // If the last changeset has the current version, then apply it. + // + model* om (last); + if (!old->contains_changeset_empty ()) + { + changeset& c (old->contains_changeset_back ().changeset ()); + if (c.version () == mv.current) + om = &patch (*last, c, g); + } + + changeset& c (diff (*om, m, *cl, in_name, ops, &mv)); + + if (!c.names_empty ()) + { + qnames& n (*c.names_begin ()); + + cerr << out_name << ": error: current version is closed" << endl; + cerr << out_name << ": info: first new change is " << + n.nameable ().kind () << " '" << n.name () << "'" << endl; + + throw operation_failed (); + } + } + + // Add a changeset for the current version unless it is the same + // as the base version. + // + if (mv.base != mv.current) + { + // Add it even if it is empty. This can be useful, for example, + // for data-only migrations were the user relies on the database + // version being updated in the version table. + // + changeset& c (diff (*last, m, *cl, in_name, ops, &mv)); + g.new_edge<alters_model> (c, *last); + g.new_edge<contains_changeset> (*cl, c); + } + + return cl; + } + } +} diff --git a/odb/odb/relational/common-query.cxx b/odb/odb/relational/common-query.cxx new file mode 100644 index 0000000..53321ce --- /dev/null +++ b/odb/odb/relational/common-query.cxx @@ -0,0 +1,169 @@ +// file : odb/relational/common-query.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <odb/relational/common-query.hxx> + +using namespace std; + +namespace relational +{ + // query_alias_traits + // + + void query_alias_traits:: + generate_decl_body () + { + os << "static const char table_name[];"; + } + + void query_alias_traits:: + generate_def (semantics::data_member& m, semantics::class_& c) + { + // Come up with a table alias. Generally, we want it to be based + // on the column name. This is straightforward for single-column + // references. In case of a composite id, we will need to use the + // column prefix which is based on the data member name, unless + // overridden by the user. In the latter case the prefix can be + // empty, in which case we will just fall back on the member's + // public name. + // + string alias; + { + string n; + + if (composite_wrapper (utype (*id_member (c)))) + { + n = column_prefix (m, key_prefix_, default_name_).prefix; + + if (n.empty ()) + n = public_name_db (m); + else if (n[n.size () - 1] == '_') + n.resize (n.size () - 1); // Remove trailing underscore. + } + else + { + bool dummy; + n = column_name (m, key_prefix_, default_name_, dummy); + } + + alias = column_prefix_.prefix + n; + } + + generate_def (public_name (m), c, alias); + } + + void query_alias_traits:: + generate_def (string const& tag, semantics::class_& c, string const& alias) + { + semantics::class_* poly_root (polymorphic (c)); + bool poly_derived (poly_root != 0 && poly_root != &c); + semantics::class_* poly_base (poly_derived ? &polymorphic_base (c) : 0); + + if (poly_derived) + generate_def (tag, *poly_base, alias); + + os << "const char alias_traits<" + << " " << class_fq_name (c) << "," << endl + << " id_" << db << "," << endl + << " " << scope_ << "::" << tag << "_tag>::" << endl + << "table_name[] = "; + + if (poly_root != 0) + os << strlit (quote_id (alias + "_" + table_name (c).uname ())); + else + os << strlit (quote_id (alias)); + + os << ";" + << endl; + } + + entry<query_alias_traits> query_alias_traits_; + + + // query_columns_base + // + + entry<query_columns_base> query_columns_base_; + + // query_columns + // + + void query_columns:: + column_ctor (string const& type, string const& name, string const& base) + { + os << name << " ("; + + if (multi_dynamic) + os << "odb::query_column< " << type << " >& qc," << endl; + + os << "const char* t, const char* c, const char* conv)" << endl + << " : " << base << " (" << (multi_dynamic ? "qc, " : "") << + "t, c, conv)" + << "{" + << "}"; + } + + void query_columns:: + column_ctor_args_extra (semantics::data_member&) + { + } + + void query_columns:: + column_common (semantics::data_member& m, + string const& type, + string const& column, + string const& suffix) + { + string name (public_name (m)); + + if (decl_) + { + string type_id (database_type_id (m)); + + os << "// " << name << endl + << "//" << endl; + + os << "typedef" << endl + << db << "::query_column<" << endl + << " " << db << "::value_traits<" << endl + << " " << type << "," << endl + << " " << type_id << " >::query_type," << endl + << " " << type_id << " >" << endl + << name << suffix << ";" + << endl; + } + else + { + // Note that here we don't use suffix. + // + string tmpl (ptr_ ? "pointer_query_columns" : "query_columns"); + tmpl += "< " + fq_name_ + ", id_" + db.string () + ", A >" + scope_; + + os << "template <typename A>" << endl + << "const typename " << tmpl << "::" << name << "_type_" << endl + << tmpl << "::" << endl + << name << " ("; + + // Pass common query column for registration. + // + if (multi_dynamic) + { + string tmpl (ptr_ ? "pointer_query_columns" : "query_columns"); + tmpl += "< " + fq_name_ + ", id_common, typename A::common_traits >" + + scope_; + + os << tmpl << "::" << name << "," << endl; + } + + os << "A::" << "table_name, " << strlit (quote_id (column)); + + string const& conv (convert_to_expr (column_type (), m)); + os << ", " << (conv.empty () ? "0" : strlit (conv)); + + column_ctor_args_extra (m); + + os << ");" + << endl; + } + } +} diff --git a/odb/odb/relational/common-query.hxx b/odb/odb/relational/common-query.hxx new file mode 100644 index 0000000..c29df6b --- /dev/null +++ b/odb/odb/relational/common-query.hxx @@ -0,0 +1,63 @@ +// file : odb/relational/common-query.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_RELATIONAL_COMMON_QUERY_HXX +#define ODB_RELATIONAL_COMMON_QUERY_HXX + +#include <odb/relational/common.hxx> + +namespace relational +{ + // + // + struct query_alias_traits: ::query_alias_traits, virtual context + { + typedef query_alias_traits base_impl; + + query_alias_traits (base const& x): base (x) {} + + virtual void + generate_decl_body (); + + virtual void + generate_def (semantics::data_member&, semantics::class_&); + + virtual void + generate_def (string const& tag, semantics::class_&, string const& alias); + }; + + // + // + struct query_columns_base: ::query_columns_base, virtual context + { + typedef query_columns_base base_impl; + + query_columns_base (base const& x): base (x) {const_ = "const ";} + }; + + // + // + struct query_columns: ::query_columns, virtual context + { + typedef query_columns base_impl; + + query_columns (base const& x): base (x) {const_ = "const ";} + + virtual string + database_type_id (semantics::data_member&) = 0; + + virtual void + column_ctor (string const& type, string const& name, string const& base); + + virtual void + column_ctor_args_extra (semantics::data_member&); + + virtual void + column_common (semantics::data_member&, + string const& type, + string const& column, + string const& suffix); + }; +} + +#endif // ODB_RELATIONAL_COMMON_QUERY_HXX diff --git a/odb/odb/relational/common.cxx b/odb/odb/relational/common.cxx new file mode 100644 index 0000000..5c9126c --- /dev/null +++ b/odb/odb/relational/common.cxx @@ -0,0 +1,27 @@ +// file : odb/relational/common.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <odb/relational/common.hxx> + +using namespace std; + +namespace relational +{ + // member_image_type + // + string member_image_type:: + image_type (semantics::data_member&) + { + assert (false); + return string (); + } + + // member_database_type_id + // + string member_database_type_id:: + database_type_id (semantics::data_member&) + { + assert (false); + return string (); + } +} diff --git a/odb/odb/relational/common.hxx b/odb/odb/relational/common.hxx new file mode 100644 index 0000000..01266a0 --- /dev/null +++ b/odb/odb/relational/common.hxx @@ -0,0 +1,273 @@ +// file : odb/relational/common.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_RELATIONAL_COMMON_HXX +#define ODB_RELATIONAL_COMMON_HXX + +#include <set> +#include <cassert> + +#include <odb/common.hxx> +#include <odb/relational/context.hxx> + +namespace relational +{ + struct member_base: traversal::data_member, virtual context + { + typedef member_base base; + + member_base (semantics::type* type, + const custom_cxx_type* ct, + string const& fq_type, + string const& key_prefix, + object_section* section = 0) + : type_override_ (type), + custom_override_ (ct), + fq_type_override_ (fq_type), + key_prefix_ (key_prefix), + section_ (section), + top_level_ (false) + { + } + + member_base (string const& var, + semantics::type* type, + const custom_cxx_type* ct, + string const& fq_type, + string const& key_prefix, + object_section* section = 0) + : var_override_ (var), + type_override_ (type), + custom_override_ (ct), + fq_type_override_ (fq_type), + key_prefix_ (key_prefix), + section_ (section), + top_level_ (false) + { + } + + protected: + // For virtual inheritance only. Should not be actually called. + // + member_base (); + + protected: + string var_override_; + semantics::type* type_override_; + const custom_cxx_type* custom_override_; + string fq_type_override_; + string key_prefix_; + object_section* section_; + + // True during the top-level call of pre() and post() below. Note that + // call via another tarverser (e.g., for a class) is not considered top- + // level. + // + bool top_level_; + }; + + // Template argument is the database SQL type (sql_type). + // + template <typename T> + struct member_base_impl: virtual member_base + { + typedef member_base_impl base_impl; + + member_base_impl (base const& x): base (x) {} + + protected: + member_base_impl () {} + + public: + virtual T const& + member_sql_type (semantics::data_member&) = 0; + + void + traverse (semantics::data_member& m, bool top_level) + { + top_level_ = top_level; + traverse (m); + top_level_ = false; + } + + struct member_info + { + semantics::data_member& m; // Member. + semantics::type& t; // Cvr-unqualified member C++ type, note + // that m.type () may not be the same as t. + const custom_cxx_type* ct; // Translation used for t, if any. + semantics::class_* ptr; // Pointed-to object if m is an object + // pointer. In this case t is the id type + // while fq_type_ is the pointer fq-type. + semantics::type* wrapper; // Wrapper type if member is a composite or + // container wrapper, also cvr-unqualified. + // In this case t is the wrapped type. + bool cq; // True if the original (wrapper) type + // is const-qualified. + T const* st; // Member SQL type (only simple values). + string& var; // Member variable name with trailing '_'. + + // C++ type fq-name. + // + string + fq_type (bool unwrap = true) const + { + semantics::names* hint; + + if (wrapper != 0 && unwrap) + { + // Use the hint from the wrapper unless the wrapped type + // is qualified. + // + hint = wrapper->get<semantics::names*> ("wrapper-hint"); + utype (*context::wrapper (*wrapper), hint); + return t.fq_name (hint); + } + + // Use the original type from 'm' instead of 't' since the hint may + // be invalid for a different type. Plus, if a type is overriden, + // then the fq_type must be as well. + // + if (ptr != 0) + { + semantics::type& t (utype (*id_member (*ptr), hint)); + return t.fq_name (hint); + } + else if (fq_type_.empty ()) + { + semantics::type& t (utype (m, hint)); + return t.fq_name (hint); + } + else + // If we are translated, then fq_type_ contains the original type. + // + return ct == 0 ? fq_type_ : t.fq_name (ct->as_hint); + } + + string + ptr_fq_type () const + { + assert (ptr != 0); + + if (fq_type_.empty ()) + { + // If type is overridden so should fq_type so it is safe to + // get the type from the member. + // + semantics::names* hint; + semantics::type& t (utype (m, hint)); + return t.fq_name (hint); + } + else + return fq_type_; + } + + string const& fq_type_; + + member_info (semantics::data_member& m_, + semantics::type& t_, + const custom_cxx_type* ct_, + semantics::type* wrapper_, + bool cq_, + string& var_, + string const& fq_type) + : m (m_), + t (t_), + ct (ct_), + ptr (0), + wrapper (wrapper_), + cq (cq_), + st (0), + var (var_), + fq_type_ (fq_type) + { + } + }; + + bool + container (member_info& mi) + { + // This cannot be a container if we have a type override. + // + return type_override_ == 0 && context::container (mi.m); + } + + // The false return value indicates that no further callbacks should be + // called for this member. + // + virtual bool + pre (member_info&) {return true;} + + virtual void + post (member_info&) {} + + // Note: calling these directly will mess up the top_level logic. + // + protected: + virtual void + traverse_composite (member_info&) {} + + virtual void + traverse_container (member_info&) {} + + // Note that by default traverse_pointer() will traverse the + // pointed-to object id type. + // + virtual void + traverse_pointer (member_info&); + + virtual void + traverse_simple (member_info&) {} + + private: + virtual void + traverse (semantics::data_member&); + }; + + // + // + struct member_image_type: virtual member_base + { + typedef member_image_type base; + + member_image_type (): member_base (0, 0, string (), string ()) {} + + member_image_type (semantics::type* type, + const custom_cxx_type* ct, + string const& fq_type = string (), + string const& key_prefix = string ()) + : member_base (type, ct, fq_type, key_prefix) {} + + // Has to be overriden. + // + virtual string + image_type (semantics::data_member&); + }; + + // + // + struct member_database_type_id: virtual member_base + { + typedef member_database_type_id base; + + member_database_type_id (): member_base (0, 0, string (), string ()) {} + member_database_type_id (semantics::type* type, + const custom_cxx_type* ct, + string const& fq_type = string (), + string const& key_prefix = string ()) + : member_base (type, ct, fq_type, key_prefix) {} + + // Has to be overriden. + // + virtual string + database_type_id (semantics::data_member&); + }; +} + +#include <odb/relational/common.txx> + +// Other common parts. +// +#include <odb/relational/common-query.hxx> + +#endif // ODB_RELATIONAL_COMMON_HXX diff --git a/odb/odb/relational/common.txx b/odb/odb/relational/common.txx new file mode 100644 index 0000000..82a4a4a --- /dev/null +++ b/odb/odb/relational/common.txx @@ -0,0 +1,125 @@ +// file : odb/relational/common.txx +// license : GNU GPL v3; see accompanying LICENSE file + +namespace relational +{ + // + // member_base_impl + // + + template <typename T> + void member_base_impl<T>:: + traverse (semantics::data_member& m) + { + if (transient (m)) + return; + + string var; + + if (!var_override_.empty ()) + var = var_override_; + else + { + string const& name (m.name ()); + var = name + (name[name.size () - 1] == '_' ? "" : "_"); + } + + bool cq (type_override_ != 0 ? false : const_member (m)); + const custom_cxx_type* ct (type_override_ != 0 ? custom_override_ : 0); + semantics::type& t (type_override_ != 0 + ? *type_override_ + : utype (m, &ct)); + + semantics::type* cont; + if (semantics::class_* c = object_pointer (t)) + { + // A pointer in view might point to an object without id. + // + data_member_path* id (id_member (*c)); + semantics::type& t (id != 0 ? utype (*id, &ct) : utype (m, &ct)); + semantics::class_* comp (id != 0 ? composite_wrapper (t) : 0); + + member_info mi (m, + (comp != 0 ? *comp : t), + ct, + (comp != 0 && wrapper (t) ? &t : 0), + cq, + var, + fq_type_override_); // Pointer type. + + mi.ptr = c; + + // Pointer in views aren't really a "column". + // + if (!view_member (m) && comp == 0) + mi.st = &member_sql_type (m); + + if (pre (mi)) + { + traverse_pointer (mi); + post (mi); + } + } + else if (semantics::class_* c = composite_wrapper (t)) + { + // If t is a wrapper, pass the wrapped type. Also pass the + // original, wrapper type. + // + member_info mi (m, + *c, + ct, + (wrapper (t) ? &t : 0), + cq, + var, + fq_type_override_); + if (pre (mi)) + { + traverse_composite (mi); + post (mi); + } + } + // This cannot be a container if we have a type override. + // + else if (type_override_ == 0 && (cont = context::container (m))) + { + // The same unwrapping logic as for composite values. + // + member_info mi (m, + *cont, + 0, // Cannot be mapped. + (wrapper (t) ? &t : 0), + cq, + var, + fq_type_override_); + if (pre (mi)) + { + traverse_container (mi); + post (mi); + } + } + else + { + member_info mi (m, t, ct, 0, cq, var, fq_type_override_); + mi.st = &member_sql_type (m); + + if (pre (mi)) + { + traverse_simple (mi); + post (mi); + } + } + } + + template <typename T> + void member_base_impl<T>:: + traverse_pointer (member_info& mi) + { + if (!view_member (mi.m)) // Not really "as if" pointed-to id member. + { + if (composite (mi.t)) // Already unwrapped. + traverse_composite (mi); + else + traverse_simple (mi); + } + } +} diff --git a/odb/odb/relational/context.cxx b/odb/odb/relational/context.cxx new file mode 100644 index 0000000..3fba69b --- /dev/null +++ b/odb/odb/relational/context.cxx @@ -0,0 +1,169 @@ +// file : odb/relational/context.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <cassert> + +#include <odb/relational/context.hxx> + +using namespace std; + +namespace relational +{ + context* context::current_; + + context:: + ~context () + { + if (current_ == this) + current_ = 0; + } + + context:: + context () + : data_ (current ().data_), + model (current ().model), + generate_grow (current ().generate_grow), + need_alias_as (current ().need_alias_as), + insert_send_auto_id (current ().insert_send_auto_id), + delay_freeing_statement_result (current ().delay_freeing_statement_result), + need_image_clone (current ().need_image_clone), + generate_bulk (current ().generate_bulk), + global_index (current ().global_index), + global_fkey (current ().global_fkey), + bind_vector (data_->bind_vector_), + truncated_vector (data_->truncated_vector_) + { + } + + context:: + context (data* d, sema_rel::model* m) + : data_ (d), + model (m), + bind_vector (data_->bind_vector_), + truncated_vector (data_->truncated_vector_) + { + assert (current_ == 0); + current_ = this; + } + + string context:: + index_name (qname const& table, string const& base) + { + string n; + + if (options.index_suffix ().count (db) != 0) + n = base + options.index_suffix ()[db]; + else + n = compose_name (base, "i"); + + // If this database has global index names, then add the table + // name as a prefix (the schema, if needed, will be added by + // database-specific create_index overrides). + // + if (global_index) + n = compose_name (table.uname (), n); + + return transform_name (n, sql_name_index); + } + + string context:: + fkey_name (qname const& table, string const& base) + { + string n; + + if (options.fkey_suffix ().count (db) != 0) + n = base + options.fkey_suffix ()[db]; + else + n = compose_name (base, "fk"); + + // If this database has global index names, then add the table + // name as a prefix (the schema, if needed, will be added by + // database-specific create_foreign_key overrides). + // + if (global_fkey) + n = compose_name (table.uname (), n); + + return transform_name (n, sql_name_fkey); + } + + string context:: + convert (string const& e, string const& c) + { + size_t p (c.find ("(?)")); + string r (c, 0, p); + r += e; + r.append (c, p + 3, string::npos); + return r; + } + + string const& context:: + convert_expr (string const&, semantics::data_member&, bool) + { + assert (false); + throw operation_failed (); + } + + bool context:: + grow_impl (semantics::class_&, user_section*) + { + return false; + } + + bool context:: + grow_impl (semantics::data_member&) + { + return false; + } + + bool context:: + grow_impl (semantics::data_member&, + semantics::type&, + const custom_cxx_type*, + string const&) + { + return false; + } + + string context:: + quote_string_impl (string const& s) const + { + string r; + r.reserve (s.size ()); + r += '\''; + + for (string::size_type i (0), n (s.size ()); i < n; ++i) + { + if (s[i] == '\'') + r += "''"; + else + r += s[i]; + } + + r += '\''; + return r; + } + + 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 += *i; + r += '"'; + } + + return r; + } +} diff --git a/odb/odb/relational/context.hxx b/odb/odb/relational/context.hxx new file mode 100644 index 0000000..db9b5be --- /dev/null +++ b/odb/odb/relational/context.hxx @@ -0,0 +1,289 @@ +// file : odb/relational/context.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_RELATIONAL_CONTEXT_HXX +#define ODB_RELATIONAL_CONTEXT_HXX + +#include <odb/context.hxx> + +#include <odb/semantics/relational.hxx> +#include <odb/traversal/relational.hxx> + +namespace relational +{ + namespace sema_rel = semantics::relational; + namespace trav_rel = traversal::relational; + + enum statement_kind + { + statement_select, + statement_insert, + statement_update, + statement_delete, + statement_where // WHERE clause. + }; + + // Index. + // + struct index + { + location_t loc; // Location of this index definition. + std::string name; // If empty, then derive from the member name. + std::string type; // E.g., "UNIQUE", etc. + std::string method; // E.g., "BTREE", etc. + std::string options; // Database-specific index options. + + struct member + { + location_t loc; // Location of this member specifier. + std::string name; // Member name, e.g., foo_, foo_.bar_. + data_member_path path; // Member path. + std::string options; // Member options, e.g., "ASC", etc. + }; + typedef std::vector<member> members_type; + + members_type members; + }; + + typedef std::vector<index> indexes; + + // Indexes in the above vector are in location order. + // + struct index_comparator + { + bool + operator() (index const& x, index const& y) const + { + return x.loc < y.loc; + } + }; + + // Custom database type mapping. + // + struct custom_db_type + { + regex type; + std::string as; + std::string to; + std::string from; + location_t loc; + }; + + typedef std::vector<custom_db_type> custom_db_types; + + class context: public virtual ::context + { + public: + // Return true if an object or value type has members for which + // the image can grow. If section is not specified, then ignore + // separately loaded members. Otherwise ignore members that do + // not belong to the section. + // + bool + grow (semantics::class_&, user_section* = 0); + + // The same for a member's value type. + // + bool + grow (semantics::data_member&); + + bool + grow (semantics::data_member&, + semantics::type&, + const custom_cxx_type*, + string const& key_prefix); + + public: + // Quote SQL string. + // + string + quote_string (string const&) const; + + // Quote SQL identifier. + // + string + quote_id (string const&) const; + + string + quote_id (qname const&) const; + + // Quoted column and table names. + // + string + column_qname (semantics::data_member& m, column_prefix const& cp) const + { + return quote_id (column_name (m, cp)); + } + + string + column_qname (data_member_path const& mp) const + { + return quote_id (column_name (mp)); + } + + string + column_qname (semantics::data_member& m, + string const& key_prefix, + string const& default_name, + column_prefix const& cp) const + { + return quote_id (column_name (m, key_prefix, default_name, cp)); + } + + string + table_qname (semantics::class_& c) const + { + return quote_id (table_name (c)); + } + + string + table_qname (semantics::class_& obj, data_member_path const& mp) const + { + return quote_id (table_name (obj, mp)); + } + + string + table_qname (semantics::data_member& m, table_prefix const& p) const + { + return quote_id (table_name (m, p)); + } + + public: + string + index_name (qname const& table, string const& base); + + string + fkey_name (qname const& table, string const& base); + + // Custom database type conversion. + // + public: + string + convert_to (string const& expr, + string const& sqlt, + semantics::data_member& m) + { + string const& conv (current ().convert_expr (sqlt, m, true)); + return conv.empty () ? expr : convert (expr, conv); + } + + string + convert_from (string const& expr, + string const& sqlt, + semantics::data_member& m) + { + string const& conv (current ().convert_expr (sqlt, m, false)); + return conv.empty () ? expr : convert (expr, conv); + } + + // These shortcut versions should only be used on special members + // (e.g., auto id, version, etc) since they may not determine the + // proper SQL type in other cases (prefixes, composite ids, etc). + // + string + convert_to (string const& expr, semantics::data_member& m) + { + return convert_to (expr, column_type (m), m); + } + + string + convert_from (string const& expr, semantics::data_member& m) + { + return convert_from (expr, column_type (m), m); + } + + // Return the conversion expression itself. + // + string const& + convert_to_expr (string const& sqlt, semantics::data_member& m) + { + return current ().convert_expr (sqlt, m, true); + } + + protected: + virtual string const& + convert_expr (string const& sqlt, semantics::data_member&, bool to); + + string + convert (string const& expr, string const& conv); + + protected: + // The default implementation returns false. + // + virtual bool + grow_impl (semantics::class_&, user_section*); + + virtual bool + grow_impl (semantics::data_member&); + + virtual bool + grow_impl (semantics::data_member&, + semantics::type&, + const custom_cxx_type*, + string const&); + + // The default implementation uses the ISO quoting ('') and + // escapes singe quotes inside the string by double-quoting + // (' -> ''). Some (most?) database systems support escape + // sequences. We may want to provide utilize that to support + // things like \n, \t, etc. + // + virtual string + quote_string_impl (string const&) const; + + // The default implementation uses the ISO quoting (""). + // + virtual string + quote_id_impl (qname const&) const; + + public: + virtual + ~context (); + context (); + + static context& + current () + { + return *current_; + } + + protected: + struct data; + typedef context base_context; + + context (data*, sema_rel::model*); + + private: + static context* current_; + + protected: + struct data: root_context::data + { + data (std::ostream& os): root_context::data (os) {} + + string bind_vector_; + string truncated_vector_; + }; + data* data_; + + public: + sema_rel::model* model; + + bool generate_grow; + bool need_alias_as; + bool insert_send_auto_id; + bool delay_freeing_statement_result; + bool need_image_clone; + bool generate_bulk; + + bool global_index; + bool global_fkey; + + string const& bind_vector; + string const& truncated_vector; + }; +} + +#include <odb/relational/context.ixx> + +#endif // ODB_RELATIONAL_CONTEXT_HXX diff --git a/odb/odb/relational/context.ixx b/odb/odb/relational/context.ixx new file mode 100644 index 0000000..abf1fb5 --- /dev/null +++ b/odb/odb/relational/context.ixx @@ -0,0 +1,44 @@ +// file : odb/relational/context.ixx +// license : GNU GPL v3; see accompanying LICENSE file + +namespace relational +{ + inline bool context:: + grow (semantics::class_& c, user_section* s) + { + return current ().grow_impl (c, s); + } + + inline bool context:: + grow (semantics::data_member& m) + { + return current ().grow_impl (m); + } + + inline bool context:: + grow (semantics::data_member& m, + semantics::type& t, + const custom_cxx_type* ct, + string const& kp) + { + return current ().grow_impl (m, t, ct, kp); + } + + inline context::string context:: + quote_string (string const& str) const + { + return current ().quote_string_impl (str); + } + + inline context::string context:: + quote_id (string const& id) const + { + return current ().quote_id_impl (qname (id)); + } + + inline context::string context:: + quote_id (qname const& id) const + { + return current ().quote_id_impl (id); + } +} diff --git a/odb/odb/relational/generate.hxx b/odb/odb/relational/generate.hxx new file mode 100644 index 0000000..e597fb8 --- /dev/null +++ b/odb/odb/relational/generate.hxx @@ -0,0 +1,81 @@ +// file : odb/relational/generate.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_RELATIONAL_GENERATE_HXX +#define ODB_RELATIONAL_GENERATE_HXX + +#include <string> +#include <libcutl/shared-ptr.hxx> + +#include <odb/context.hxx> +#include <odb/semantics/relational/model.hxx> +#include <odb/semantics/relational/changeset.hxx> +#include <odb/semantics/relational/changelog.hxx> + +namespace relational +{ + namespace header + { + void + generate (); + } + + namespace inline_ + { + void + generate (); + } + + namespace source + { + void + generate (); + } + + namespace model + { + cutl::shared_ptr<semantics::relational::model> + generate (); + } + + namespace changelog + { + // Returns NULL if the changelog is unchanged. + // + cutl::shared_ptr<semantics::relational::changelog> + generate (semantics::relational::model&, + model_version const&, + semantics::relational::changelog* old, // Can be NULL. + std::string const& in_name, + std::string const& out_name, + options const&); + } + + namespace schema + { + void + generate_prologue (); + + void + generate_epilogue (); + + void + generate_drop (); + + void + generate_create (); + + void + generate_migrate_pre (semantics::relational::changeset&); + + void + generate_migrate_post (semantics::relational::changeset&); + + // Generate embedded schema. + // + void + generate_source (semantics::relational::changelog*); + } +} + +#endif // ODB_RELATIONAL_GENERATE_HXX diff --git a/odb/odb/relational/header.cxx b/odb/odb/relational/header.cxx new file mode 100644 index 0000000..364d48e --- /dev/null +++ b/odb/odb/relational/header.cxx @@ -0,0 +1,1146 @@ +// file : odb/relational/header.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <odb/relational/header.hxx> +#include <odb/relational/generate.hxx> + +using namespace std; + +void relational::header::class1:: +traverse_object (type& c) +{ + using semantics::data_member; + + data_member_path* id (id_member (c)); + data_member* idf (id ? id->front () : 0); + data_member* idb (id ? id->back () : 0); + bool auto_id (id && auto_ (*id)); + bool base_id (id && &idf->scope () != &c); // Comes from base. + + data_member* opt (context::optimistic (c)); + + type* poly_root (polymorphic (c)); + bool poly (poly_root != 0); + bool poly_derived (poly && poly_root != &c); + data_member* discriminator (poly ? context::discriminator (*poly_root) : 0); + + bool abst (abstract (c)); + bool reuse_abst (abst && !poly); + + bool versioned (context::versioned (c)); + + string const& type (class_fq_name (c)); + + // Sections. + // + user_sections& uss (c.get<user_sections> ("user-sections")); + + os << "// " << class_name (c) << endl + << "//" << endl; + + // pointer_query_columns & query_columns + // + if (options.generate_query ()) + { + // If we don't have object pointers, then also generate + // query_columns (in this case pointer_query_columns and + // query_columns are the same and the former inherits from + // the latter). Otherwise we have to postpone query_columns + // generation until the second pass to deal with forward- + // declared objects. + // + if (!has_a (c, test_pointer | include_base)) + query_columns_type_->traverse (c); + + pointer_query_columns_type_->traverse (c); + } + + // object_traits_impl + // + os << "template <>" << endl + << "class " << exp << "access::object_traits_impl< " << type << ", " << + "id_" << db << " >:" << endl + << " public access::object_traits< " << type << " >" + << "{" + << "public:" << endl; + + object_public_extra_pre (c); + + // For dynamic multi-database support also generate common traits + // alias (used in query aliasing). + // + if (options.generate_query () && multi_dynamic) + { + os << "typedef access::object_traits_impl< " << type << ", " << + "id_common > common_traits;" + << endl; + } + + // Polymorphic root_traits, base_traits, and discriminator_image_type. + // + if (poly) + { + if (!abst) + os << "typedef polymorphic_entry<object_type, id_" << db << + "> entry_type;"; + + os << "typedef object_traits_impl<root_type, id_" << db << "> " << + "root_traits;"; + + if (poly_derived) + { + os << "typedef object_traits_impl<base_type, id_" << db << "> " << + "base_traits;" + << endl; + } + else + { + os << endl + << "struct discriminator_image_type" + << "{"; + + discriminator_image_member_->traverse (*discriminator); + + if (opt != 0) + version_image_member_->traverse (*opt); + + os << "std::size_t version;" + << "};"; + } + } + + // id_image_type + // + if (id != 0) + { + if (base_id) + { + if (poly_derived) + os << "typedef root_traits::id_image_type id_image_type;" + << endl; + else + { + semantics::class_& b ( + dynamic_cast<semantics::class_&> (idf->scope ())); + + os << "typedef object_traits_impl< " << class_fq_name (b) << ", " << + "id_" << db << " >::id_image_type id_image_type;" + << endl; + } + } + else + { + os << "struct id_image_type" + << "{"; + + id_image_member_->traverse (*idb); + + if (opt != 0) + version_image_member_->traverse (*opt); + + os << "std::size_t version;" + << "};"; + } + } + + // Polymorphic map. + // + if (poly) + { + if (!poly_derived) + os << "static map_type* map;"; + + os << "static const " << (abst ? "abstract_" : "") << "info_type info;" + << endl; + } + + // image_type + // + image_type_->traverse (c); + + // Extra (container, section) statement cache (forward declaration). + // + if (!reuse_abst && id != 0) + os << "struct extra_statement_cache_type;" + << endl; + + // + // Containers (abstract and concrete). + // + + { + instance<container_traits> t (c); + t->traverse (c); + } + + // + // Sections (abstract and concrete). + // + + for (user_sections::iterator i (uss.begin ()); i != uss.end (); ++i) + { + instance<section_traits> t (c); + t->traverse (*i); + } + + // + // Query (abstract and concrete). + // + + if (options.generate_query ()) + { + // Generate object pointer tags here unless we are generating dynamic + // multi-database support, in which case they generated in object_traits. + // + if (!multi_dynamic && has_a (c, test_pointer | exclude_base)) + { + query_tags t; // Not customizable. + t.traverse (c); + } + } + + // + // Functions (abstract and concrete). + // + + if (id != 0 || !reuse_abst) + os << "using object_traits<object_type>::id;" + << endl; + + if (opt != 0) + os << "using object_traits<object_type>::version;" + << endl; + + if (!poly_derived && id != 0) + { + if (auto_id) + os << "static id_type" << endl + << "id (const id_image_type&);" + << endl; + + if (options.generate_query ()) + os << "static id_type" << endl + << "id (const image_type&);" + << endl; + + if (opt != 0) + os << "static version_type" << endl + << "version (const image_type&);" + << endl; + } + + // discriminator() + // + if (poly && !poly_derived) + os << "static discriminator_type" << endl + << "discriminator (const image_type&);" + << endl; + + // grow () + // + if (generate_grow) + { + // For derived classes in a polymorphic hierarchy, grow() will + // check bases up to the specified depth. If one of the base + // images has grown, then it will increment its version. But + // the return value only indicates the state of this image, + // excluding polymorphic bases (in other words, it is possible + // that one of the bases has grown but this function returns + // false). + // + os << "static bool" << endl + << "grow (image_type&," << endl + << truncated_vector; + + if (versioned) + os << "," << endl + << "const schema_version_migration&"; + + if (poly_derived) + os << "," << endl + << "std::size_t = depth"; + + os << ");" + << endl; + } + + // bind (image_type) + // + os << "static void" << endl + << "bind (" << bind_vector << "," << endl; + + // If we are a derived type in a polymorphic hierarchy, then + // we get the the external id binding. + // + if (poly_derived) + os << "const " << bind_vector << " id," << endl + << "std::size_t id_size," << endl; + + os << "image_type&," << endl + << db << "::statement_kind"; + + if (versioned) + os << "," << endl + << "const schema_version_migration&"; + + os << ");" + << endl; + + // bind (id_image_type) + // + if (id != 0) + { + os << "static void" << endl + << "bind (" << bind_vector << ", id_image_type&" << + (opt != 0 ? ", bool bind_version = true" : "") << ");" + << endl; + } + + // init (image, object) + // + os << "static " << (generate_grow ? "bool" : "void") << endl + << "init (image_type&," << endl + << "const object_type&," << endl + << db << "::statement_kind"; + + if (versioned) + os << "," << endl + << "const schema_version_migration&"; + + os << ");" + << endl; + + // init (object, image) + // + os << "static void" << endl + << "init (object_type&," << endl + << "const image_type&," << endl + << "database*"; + + if (versioned) + os << "," << endl + << "const schema_version_migration&"; + + if (poly_derived) + os << "," << endl + << "std::size_t = depth"; + + os << ");" + << endl; + + // init (id_image, id) + // + if (id != 0) + { + os << "static void" << endl + << "init (id_image_type&, const id_type&" << + (opt != 0 ? ", const version_type* = 0" : "") << ");" + << endl; + } + + if (poly_derived) + { + // check_version + // + os << "static bool" << endl + << "check_version (const std::size_t*, const image_type&);" + << endl; + + // update_version + // + os << "static void" << endl + << "update_version (std::size_t*, const image_type&, " << + db << "::binding*);" + << endl; + } + + // The rest does not apply to reuse-abstract objects. + // + if (reuse_abst) + { + object_public_extra_post (c); + os << "};"; + return; + } + + column_count_type const& cc (column_count (c)); + + // Statements typedefs. + // + if (poly) + { + if (poly_derived) + os << "typedef" << endl + << db << "::polymorphic_derived_object_statements" << + "<object_type>" << endl + << "statements_type;" + << endl + << "typedef" << endl + << db << "::polymorphic_root_object_statements<root_type>" << endl + << "root_statements_type;" + << endl; + else + os << "typedef" << endl + << db << "::polymorphic_root_object_statements<object_type>" << endl + << "statements_type;" + << endl + << "typedef statements_type root_statements_type;" + << endl; + } + else + { + if (id != 0) + os << "typedef " << db << "::object_statements<object_type> " << + "statements_type;" + << endl; + else + os << "typedef " << db << "::no_id_object_statements<object_type> " << + "statements_type;" + << endl; + } + + // + // Query (concrete). + // + + if (options.generate_query ()) + { + // query_base_type + // + os << "typedef " << db << "::query_base query_base_type;" + << endl; + } + + // + // Containers (concrete). + // + + // + // Sections (concrete). + // + + // column_count + // + os << "static const std::size_t column_count = " << cc.total << "UL;" + << "static const std::size_t id_column_count = " << cc.id << "UL;" + << "static const std::size_t inverse_column_count = " << + cc.inverse << "UL;" + << "static const std::size_t readonly_column_count = " << + cc.readonly << "UL;" + << "static const std::size_t managed_optimistic_column_count = " << + cc.optimistic_managed << "UL;"; + + if (poly && !poly_derived) + os << "static const std::size_t discriminator_column_count = " << + cc.discriminator << "UL;"; + + os << endl + << "static const std::size_t separate_load_column_count = " << + cc.separate_load << "UL;" + << "static const std::size_t separate_update_column_count = " << + cc.separate_update << "UL;" + << endl; + + os << "static const bool versioned = " << versioned << ";" + << endl; + + // Statements. + // + os << "static const char persist_statement[];"; + + if (id != 0) + { + if (poly_derived) + { + char const* n (abst ? "1" : "depth"); + + os << "static const char* const find_statements[" << n << "];" + << "static const std::size_t find_column_counts[" << n << "];"; + } + else + { + os << "static const char find_statement[];"; + + if (poly) + os << "static const char find_discriminator_statement[];"; + } + + if (cc.total != cc.id + cc.inverse + cc.readonly + cc.separate_update) + os << "static const char update_statement[];"; + + os << "static const char erase_statement[];"; + + if (opt != 0 && !poly_derived) + os << "static const char optimistic_erase_statement[];"; + } + + if (options.generate_query ()) + { + os << "static const char query_statement[];" + << "static const char erase_query_statement[];" + << endl + << "static const char table_name[];"; + } + + os << endl; + + // + // Functions (concrete). + // + + // persist () + // + os << "static void" << endl + << "persist (database&, " << (auto_id ? "" : "const ") << "object_type&"; + + if (poly) + os << ", bool top = true, bool dyn = true"; + + os << ");" + << endl; + + if (c.count ("bulk-persist")) + os << "static void" << endl + << "persist (database&, " << (auto_id ? "" : "const ") << + "object_type**, std::size_t, multiple_exceptions&);" + << endl; + + if (id != 0) + { + // find (id) + // + if (c.default_ctor ()) + os << "static pointer_type" << endl + << "find (database&, const id_type&);" + << endl; + + // find (id, obj) + // + os << "static bool" << endl + << "find (database&, const id_type&, object_type&"; + + if (poly) + os << ", bool dyn = true"; + + os << ");" + << endl; + + // reload () + // + os << "static bool" << endl + << "reload (database&, object_type&"; + + if (poly) + os << ", bool dyn = true"; + + os << ");" + << endl; + + // update () + // + // In case of a polymorphic object, we generate update() even if it is + // readonly since the potentially-readwrite base will rely on it to + // initialize the id image. + // + // + if (!readonly (c) || poly) + { + os << "static void" << endl + << "update (database&, const object_type&"; + + if (poly) + os << ", bool top = true, bool dyn = true"; + + os << ");" + << endl; + + if (c.count ("bulk-update")) + os << "static void" << endl + << "update (database&, const object_type**, std::size_t, " << + "multiple_exceptions&);" + << endl; + } + + // erase () + // + os << "static void" << endl + << "erase (database&, const id_type&"; + + if (poly) + os << ", bool top = true, bool dyn = true"; + + os << ");" + << endl; + + os << "static void" << endl + << "erase (database&, const object_type&"; + + if (poly) + os << ", bool top = true, bool dyn = true"; + + os << ");" + << endl; + + if (c.count ("bulk-erase")) + { + os << "static std::size_t" << endl + << "erase (database&, const id_type**, std::size_t, " << + "multiple_exceptions&);" + << endl; + + os << "static void" << endl + << "erase (database&, const object_type**, std::size_t, " << + "multiple_exceptions&);" + << endl; + } + + // Sections. + // + // We treat all polymorphic sections as (potentially) having something + // to load or to update since we cannot predict what will be added to + // them in overrides. + // + if (uss.count (user_sections::count_total | + user_sections::count_load | + (poly ? user_sections::count_load_empty : 0)) != 0) + os << "static bool" << endl + << "load (connection&, object_type&, section&" << + (poly ? ", const info_type* = 0" : "") << ");" + << endl; + + if (uss.count (user_sections::count_total | + user_sections::count_update | + (poly ? user_sections::count_update_empty : 0)) != 0) + os << "static bool" << endl + << "update (connection&, const object_type&, const section&" << + (poly ? ", const info_type* = 0" : "") << ");" + << endl; + } + + // query () + // + if (options.generate_query ()) + { + if (!options.omit_unprepared ()) + { + os << "static result<object_type>" << endl + << "query (database&, const query_base_type&);" + << endl; + + if (multi_dynamic) + os << "static result<object_type>" << endl + << "query (database&, const odb::query_base&);" + << endl; + } + + os << "static unsigned long long" << endl + << "erase_query (database&, const query_base_type&);" + << endl; + + if (multi_dynamic) + os << "static unsigned long long" << endl + << "erase_query (database&, const odb::query_base&);" + << endl; + + if (options.generate_prepared ()) + { + os << "static odb::details::shared_ptr<prepared_query_impl>" << endl + << "prepare_query (connection&, const char*, const query_base_type&);" + << endl; + + if (multi_dynamic) + os << "static odb::details::shared_ptr<prepared_query_impl>" << endl + << "prepare_query (connection&, const char*, " << + "const odb::query_base&);" + << endl; + + os << "static odb::details::shared_ptr<result_impl>" << endl + << "execute_query (prepared_query_impl&);" + << endl; + } + } + + object_public_extra_post (c); + + // Implementation details. + // + os << "public:" << endl; + + if (id != 0) + { + // Load the object image. + // + os << "static bool" << endl + << "find_ (statements_type&," << endl + << "const id_type*"; + + if (versioned) + os << "," << endl + << "const schema_version_migration&"; + + if (poly_derived && !abst) + os << "," << endl + << "std::size_t = depth"; + + os << ");" + << endl; + + // Load the rest of the object (containers, etc). Expects the id + // image in the object statements to be initialized to the object + // id. + // + os << "static void" << endl + << "load_ (statements_type&," << endl + << "object_type&," << endl + << "bool reload"; + + if (versioned) + os << "," << endl + << "const schema_version_migration&"; + + if (poly_derived) + os << "," << endl + << "std::size_t = depth"; + + os << ");" + << endl; + } + + // discriminator_ () + // + if (poly && !poly_derived) + { + os << "static void" << endl + << "discriminator_ (statements_type&," << endl + << "const id_type&," << endl + << "discriminator_type*"; + + if (opt != 0) + os << "," << endl + << "version_type* = 0"; + + os << ");" + << endl; + } + + // Load the dynamic part of the object. Depth inidicates where + // the dynamic part starts. Expects the id image in the object + // statements to be initialized to the object id. We don't need + // it if we are poly-abstract. + // + if (poly_derived && !abst) + os << "static void" << endl + << "load_ (database&, root_type&, std::size_t);" + << endl; + + // Image chain manipulation. + // + if (poly && need_image_clone && options.generate_query ()) + { + os << "static root_traits::image_type&" << endl + << "root_image (image_type&);" + << endl; + + // Note that the original image is non-const since for some databases + // the copy "steals" stuff from the original (e.g., LOB descriptors in + // Oracle). + // + os << "static image_type*" << endl + << "clone_image (image_type&);" + << endl; + + os << "static void" << endl + << "copy_image (image_type&, image_type&);" + << endl; + + os << "static void" << endl + << "free_image (image_type*);" + << endl; + } + + os << "};"; + + + // object_traits_impl< , id_common> + // + // Note that it is not generated for reuse-abstract classes. + // + if (options.default_database_specified () && + options.default_database () == db) + { + os << "template <>" << endl + << "class access::object_traits_impl< " << type << ", " << + "id_common >:" << endl + << " public access::object_traits_impl< " << type << ", " << + "id_" << db << " >" + << "{" + << "};"; + } +} + +void relational::header::class1:: +traverse_view (type& c) +{ + bool versioned (context::versioned (c)); + + string const& type (class_fq_name (c)); + size_t columns (column_count (c).total); + size_t obj_count (c.get<size_t> ("object-count")); + + os << "// " << class_name (c) << endl + << "//" << endl; + + // view_traits_impl + // + os << "template <>" << endl + << "class " << exp << "access::view_traits_impl< " << type << ", " << + "id_" << db << " >:" << endl + << " public access::view_traits< " << type << " >" + << "{" + << "public:" << endl; + + view_public_extra_pre (c); + + // For dynamic multi-database support also generate common traits + // alias (used in query aliasing). + // + if (multi_dynamic) + { + os << "typedef access::view_traits_impl< " << type << ", " << + "id_common > common_traits;" + << endl; + } + + // image_type + // + image_type_->traverse (c); + + os << "typedef " << db << "::view_statements<view_type> statements_type;" + << endl; + + // + // Query. + // + + // Generate associated object tags here unless we are generating dynamic + // multi-database support, in which case they generated in object_traits. + // + if (!multi_dynamic) + { + query_tags t; // Not customizable. + t.traverse (c); + } + + // query_base_type and query_columns (definition generated by class2). + // + os << "typedef " << db << "::query_base query_base_type;" + << "struct query_columns"; + + if (obj_count == 0) + os << "{" + << "};"; + else + os << ";" + << endl; + + os << "static const bool versioned = " << versioned << ";" + << endl; + + // + // Functions. + // + + // grow () + // + if (generate_grow) + { + os << "static bool" << endl + << "grow (image_type&," << endl + << truncated_vector; + + if (versioned) + os << "," << endl + << "const schema_version_migration&"; + + os << ")" << (columns != 0 ? ";\n" : "{}"); + } + + // bind (image_type) + // + os << "static void" << endl + << "bind (" << bind_vector << "," << endl + << "image_type&"; + + if (versioned) + os << "," << endl + << "const schema_version_migration&"; + + os << ")" << (columns != 0 ? ";\n" : "{}"); + + // init (view, image) + // + os << "static void" << endl + << "init (view_type&," << endl + << "const image_type&," << endl + << "database*"; + + if (versioned) + os << "," << endl + << "const schema_version_migration&"; + + os << ")" << (columns != 0 ? ";\n" : "{}"); + + // column_count + // + os << "static const std::size_t column_count = " << columns << "UL;" + << endl; + + // Statements. + // + view_query& vq (c.get<view_query> ("query")); + + if (vq.kind != view_query::runtime) + { + os << "static query_base_type" << endl + << "query_statement (const query_base_type&);" + << endl; + } + + // + // Functions. + // + + // query () + // + if (!options.omit_unprepared ()) + { + os << "static result<view_type>" << endl + << "query (database&, const query_base_type&);" + << endl; + + if (multi_dynamic) + os << "static result<view_type>" << endl + << "query (database&, const odb::query_base&);" + << endl; + } + + if (options.generate_prepared ()) + { + os << "static odb::details::shared_ptr<prepared_query_impl>" << endl + << "prepare_query (connection&, const char*, const query_base_type&);" + << endl; + + if (multi_dynamic) + os << "static odb::details::shared_ptr<prepared_query_impl>" << endl + << "prepare_query (connection&, const char*, " << + "const odb::query_base&);" + << endl; + + os << "static odb::details::shared_ptr<result_impl>" << endl + << "execute_query (prepared_query_impl&);" + << endl; + } + + view_public_extra_post (c); + + os << "};"; + + // view_traits_impl< , id_common> + // + if (options.default_database_specified () && + options.default_database () == db) + { + os << "template <>" << endl + << "class access::view_traits_impl< " << type << ", " << + "id_common >:" << endl + << " public access::view_traits_impl< " << type << ", " << + "id_" << db << " >" + << "{" + << "};"; + } +} + +void relational::header::class1:: +traverse_composite (type& c) +{ + bool versioned (context::versioned (c)); + + string const& type (class_fq_name (c)); + + os << "// " << class_name (c) << endl + << "//" << endl; + + // While composite_value_traits is not used directly by user code, we + // still need to export it if the generated code for the same database + // is split into several DLLs. + // + os << "template <>" << endl + << "class " << exp << "access::composite_value_traits< " << type << + ", id_" << db << " >" + << "{" + << "public:" << endl; + + // value_type + // + os << "typedef " << type << " value_type;" + << endl; + + // image_type + // + image_type_->traverse (c); + + // Containers. + // + { + instance<container_traits> t (c); + t->traverse (c); + } + + // grow () + // + if (generate_grow) + { + os << "static bool" << endl + << "grow (image_type&," << endl + << truncated_vector; + + if (versioned) + os << "," << endl + << "const schema_version_migration&"; + + os << ");" + << endl; + } + + // bind (image_type) + // + os << "static void" << endl + << "bind (" << bind_vector << "," << endl + << "image_type&," << endl + << db << "::statement_kind"; + + if (versioned) + os << "," << endl + << "const schema_version_migration&"; + + os << ");" + << endl; + + // init (image, value) + // + os << "static " << (generate_grow ? "bool" : "void") << endl + << "init (image_type&," << endl + << "const value_type&," << endl + << db << "::statement_kind"; + + if (versioned) + os << "," << endl + << "const schema_version_migration&"; + + os << ");" + << endl; + + // init (value, image) + // + os << "static void" << endl + << "init (value_type&," << endl + << "const image_type&," << endl + << "database*"; + + if (versioned) + os << "," << endl + << "const schema_version_migration&"; + + os << ");" + << endl; + + if (!has_a (c, test_container)) + { + // get_null (image) + // + os << "static bool" << endl + << "get_null (const image_type&"; + + if (versioned) + os << "," << endl + << "const schema_version_migration&"; + + os << ");" + << endl; + + // set_null (image) + // + os << "static void" << endl + << "set_null (image_type&," << endl + << db << "::statement_kind"; + + if (versioned) + os << "," << endl + << "const schema_version_migration&"; + + os << ");" + << endl; + } + + column_count_type const& cc (column_count (c)); + os << "static const std::size_t column_count = " << cc.total << "UL;"; + + os << "};"; +} + +void relational::header:: +generate () +{ + context ctx; + ostream& os (ctx.os); + + instance<include> i; + i->generate (); + + os << "namespace odb" + << "{"; + + { + traversal::unit unit; + traversal::defines unit_defines; + typedefs unit_typedefs (false); + traversal::namespace_ ns; + instance<class1> c; + + unit >> unit_defines >> ns; + unit_defines >> c; + unit >> unit_typedefs >> c; + + traversal::defines ns_defines; + typedefs ns_typedefs (false); + + ns >> ns_defines >> ns; + ns_defines >> c; + ns >> ns_typedefs >> c; + + unit.dispatch (ctx.unit); + } + + { + traversal::unit unit; + traversal::defines unit_defines; + typedefs unit_typedefs (false); + traversal::namespace_ ns; + instance<class2> c; + + unit >> unit_defines >> ns; + unit_defines >> c; + unit >> unit_typedefs >> c; + + traversal::defines ns_defines; + typedefs ns_typedefs (false); + + ns >> ns_defines >> ns; + ns_defines >> c; + ns >> ns_typedefs >> c; + + unit.dispatch (ctx.unit); + } + + os << "}"; +} diff --git a/odb/odb/relational/header.hxx b/odb/odb/relational/header.hxx new file mode 100644 index 0000000..964aff2 --- /dev/null +++ b/odb/odb/relational/header.hxx @@ -0,0 +1,1466 @@ +// file : odb/relational/header.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_RELATIONAL_HEADER_HXX +#define ODB_RELATIONAL_HEADER_HXX + +#include <odb/relational/context.hxx> +#include <odb/relational/common.hxx> + +namespace relational +{ + namespace header + { + // + // image_type + // + + struct image_member: virtual member_base + { + typedef image_member base; + + image_member (string const& var = string ()) + : member_base (var, 0, 0, string (), string ()) {} + + image_member (string const& var, + semantics::type& t, + const custom_cxx_type* ct, + string const& fq_type, + string const& key_prefix) + : member_base (var, &t, ct, fq_type, key_prefix) {} + }; + + template <typename T> + struct image_member_impl: image_member, virtual member_base_impl<T> + { + typedef image_member_impl base_impl; + + image_member_impl (base const& x) + : member_base::base (x), // virtual base + base (x), + member_image_type_ (base::type_override_, + base::custom_override_, + base::fq_type_override_, + base::key_prefix_) + { + } + + typedef typename member_base_impl<T>::member_info member_info; + + using member_base_impl<T>::container; + + virtual bool + pre (member_info& mi) + { + if (container (mi)) + return false; + + image_type = member_image_type_->image_type (mi.m); + + if (var_override_.empty ()) + os << "// " << mi.m.name () << endl + << "//" << endl; + + return true; + } + + virtual void + traverse_pointer (member_info& mi) + { + // Object pointers in views require special treatment. + // + if (view_member (mi.m)) + { + using semantics::class_; + + class_& c (*mi.ptr); + class_* poly_root (polymorphic (c)); + bool poly_derived (poly_root != 0 && poly_root != &c); + + if (poly_derived) + // Use a helper to create a complete chain of images all + // the way to the root (see libodb/odb/view-image.hxx). + // + os << "view_object_image<" << endl + << " " << class_fq_name (c) << "," << endl + << " " << class_fq_name (*poly_root) << "," << endl + << " id_" << db << " >"; + else + os << "object_traits_impl< " << class_fq_name (c) << ", " << + "id_" << db << " >::image_type"; + + os << " " << mi.var << "value;" + << endl; + } + else + member_base_impl<T>::traverse_pointer (mi); + } + + virtual void + traverse_composite (member_info& mi) + { + os << image_type << " " << mi.var << "value;" + << endl; + } + + protected: + string image_type; + instance<member_image_type> member_image_type_; + }; + + struct image_base: traversal::class_, virtual context + { + typedef image_base base; + + image_base (): first_ (true) {} + + virtual void + traverse (type& c) + { + bool obj (object (c)); + + // Ignore transient bases. Not used for views. + // + if (!(obj || composite (c))) + return; + + if (first_) + { + os << ": "; + first_ = false; + } + else + { + os << "," << endl + << " "; + } + + string const& type (class_fq_name (c)); + + if (obj) + os << "object_traits_impl< " << type << ", id_" << db << + " >::image_type"; + else + os << "composite_value_traits< " << type << ", id_" << db << + " >::image_type"; + } + + private: + bool first_; + }; + + struct image_type: traversal::class_, virtual context + { + typedef image_type base; + + image_type () + { + *this >> names_member_ >> member_; + } + + image_type (image_type const&) + : root_context (), context () //@@ -Wextra + { + *this >> names_member_ >> member_; + } + + virtual void + image_extra (type&) + { + } + + virtual void + traverse (type& c) + { + type* poly_root (polymorphic (c)); + bool poly_derived (poly_root != 0 && poly_root != &c); + + os << "struct image_type"; + + if (!view (c)) + { + // Don't go into the base if we are a derived type in a + // polymorphic hierarchy. + // + if (!poly_derived) + { + instance<image_base> b; + traversal::inherits i (*b); + inherits (c, i); + } + } + + os << "{"; + + if (poly_derived) + os << "base_traits::image_type* base;" + << endl; + + names (c); + + // We don't need a version if this is a composite value type + // or reuse-abstract object. + // + if (!(composite (c) || (abstract (c) && !polymorphic (c)))) + os << "std::size_t version;" + << endl; + + image_extra (c); + + os << "};"; + } + + private: + instance<image_member> member_; + traversal::names names_member_; + }; + + // Member-specific traits types for container members. + // + struct container_traits: object_members_base, virtual context + { + typedef container_traits base; + + container_traits (semantics::class_& c) + : object_members_base (true, false, false), c_ (c) + { + } + + virtual void + traverse_pointer (semantics::data_member&, semantics::class_&) + { + // We don't want to traverse composite id. + } + + virtual void + traverse_composite (semantics::data_member* m, semantics::class_& c) + { + if (object (c_)) + object_members_base::traverse_composite (m, c); + else + { + // If we are generating traits for a composite value type, then + // we don't want to go into its bases or it composite members. + // + if (m == 0 && &c == &c_) + names (c); + } + } + + virtual void + container_public_extra_pre (semantics::data_member&, semantics::type&) + { + } + + virtual void + container_public_extra_post (semantics::data_member&, semantics::type&) + { + } + + virtual void + traverse_container (semantics::data_member& m, semantics::type& c) + { + using semantics::type; + using semantics::class_; + + // Figure out if this member is from a base object or composite + // value and if it's from an object, whether it is reuse-abstract. + // + bool base, reuse_abst; + + if (object (c_)) + { + base = cur_object != &c_ || + !object (dynamic_cast<type&> (m.scope ())); + reuse_abst = abstract (c_) && !polymorphic (c_); + } + else + { + base = false; // We don't go into bases. + reuse_abst = true; // Always abstract. + } + + container_kind_type ck (container_kind (c)); + + const custom_cxx_type* vct (0); + const custom_cxx_type* ict (0); + const custom_cxx_type* kct (0); + + type& vt (container_vt (m, &vct)); + type* it (0); + type* kt (0); + + bool ordered (false); + bool inverse (context::inverse (m, "value")); + + switch (ck) + { + case ck_ordered: + { + if (!unordered (m)) + { + it = &container_it (m, &ict); + ordered = true; + } + break; + } + case ck_map: + case ck_multimap: + { + kt = &container_kt (m, &kct); + break; + } + case ck_set: + case ck_multiset: + { + break; + } + } + + bool smart (!inverse && + (ck != ck_ordered || ordered) && + container_smart (c)); + + string name (flat_prefix_ + public_name (m) + "_traits"); + + // Figure out column counts. + // + size_t id_columns, value_columns, data_columns, cond_columns; + bool versioned (context::versioned (m)); + + if (!reuse_abst) + { + type& idt (container_idt (m)); + + if (class_* idc = composite_wrapper (idt)) + id_columns = column_count (*idc).total; + else + id_columns = 1; + + data_columns = cond_columns = id_columns; + + switch (ck) + { + case ck_ordered: + { + // Add one for the index. + // + if (ordered) + { + data_columns++; + + if (smart) + cond_columns++; + } + break; + } + case ck_map: + case ck_multimap: + { + // Add some for the key. + // + size_t n; + + class_* ptr (object_pointer (*kt)); + semantics::type& t (ptr == 0 ? *kt : utype (*id_member (*ptr))); + + if (class_* comp = composite_wrapper (t)) + n = column_count (*comp).total; + else + n = 1; + + data_columns += n; + + // Key is not currently used (see also bind()). + // + // cond_columns += n; + + break; + } + case ck_set: + case ck_multiset: + { + // Not currently used (see also bind()) + // + // Value is also a key. + // + // class_* ptr (object_pointer (vt)); + // semantics::type& t (ptr == 0 ? vt : utype (*id_member (*ptr))); + // + // if (class_* comp = composite_wrapper (t)) + // cond_columns += column_count (*comp).total; + // else + // cond_columns++; + // + break; + } + } + + { + class_* ptr (object_pointer (vt)); + semantics::type& t (ptr == 0 ? vt : utype (*id_member (*ptr))); + + if (class_* comp = composite_wrapper (t)) + value_columns = column_count (*comp).total; + else + value_columns = 1; + + data_columns += value_columns; + } + + // Store column counts for the source generator. + // + m.set ("id-column-count", id_columns); + m.set ("value-column-count", value_columns); + m.set ("cond-column-count", cond_columns); + m.set ("data-column-count", data_columns); + } + + os << "// " << m.name () << endl + << "//" << endl + << "struct " << exp << name; + + if (base) + { + semantics::class_& b (dynamic_cast<semantics::class_&> (m.scope ())); + string const& type (class_fq_name (b)); + + if (object (b)) + os << ": access::object_traits_impl< " << type << ", id_" << + db << " >::" << name; + else + os << ": access::composite_value_traits< " << type << ", id_" << + db << " >::" << public_name (m) << "_traits"; // No prefix_. + } + + os << "{"; + + container_public_extra_pre (m, c); + + if (!reuse_abst) + { + // column_count + // + os << "static const std::size_t id_column_count = " << + id_columns << "UL;"; + + if (smart) + os << "static const std::size_t value_column_count = " << + value_columns << "UL;" + << "static const std::size_t cond_column_count = " << + cond_columns << "UL;"; + + os << "static const std::size_t data_column_count = " << + data_columns << "UL;" + << endl; + + os << "static const bool versioned = " << versioned << ";" + << endl; + + // Statements. + // + os << "static const char insert_statement[];" + << "static const char select_statement[];"; + + if (smart) + os << "static const char update_statement[];"; + + os << "static const char delete_statement[];" + << endl; + } + + if (base) + { + container_public_extra_post (m, c); + os << "};"; + + return; + } + + // container_type + // container_traits + // index_type + // key_type + // value_type + // + os << "typedef "; + + { + semantics::names* hint; + semantics::type& t (utype (m, hint)); + + if (semantics::type* wt = wrapper (t)) + { + // Use the hint from the wrapper unless the wrapped type is + // qualified. In this case use the hint for the unqualified + // type. + // + hint = t.get<semantics::names*> ("wrapper-hint"); + utype (*wt, hint); + + os << c.fq_name (hint); + } + else + // t and c are the same. + // + os << t.fq_name (hint); + } + + os << " container_type;"; + + os << "typedef" << endl + << "odb::access::container_traits<container_type>" << endl + << "container_traits_type;"; + + switch (ck) + { + case ck_ordered: + { + os << "typedef container_traits_type::index_type index_type;"; + break; + } + case ck_map: + case ck_multimap: + { + os << "typedef container_traits_type::key_type key_type;"; + } + case ck_set: + case ck_multiset: + { + break; + } + } + + os << "typedef container_traits_type::value_type value_type;" + << endl; + + // functions_type + // + switch (ck) + { + case ck_ordered: + { + os << "typedef " << (smart ? "smart_" : "") << + "ordered_functions<index_type, value_type> functions_type;"; + break; + } + case ck_map: + case ck_multimap: + { + os << "typedef map_functions<key_type, value_type> " << + "functions_type;"; + break; + } + case ck_set: + case ck_multiset: + { + os << "typedef set_functions<value_type> functions_type;"; + break; + } + } + + os << "typedef " << db << "::" << (smart ? "smart_" : "") + << "container_statements< " << name << " > statements_type;" + << endl; + + // cond_image_type (object id is taken from the object image). + // + // For dumb containers we use the id binding directly. + // + if (smart) + { + os << "struct cond_image_type" + << "{"; + + switch (ck) + { + case ck_ordered: + { + if (ordered) + { + os << "// index" << endl + << "//" << endl; + instance<image_member> im ( + "index_", *it, ict, "index_type", "index"); + im->traverse (m); + } + break; + } + case ck_map: + case ck_multimap: + { + os << "// key" << endl + << "//" << endl; + instance<image_member> im ("key_", *kt, kct, "key_type", "key"); + im->traverse (m); + break; + } + case ck_set: + case ck_multiset: + { + os << "// value" << endl + << "//" << endl; + instance<image_member> im ( + "value_", vt, vct, "value_type", "value"); + im->traverse (m); + break; + } + } + + os << "std::size_t version;" + << "};"; + } + + // data_image_type (object id is taken from the object image) + // + os << "struct data_image_type" + << "{"; + + switch (ck) + { + case ck_ordered: + { + if (ordered) + { + os << "// index" << endl + << "//" << endl; + instance<image_member> im ( + "index_", *it, ict, "index_type", "index"); + im->traverse (m); + } + break; + } + case ck_map: + case ck_multimap: + { + os << "// key" << endl + << "//" << endl; + instance<image_member> im ("key_", *kt, kct, "key_type", "key"); + im->traverse (m); + break; + } + case ck_set: + case ck_multiset: + { + break; + } + } + + os << "// value" << endl + << "//" << endl; + instance<image_member> im ("value_", vt, vct, "value_type", "value"); + im->traverse (m); + + os << "std::size_t version;" + << "};"; + + // bind (cond_image) + // + if (smart) + os << "static void" << endl + << "bind (" << bind_vector << "," << endl + << "const " << bind_vector << " id," << endl + << "std::size_t id_size," << endl + << "cond_image_type&);" + << endl; + + // bind (data_image) + // + os << "static void" << endl + << "bind (" << bind_vector << "," << endl + << "const " << bind_vector << " id," << endl + << "std::size_t id_size," << endl + << "data_image_type&"; + + if (versioned) + os << "," << endl + << "const schema_version_migration&"; + + os << ");" + << endl; + + // bind (cond_image, data_image) (update) + // + if (smart) + { + os << "static void" << endl + << "bind (" << bind_vector << "," << endl + << "const " << bind_vector << " id," << endl + << "std::size_t id_size," << endl + << "cond_image_type&," << endl + << "data_image_type&"; + + if (versioned) + os << "," << endl + << "const schema_version_migration&"; + + os << ");" + << endl; + } + + // grow () + // + if (generate_grow) + { + os << "static void" << endl + << "grow (data_image_type&," << endl + << truncated_vector; + + if (versioned) + os << "," << endl + << "const schema_version_migration&"; + + os << ");" + << endl; + } + + // init (data_image) + // + if (!inverse) + { + os << "static void" << endl + << "init (data_image_type&," << endl; + + switch (ck) + { + case ck_ordered: + { + if (ordered) + os << "index_type*," << endl; + break; + } + case ck_map: + case ck_multimap: + { + os << "const key_type*," << endl; + break; + } + case ck_set: + case ck_multiset: + break; + } + + os << "const value_type&"; + + if (versioned) + os << "," << endl + << "const schema_version_migration&"; + + os << ");" + << endl; + } + + // init (cond_image) + // + if (smart) + { + os << "static void" << endl; + + switch (ck) + { + case ck_ordered: + { + os << "init (cond_image_type&, index_type);"; + break; + } + case ck_map: + case ck_multimap: + { + // os << "init (data_image_type&, const key_type&);"; + break; + } + case ck_set: + case ck_multiset: + { + // os << "init (data_image_type&, const value_type&);"; + break; + } + } + + os << endl; + } + + // init (data) + // + os << "static void" << endl + << "init ("; + + switch (ck) + { + case ck_ordered: + { + if (ordered) + os << "index_type&," << endl; + break; + } + case ck_map: + case ck_multimap: + { + os << "key_type&," << endl; + break; + } + case ck_set: + case ck_multiset: + break; + } + + os << "value_type&," << endl; + os << "const data_image_type&," << endl + << "database*"; + + if (versioned) + os << "," << endl + << "const schema_version_migration&"; + + os << ");" + << endl; + + // insert + // + os << "static void" << endl; + + switch (ck) + { + case ck_ordered: + { + os << "insert (index_type, const value_type&, void*);"; + break; + } + case ck_map: + case ck_multimap: + { + os << "insert (const key_type&, const value_type&, void*);"; + break; + } + case ck_set: + case ck_multiset: + { + os << "insert (const value_type&, void*);"; + break; + } + } + + os << endl; + + // select + // + os << "static bool" << endl; + + switch (ck) + { + case ck_ordered: + { + os << "select (index_type&, value_type&, void*);"; + break; + } + case ck_map: + case ck_multimap: + { + os << "select (key_type&, value_type&, void*);"; + break; + } + case ck_set: + case ck_multiset: + { + os << "select (value_type&, void*);"; + break; + } + } + + os << endl; + + // update + // + if (smart) + { + os << "static void" << endl; + + switch (ck) + { + case ck_ordered: + { + os << "update (index_type, const value_type&, void*);"; + break; + } + case ck_map: + case ck_multimap: + { + //os << "update (const key_type&, const value_type&, void*);"; + break; + } + case ck_set: + case ck_multiset: + { + //os << "update (const value_type&, const value_type&, void*);"; + break; + } + } + + os << endl; + } + + // delete_ + // + os << "static void" << endl + << "delete_ ("; + + if (smart) + { + switch (ck) + { + case ck_ordered: + { + os << "index_type, "; + break; + } + case ck_map: + case ck_multimap: + { + break; + } + case ck_set: + case ck_multiset: + { + break; + } + } + } + + os << "void*);" + << endl; + + // persist + // + if (!inverse) + { + os << "static void" << endl + << "persist (const container_type&," << endl + << "statements_type&"; + + if (versioned) + os << "," << endl + << "const schema_version_migration&"; + + os << ");" + << endl; + } + + // load + // + os << "static void" << endl + << "load (container_type&," << endl + << "statements_type&"; + + if (versioned) + os << "," << endl + << "const schema_version_migration&"; + + os << ");" + << endl; + + // update + // + if (!(inverse || readonly (member_path_, member_scope_))) + { + os << "static void" << endl + << "update (const container_type&," << endl + << "statements_type&"; + + if (versioned) + os << "," << endl + << "const schema_version_migration&"; + + os << ");" + << endl; + } + + // erase + // + if (!inverse) + { + os << "static void" << endl + << "erase ("; + + if (smart) + os << "const container_type*, "; + + os << "statements_type&);" + << endl; + } + + container_public_extra_post (m, c); + + os << "};"; + } + + protected: + semantics::class_& c_; + }; + + // + // + struct section_traits: virtual context + { + typedef section_traits base; + + section_traits (semantics::class_& c): c_ (c) {} + + virtual void + section_public_extra_pre (user_section&) + { + } + + virtual void + section_public_extra_post (user_section&) + { + } + + virtual void + traverse (user_section& s) + { + semantics::class_* poly_root (polymorphic (c_)); + bool poly (poly_root != 0); + bool poly_derived (poly && poly_root != &c_); + + semantics::data_member* opt (optimistic (c_)); + + // Treat the special version update sections as abstract in reuse + // inheritance. + // + bool reuse_abst (!poly && + (abstract (c_) || + s.special == user_section::special_version)); + + bool load (s.total != 0 && s.separate_load ()); + bool load_con (s.containers && s.separate_load ()); + bool load_opt (s.optimistic () && s.separate_load ()); + + bool update (s.total != s.inverse + s.readonly); // Always separate. + bool update_con (s.readwrite_containers); + bool update_opt (s.optimistic () && (s.readwrite_containers || poly)); + + // Don't generate anything for empty sections. + // + if (!(load || load_con || load_opt || + update || update_con || update_opt)) + return; + + // If we are adding a new section to a derived class in an optimistic + // hierarchy, then pretend it inherits from the special version update + // section. + // + user_section* rs (0); + if (opt != 0) + { + // Skip overrides and get to the new section if polymorphic. + // + for (rs = &s; poly && rs->base != 0; rs = rs->base) ; + + if (rs != 0) + { + if (rs->object != &opt->scope ()) + rs->base = &(poly ? poly_root : &opt->scope ())-> + get<user_sections> ("user-sections").back (); + else + rs = 0; + } + } + + string name (public_name (*s.member) + "_traits"); + + os << "// " << s.member->name () << endl + << "//" << endl + << "struct " << exp << name + << "{"; + + os << "typedef object_traits_impl<object_type, id_" << db << + ">::image_type image_type;" + << "typedef object_traits_impl<object_type, id_" << db << + ">::id_image_type id_image_type;" + << endl; + + section_public_extra_pre (s); + + // bind (id, image_type) + // + // If id is NULL, then id is ignored (select). Otherwise, it is + // copied at the end (update). + // + if (load || load_opt || update || update_opt) + { + os << "static std::size_t" << endl + << "bind (" << bind_vector << "," << endl + << "const " << bind_vector << " id," << endl + << "std::size_t id_size," << endl + << "image_type&," << endl + << db << "::statement_kind"; + + if (s.versioned) + os << "," << endl + << "const schema_version_migration&"; + + os << ");" + << endl; + } + + // grow () + // + // We have to have out own version because the truncated vector + // will have different number of elements. + // + if (generate_grow && (load || load_opt)) + { + os << "static bool" << endl + << "grow (image_type&," << endl + << truncated_vector; + + if (s.versioned) + os << "," << endl + << "const schema_version_migration&"; + + os << ");" + << endl; + } + + // init (object, image) + // + if (load) + { + os << "static void" << endl + << "init (object_type&," << endl + << "const image_type&," << endl + << "database*"; + + if (s.versioned) + os << "," << endl + << "const schema_version_migration&"; + + os << ");" + << endl; + } + + // init (image, object) + // + if (update) + { + os << "static " << (generate_grow ? "bool" : "void") << endl + << "init (image_type&," << endl + << "const object_type&"; + + if (s.versioned) + os << "," << endl + << "const schema_version_migration&"; + + os << ");" + << endl; + } + + // The rest does not apply to reuse-abstract sections. + // + if (reuse_abst) + { + section_public_extra_post (s); + os << "};"; + return; + } + + // column_count + // + column_count_type const& cc (column_count (poly ? *poly_root : c_)); + + // Generate load and update column counts even when they are zero so + // that we can instantiate section_statements. + // + os << "static const std::size_t id_column_count = " << cc.id << "UL;"; + + os << "static const std::size_t managed_optimistic_load_column_count" << + " = " << cc.optimistic_managed << "UL;" + << "static const std::size_t load_column_count = " << + (load ? s.total_total () : 0) << "UL;"; + + os << "static const std::size_t managed_optimistic_update_column_count" << + " = " << (poly_derived ? 0 : cc.optimistic_managed) << "UL;" + << "static const std::size_t update_column_count = " << + (update ? s.total - s.inverse - s.readonly : 0) << "UL;" + << endl; + + os << "static const bool versioned = " << s.versioned << ";" + << endl; + + // Statements. + // + if (load || load_opt) + os << "static const char select_statement[];" + << endl; + + if (update || update_opt) + os << "static const char update_statement[];" + << endl; + + // Section statements. + // + if (load || load_opt || update || update_opt) + os << "typedef " << db << "::section_statements< object_type, " << + name << " > statements_type;" + << endl; + + // We pass statement cache instead of just statements because + // we may also need statements for containers. + // + + // load () + // + if (load || load_opt || load_con) + os << "static void" << endl + << "load (extra_statement_cache_type&, object_type&" << + (poly ? ", bool top = true" : "") << ");" + << endl; + + // update () + // + if (update || update_opt || update_con) + os << "static void" << endl + << "update (extra_statement_cache_type&, const object_type&" << + (poly_derived && s.base != 0 ? ", bool base = true" : "") << ");" + << endl; + + section_public_extra_post (s); + + os << "};"; + + if (rs != 0) + rs->base = 0; + } + + protected: + semantics::class_& c_; + }; + + // First pass over objects, views, and composites. Some code must be + // split into two parts to deal with yet undefined types. + // + struct class1: traversal::class_, virtual context + { + typedef class1 base; + + class1 () + : typedefs_ (false), + id_image_member_ ("id_"), + version_image_member_ ("version_"), + discriminator_image_member_ ("discriminator_"), + query_columns_type_ (false, true, false), + pointer_query_columns_type_ (true, true, false) + { + *this >> defines_ >> *this; + *this >> typedefs_ >> *this; + } + + class1 (class1 const&) + : root_context (), //@@ -Wextra + context (), + typedefs_ (false), + id_image_member_ ("id_"), + version_image_member_ ("version_"), + discriminator_image_member_ ("discriminator_"), + query_columns_type_ (false, true, false), + pointer_query_columns_type_ (true, true, false) + { + *this >> defines_ >> *this; + *this >> typedefs_ >> *this; + } + + virtual void + traverse (type& c) + { + class_kind_type ck (class_kind (c)); + + if (ck == class_other || + (!options.at_once () && class_file (c) != unit.file ())) + return; + + names (c); + + switch (ck) + { + case class_object: traverse_object (c); break; + case class_view: traverse_view (c); break; + case class_composite: traverse_composite (c); break; + default: break; + } + } + + virtual void + object_public_extra_pre (type&) + { + } + + virtual void + object_public_extra_post (type&) + { + } + + virtual void + traverse_object (type&); + + virtual void + view_public_extra_pre (type&) + { + } + + virtual void + view_public_extra_post (type&) + { + } + + virtual void + traverse_view (type&); + + virtual void + traverse_composite (type&); + + private: + traversal::defines defines_; + typedefs typedefs_; + + instance<image_type> image_type_; + instance<image_member> id_image_member_; + instance<image_member> version_image_member_; + instance<image_member> discriminator_image_member_; + + instance<query_columns_type> query_columns_type_; + instance<query_columns_type> pointer_query_columns_type_; + }; + + // Second pass over objects, views, and composites. + // + struct class2: traversal::class_, virtual context + { + typedef class2 base; + + class2 () + : typedefs_ (false), + query_columns_type_ (false, true, false), + query_columns_type_inst_ (false, false, true), + view_query_columns_type_ (true) + { + *this >> defines_ >> *this; + *this >> typedefs_ >> *this; + } + + class2 (class2 const&) + : root_context (), //@@ -Wextra + context (), + typedefs_ (false), + query_columns_type_ (false, true, false), + query_columns_type_inst_ (false, false, true), + view_query_columns_type_ (true) + { + *this >> defines_ >> *this; + *this >> typedefs_ >> *this; + } + + virtual void + traverse (type& c) + { + class_kind_type ck (class_kind (c)); + + if (ck == class_other || + (!options.at_once () && class_file (c) != unit.file ())) + return; + + names (c); + + switch (ck) + { + case class_object: traverse_object (c); break; + case class_view: traverse_view (c); break; + case class_composite: traverse_composite (c); break; + default: break; + } + } + + virtual void + traverse_object (type& c) + { + if (options.generate_query ()) + { + os << "// " << class_name (c) << endl + << "//" << endl; + + // query_columns + // + // If we don't have any pointers, then query_columns is generated + // in pass 1 (see the comment in class1 for details). + // + if (has_a (c, test_pointer | include_base)) + query_columns_type_->traverse (c); + + // Generate extern template declarations. + // + if (multi_dynamic) + query_columns_type_inst_->traverse (c); + } + + // Move header comment out of if-block if adding any code here. + } + + virtual void + traverse_view (type& c) + { + // query_columns + // + if (c.get<size_t> ("object-count") != 0) + { + os << "// " << class_name (c) << endl + << "//" << endl; + + view_query_columns_type_->traverse (c); + } + + // Move header comment out of if-block if adding any code here. + } + + virtual void + traverse_composite (type&) + { + } + + private: + traversal::defines defines_; + typedefs typedefs_; + + instance<query_columns_type> query_columns_type_; + instance<query_columns_type> query_columns_type_inst_; + instance<view_query_columns_type> view_query_columns_type_; + }; + + struct include: virtual context + { + typedef include base; + + virtual void + generate () + { + os << "#include <odb/details/buffer.hxx>" << endl + << endl; + + os << "#include <odb/" << db << "/version.hxx>" << endl + << "#include <odb/" << db << "/forward.hxx>" << endl + << "#include <odb/" << db << "/binding.hxx>" << endl + << "#include <odb/" << db << "/" << db << "-types.hxx>" << endl; + + if (options.generate_query ()) + { + os << "#include <odb/" << db << "/query.hxx>" << endl; + + if (multi_dynamic) + os << "#include <odb/" << db << "/query-dynamic.hxx>" << endl; + } + + os << endl; + } + }; + } +} + +#endif // ODB_RELATIONAL_HEADER_HXX diff --git a/odb/odb/relational/inline.cxx b/odb/odb/relational/inline.cxx new file mode 100644 index 0000000..5e60705 --- /dev/null +++ b/odb/odb/relational/inline.cxx @@ -0,0 +1,47 @@ +// file : odb/relational/inline.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <odb/relational/inline.hxx> +#include <odb/relational/generate.hxx> + +using namespace std; + +namespace relational +{ + namespace inline_ + { + void + generate () + { + context ctx; + ostream& os (ctx.os); + + instance<include> i; + i->generate (); + + traversal::unit unit; + traversal::defines unit_defines; + typedefs unit_typedefs (false); + traversal::namespace_ ns; + class_ c; + + unit >> unit_defines >> ns; + unit_defines >> c; + unit >> unit_typedefs >> c; + + traversal::defines ns_defines; + typedefs ns_typedefs (false); + + ns >> ns_defines >> ns; + ns_defines >> c; + ns >> ns_typedefs >> c; + + os << "namespace odb" + << "{"; + + unit.dispatch (ctx.unit); + + os << "}"; + } + } +} diff --git a/odb/odb/relational/inline.hxx b/odb/odb/relational/inline.hxx new file mode 100644 index 0000000..a609cc1 --- /dev/null +++ b/odb/odb/relational/inline.hxx @@ -0,0 +1,693 @@ +// file : odb/relational/inline.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_RELATIONAL_INLINE_HXX +#define ODB_RELATIONAL_INLINE_HXX + +#include <odb/diagnostics.hxx> +#include <odb/relational/context.hxx> +#include <odb/relational/common.hxx> + +namespace relational +{ + namespace inline_ + { + // + // get/set null (composite value only) + // + + struct null_member: virtual member_base + { + typedef null_member base; + + null_member (bool get) + : member_base (0, 0, string (), string ()), get_ (get) {} + + protected: + bool get_; + }; + + template <typename T> + struct null_member_impl: null_member, virtual member_base_impl<T> + { + typedef null_member_impl base_impl; + + null_member_impl (base const& x): base (x) {} + + typedef typename member_base_impl<T>::member_info member_info; + + virtual bool + pre (member_info& mi) + { + // If the member is soft- added or deleted, check the version. + // + unsigned long long av (added (mi.m)); + unsigned long long dv (deleted (mi.m)); + if (av != 0 || dv != 0) + { + os << "if ("; + + if (av != 0) + os << "svm >= schema_version_migration (" << av << "ULL, true)"; + + if (av != 0 && dv != 0) + os << " &&" << endl; + + if (dv != 0) + os << "svm <= schema_version_migration (" << dv << "ULL, true)"; + + os << ")" + << "{"; + } + + // If the whole value type is readonly, then set will never be + // called with sk == statement_update. + // + if (!get_ && !readonly (*context::top_object)) + { + semantics::class_* c; + + if (readonly (mi.m) || ((c = composite (mi.t)) && readonly (*c))) + os << "if (sk == statement_insert)" << endl; + } + + return true; + } + + virtual void + post (member_info& mi) + { + if (added (mi.m) || deleted (mi.m)) + os << "}"; + } + + virtual void + traverse_composite (member_info& mi) + { + string traits ("composite_value_traits< " + mi.fq_type () + ", id_" + + db.string () + " >"); + + if (get_) + os << "r = r && " << traits << "::get_null (" << + "i." << mi.var << "value"; + else + os << traits << "::set_null (i." << mi.var << "value, sk"; + + if (versioned (*composite (mi.t))) + os << ", svm"; + + os << ");"; + } + }; + + struct null_base: traversal::class_, virtual context + { + typedef null_base base; + + null_base (bool get): get_ (get) {} + + virtual void + traverse (type& c) + { + // Ignore transient bases. + // + if (!composite (c)) + return; + + string traits ("composite_value_traits< " + class_fq_name (c) + + ", id_" + db.string () + " >"); + + // If the derived value type is readonly, then set will never be + // called with sk == statement_update. + // + if (!get_ && readonly (c) && !readonly (*context::top_object)) + os << "if (sk == statement_insert)" << endl; + + if (get_) + os << "r = r && " << traits << "::get_null (i"; + else + os << traits << "::set_null (i, sk"; + + if (versioned (c)) + os << ", svm"; + + os << ");"; + } + + protected: + bool get_; + }; + + // + // + struct class_: traversal::class_, virtual context + { + typedef class_ base; + + class_ () + : typedefs_ (false), + get_null_base_ (true), + get_null_member_ (true), + set_null_base_ (false), + set_null_member_ (false) + { + init (); + } + + class_ (class_ const&) + : root_context (), //@@ -Wextra + context (), + typedefs_ (false), + get_null_base_ (true), + get_null_member_ (true), + set_null_base_ (false), + set_null_member_ (false) + { + init (); + } + + void + init () + { + *this >> defines_ >> *this; + *this >> typedefs_ >> *this; + + get_null_base_inherits_ >> get_null_base_; + get_null_member_names_ >> get_null_member_; + + set_null_base_inherits_ >> set_null_base_; + set_null_member_names_ >> set_null_member_; + } + + virtual void + traverse (type& c) + { + class_kind_type ck (class_kind (c)); + + if (ck == class_other || + (!options.at_once () && class_file (c) != unit.file ())) + return; + + names (c); + + context::top_object = context::cur_object = &c; + + switch (ck) + { + case class_object: traverse_object (c); break; + case class_view: traverse_view (c); break; + case class_composite: traverse_composite (c); break; + default: break; + } + + context::top_object = context::cur_object = 0; + } + + virtual void + object_extra (type&) + { + } + + virtual void + traverse_object (type& c) + { + using semantics::data_member; + + data_member_path* id (id_member (c)); + data_member* idf (id ? id->front () : 0); + bool auto_id (id && auto_ (*id)); + bool base_id (id && &idf->scope () != &c); // Comes from base. + data_member* optimistic (context::optimistic (c)); + + // Base class that contains the object id and version for optimistic + // concurrency. + // + type* base (base_id ? dynamic_cast<type*> (&idf->scope ()) : 0); + + type* poly_root (context::polymorphic (c)); + bool poly (poly_root != 0); + bool poly_derived (poly && poly_root != &c); + + bool abst (abstract (c)); + bool reuse_abst (abst && !poly); + + bool versioned (context::versioned (c)); + + string const& type (class_fq_name (c)); + string traits ("access::object_traits_impl< " + type + ", id_" + + db.string () + " >"); + + user_sections& uss (c.get<user_sections> ("user-sections")); + + os << "// " << class_name (c) << endl + << "//" << endl + << endl; + + object_extra (c); + + if (id != 0 && base_id) + { + if (!poly_derived) + { + // id (id_image_type) + // + if (auto_id) + { + os << "inline" << endl + << traits << "::id_type" << endl + << traits << "::" << endl + << "id (const id_image_type& i)" + << "{" + << "return object_traits_impl< " << class_fq_name (*base) << + ", id_" << db << " >::id (i);" + << "}"; + } + + // id (image_type) + // + if (options.generate_query ()) + { + os << "inline" << endl + << traits << "::id_type" << endl + << traits << "::" << endl + << "id (const image_type& i)" + << "{" + << "return object_traits_impl< " << class_fq_name (*base) << + ", id_" << db << " >::id (i);" + << "}"; + } + + // version (image_type) + // + if (optimistic != 0) + { + os << "inline" << endl + << traits << "::version_type" << endl + << traits << "::" << endl + << "version (const image_type& i)" + << "{" + << "return object_traits_impl< " << class_fq_name (*base) << + ", id_" << db << " >::version (i);" + << "}"; + } + } + + // bind (id_image_type) + // + os << "inline" << endl + << "void " << traits << "::" << endl + << "bind (" << bind_vector << " b, id_image_type& i" << + (optimistic != 0 ? ", bool bv" : "") << ")" + << "{" + << "object_traits_impl< " << class_fq_name (*base) << ", id_" << + db << " >::bind (b, i" << (optimistic != 0 ? ", bv" : "") << ");" + << "}"; + + os << "inline" << endl + << "void " << traits << "::" << endl + << "init (id_image_type& i, const id_type& id" << + (optimistic != 0 ? ", const version_type* v" : "") << ")" + << "{" + << "object_traits_impl< " << class_fq_name (*base) << ", id_" << + db << " >::init (i, id" << (optimistic != 0 ? ", v" : "") << ");" + << "}"; + } + + if (poly_derived) + { + size_t depth (polymorphic_depth (c)); + + // check_version + // + os << "inline" << endl + << "bool " << traits << "::" << endl + << "check_version (const std::size_t* v, const image_type& i)" + << "{" + << "return "; + + string image ("i."); + for (size_t i (0); i < depth; ++i) + { + os << (i == 0 ? "" : " ||") << endl + << " v[" << i << "UL] != " << image << "version"; + + image += "base->"; + } + + os << ";" + << "}"; + + // update_version + // + os << "inline" << endl + << "void " << traits << "::" << endl + << "update_version (std::size_t* v, const image_type& i, " << + db << "::binding* b)" + << "{"; + + image = "i."; + for (size_t i (0); i < depth; ++i) + { + os << "v[" << i << "UL] = " << image << "version;"; + image += "base->"; + } + + // A poly-abstract class always has only one entry in the + // bindings array. + // + if (abst) + os << "b[0].version++;"; + else + for (size_t i (0); i < depth; ++i) + os << "b[" << i << "UL].version++;"; + + os << "}"; + } + + // The rest does not apply to reuse-abstract objects. + // + if (reuse_abst) + return; + + // erase (object_type) + // + if (id != 0 && !poly && optimistic == 0 && + !has_a (c, test_smart_container)) + { + os << "inline" << endl + << "void " << traits << "::" << endl + << "erase (database& db, const object_type& obj)" + << "{" + << "callback (db, obj, callback_event::pre_erase);" + << "erase (db, id (obj));"; + + // Note that we don't reset sections since the object is now + // transient and the state of a section in a transient object + // is undefined. + + os << "callback (db, obj, callback_event::post_erase);" + << "}"; + } + + // load (section) [thunk version; poly_derived is true] + // + if (uss.count (user_sections::count_total | + user_sections::count_load | + (poly ? user_sections::count_load_empty : 0)) != 0 && + uss.count (user_sections::count_new | + user_sections::count_load | + (poly ? user_sections::count_load_empty : 0)) == 0) + { + os << "inline" << endl + << "bool " << traits << "::" << endl + << "load (connection& conn, object_type& obj, section& s, " << + "const info_type* pi)" + << "{" + << "return base_traits::load (conn, obj, s, pi);" + << "}"; + } + + // update (section) [thunk version; poly_derived is true] + // + if (uss.count (user_sections::count_total | + user_sections::count_update | + (poly ? user_sections::count_update_empty : 0)) != 0 && + uss.count (user_sections::count_new | + user_sections::count_update | + (poly ? user_sections::count_update_empty : 0)) == 0) + { + os << "inline" << endl + << "bool " << traits << "::" << endl + << "update (connection& conn, const object_type& obj, " << + "const section& s, const info_type* pi)" + << "{" + << "return base_traits::update (conn, obj, s, pi);" + << "}"; + } + + // load_() + // + if (id != 0 && + !(poly_derived || + has_a (c, test_container | include_eager_load, &main_section) || + uss.count (user_sections::count_new | + user_sections::count_load | + (poly ? user_sections::count_load_empty : 0)) != 0)) + { + os << "inline" << endl + << "void " << traits << "::" << endl + << "load_ (statements_type& sts," << endl + << "object_type& obj," << endl + << "bool"; + + if (versioned) + os << "," << endl + << "const schema_version_migration& svm"; + + os << ")" + << "{" + << "ODB_POTENTIALLY_UNUSED (sts);" + << "ODB_POTENTIALLY_UNUSED (obj);"; + + if (versioned) + os << "ODB_POTENTIALLY_UNUSED (svm);"; + + os << endl; + + // Mark eager sections as loaded. + // + for (user_sections::iterator i (uss.begin ()); i != uss.end (); ++i) + { + // Skip special sections. + // + if (i->special == user_section::special_version) + continue; + + data_member& m (*i->member); + + // If the section is soft- added or deleted, check the version. + // We can only end up here if the object itself is versioned + // (simple value section). + // + unsigned long long av (added (m)); + unsigned long long dv (deleted (m)); + if (av != 0 || dv != 0) + { + os << "if ("; + + if (av != 0) + os << "svm >= schema_version_migration (" << av << "ULL, true)"; + + if (av != 0 && dv != 0) + os << " &&" << endl; + + if (dv != 0) + os << "svm <= schema_version_migration (" << dv << "ULL, true)"; + + os << ")" << endl; + } + + // Section access is always by reference. + // + member_access& ma (m.get<member_access> ("get")); + if (!ma.synthesized) + os << "// From " << location_string (ma.loc, true) << endl; + + os << ma.translate ("obj") << ".reset (true, false);" + << endl; + } + + os << "}"; + } + + if (poly && need_image_clone && options.generate_query ()) + { + // root_image () + // + os << "inline" << endl + << traits << "::root_traits::image_type&" << endl + << traits << "::" << endl + << "root_image (image_type& i)" + << "{"; + + if (poly_derived) + os << "return base_traits::root_image (*i.base);"; + else + os << "return i;"; + + os << "}"; + + // clone_image () + // + os << "inline" << endl + << traits << "::image_type*" << endl + << traits << "::" << endl + << "clone_image (image_type& i)" + << "{"; + + if (poly_derived) + os << "details::unique_ptr<base_traits::image_type> p (" << endl + << "base_traits::clone_image (*i.base));" + << "image_type* c (new image_type (i));" + << "c->base = p.release ();" + << "return c;"; + else + os << "return new image_type (i);"; + + os << "}"; + + // copy_image () + // + os << "inline" << endl + << "void " << traits << "::" << endl + << "copy_image (image_type& d, image_type& s)" + << "{"; + + if (poly_derived) + os << "base_traits::image_type* b (d.base);" + << "base_traits::copy_image (*b, *s.base);" + << "d = s;" // Overwrites the base pointer. + << "d.base = b;"; + else + os << "d = s;"; + + os << "}"; + + // free_image () + // + os << "inline" << endl + << "void " << traits << "::" << endl + << "free_image (image_type* i)" + << "{"; + + if (poly_derived) + os << "base_traits::free_image (i->base);"; + + os << "delete i;" + << "}"; + } + } + + virtual void + view_extra (type&) + { + } + + virtual void + traverse_view (type& c) + { + string const& type (class_fq_name (c)); + string traits ("access::view_traits_impl< " + type + ", id_" + + db.string () + " >"); + + os << "// " << class_name (c) << endl + << "//" << endl + << endl; + + view_extra (c); + } + + virtual void + traverse_composite (type& c) + { + bool versioned (context::versioned (c)); + + string const& type (class_fq_name (c)); + string traits ("access::composite_value_traits< " + type + ", id_" + + db.string () + " >"); + + os << "// " << class_name (c) << endl + << "//" << endl + << endl; + + if (!has_a (c, test_container)) + { + // get_null (image) + // + os << "inline" << endl + << "bool " << traits << "::" << endl + << "get_null (const image_type& i"; + + if (versioned) + os << "," << endl + << "const schema_version_migration& svm"; + + os << ")" + << "{"; + + if (versioned) + os << "ODB_POTENTIALLY_UNUSED (svm);" + << endl; + + os << "bool r (true);"; + + inherits (c, get_null_base_inherits_); + names (c, get_null_member_names_); + + os << "return r;" + << "}"; + + // set_null (image) + // + os << "inline" << endl + << "void " << traits << "::" << endl + << "set_null (image_type& i," << endl + << db << "::statement_kind sk"; + + if (versioned) + os << "," << endl + << "const schema_version_migration& svm"; + + os << ")" + << "{" + << "ODB_POTENTIALLY_UNUSED (sk);"; + + if (versioned) + os << "ODB_POTENTIALLY_UNUSED (svm);"; + + os << endl + << "using namespace " << db << ";" + << endl; + + inherits (c, set_null_base_inherits_); + names (c, set_null_member_names_); + + os << "}"; + } + } + + private: + traversal::defines defines_; + typedefs typedefs_; + + instance<null_base> get_null_base_; + traversal::inherits get_null_base_inherits_; + instance<null_member> get_null_member_; + traversal::names get_null_member_names_; + + instance<null_base> set_null_base_; + traversal::inherits set_null_base_inherits_; + instance<null_member> set_null_member_; + traversal::names set_null_member_names_; + }; + + struct include: virtual context + { + typedef include base; + + virtual void + generate () + { + if (versioned ()) + os << "#include <odb/schema-version.hxx>" << endl + << endl; + + if (features.polymorphic_object && options.generate_query ()) + os << "#include <odb/details/unique-ptr.hxx>" << endl + << endl; + } + }; + } +} + +#endif // ODB_RELATIONAL_INLINE_HXX diff --git a/odb/odb/relational/model.cxx b/odb/odb/relational/model.cxx new file mode 100644 index 0000000..45d555a --- /dev/null +++ b/odb/odb/relational/model.cxx @@ -0,0 +1,121 @@ +// file : odb/relational/model.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <odb/diagnostics.hxx> + +#include <odb/relational/model.hxx> +#include <odb/relational/generate.hxx> + +using namespace std; + +namespace relational +{ + namespace model + { + // object_columns + // + string object_columns:: + default_ (semantics::data_member& m) + { + default_value* dv (0); + + semantics::type& t (utype (m)); + + if (m.count ("default")) + dv = &m.get<default_value> ("default"); + else if (t.count ("default")) + dv = &t.get<default_value> ("default"); + else + return ""; // No default value for this column. + + switch (dv->kind) + { + case default_value::reset: + { + return ""; // No default value. + } + case default_value::null: + { + return default_null (m); + } + case default_value::boolean: + { + return default_bool (m, dv->literal == "true"); + } + case default_value::integer: + { + return default_integer (m, dv->int_value, dv->literal == "-"); + } + case default_value::floating: + { + return default_float (m, dv->float_value); + } + case default_value::string: + { + return default_string (m, dv->literal); + } + case default_value::enumerator: + { + return default_enum (m, dv->enum_value, dv->literal); + } + } + + return ""; + } + + cutl::shared_ptr<sema_rel::model> + generate () + { + context ctx; + cutl::shared_ptr<sema_rel::model> m ( + new (shared) sema_rel::model ( + ctx.versioned () ? ctx.version ().current : 0)); + m->set ("deleted-map", deleted_table_map ()); + + traversal::unit unit; + traversal::defines unit_defines; + typedefs unit_typedefs (false); + traversal::namespace_ ns; + instance<class_> c (*m); + + unit >> unit_defines >> ns; + unit_defines >> c; + unit >> unit_typedefs >> c; + + traversal::defines ns_defines; + typedefs ns_typedefs (false); + + ns >> ns_defines >> ns; + ns_defines >> c; + ns >> ns_typedefs >> c; + + try + { + unit.dispatch (ctx.unit); + } + catch (sema_rel::duplicate_name const& e) + { + location const& o (e.orig.get<location> ("cxx-location")); + location const& d (e.dup.get<location> ("cxx-location")); + + error (d) << e.dup.kind () << " name '" << e.name << "' conflicts " + << "with an already defined " << e.orig.kind () << " name" + << endl; + + info (o) << "conflicting " << e.orig.kind () << " is defined here" + << endl; + + if (e.dup.kind () == "index") + info (d) << "use #pragma db index to change its name" << endl; + else if (e.dup.kind () == "table") + info (d) << "use #pragma db table to change its name" << endl; + else + info (d) << "use #pragma db column to change its name" << endl; + + throw operation_failed (); + } + + return m; + } + } +} diff --git a/odb/odb/relational/model.hxx b/odb/odb/relational/model.hxx new file mode 100644 index 0000000..fdfa8fd --- /dev/null +++ b/odb/odb/relational/model.hxx @@ -0,0 +1,868 @@ +// file : odb/relational/model.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_RELATIONAL_MODEL_HXX +#define ODB_RELATIONAL_MODEL_HXX + +#include <map> +#include <set> +#include <cassert> +#include <sstream> + +#include <odb/semantics/relational.hxx> + +#include <odb/relational/common.hxx> +#include <odb/relational/context.hxx> + +namespace relational +{ + namespace model + { + typedef std::set<qname> tables; + typedef std::map<qname, semantics::node*> deleted_table_map; + typedef std::map<uname, semantics::data_member*> deleted_column_map; + + struct object_columns: object_columns_base, virtual context + { + typedef object_columns base; + + object_columns (sema_rel::model& model, + sema_rel::table& table, + bool object) + : model_ (model), + table_ (table), + object_ (object), + pkey_ (0), + id_override_ (false) + { + } + + virtual void + traverse_object (semantics::class_& c) + { + if (context::top_object != &c) + { + // We are in one of the bases. Set the id_prefix to its + // (unqualified) name. + // + string t (id_prefix_); + id_prefix_ = class_name (c) + "::"; + object_columns_base::traverse_object (c); + id_prefix_ = t; + } + else + object_columns_base::traverse_object (c); + } + + virtual void + traverse_composite (semantics::data_member* m, semantics::class_& c) + { + string t (id_prefix_); + + if (m != 0) + // Member of a composite type. Add the data member to id_prefix. + // + if (!id_override_) + id_prefix_ += m->name () + "."; + else + id_override_ = false; + else + // Composite base. Add its unqualified name to id_prefix. + // + id_prefix_ += class_name (c) + "::"; + + object_columns_base::traverse_composite (m, c); + + id_prefix_ = t; + } + + virtual void + traverse (semantics::data_member& m, + semantics::type& t, + string const& kp, + string const& dn, + semantics::class_* to = 0) + { + // This overrides the member name for a composite container value + // or key. + // + if (!kp.empty ()) + { + semantics::class_* c (object_pointer (t)); + if (composite_wrapper (c == 0 ? t : utype (*id_member (*c)))) + { + id_prefix_ = kp + "."; + id_override_ = true; + } + } + + object_columns_base::traverse (m, t, kp, dn, to); + } + + using object_columns_base::traverse; + + virtual bool + traverse_column (semantics::data_member& m, string const& name, bool) + { + if (semantics::data_member* m = deleted_member (member_path_)) + { + table_.get<deleted_column_map> ("deleted-map")[name] = m; + return false; + } + + string col_id (id_prefix_ + + (key_prefix_.empty () ? m.name () : key_prefix_)); + + sema_rel::column& c ( + model_.new_node<sema_rel::column> (col_id, type (m), null (m))); + c.set ("cxx-location", m.location ()); + c.set ("member-path", member_path_); + model_.new_edge<sema_rel::unames> (table_, c, name); + + // An id member cannot have a default value. + // + if (!object_columns_base::id ()) + { + string const& d (default_ (m)); + + if (!d.empty ()) + c.default_ (d); + } + + // If we have options, add them. + // + string const& o (column_options (m, key_prefix_)); + + if (!o.empty ()) + c.options (o); + + constraints (m, name, col_id, c); + return true; + } + + virtual string + type (semantics::data_member&) + { + return object_columns_base::column_type (); + } + + virtual bool + null (semantics::data_member&) + { + return !object_columns_base::id () && object_columns_base::null (); + } + + virtual string + default_null (semantics::data_member&) + { + return "NULL"; + } + + virtual string + default_bool (semantics::data_member&, bool v) + { + // Most databases do not support boolean literals. Those that + // do should override this. + // + return (v ? "1" : "0"); + } + + virtual string + default_integer (semantics::data_member&, unsigned long long v, bool neg) + { + std::ostringstream ostr; + ostr << (neg ? "-" : "") << v; + return ostr.str (); + } + + virtual string + default_float (semantics::data_member&, double v) + { + std::ostringstream ostr; + ostr << v; + return ostr.str (); + } + + virtual string + default_string (semantics::data_member&, string const& v) + { + return quote_string (v); + } + + virtual string + default_enum (semantics::data_member&, + tree /*enumerator*/, + string const& /*name*/) + { + // Has to be implemented by the database-specific override. + // + assert (false); + return string (); + } + + virtual void + primary_key (sema_rel::primary_key&) + { + } + + virtual void + constraints (semantics::data_member& m, + string const& /* name */, + string const& /* id */, + sema_rel::column& c) + { + if (object_) + { + if (semantics::data_member* idm = id ()) + { + if (pkey_ == 0) + { + pkey_ = &model_.new_node<sema_rel::primary_key> ( + m.count ("auto")); + pkey_->set ("cxx-location", idm->location ()); + + // In most databases the primary key constraint can be + // manipulated without an explicit name. So we use the special + // empty name for primary keys in order not to clash with + // columns and other constraints. If the target database does + // not support unnamed primary key manipulation, then the + // database-specific code will have to come up with a suitable + // name. + // + model_.new_edge<sema_rel::unames> (table_, *pkey_, ""); + primary_key (*pkey_); + } + + model_.new_edge<sema_rel::contains> (*pkey_, c); + } + } + } + + virtual void + traverse_pointer (semantics::data_member& m, semantics::class_& c) + { + // Ignore inverse object pointers. + // + if (inverse (m, key_prefix_)) + return; + + if (deleted (member_path_)) + { + // Still traverse it as columns so that we can populate the + // deleted map. + // + object_columns_base::traverse_pointer (m, c); + return; + } + + // Get the position of the last column. + // + sema_rel::table::names_iterator i (table_.names_end ()); + + while (i != table_.names_begin ()) + { + --i; + if (i->nameable ().is_a<sema_rel::column> ()) + break; + } + + // Traverse the object pointer as columns. + // + object_columns_base::traverse_pointer (m, c); + + // Get to the first column that we have added. + // + if (i != table_.names_end ()) + ++i; // Next column. + else + i = table_.names_begin (); + + foreign_key (m, c, i); + } + + virtual void + traverse_points_to (semantics::data_member& m, semantics::class_& c) + { + if (deleted (member_path_)) + { + // Still traverse it as columns so that we can populate the + // deleted map. + // + object_columns_base::traverse_points_to (m, c); + return; + } + + // Get the position of the last column. + // + sema_rel::table::names_iterator i (table_.names_end ()); + + while (i != table_.names_begin ()) + { + --i; + if (i->nameable ().is_a<sema_rel::column> ()) + break; + } + + // Traverse the data member as columns. + // + object_columns_base::traverse_points_to (m, c); + + // Get to the first column that we have added. + // + if (i != table_.names_end ()) + ++i; // Next column. + else + i = table_.names_begin (); + + foreign_key (m, c, i); + } + + virtual void + foreign_key (semantics::data_member& m, + semantics::class_& c, + sema_rel::table::names_iterator i) + { + using sema_rel::column; + using sema_rel::foreign_key; + + string id (id_prefix_ + + (key_prefix_.empty () ? m.name () : key_prefix_)); + + deferrable def ( + m.get<deferrable> ("deferrable", + options.fkeys_deferrable_mode ()[db])); + + foreign_key::action_type on_delete ( + m.get<foreign_key::action_type> ( + "on-delete", foreign_key::no_action)); + + foreign_key& fk ( + model_.new_node<foreign_key> (id, table_name (c), def, on_delete)); + + fk.set ("cxx-location", m.location ()); + + bool simple; + + // Get referenced columns. + // + { + data_member_path& id (*id_member (c)); + + instance<object_columns_list> ocl; + ocl->traverse (id); + + for (object_columns_list::iterator i (ocl->begin ()); + i != ocl->end (); ++i) + fk.referenced_columns ().push_back (i->name); + + simple = (fk.referenced_columns ().size () == 1); + } + + // Get referencing columns. + // + for (; i != table_.names_end (); ++i) + { + if (column* c = dynamic_cast<column*> (&i->nameable ())) + model_.new_edge<sema_rel::contains> (fk, *c); + else + break; + } + + // Derive the constraint name. Generally, we want it to be based + // on the column name. This is straightforward for single-column + // references. In case of a composite id, we will need to use the + // column prefix which is based on the data member name, unless + // overridden by the user. In the latter case the prefix can be + // empty, in which case we will just fall back on the member's + // public name. + // + string name; + + if (simple) + name = fk.contains_begin ()->column ().name (); + else + { + string p (column_prefix (m, key_prefix_, default_name_).prefix); + + if (p.empty ()) + p = public_name_db (m); + else if (p[p.size () - 1] == '_') + p.resize (p.size () - 1); // Remove trailing underscore. + + name = column_prefix_.prefix + p; + } + + model_.new_edge<sema_rel::unames> ( + table_, fk, fkey_name (table_.name (), name)); + } + + protected: + string + default_ (semantics::data_member&); + + protected: + sema_rel::model& model_; + sema_rel::table& table_; + bool object_; + sema_rel::primary_key* pkey_; + string id_prefix_; + bool id_override_; + }; + + struct object_indexes: traversal::class_, virtual context + { + typedef object_indexes base; + + object_indexes (sema_rel::model& model, sema_rel::table& table) + : model_ (model), table_ (table) + { + *this >> inherits_ >> *this; + } + + object_indexes (object_indexes const& x) + : root_context (), context (), //@@ -Wextra + model_ (x.model_), table_ (x.table_) + { + *this >> inherits_ >> *this; + } + + virtual void + traverse (type& c) + { + if (!object (c)) // Ignore transient bases. + return; + + // Polymorphic bases get their own tables. + // + if (!polymorphic (c)) + inherits (c); + + indexes& ins (c.get<indexes> ("index")); + + for (indexes::iterator i (ins.begin ()); i != ins.end (); ++i) + { + // Using index name as its id. + // + sema_rel::index& in ( + model_.new_node<sema_rel::index> ( + i->name, i->type, i->method, i->options)); + in.set ("cxx-location", location (i->loc)); + model_.new_edge<sema_rel::unames> (table_, in, i->name); + + for (index::members_type::iterator j (i->members.begin ()); + j != i->members.end (); ++j) + { + using sema_rel::column; + + index::member& im (*j); + + semantics::type* t (&utype (*im.path.back ())); + + if (semantics::class_* ptr = object_pointer (*t)) + t = &utype (*id_member (*ptr)); + + if (type* comp = composite_wrapper (*t)) + { + // Composite value. Get the list of the columns. Note that + // the column prefix needs to contain all the components. + // + instance<object_columns_list> ocl ( + column_prefix (im.path, true)); + ocl->traverse (*comp); + + for (object_columns_list::iterator i (ocl->begin ()); + i != ocl->end (); ++i) + { + column* c (table_.find<column> (i->name)); + assert (c != 0); + model_.new_edge<sema_rel::contains> (in, *c, im.options); + } + } + else + { + // Simple value. Get the column name and look it up in the + // table. + // + column* c (table_.find<column> (column_name (im.path))); + assert (c != 0); + model_.new_edge<sema_rel::contains> (in, *c, im.options); + } + } + } + } + + private: + sema_rel::model& model_; + sema_rel::table& table_; + + traversal::inherits inherits_; + }; + + struct member_create: object_members_base, virtual context + { + typedef member_create base; + + member_create (sema_rel::model& model) + : object_members_base (false, true, false), model_ (model) + { + } + + virtual void + traverse_pointer (semantics::data_member&, semantics::class_&) + { + // We don't want to traverse composite id. + } + + virtual void + traverse_object (semantics::class_& c) + { + if (context::top_object != &c) + { + // We are in one of the bases. Set the id_prefix to its + // (unqualified) name. + // + string t (id_prefix_); + id_prefix_ = class_name (c) + "::"; + object_members_base::traverse_object (c); + id_prefix_ = t; + } + else + { + // Top-level object. Set its id as a prefix. + // + id_prefix_ = string (class_fq_name (c), 2) + "::"; + object_members_base::traverse_object (c); + } + } + + virtual void + traverse_composite (semantics::data_member* m, semantics::class_& c) + { + string t (id_prefix_); + + if (m != 0) + // Member of a composite type. Add the data member to id_prefix. + // + id_prefix_ += m->name () + "."; + else + // Composite base. Add its unqualified name to id_prefix. + // + id_prefix_ += class_name (c) + "::"; + + object_members_base::traverse_composite (m, c); + + id_prefix_ = t; + } + + virtual string + table_options (semantics::data_member& m, semantics::type& ct) + { + return context::table_options (m, ct); + } + + virtual void + traverse_container (semantics::data_member& m, semantics::type& ct) + { + using semantics::type; + using semantics::data_member; + + using sema_rel::column; + + // Ignore inverse containers of object pointers. + // + if (inverse (m, "value")) + return; + + container_kind_type ck (container_kind (ct)); + qname const& name (table_name (m, table_prefix_)); + + // Ignore deleted container members. + // + if (semantics::data_member* m = deleted_member (member_path_)) + { + model_.get<deleted_table_map> ("deleted-map")[name] = m; + return; + } + + // Add the [] decorator to distinguish this id from non-container + // ids (we don't want to ever end up comparing, for example, an + // object table to a container table). + // + string id (id_prefix_ + m.name () + "[]"); + + sema_rel::table& t (model_.new_node<sema_rel::table> (id)); + t.set ("cxx-location", m.location ()); + t.set ("member-path", member_path_); + t.set ("deleted-map", deleted_column_map ()); + model_.new_edge<sema_rel::qnames> (model_, t, name); + + t.options (table_options (m, ct)); + t.extra ()["kind"] = "container"; + + // object_id + // + { + bool f (false); //@@ (im)persfect forwarding. + instance<object_columns> oc (model_, t, f); + oc->traverse (m, container_idt (m), "id", "object_id"); + } + + // Foreign key and index for the object id. Keep this foreign + // key first since we reply on this information to lookup the + // corresponding object table. + // + { + // Derive the name prefix. See the comment for the other foreign + // key code above. + // + // Note also that id_name can be a column prefix (if id is + // composite), in which case it can be empty. In this case + // we just fallback on the default name. + // + // Finally, this is a top-level column, so there is no column + // prefix. + // + string id_name ( + column_name (m, "id", "object_id", column_prefix ())); + + if (id_name.empty ()) + id_name = "object_id"; + + // Foreign key. + // + sema_rel::foreign_key& fk ( + model_.new_node<sema_rel::foreign_key> ( + id + ".id", + table_name (*context::top_object), + sema_rel::deferrable::not_deferrable, + sema_rel::foreign_key::cascade)); + fk.set ("cxx-location", m.location ()); + model_.new_edge<sema_rel::unames> ( + t, fk, fkey_name (t.name (), id_name)); + + // Get referenced columns. + // + { + data_member_path& id (*id_member (*context::top_object)); + + instance<object_columns_list> ocl; + ocl->traverse (id); + + for (object_columns_list::iterator i (ocl->begin ()); + i != ocl->end (); ++i) + fk.referenced_columns ().push_back (i->name); + } + + // All the columns we have in this table so far are for the + // object id. Add them to the foreign key. + // + for (sema_rel::table::names_iterator i (t.names_begin ()); + i != t.names_end (); + ++i) + { + if (column* c = dynamic_cast<column*> (&i->nameable ())) + model_.new_edge<sema_rel::contains> (fk, *c); + } + + // Index. See if we have a custom index. + // + index* sin (m.count ("id-index") ? &m.get<index> ("id-index") : 0); + sema_rel::index* in (0); + + if (sin != 0) + { + in = &model_.new_node<sema_rel::index> ( + id + ".id", sin->type, sin->method, sin->options); + in->set ("cxx-location", location (sin->loc)); + } + else + { + in = &model_.new_node<sema_rel::index> (id + ".id"); + in->set ("cxx-location", m.location ()); + } + + model_.new_edge<sema_rel::unames> ( + t, + *in, + sin != 0 && !sin->name.empty () + ? sin->name + : index_name (name, id_name)); + + // All the columns we have in this table so far are for the + // object id. Add them to the index. + // + for (sema_rel::table::names_iterator i (t.names_begin ()); + i != t.names_end (); + ++i) + { + if (column* c = dynamic_cast<column*> (&i->nameable ())) + model_.new_edge<sema_rel::contains> ( + *in, *c, (sin != 0 ? sin->members.back ().options : "")); + } + } + + // index (simple value) + // + bool ordered (ck == ck_ordered && !unordered (m)); + if (ordered) + { + // Column. + // + { + bool f (false); //@@ (im)persfect forwarding. + instance<object_columns> oc (model_, t, f); + oc->traverse (m, container_it (m), "index", "index"); + } + + // This is a simple value so the name cannot be empty. It is + // also a top-level column, so there is no column prefix. + // + string col (column_name (m, "index", "index", column_prefix ())); + + // Index. See if we have a custom index. + // + index* sin (m.count ("index-index") + ? &m.get<index> ("index-index") + : 0); + sema_rel::index* in (0); + + if (sin != 0) + { + in = &model_.new_node<sema_rel::index> ( + id + ".index", sin->type, sin->method, sin->options); + in->set ("cxx-location", location (sin->loc)); + } + else + { + in = &model_.new_node<sema_rel::index> (id + ".index"); + in->set ("cxx-location", m.location ()); + } + + model_.new_edge<sema_rel::unames> ( + t, + *in, + sin != 0 && !sin->name.empty () + ? sin->name + : index_name (name, col)); + + column* c (t.find<column> (col)); + assert (c != 0); + + model_.new_edge<sema_rel::contains> ( + *in, + *c, + (sin != 0 ? sin->members.back ().options : "")); + } + + // key + // + if (ck == ck_map || ck == ck_multimap) + { + bool f (false); //@@ (im)persfect forwarding. + instance<object_columns> oc (model_, t, f); + oc->traverse (m, container_kt (m), "key", "key"); + } + + // value + // + { + bool f (false); //@@ (im)persfect forwarding. + instance<object_columns> oc (model_, t, f); + oc->traverse (m, container_vt (m), "value", "value"); + } + } + + protected: + sema_rel::model& model_; + string id_prefix_; + }; + + struct class_: traversal::class_, virtual context + { + typedef class_ base; + + class_ (sema_rel::model& model): model_ (model) {} + + virtual string + table_options (type& c) + { + return context::table_options (c); + } + + virtual void + traverse (type& c) + { + if (!options.at_once () && class_file (c) != unit.file ()) + return; + + if (!object (c)) + return; + + semantics::class_* poly (polymorphic (c)); + + if (abstract (c) && poly == 0) + return; + + qname const& name (table_name (c)); + + // If the table with this name was already seen, assume the + // user knows what they are doing and skip it. + // + if (tables_.count (name)) + return; + + if (deleted (c)) + { + model_.get<deleted_table_map> ("deleted-map")[name] = &c; + return; + } + + string id (class_fq_name (c), 2); // Remove leading '::'. + + sema_rel::table& t (model_.new_node<sema_rel::table> (id)); + t.set ("cxx-location", c.location ()); + t.set ("class", &c); + t.set ("deleted-map", deleted_column_map ()); + model_.new_edge<sema_rel::qnames> (model_, t, name); + + t.options (table_options (c)); + + t.extra ()["kind"] =(poly == 0 + ? "object" + : (poly == &c + ? "polymorphic root object" + : "polymorphic derived object")); + + // Add columns. + // + { + bool tr (true); //@@ (im)persfect forwarding. + instance<object_columns> oc (model_, t, tr); + oc->traverse (c); + } + + // Add indexes. + // + { + instance<object_indexes> oi (model_, t); + oi->traverse (c); + } + + tables_.insert (name); + + // Create tables for members. + // + { + instance<member_create> mc (model_); + mc->traverse (c); + } + } + + protected: + sema_rel::model& model_; + tables tables_; + }; + } +} + +#endif // ODB_RELATIONAL_MODEL_HXX diff --git a/odb/odb/relational/mssql/common.cxx b/odb/odb/relational/mssql/common.cxx new file mode 100644 index 0000000..1070d21 --- /dev/null +++ b/odb/odb/relational/mssql/common.cxx @@ -0,0 +1,603 @@ +// file : odb/relational/mssql/common.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <cassert> + +#include <odb/relational/mssql/common.hxx> + +using namespace std; + +namespace relational +{ + namespace mssql + { + // + // member_base + // + + sql_type const& member_base:: + member_sql_type (semantics::data_member& m) + { + return parse_sql_type (column_type (m, key_prefix_), m); + } + + void member_base:: + traverse_simple (member_info& mi) + { + const sql_type& st (*mi.st); + + // The same long/short data test as in context.cxx:long_data(). + // + switch (st.type) + { + // Integral types. + // + case sql_type::BIT: + case sql_type::TINYINT: + case sql_type::SMALLINT: + case sql_type::INT: + case sql_type::BIGINT: + { + traverse_integer (mi); + break; + } + + // Fixed and floating point types. + // + case sql_type::DECIMAL: + { + traverse_decimal (mi); + break; + } + case sql_type::SMALLMONEY: + { + traverse_smallmoney (mi); + break; + } + case sql_type::MONEY: + { + traverse_money (mi); + break; + } + case sql_type::FLOAT: + { + if (st.prec > 24) + traverse_float8 (mi); + else + traverse_float4 (mi); + + break; + } + + // String and binary types. + // + case sql_type::CHAR: + case sql_type::VARCHAR: + { + // Zero precision means max in VARCHAR(max). + // + if (st.prec == 0 || st.prec > options.mssql_short_limit ()) + traverse_long_string (mi); + else + traverse_string (mi); + + break; + } + case sql_type::TEXT: + { + traverse_long_string (mi); + break; + } + case sql_type::NCHAR: + case sql_type::NVARCHAR: + { + // Zero precision means max in NVARCHAR(max). Note that + // the precision is in 2-byte UCS-2 characters, not bytes. + // + if (st.prec == 0 || st.prec * 2 > options.mssql_short_limit ()) + traverse_long_nstring (mi); + else + traverse_nstring (mi); + + break; + } + case sql_type::NTEXT: + { + traverse_long_nstring (mi); + break; + } + case sql_type::BINARY: + case sql_type::VARBINARY: + { + // Zero precision means max in VARCHAR(max). + // + if (st.prec == 0 || st.prec > options.mssql_short_limit ()) + traverse_long_binary (mi); + else + traverse_binary (mi); + + break; + } + case sql_type::IMAGE: + { + traverse_long_binary (mi); + break; + } + + // Date-time types. + // + case sql_type::DATE: + { + traverse_date (mi); + break; + } + case sql_type::TIME: + { + traverse_time (mi); + break; + } + case sql_type::DATETIME: + case sql_type::DATETIME2: + case sql_type::SMALLDATETIME: + { + traverse_datetime (mi); + break; + } + case sql_type::DATETIMEOFFSET: + { + traverse_datetimeoffset (mi); + break; + } + + // Other types. + // + case sql_type::UNIQUEIDENTIFIER: + { + traverse_uniqueidentifier (mi); + break; + } + case sql_type::ROWVERSION: + { + traverse_rowversion (mi); + break; + } + case sql_type::invalid: + { + assert (false); + break; + } + } + } + + // + // member_image_type + // + + static const char* integer_types[] = + { + "unsigned char", + "unsigned char", + "short", + "int", + "long long" + }; + + member_image_type:: + member_image_type (base const& x) + : member_base::base (x), // virtual base + base (x) {} + + member_image_type:: + member_image_type () + : relational::member_base (0, 0, string (), string ()) {} + + member_image_type:: + member_image_type (semantics::type* type, + const custom_cxx_type* ct, + string const& fq_type, + string const& key_prefix) + : relational::member_base (type, ct, fq_type, key_prefix) {} + + string member_image_type:: + image_type (semantics::data_member& m) + { + type_.clear (); + member_base::traverse (m, true); + return type_; + } + + void member_image_type:: + traverse_composite (member_info& mi) + { + type_ = "composite_value_traits< " + mi.fq_type () + + ", id_mssql >::image_type"; + } + + void member_image_type:: + traverse_integer (member_info& mi) + { + type_ = integer_types[mi.st->type - sql_type::BIT]; + } + + void member_image_type:: + traverse_decimal (member_info&) + { + type_ = "mssql::decimal"; + } + + void member_image_type:: + traverse_smallmoney (member_info&) + { + type_ = "mssql::smallmoney"; + } + + void member_image_type:: + traverse_money (member_info&) + { + type_ = "mssql::money"; + } + + void member_image_type:: + traverse_float4 (member_info&) + { + type_ = "float"; + } + + void member_image_type:: + traverse_float8 (member_info&) + { + type_ = "double"; + } + + void member_image_type:: + traverse_string (member_info&) + { + type_ = "char*"; + } + + void member_image_type:: + traverse_long_string (member_info&) + { + type_ = "mssql::long_callback"; + } + + void member_image_type:: + traverse_nstring (member_info&) + { + type_ = "mssql::ucs2_char*"; + } + + void member_image_type:: + traverse_long_nstring (member_info&) + { + type_ = "mssql::long_callback"; + } + + void member_image_type:: + traverse_binary (member_info&) + { + type_ = "char*"; + } + + void member_image_type:: + traverse_long_binary (member_info&) + { + type_ = "mssql::long_callback"; + } + + void member_image_type:: + traverse_date (member_info&) + { + type_ = "mssql::date"; + } + + void member_image_type:: + traverse_time (member_info&) + { + type_ = "mssql::time"; + } + + void member_image_type:: + traverse_datetime (member_info&) + { + type_ = "mssql::datetime"; + } + + void member_image_type:: + traverse_datetimeoffset (member_info&) + { + type_ = "mssql::datetimeoffset"; + } + + void member_image_type:: + traverse_uniqueidentifier (member_info&) + { + type_ = "mssql::uniqueidentifier"; + } + + void member_image_type:: + traverse_rowversion (member_info&) + { + type_ = "unsigned char*"; + } + + entry<member_image_type> member_image_type_; + + // + // member_database_type + // + + static const char* integer_database_id[] = + { + "mssql::id_bit", + "mssql::id_tinyint", + "mssql::id_smallint", + "mssql::id_int", + "mssql::id_bigint" + }; + + member_database_type_id:: + member_database_type_id (base const& x) + : member_base::base (x), // virtual base + base (x) + { + } + + member_database_type_id:: + member_database_type_id () + : member_base::base (0, 0, string (), string ()), // virtual base + base (0, 0, string (), string ()) + { + } + + member_database_type_id:: + member_database_type_id (semantics::type* type, + const custom_cxx_type* ct, + string const& fq_type, + string const& key_prefix) + : member_base::base (type, ct, fq_type, key_prefix), // virtual base + base (type, ct, fq_type, key_prefix) + { + } + + string member_database_type_id:: + database_type_id (semantics::data_member& m) + { + type_id_.clear (); + member_base::traverse (m, true); + return type_id_; + } + + void member_database_type_id:: + traverse_composite (member_info&) + { + assert (false); + } + + void member_database_type_id:: + traverse_integer (member_info& mi) + { + type_id_ = integer_database_id[mi.st->type - sql_type::BIT]; + } + + void member_database_type_id:: + traverse_decimal (member_info&) + { + type_id_ = "mssql::id_decimal"; + } + + void member_database_type_id:: + traverse_smallmoney (member_info&) + { + type_id_ = "mssql::id_smallmoney"; + } + + void member_database_type_id:: + traverse_money (member_info&) + { + type_id_ = "mssql::id_money"; + } + + void member_database_type_id:: + traverse_float4 (member_info&) + { + type_id_ = "mssql::id_float4"; + } + + void member_database_type_id:: + traverse_float8 (member_info&) + { + type_id_ = "mssql::id_float8"; + } + + void member_database_type_id:: + traverse_string (member_info&) + { + type_id_ = "mssql::id_string"; + } + + void member_database_type_id:: + traverse_long_string (member_info&) + { + type_id_ = "mssql::id_long_string"; + } + + void member_database_type_id:: + traverse_nstring (member_info&) + { + type_id_ = "mssql::id_nstring"; + } + + void member_database_type_id:: + traverse_long_nstring (member_info&) + { + type_id_ = "mssql::id_long_nstring"; + } + + void member_database_type_id:: + traverse_binary (member_info&) + { + type_id_ = "mssql::id_binary"; + } + + void member_database_type_id:: + traverse_long_binary (member_info&) + { + type_id_ = "mssql::id_long_binary"; + } + + void member_database_type_id:: + traverse_date (member_info&) + { + type_id_ = "mssql::id_date"; + } + + void member_database_type_id:: + traverse_time (member_info&) + { + type_id_ = "mssql::id_time"; + } + + void member_database_type_id:: + traverse_datetime (member_info&) + { + type_id_ = "mssql::id_datetime"; + } + + void member_database_type_id:: + traverse_datetimeoffset (member_info&) + { + type_id_ = "mssql::id_datetimeoffset"; + } + + void member_database_type_id:: + traverse_uniqueidentifier (member_info&) + { + type_id_ = "mssql::id_uniqueidentifier"; + } + + void member_database_type_id:: + traverse_rowversion (member_info&) + { + type_id_ = "mssql::id_rowversion"; + } + + entry<member_database_type_id> member_database_type_id_; + + // + // query_columns + // + + struct query_columns: relational::query_columns, context + { + query_columns (base const& x): base_impl (x) {} + + virtual string + database_type_id (semantics::data_member& m) + { + return member_database_type_id_.database_type_id (m); + } + + virtual void + column_ctor (string const& type, string const& name, string const& base) + { + os << name << " ("; + + if (multi_dynamic) + os << "odb::query_column< " << type << " >& qc," << endl; + + os << "const char* t," << endl + << "const char* c," << endl + << "const char* conv," << endl + << "unsigned short p = 0," << endl + << "unsigned short s = 0xFFFF)" << endl + << " : " << base << " (" << (multi_dynamic ? "qc, " : "") << + "t, c, conv, p, s)" + << "{" + << "}"; + } + + virtual void + column_ctor_args_extra (semantics::data_member& m) + { + // For some types we need to pass precision and scale. + // + sql_type const& st (parse_sql_type (column_type (), m)); + + switch (st.type) + { + case sql_type::DECIMAL: + { + os << ", " << st.prec << ", " << st.scale; + break; + } + case sql_type::FLOAT: + { + os << ", " << st.prec; + break; + } + case sql_type::CHAR: + case sql_type::VARCHAR: + { + os << ", " << st.prec; + break; + } + case sql_type::TEXT: + { + os << ", 0"; // Unlimited. + break; + } + case sql_type::NCHAR: + case sql_type::NVARCHAR: + { + os << ", " << st.prec; // In 2-byte characters. + break; + } + case sql_type::NTEXT: + { + os << ", 0"; // Unlimited. + break; + } + case sql_type::BINARY: + case sql_type::VARBINARY: + { + os << ", " << st.prec; + break; + } + case sql_type::IMAGE: + { + os << ", 0"; // Unlimited. + break; + } + // Date-time types. + // + case sql_type::TIME: + case sql_type::DATETIME2: + case sql_type::DATETIMEOFFSET: + { + os << ", 0, " << st.scale; // Fractional seconds (scale). + break; + } + case sql_type::DATETIME: + { + os << ", 0, 3"; + break; + } + case sql_type::SMALLDATETIME: + { + os << ", 0, 8"; + break; + } + default: + { + break; + } + } + } + + private: + member_database_type_id member_database_type_id_; + }; + entry<query_columns> query_columns_; + } +} diff --git a/odb/odb/relational/mssql/common.hxx b/odb/odb/relational/mssql/common.hxx new file mode 100644 index 0000000..42ea412 --- /dev/null +++ b/odb/odb/relational/mssql/common.hxx @@ -0,0 +1,293 @@ +// file : odb/relational/mssql/common.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_RELATIONAL_MSSQL_COMMON_HXX +#define ODB_RELATIONAL_MSSQL_COMMON_HXX + +#include <odb/relational/common.hxx> +#include <odb/relational/mssql/context.hxx> + +namespace relational +{ + namespace mssql + { + struct member_base: virtual relational::member_base_impl<sql_type>, context + { + member_base (base const& x): base (x), base_impl (x) {} + + // This c-tor is for the direct use inside the mssql namespace. + // If you do use this c-tor, you should also explicitly call + // relational::member_base (aka base). + // + member_base () {} + + virtual sql_type const& + member_sql_type (semantics::data_member&); + + virtual void + traverse_simple (member_info&); + + virtual void + traverse_integer (member_info&) + { + } + + virtual void + traverse_decimal (member_info&) + { + } + + virtual void + traverse_smallmoney (member_info&) + { + } + + virtual void + traverse_money (member_info&) + { + } + + virtual void + traverse_float4 (member_info&) + { + } + + virtual void + traverse_float8 (member_info&) + { + } + + virtual void + traverse_string (member_info&) + { + } + + virtual void + traverse_long_string (member_info&) + { + } + + virtual void + traverse_nstring (member_info&) + { + } + + virtual void + traverse_long_nstring (member_info&) + { + } + + virtual void + traverse_binary (member_info&) + { + } + + virtual void + traverse_long_binary (member_info&) + { + } + + virtual void + traverse_date (member_info&) + { + } + + virtual void + traverse_time (member_info&) + { + } + + virtual void + traverse_datetime (member_info&) + { + } + + virtual void + traverse_datetimeoffset (member_info&) + { + } + + virtual void + traverse_uniqueidentifier (member_info&) + { + } + + virtual void + traverse_rowversion (member_info&) + { + } + }; + + struct member_image_type: relational::member_image_type, + member_base + { + member_image_type (base const&); + member_image_type (); + member_image_type (semantics::type* type, + const custom_cxx_type*, + string const& fq_type = string (), + string const& key_prefix = string ()); + virtual string + image_type (semantics::data_member&); + + virtual void + traverse_composite (member_info&); + + virtual void + traverse_integer (member_info&); + + virtual void + traverse_decimal (member_info&); + + virtual void + traverse_smallmoney (member_info&); + + virtual void + traverse_money (member_info&); + + virtual void + traverse_float4 (member_info&); + + virtual void + traverse_float8 (member_info&); + + virtual void + traverse_string (member_info&); + + virtual void + traverse_long_string (member_info&); + + virtual void + traverse_nstring (member_info&); + + virtual void + traverse_long_nstring (member_info&); + + virtual void + traverse_binary (member_info&); + + virtual void + traverse_long_binary (member_info&); + + virtual void + traverse_date (member_info&); + + virtual void + traverse_time (member_info&); + + virtual void + traverse_datetime (member_info&); + + virtual void + traverse_datetimeoffset (member_info&); + + virtual void + traverse_uniqueidentifier (member_info&); + + virtual void + traverse_rowversion (member_info&); + + private: + string type_; + }; + + struct member_database_type_id: relational::member_database_type_id, + member_base + { + member_database_type_id (base const&); + member_database_type_id (); + member_database_type_id (semantics::type* type, + const custom_cxx_type*, + string const& fq_type = string (), + string const& key_prefix = string ()); + + virtual string + database_type_id (semantics::data_member&); + + virtual void + traverse_composite (member_info&); + + virtual void + traverse_integer (member_info&); + + virtual void + traverse_decimal (member_info&); + + virtual void + traverse_smallmoney (member_info&); + + virtual void + traverse_money (member_info&); + + virtual void + traverse_float4 (member_info&); + + virtual void + traverse_float8 (member_info&); + + virtual void + traverse_string (member_info&); + + virtual void + traverse_long_string (member_info&); + + virtual void + traverse_nstring (member_info&); + + virtual void + traverse_long_nstring (member_info&); + + virtual void + traverse_binary (member_info&); + + virtual void + traverse_long_binary (member_info&); + + virtual void + traverse_date (member_info&); + + virtual void + traverse_time (member_info&); + + virtual void + traverse_datetime (member_info&); + + virtual void + traverse_datetimeoffset (member_info&); + + virtual void + traverse_uniqueidentifier (member_info&); + + virtual void + traverse_rowversion (member_info&); + + private: + string type_id_; + }; + + struct has_long_data: object_columns_base, context + { + has_long_data (bool& r): r_ (r) {} + + virtual void + traverse_pointer (semantics::data_member& m, semantics::class_& c) + { + if (!inverse (m, key_prefix_)) + object_columns_base::traverse_pointer (m, c); + } + + virtual bool + traverse_column (semantics::data_member& m, string const&, bool) + { + if (long_data (parse_sql_type (column_type (), m))) + r_ = true; + + return true; + } + + private: + bool& r_; + }; + } +} +#endif // ODB_RELATIONAL_MSSQL_COMMON_HXX diff --git a/odb/odb/relational/mssql/context.cxx b/odb/odb/relational/mssql/context.cxx new file mode 100644 index 0000000..afe1aa5 --- /dev/null +++ b/odb/odb/relational/mssql/context.cxx @@ -0,0 +1,766 @@ +// file : odb/relational/mssql/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/mssql/context.hxx> + +using namespace std; + +namespace relational +{ + namespace mssql + { + 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", "BIT", 0, false}, + + {"char", "CHAR(1)", 0, false}, + {"wchar_t", "NCHAR(1)", 0, false}, + {"signed char", "TINYINT", 0, false}, + {"unsigned char", "TINYINT", 0, false}, + + {"short int", "SMALLINT", 0, false}, + {"short unsigned int", "SMALLINT", 0, false}, + + {"int", "INT", 0, false}, + {"unsigned int", "INT", 0, false}, + + {"long int", "BIGINT", 0, false}, + {"long unsigned int", "BIGINT", 0, false}, + + {"long long int", "BIGINT", 0, false}, + {"long long unsigned int", "BIGINT", 0, false}, + + {"float", "REAL", 0, false}, + {"double", "FLOAT", 0, false}, + + {"::std::string", "VARCHAR(512)", "VARCHAR(256)", false}, + {"::std::wstring", "NVARCHAR(512)", "NVARCHAR(256)", false}, + + {"::size_t", "BIGINT", 0, false}, + {"::std::size_t", "BIGINT", 0, false}, + + // Windows GUID/UUID (typedef struct _GUID {...} GUID, UUID;). + // + {"::_GUID", "UNIQUEIDENTIFIER", 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 = true; + insert_send_auto_id = false; + delay_freeing_statement_result = true; + need_image_clone = true; + generate_bulk = true; + global_index = false; + global_fkey = true; + data_->bind_vector_ = "mssql::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; + + // Warn if the name is greater than the 128 limit. + // + if (i->size () > 128) + { + cerr << "warning: SQL name '" << *i << "' is longer than the " + << "SQL Server name limit of 128 characters and will be " + << "truncated" << endl; + + cerr << "info: consider shortening it using #pragma db " + << "table/column/index or --*-regex options" << endl; + } + + if (f) + f = false; + else + r += '.'; + + r += '['; + r.append (*i, 0, 128); // Max identifier length is 128. + 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 ()); + bool c (bt.is_a<semantics::fund_char> ()); + + if (c || bt.is_a<semantics::fund_wchar> ()) + { + unsigned long long n (a->size ()); + + if (n == 0) + return r; + if (n == 1) + r = c ? "CHAR(" : "NCHAR("; + else + { + r = c ? "VARCHAR(" : "NVARCHAR("; + n--; + } + + if (n > (c ? 8000 : 4000)) + r += "max)"; + else + { + ostringstream ostr; + ostr << n; + r += ostr.str (); + r += ')'; + } + } + } + + return r; + } + + bool context:: + long_data (sql_type const& st) + { + bool r (false); + + // The same test as in common.cxx:traverse_simple(). + // + switch (st.type) + { + case sql_type::CHAR: + case sql_type::VARCHAR: + case sql_type::BINARY: + case sql_type::VARBINARY: + { + // Zero precision means max in VARCHAR(max). + // + if (st.prec == 0 || st.prec > options.mssql_short_limit ()) + r = true; + + break; + } + case sql_type::NCHAR: + case sql_type::NVARCHAR: + { + // Zero precision means max in NVARCHAR(max). Note that + // the precision is in 2-byte UCS-2 characters, not bytes. + // + if (st.prec == 0 || st.prec * 2 > options.mssql_short_limit ()) + r = true; + + break; + } + case sql_type::TEXT: + case sql_type::NTEXT: + case sql_type::IMAGE: + { + r = true; + break; + } + default: + break; + } + + return r; + } + + // + // SQL type parsing. + // + + namespace + { + struct sql_parser + { + typedef context::invalid_sql_type invalid_sql_type; + + sql_parser (custom_db_types const* ct): ct_ (ct) {} + + sql_type + parse (std::string sql) + { + r_ = sql_type (); + m_.clear (); + + // 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 (sql)) + { + r_.to = t.type.replace (sql, t.to); + r_.from = t.type.replace (sql, t.from); + sql = t.type.replace (sql, t.as); + break; + } + } + } + + l_.lex (sql); + + bool ok (true); + + try + { + ok = parse_name (); + } + catch (sql_lexer::invalid_input const& e) + { + ok = false; + m_ = "invalid SQL Server type declaration: " + e.message; + } + + if (!ok) + { + if (ct_ == 0) + return sql_type (); + else + throw invalid_sql_type (m_); + } + + return r_; + } + + bool + parse_name () + { + sql_token t (l_.next ()); + + if (t.type () != sql_token::t_identifier) + { + m_ = "expected SQL Server type name instead of '" + + t.string () + "'"; + return false; + } + + string id (upcase (t.identifier ())); + + if (id == "BIT") + { + r_.type = sql_type::BIT; + } + else if (id == "TINYINT") + { + r_.type = sql_type::TINYINT; + } + else if (id == "SMALLINT") + { + r_.type = sql_type::SMALLINT; + } + else if (id == "INT" || + id == "INTEGER") + { + r_.type = sql_type::INT; + } + else if (id == "BIGINT") + { + r_.type = sql_type::BIGINT; + } + else if (id == "DECIMAL" || + id == "NUMERIC" || + id == "DEC") + { + r_.type = sql_type::DECIMAL; + + r_.has_prec = true; + r_.prec = 18; + + r_.has_scale = true; + r_.scale = 0; + + if (!parse_precision (l_.next ())) + return false; + } + else if (id == "SMALLMONEY") + { + r_.type = sql_type::SMALLMONEY; + } + else if (id == "MONEY") + { + r_.type = sql_type::MONEY; + } + else if (id == "REAL") + { + r_.type = sql_type::FLOAT; + + r_.has_prec = true; + r_.prec = 24; + } + else if (id == "FLOAT") + { + r_.type = sql_type::FLOAT; + + r_.has_prec = true; + r_.prec = 53; + + if (!parse_precision (l_.next ())) + return false; + } + else if (id == "DOUBLE") + { + t = l_.next (); + + if (t.type () != sql_token::t_identifier || + upcase (t.identifier ()) != "PRECISION") + { + m_ = "expected 'PRECISION' instead of '" + t.string () + "'"; + return false; + } + + r_.type = sql_type::FLOAT; + + r_.has_prec = true; + r_.prec = 53; + + // It appears that DOUBLE PRECISION can be followed by the + // precision specification. + // + if (!parse_precision (l_.next ())) + return false; + } + else if (id == "CHAR" || + id == "CHARACTER") + { + if (!parse_char_trailer (false)) + return false; + } + else if (id == "VARCHAR") + { + r_.type = sql_type::VARCHAR; + + r_.has_prec = true; + r_.prec = 1; + + if (!parse_precision (l_.next ())) + return false; + } + else if (id == "TEXT") + { + r_.type = sql_type::TEXT; + r_.has_prec = true; + r_.prec = 0; + } + else if (id == "NCHAR") + { + r_.type = sql_type::NCHAR; + + r_.has_prec = true; + r_.prec = 1; + + if (!parse_precision (l_.next ())) + return false; + } + else if (id == "NVARCHAR") + { + r_.type = sql_type::NVARCHAR; + + r_.has_prec = true; + r_.prec = 1; + + if (!parse_precision (l_.next ())) + return false; + } + else if (id == "NTEXT") + { + r_.type = sql_type::NTEXT; + r_.has_prec = true; + r_.prec = 0; + } + else if (id == "NATIONAL") + { + t = l_.next (); + + if (t.type () == sql_token::t_identifier) + id = upcase (t.identifier ()); + + if (id == "TEXT") + { + r_.type = sql_type::NTEXT; + r_.has_prec = true; + r_.prec = 0; + } + else if (id == "CHAR" || + id == "CHARACTER") + { + if (!parse_char_trailer (true)) + return false; + } + else + { + m_ = "expected 'CHAR', 'CHARACTER', or 'TEXT' instead of '" + + t.string () + "'"; + return false; + } + } + else if (id == "BINARY") + { + // Can be just BINARY or BINARY VARYING. + // + t = l_.next (); + + if (t.type () == sql_token::t_identifier) + id = upcase (t.identifier ()); + + if (id == "VARYING") + { + r_.type = sql_type::VARBINARY; + t = l_.next (); + } + else + r_.type = sql_type::BINARY; + + r_.has_prec = true; + r_.prec = 1; + + if (!parse_precision (t)) + return false; + } + else if (id == "VARBINARY") + { + r_.type = sql_type::VARBINARY; + + r_.has_prec = true; + r_.prec = 1; + + if (!parse_precision (l_.next ())) + return false; + } + else if (id == "IMAGE") + { + r_.type = sql_type::IMAGE; + r_.has_prec = true; + r_.prec = 0; + } + else if (id == "DATE") + { + r_.type = sql_type::DATE; + } + else if (id == "TIME") + { + r_.type = sql_type::TIME; + + r_.has_scale = true; + r_.scale = 7; + + if (!parse_precision (l_.next ())) + return false; + } + else if (id == "DATETIME") + { + r_.type = sql_type::DATETIME; + } + else if (id == "DATETIME2") + { + r_.type = sql_type::DATETIME2; + + r_.has_scale = true; + r_.scale = 7; + + if (!parse_precision (l_.next ())) + return false; + } + else if (id == "SMALLDATETIME") + { + r_.type = sql_type::SMALLDATETIME; + } + else if (id == "DATETIMEOFFSET") + { + r_.type = sql_type::DATETIMEOFFSET; + + r_.has_scale = true; + r_.scale = 7; + + if (!parse_precision (l_.next ())) + return false; + } + else if (id == "UNIQUEIDENTIFIER") + { + r_.type = sql_type::UNIQUEIDENTIFIER; + } + else if (id == "ROWVERSION" || + id == "TIMESTAMP") + { + r_.type = sql_type::ROWVERSION; + } + else + { + m_ = "unexpected SQL Server type name '" + t.identifier () + "'"; + return false; + } + + return true; + } + + bool + parse_precision (sql_token t) + { + if (t.punctuation () == sql_token::p_lparen) + { + // Parse the precision. + // + t = l_.next (); + + if (t.type () == sql_token::t_identifier && + upcase (t.identifier ()) == "MAX") + { + r_.prec = 0; + r_.has_prec = true; + } + else if (t.type () == sql_token::t_int_lit) + { + unsigned short v; + istringstream is (t.literal ()); + + if (!(is >> v && is.eof ())) + { + m_ = "invalid precision value '" + t.literal () + "' in SQL " + "Server type declaration"; + return false; + } + + switch (r_.type) + { + case sql_type::TIME: + case sql_type::DATETIME2: + case sql_type::DATETIMEOFFSET: + { + r_.scale = v; + r_.has_scale = true; + break; + } + default: + { + r_.prec = v; + r_.has_prec = true; + break; + } + } + } + else + { + m_ = "integer precision expected in SQL Server type declaration"; + return false; + } + + // Parse the scale if present. + // + t = l_.next (); + + if (t.punctuation () == sql_token::p_comma) + { + // Scale can only be specified for the DECIMAL type. + // + if (r_.type != sql_type::DECIMAL) + { + m_ = "unexpected scale in SQL Server type declaration"; + return false; + } + + t = l_.next (); + + if (t.type () != sql_token::t_int_lit) + { + m_ = "integer scale expected in SQL Server type declaration"; + return false; + } + + istringstream is (t.literal ()); + + if (!(is >> r_.scale && is.eof ())) + { + m_ = "invalid scale value '" + t.literal () + "' in SQL " + "Server type declaration"; + return false; + } + + r_.has_scale = true; + t = l_.next (); + } + + if (t.punctuation () != sql_token::p_rparen) + { + m_ = "expected ')' in SQL Server type declaration"; + return false; + } + } + + return true; + } + + bool + parse_char_trailer (bool nat) + { + sql_token t (l_.next ()); + + string id; + + if (t.type () == sql_token::t_identifier) + id = upcase (t.identifier ()); + + if (id == "VARYING") + { + r_.type = nat ? sql_type::NVARCHAR : sql_type::VARCHAR; + t = l_.next (); + } + else + r_.type = nat ? sql_type::NCHAR : sql_type::CHAR; + + r_.has_prec = true; + r_.prec = 1; + + return parse_precision (t); + } + + private: + string + upcase (string const& s) + { + return context::upcase (s); + } + + private: + custom_db_types const* ct_; + sql_lexer l_; + sql_type r_; + string m_; // Error message. + }; + } + + 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 (); + } + } + } + + sql_type context:: + parse_sql_type (string const& sqlt, custom_db_types const* ct) + { + sql_parser p (ct); + return p.parse (sqlt); + } + } +} diff --git a/odb/odb/relational/mssql/context.hxx b/odb/odb/relational/mssql/context.hxx new file mode 100644 index 0000000..7701aaa --- /dev/null +++ b/odb/odb/relational/mssql/context.hxx @@ -0,0 +1,194 @@ +// file : odb/relational/mssql/context.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_RELATIONAL_MSSQL_CONTEXT_HXX +#define ODB_RELATIONAL_MSSQL_CONTEXT_HXX + +#include <map> + +#include <odb/relational/context.hxx> + +namespace relational +{ + namespace mssql + { + struct sql_type + { + // Keep the order in each block of types. + // + enum core_type + { + // Integral types. + // + BIT, + TINYINT, + SMALLINT, + INT, + BIGINT, + + // Fixed and floating point types. + // + DECIMAL, + SMALLMONEY, + MONEY, + FLOAT, + + // String and binary types. + // + CHAR, + VARCHAR, + TEXT, + + NCHAR, + NVARCHAR, + NTEXT, + + BINARY, + VARBINARY, + IMAGE, + + // Date-time types. + // + DATE, + TIME, + DATETIME, + DATETIME2, + SMALLDATETIME, + DATETIMEOFFSET, + + // Other types. + // + UNIQUEIDENTIFIER, + ROWVERSION, + + // Invalid type. + // + invalid + }; + + sql_type () : + type (invalid), + has_prec (false), prec (0), + has_scale (false), scale (0) + { + } + + core_type type; + + bool has_prec; + unsigned short prec; // Max numeric value is 8000. 0 indicates + // 'max' as in VARCHAR(max). + bool has_scale; + unsigned short scale; // Max value is 38. + + // Conversion expressions for custom database types. + // + std::string to; + std::string from; + }; + + class context: public virtual relational::context + { + public: + sql_type const& + parse_sql_type (string const&, + semantics::data_member&, + bool custom = true); + + // Return true if this type is long data. + // + bool + long_data (sql_type const&); + + public: + struct invalid_sql_type + { + invalid_sql_type (string const& message): message_ (message) {} + + string const& + message () const {return message_;} + + private: + string message_; + }; + + // If custom_db_types is NULL, then this function returns + // invalid type instead of throwing in case an unknown type + // is encountered. + // + static sql_type + parse_sql_type (string const&, custom_db_types const* = 0); + + protected: + virtual string const& + convert_expr (string const&, semantics::data_member&, bool); + + virtual string + quote_id_impl (qname const&) const; + + protected: + virtual string + database_type_impl (semantics::type&, semantics::names*, bool, bool*); + + public: + virtual + ~context (); + + context (); + context (std::ostream&, + semantics::unit&, + options_type const&, + features_type&, + sema_rel::model*); + + static context& + current () + { + return *current_; + } + + private: + static context* current_; + + private: + struct data: base_context::data + { + data (std::ostream& os): base_context::data (os) {} + + struct sql_type_cache_entry + { + sql_type_cache_entry () + : custom_cached (false), straight_cached (false) {} + + sql_type const& + cache_custom (sql_type const& t) + { + custom = t; + custom_cached = true; + return custom; + } + + sql_type const& + cache_straight (sql_type const& t) + { + straight = t; + straight_cached = true; + return straight; + } + + sql_type custom; // With custom mapping. + sql_type straight; // Without custom mapping. + + bool custom_cached; + bool straight_cached; + }; + + typedef std::map<string, sql_type_cache_entry> sql_type_cache; + sql_type_cache sql_type_cache_; + }; + data* data_; + }; + } +} + +#endif // ODB_RELATIONAL_MSSQL_CONTEXT_HXX diff --git a/odb/odb/relational/mssql/header.cxx b/odb/odb/relational/mssql/header.cxx new file mode 100644 index 0000000..ebdc734 --- /dev/null +++ b/odb/odb/relational/mssql/header.cxx @@ -0,0 +1,312 @@ +// file : odb/relational/mssql/header.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <odb/relational/header.hxx> + +#include <odb/relational/mssql/common.hxx> +#include <odb/relational/mssql/context.hxx> + +namespace relational +{ + namespace mssql + { + namespace header + { + namespace relational = relational::header; + + struct class1: relational::class1, context + { + class1 (base const& x): base (x) {} + + virtual void + object_public_extra_pre (type& c) + { + bool abst (abstract (c)); + + type* poly_root (polymorphic (c)); + bool poly (poly_root != 0); + bool poly_derived (poly && poly_root != &c); + + if (poly_derived || (abst && !poly)) + return; + + // Bulk operations batch size. + // + { + unsigned long long b (c.count ("bulk") + ? c.get<unsigned long long> ("bulk") + : 1); + + os << "static const std::size_t batch = " << b << "UL;" + << endl; + } + + // rowvesion + // + bool rv (false); + if (semantics::data_member* m = optimistic (c)) + { + sql_type t (parse_sql_type (column_type (*m), *m)); + rv = (t.type == sql_type::ROWVERSION); + } + + os << "static const bool rowversion = " << rv << ";" + << endl; + + // Disable bulk update if we have ROWVERSION since we don't + // yet support batch extraction of the version. + // + if (rv && c.count ("bulk-update")) + c.remove ("bulk-update"); + } + + virtual void + object_public_extra_post (type& c) + { + bool abst (abstract (c)); + + type* poly_root (polymorphic (c)); + bool poly (poly_root != 0); + bool poly_derived (poly && poly_root != &c); + + if (poly_derived || (abst && !poly)) + return; + + if (semantics::data_member* m = optimistic (c)) + { + sql_type t (parse_sql_type (column_type (*m), *m)); + if (t.type == sql_type::ROWVERSION) + { + os << "static version_type" << endl + << "version (const id_image_type&);" + << endl; + } + } + } + }; + entry<class1> class1_entry_; + + struct section_traits: relational::section_traits, context + { + section_traits (base const& x): base (x) {} + + virtual void + section_public_extra_pre (user_section&) + { + if (abstract (c_) && !polymorphic (c_)) + return; + + // rowvesion + // + bool rv (false); + if (semantics::data_member* m = optimistic (c_)) + { + sql_type t (parse_sql_type (column_type (*m), *m)); + rv = (t.type == sql_type::ROWVERSION); + } + + os << "static const bool rowversion = " << rv << ";" + << endl; + } + }; + entry<section_traits> section_traits_; + + struct image_type: relational::image_type, context + { + image_type (base const& x): base (x) {}; + + virtual void + image_extra (type& c) + { + if (!(composite (c) || (abstract (c) && !polymorphic (c)))) + { + type* poly_root (polymorphic (c)); + + // If this is a polymorphic type, only add callback to the root. + // + if (poly_root == 0 || poly_root == &c) + { + bool gc (options.generate_query ()); + + if (gc) + os << "mssql::change_callback change_callback_;" + << endl; + + os << "mssql::change_callback*" << endl + << "change_callback ()" + << "{"; + + if (gc) + os << "return &change_callback_;"; + else + os << "return 0;"; + + os << "}"; + } + } + } + }; + entry<image_type> image_type_; + + struct image_member: relational::image_member_impl<sql_type>, + member_base + { + image_member (base const& x) + : member_base::base (x), // virtual base + member_base::base_impl (x), // virtual base + base_impl (x), + member_base (x) {} + + virtual void + traverse_integer (member_info& mi) + { + os << image_type << " " << mi.var << "value;" + << "SQLLEN " << mi.var << "size_ind;" + << endl; + } + + virtual void + traverse_decimal (member_info& mi) + { + os << image_type << " " << mi.var << "value;" + << "SQLLEN " << mi.var << "size_ind;" + << endl; + } + + virtual void + traverse_smallmoney (member_info& mi) + { + os << image_type << " " << mi.var << "value;" + << "SQLLEN " << mi.var << "size_ind;" + << endl; + } + + virtual void + traverse_money (member_info& mi) + { + os << image_type << " " << mi.var << "value;" + << "SQLLEN " << mi.var << "size_ind;" + << endl; + } + + virtual void + traverse_float4 (member_info& mi) + { + os << image_type << " " << mi.var << "value;" + << "SQLLEN " << mi.var << "size_ind;" + << endl; + } + + virtual void + traverse_float8 (member_info& mi) + { + os << image_type << " " << mi.var << "value;" + << "SQLLEN " << mi.var << "size_ind;" + << endl; + } + + virtual void + traverse_string (member_info& mi) + { + // Extra character for the null-terminator that ODBC always adds. + // + os << "char " << mi.var << "value[" << mi.st->prec + 1 << "];" + << "SQLLEN " << mi.var << "size_ind;" + << endl; + } + + virtual void + traverse_long_string (member_info& mi) + { + os << "mutable " << image_type << " " << mi.var << "callback;" + << "SQLLEN " << mi.var << "size_ind;" + << endl; + } + + virtual void + traverse_nstring (member_info& mi) + { + // Extra character for the null-terminator that ODBC always adds. + // + os << "mssql::ucs2_char " << mi.var << "value[" << + mi.st->prec + 1 << "];" + << "SQLLEN " << mi.var << "size_ind;" + << endl; + } + + virtual void + traverse_long_nstring (member_info& mi) + { + os << "mutable " << image_type << " " << mi.var << "callback;" + << "SQLLEN " << mi.var << "size_ind;" + << endl; + } + + virtual void + traverse_binary (member_info& mi) + { + os << "char " << mi.var << "value[" << mi.st->prec << "];" + << "SQLLEN " << mi.var << "size_ind;" + << endl; + } + + virtual void + traverse_long_binary (member_info& mi) + { + os << "mutable " << image_type << " " << mi.var << "callback;" + << "SQLLEN " << mi.var << "size_ind;" + << endl; + } + + virtual void + traverse_date (member_info& mi) + { + os << image_type << " " << mi.var << "value;" + << "SQLLEN " << mi.var << "size_ind;" + << endl; + } + + virtual void + traverse_time (member_info& mi) + { + os << image_type << " " << mi.var << "value;" + << "SQLLEN " << mi.var << "size_ind;" + << endl; + } + + virtual void + traverse_datetime (member_info& mi) + { + os << image_type << " " << mi.var << "value;" + << "SQLLEN " << mi.var << "size_ind;" + << endl; + } + + virtual void + traverse_datetimeoffset (member_info& mi) + { + os << image_type << " " << mi.var << "value;" + << "SQLLEN " << mi.var << "size_ind;" + << endl; + } + + virtual void + traverse_uniqueidentifier (member_info& mi) + { + os << image_type << " " << mi.var << "value;" + << "SQLLEN " << mi.var << "size_ind;" + << endl; + } + + virtual void + traverse_rowversion (member_info& mi) + { + os << "unsigned char " << mi.var << "value[8];" + << "SQLLEN " << mi.var << "size_ind;" + << endl; + } + }; + entry<image_member> image_member_; + } + } +} diff --git a/odb/odb/relational/mssql/inline.cxx b/odb/odb/relational/mssql/inline.cxx new file mode 100644 index 0000000..eb581d6 --- /dev/null +++ b/odb/odb/relational/mssql/inline.cxx @@ -0,0 +1,42 @@ +// file : odb/relational/mssql/inline.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <odb/relational/inline.hxx> + +#include <odb/relational/mssql/common.hxx> +#include <odb/relational/mssql/context.hxx> + +using namespace std; + +namespace relational +{ + namespace mssql + { + namespace inline_ + { + namespace relational = relational::inline_; + + struct null_member: relational::null_member_impl<sql_type>, + member_base + { + null_member (base const& x) + : member_base::base (x), // virtual base + member_base::base_impl (x), // virtual base + base_impl (x), + member_base (x) + { + } + + virtual void + traverse_simple (member_info& mi) + { + if (get_) + os << "r = r && i." << mi.var << "size_ind == SQL_NULL_DATA;"; + else + os << "i." << mi.var << "size_ind = SQL_NULL_DATA;"; + } + }; + entry<null_member> null_member_; + } + } +} diff --git a/odb/odb/relational/mssql/model.cxx b/odb/odb/relational/mssql/model.cxx new file mode 100644 index 0000000..0f5a85c --- /dev/null +++ b/odb/odb/relational/mssql/model.cxx @@ -0,0 +1,66 @@ +// file : odb/relational/mssql/model.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <sstream> + +#include <odb/relational/model.hxx> + +#include <odb/relational/mssql/common.hxx> +#include <odb/relational/mssql/context.hxx> + +using namespace std; + +namespace relational +{ + namespace mssql + { + namespace model + { + namespace relational = relational::model; + + struct object_columns: relational::object_columns, context + { + object_columns (base const& x): base (x) {} + + virtual string + default_enum (semantics::data_member& m, tree en, string const&) + { + // Make sure the column is mapped to an integer or DECIMAL type. + // + switch (parse_sql_type (column_type (), m, false).type) + { + case sql_type::BIT: + case sql_type::TINYINT: + case sql_type::SMALLINT: + case sql_type::INT: + case sql_type::BIGINT: + case sql_type::DECIMAL: + break; + default: + { + cerr << m.file () << ":" << m.line () << ":" << m.column () + << ": error: column with default value specified as C++ " + << "enumerator must map to SQL Server integer type" << endl; + + throw operation_failed (); + } + } + + using semantics::enumerator; + + enumerator& e (dynamic_cast<enumerator&> (*unit.find (en))); + + ostringstream ostr; + + if (e.enum_ ().unsigned_ ()) + ostr << e.value (); + else + ostr << static_cast<long long> (e.value ()); + + return ostr.str (); + } + }; + entry<object_columns> object_columns_; + } + } +} diff --git a/odb/odb/relational/mssql/schema.cxx b/odb/odb/relational/mssql/schema.cxx new file mode 100644 index 0000000..c5f6bc1 --- /dev/null +++ b/odb/odb/relational/mssql/schema.cxx @@ -0,0 +1,651 @@ +// file : odb/relational/mssql/schema.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <odb/relational/schema.hxx> + +#include <odb/relational/mssql/common.hxx> +#include <odb/relational/mssql/context.hxx> + +using namespace std; + +namespace relational +{ + namespace mssql + { + namespace schema + { + namespace relational = relational::schema; + using relational::table_set; + + struct sql_emitter: relational::sql_emitter + { + sql_emitter (const base& x): base (x) {} + + virtual void + post () + { + if (!first_) // Ignore empty statements. + { + os << ';' << endl + << "GO" << endl + << endl; + } + } + }; + entry<sql_emitter> sql_emitter_; + + // + // File. + // + + struct sql_file: relational::sql_file, context + { + sql_file (const base& x): base (x) {} + + virtual void + prologue () + { + // Suppress the (x rows affected) messages from sqlcmd for DML + // statements. We only use DML for schema version management. + // + if ((model == 0 || model->version () != 0) && + !options.suppress_schema_version ()) + os << "SET NOCOUNT ON;" << endl + << endl; + } + }; + entry<sql_file> sql_file_; + + // + // Drop. + // + + struct drop_column: relational::drop_column, context + { + drop_column (base const& x): base (x) {} + + virtual void + traverse (sema_rel::drop_column& dc) + { + if (first_) + first_ = false; + else + os << "," << endl + << " "; + + os << quote_id (dc.name ()); + } + }; + entry<drop_column> drop_column_; + + struct drop_foreign_key: relational::drop_foreign_key, context + { + drop_foreign_key (base const& x): base (x) {} + + virtual void + drop (sema_rel::table& t, sema_rel::foreign_key& fk) + { + bool migration (dropped_ == 0); + + if (migration) + { + if (fk.not_deferrable ()) + pre_statement (); + else + { + if (format_ != schema_format::sql) + return; + + os << "/*" << endl; + } + } + else + { + // Here we drop potentially deferrable keys and also need to + // test if the key exists. + // + pre_statement (); + + os << "IF OBJECT_ID(" << quote_string (fk.name ()) << ", " << + quote_string ("F") << ") IS NOT NULL" << endl + << " "; + } + + os << "ALTER TABLE " << quote_id (t.name ()) << endl + << (migration ? " " : " ") << "DROP CONSTRAINT " << + quote_id (fk.name ()) << endl; + + + if (!migration || fk.not_deferrable ()) + post_statement (); + else + os << "*/" << endl + << endl; + } + + virtual void + traverse (sema_rel::drop_foreign_key& dfk) + { + // Find the foreign key we are dropping in the base model. + // + sema_rel::foreign_key& fk (find<sema_rel::foreign_key> (dfk)); + + bool c (!fk.not_deferrable () && !in_comment); + + if (c && format_ != schema_format::sql) + return; + + if (!first_) + os << (c ? "" : ",") << endl + << " "; + + if (c) + os << "/* "; + + os << quote_id (fk.name ()); + + if (c) + os << " */"; + + if (first_) + { + if (c) + // There has to be a real name otherwise the whole statement + // would have been commented out. + // + os << endl + << " "; + else + first_ = false; + } + } + }; + entry<drop_foreign_key> drop_foreign_key_; + + struct drop_index: relational::drop_index, context + { + drop_index (base const& x): base (x) {} + + virtual void + drop (sema_rel::index& in) + { + sema_rel::table& t (static_cast<sema_rel::table&> (in.scope ())); + + os << "DROP INDEX " << name (in) << " ON " << + quote_id (t.name ()) << endl; + } + }; + entry<drop_index> drop_index_; + + struct drop_table: relational::drop_table, context + { + drop_table (base const& x): base (x) {} + + virtual void + drop (sema_rel::table& t, bool migration) + { + // SQL Server has no IF EXISTS conditional for dropping tables. + // The following approach appears to be the recommended way to + // drop a table if it exists. + // + sema_rel::qname const& name (t.name ()); + + pre_statement (); + + if (!migration) + os << "IF OBJECT_ID(" << quote_string (name.string ()) << + ", " << quote_string ("U") << ") IS NOT NULL" << endl + << " "; + + os << "DROP TABLE " << quote_id (name) << endl; + + post_statement (); + } + }; + entry<drop_table> drop_table_; + + // + // Create. + // + + struct create_column: relational::create_column, context + { + create_column (base const& x): base (x) {} + + virtual void + traverse (sema_rel::add_column& ac) + { + if (first_) + first_ = false; + else + os << "," << endl + << " "; + + create (ac); + } + + virtual void + auto_ (sema_rel::primary_key&) + { + os << " IDENTITY"; + } + }; + entry<create_column> create_column_; + + struct create_foreign_key: relational::create_foreign_key, context + { + create_foreign_key (base const& x): base (x) {} + + void + diagnose (sema_rel::foreign_key& fk) + { + if (fk.on_delete () != sema_rel::foreign_key::no_action) + { + cerr << "warning: foreign key '" << fk.name () << "' has " << + "ON DELETE clause but is disabled in SQL Server due to lack " + "of deferrable constraint support" << endl; + + cerr << "info: consider using non-deferrable foreign keys (" << + "--fkeys-deferrable-mode)" << endl; + } + } + + virtual void + traverse_create (sema_rel::foreign_key& fk) + { + // SQL Server does not support deferrable constraint checking. + // Output such foreign keys as comments, for documentation, + // unless we are generating embedded schema. + // + if (fk.not_deferrable ()) + base::traverse_create (fk); + else + { + diagnose (fk); + + // Don't bloat C++ code with comment strings if we are + // generating embedded schema. + // + if (format_ != schema_format::sql) + return; + + os << endl + << " /*" << endl + << " CONSTRAINT "; + create (fk); + os << endl + << " */"; + } + } + + virtual void + traverse_add (sema_rel::foreign_key& fk) + { + bool c (!fk.not_deferrable () && !in_comment); + + if (c) + diagnose (fk); + + if (c && format_ != schema_format::sql) + return; + + if (!first_) + os << (c ? "" : ",") << endl + << " "; + + if (c) + os << "/*" << endl + << " "; + + os << "CONSTRAINT "; + create (fk); + + if (c) + os << endl + << " */"; + + if (first_) + { + if (c) + // There has to be a real key otherwise the whole statement + // would have been commented out. + // + os << endl + << " "; + else + first_ = false; + } + } + + virtual void + deferrable (sema_rel::deferrable) + { + // This will still be called to output the comment. + } + }; + entry<create_foreign_key> create_foreign_key_; + + struct create_table: relational::create_table, context + { + create_table (base const& x): base (x) {} + + // See if there are any undefined foreign keys that are not + // deferrable. + // + bool + check_undefined_fk_deferrable_only (sema_rel::table& t) + { + for (sema_rel::table::names_iterator i (t.names_begin ()); + i != t.names_end (); ++i) + { + using sema_rel::foreign_key; + + if (foreign_key* fk = dynamic_cast<foreign_key*> (&i->nameable ())) + { + if (!fk->count ("mssql-fk-defined") && + fk->not_deferrable ()) + return false; + } + } + return true; + } + + virtual void + traverse (sema_rel::table& t) + { + if (pass_ == 1) + base::traverse (t); + else + { + // Add undefined foreign keys. + // + if (check_undefined_fk (t)) + { + bool deferrable (check_undefined_fk_deferrable_only (t)); + + if (!deferrable || format_ == schema_format::sql) + { + if (deferrable) + { + os << "/*" << endl; + in_comment = true; + } + else + pre_statement (); + + os << "ALTER TABLE " << quote_id (t.name ()) << endl + << " ADD "; + + instance<create_foreign_key> cfk (*this); + trav_rel::unames n (*cfk); + names (t, n); + os << endl; + + if (deferrable) + { + in_comment = false; + os << "*/" << endl + << endl; + } + else + post_statement (); + } + } + } + } + }; + entry<create_table> create_table_; + + // + // Alter. + // + + struct alter_column: relational::alter_column, context + { + alter_column (base const& x): base (x) {} + + virtual void + traverse (sema_rel::column& c) + { + // Relax (NULL) in pre and tighten (NOT NULL) in post. + // + if (pre_ != c.null ()) + return; + + using sema_rel::table; + table& at (static_cast<table&> (c.scope ())); + + pre_statement (); + + os << "ALTER TABLE " << quote_id (at.name ()) << endl + << " ALTER COLUMN "; + alter (c); + os << endl; + + post_statement (); + } + }; + entry<alter_column> alter_column_; + + struct alter_table_pre: relational::alter_table_pre, context + { + alter_table_pre (base const& x): base (x) {} + + // Check if we are only dropping deferrable foreign keys. + // + bool + check_drop_deferrable_only (sema_rel::alter_table& at) + { + for (sema_rel::alter_table::names_iterator i (at.names_begin ()); + i != at.names_end (); ++i) + { + using sema_rel::foreign_key; + using sema_rel::drop_foreign_key; + + if (drop_foreign_key* dfk = + dynamic_cast<drop_foreign_key*> (&i->nameable ())) + { + foreign_key& fk (find<foreign_key> (*dfk)); + + if (fk.not_deferrable ()) + return false; + } + } + return true; + } + + virtual void + alter (sema_rel::alter_table& at) + { + // SQL Server can only alter one kind of thing at a time. + // + if (check<sema_rel::drop_foreign_key> (at)) + { + bool deferrable (check_drop_deferrable_only (at)); + + if (!deferrable || format_ == schema_format::sql) + { + if (deferrable) + { + os << "/*" << endl; + in_comment = true; + } + else + pre_statement (); + + os << "ALTER TABLE " << quote_id (at.name ()) << endl + << " DROP CONSTRAINT "; + + instance<drop_foreign_key> dfc (*this); + trav_rel::unames n (*dfc); + names (at, n); + os << endl; + + if (deferrable) + { + in_comment = false; + os << "*/" << endl + << endl; + } + else + post_statement (); + } + } + + if (check<sema_rel::add_column> (at)) + { + pre_statement (); + + os << "ALTER TABLE " << quote_id (at.name ()) << endl + << " ADD "; + + instance<create_column> cc (*this); + trav_rel::unames n (*cc); + names (at, n); + os << endl; + + post_statement (); + } + + // For ALTER COLUMN, SQL Server can only have one per ALTER TABLE. + // + { + bool tl (true); // (Im)perfect forwarding. + instance<alter_column> ac (*this, tl); + trav_rel::unames n (*ac); + names (at, n); + } + } + }; + entry<alter_table_pre> alter_table_pre_; + + struct alter_table_post: relational::alter_table_post, context + { + alter_table_post (base const& x): base (x) {} + + // Check if we are only adding deferrable foreign keys. + // + bool + check_add_deferrable_only (sema_rel::alter_table& at) + { + for (sema_rel::alter_table::names_iterator i (at.names_begin ()); + i != at.names_end (); ++i) + { + using sema_rel::add_foreign_key; + + if (add_foreign_key* afk = + dynamic_cast<add_foreign_key*> (&i->nameable ())) + { + if (afk->not_deferrable ()) + return false; + } + } + return true; + } + + virtual void + alter (sema_rel::alter_table& at) + { + // SQL Server can only alter one kind of thing at a time. + // + if (check<sema_rel::drop_column> (at)) + { + pre_statement (); + + os << "ALTER TABLE " << quote_id (at.name ()) << endl + << " DROP COLUMN "; + + instance<drop_column> dc (*this); + trav_rel::unames n (*dc); + names (at, n); + os << endl; + + post_statement (); + } + + // For ALTER COLUMN, SQL Server can only have one per ALTER TABLE. + // + { + bool fl (false); // (Im)perfect forwarding. + instance<alter_column> ac (*this, fl); + trav_rel::unames n (*ac); + names (at, n); + } + + if (check<sema_rel::add_foreign_key> (at)) + { + bool deferrable (check_add_deferrable_only (at)); + + if (!deferrable || format_ == schema_format::sql) + { + if (deferrable) + { + os << "/*" << endl; + in_comment = true; + } + else + pre_statement (); + + os << "ALTER TABLE " << quote_id (at.name ()) << endl + << " ADD "; + + instance<create_foreign_key> cfc (*this); + trav_rel::unames n (*cfc); + names (at, n); + os << endl; + + if (deferrable) + { + in_comment = false; + os << "*/" << endl + << endl; + } + else + post_statement (); + } + } + } + }; + entry<alter_table_post> alter_table_post_; + + // + // Schema version table. + // + + struct version_table: relational::version_table, context + { + version_table (base const& x): base (x) {} + + virtual void + create_table () + { + pre_statement (); + + os << "IF OBJECT_ID(" << quote_string (table_.string ()) << + ", " << quote_string ("U") << ") IS NULL" << endl + << " CREATE TABLE " << qt_ << " (" << endl + << " " << qn_ << " VARCHAR(256) NOT NULL PRIMARY KEY," << endl + << " " << qv_ << " BIGINT NOT NULL," << endl + << " " << qm_ << " BIT NOT NULL)" << endl; + + post_statement (); + } + + virtual void + create (sema_rel::version v) + { + pre_statement (); + + os << "IF NOT EXISTS (SELECT 1 FROM " << qt_ << " WHERE " << qn_ << + " = " << qs_ << ")" << endl + << " INSERT INTO " << qt_ << " (" << endl + << " " << qn_ << ", " << qv_ << ", " << qm_ << ")" << endl + << " VALUES (" << qs_ << ", " << v << ", 0)" << endl; + + post_statement (); + } + }; + entry<version_table> version_table_; + } + } +} diff --git a/odb/odb/relational/mssql/source.cxx b/odb/odb/relational/mssql/source.cxx new file mode 100644 index 0000000..573104d --- /dev/null +++ b/odb/odb/relational/mssql/source.cxx @@ -0,0 +1,1201 @@ +// file : odb/relational/mssql/source.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <odb/relational/source.hxx> + +#include <odb/relational/mssql/common.hxx> +#include <odb/relational/mssql/context.hxx> + +using namespace std; + +namespace relational +{ + namespace mssql + { + namespace source + { + namespace relational = relational::source; + + // + // + struct query_parameters: relational::query_parameters + { + query_parameters (base const& x): base (x) {} + + virtual string + auto_id (semantics::data_member&, const string&, const string&) + { + return ""; + } + }; + entry<query_parameters> query_parameters_; + + // + // + struct object_columns: relational::object_columns, context + { + object_columns (base const& x) + : base (x), rowversion_ (false), column_count_ (0) {} + + virtual bool + column (semantics::data_member& m, + string const& table, + string const& column) + { + // Don't add a column for auto id in the INSERT statement. + // Only simple, direct id can be auto. + // + if (sk_ == statement_insert && key_prefix_.empty () && auto_ (m)) + return false; + + // Don't update the ROWVERSION column explicitly. + // + if (sk_ == statement_update) + { + sql_type t (parse_sql_type (column_type (), m)); + if (t.type == sql_type::ROWVERSION) + { + rowversion_ = true; + return false; + } + } + + bool r (base::column (m, table, column)); + + // Count the number of columns in the UPDATE statement, but + // excluding soft-deleted. + // + if (sk_ == statement_update && r && !deleted (member_path_)) + column_count_++; + + return r; + } + + virtual void + traverse_post (semantics::nameable& n) + { + if (rowversion_ && column_count_ == 0) + { + location l (n.location ()); + error (l) << "ROWVERSION in an object without any readwrite " + "data members" << endl; + error (l) << "UPDATE statement will be empty" << endl; + throw operation_failed (); + } + } + + private: + bool rowversion_; + size_t column_count_; + }; + entry<object_columns> object_columns_; + + // + // + struct persist_statement_params: relational::persist_statement_params, + context + { + persist_statement_params (base const& x): base (x) {} + + virtual string + version_value (semantics::data_member& m) + { + sql_type t (parse_sql_type (column_type (), m)); + return t.type == sql_type::ROWVERSION ? "DEFAULT" : "1"; + } + }; + entry<persist_statement_params> persist_statement_params_; + + // + // bind + // + + static const char* integer_buffer_types[] = + { + "mssql::bind::bit", + "mssql::bind::tinyint", + "mssql::bind::smallint", + "mssql::bind::int_", + "mssql::bind::bigint" + }; + + struct bind_member: relational::bind_member_impl<sql_type>, + member_base + { + bind_member (base const& x) + : member_base::base (x), // virtual base + member_base::base_impl (x), // virtual base + base_impl (x), + member_base (x) + { + } + + virtual void + traverse_integer (member_info& mi) + { + os << b << ".type = " << + integer_buffer_types[mi.st->type - sql_type::BIT] << ";" + << b << ".buffer = &" << arg << "." << mi.var << "value;" + << b << ".size_ind = &" << arg << "." << mi.var << "size_ind;"; + } + + virtual void + traverse_decimal (member_info& mi) + { + os << b << ".type = mssql::bind::decimal;" + << b << ".buffer = &" << arg << "." << mi.var << "value;" + << b << ".size_ind = &" << arg << "." << mi.var << "size_ind;" + // Encode precision (p) and scale (s) as (p * 100 + s). + // + << b << ".capacity = " << mi.st->prec * 100 + mi.st->scale << ";"; + } + + virtual void + traverse_smallmoney (member_info& mi) + { + os << b << ".type = mssql::bind::smallmoney;" + << b << ".buffer = &" << arg << "." << mi.var << "value;" + << b << ".size_ind = &" << arg << "." << mi.var << "size_ind;"; + } + + virtual void + traverse_money (member_info& mi) + { + os << b << ".type = mssql::bind::money;" + << b << ".buffer = &" << arg << "." << mi.var << "value;" + << b << ".size_ind = &" << arg << "." << mi.var << "size_ind;"; + } + + virtual void + traverse_float4 (member_info& mi) + { + os << b << ".type = mssql::bind::float4;" + << b << ".buffer = &" << arg << "." << mi.var << "value;" + << b << ".size_ind = &" << arg << "." << mi.var << "size_ind;" + << b << ".capacity = " << mi.st->prec << ";"; + } + + virtual void + traverse_float8 (member_info& mi) + { + os << b << ".type = mssql::bind::float8;" + << b << ".buffer = &" << arg << "." << mi.var << "value;" + << b << ".size_ind = &" << arg << "." << mi.var << "size_ind;" + << b << ".capacity = " << mi.st->prec << ";"; + } + + virtual void + traverse_string (member_info& mi) + { + os << b << ".type = mssql::bind::string;" + << b << ".buffer = &" << arg << "." << mi.var << "value;" + << b << ".size_ind = &" << arg << "." << mi.var << "size_ind;" + << b << ".capacity = static_cast<SQLLEN> (sizeof (" << + arg << "." << mi.var << "value));"; + } + + virtual void + traverse_long_string (member_info& mi) + { + os << b << ".type = mssql::bind::long_string;" + << b << ".buffer = &" << arg << "." << mi.var << "callback;" + << b << ".size_ind = &" << arg << "." << mi.var << "size_ind;" + // Encode the column size with 0 indicating unlimited. + // + << b << ".capacity = " << mi.st->prec << ";"; + } + + virtual void + traverse_nstring (member_info& mi) + { + os << b << ".type = mssql::bind::nstring;" + << b << ".buffer = &" << arg << "." << mi.var << "value;" + << b << ".size_ind = &" << arg << "." << mi.var << "size_ind;" + << b << ".capacity = static_cast<SQLLEN> (sizeof (" << + arg << "." << mi.var << "value));"; + } + + virtual void + traverse_long_nstring (member_info& mi) + { + os << b << ".type = mssql::bind::long_nstring;" + << b << ".buffer = &" << arg << "." << mi.var << "callback;" + << b << ".size_ind = &" << arg << "." << mi.var << "size_ind;" + // Encode the column size (in bytes) with 0 indicating unlimited. + // + << b << ".capacity = " << mi.st->prec * 2 << ";"; + } + + virtual void + traverse_binary (member_info& mi) + { + os << b << ".type = mssql::bind::binary;" + << b << ".buffer = &" << arg << "." << mi.var << "value;" + << b << ".size_ind = &" << arg << "." << mi.var << "size_ind;" + << b << ".capacity = static_cast<SQLLEN> (sizeof (" << + arg << "." << mi.var << "value));"; + } + + virtual void + traverse_long_binary (member_info& mi) + { + os << b << ".type = mssql::bind::long_binary;" + << b << ".buffer = &" << arg << "." << mi.var << "callback;" + << b << ".size_ind = &" << arg << "." << mi.var << "size_ind;" + // Encode the column size with 0 indicating unlimited. + // + << b << ".capacity = " << mi.st->prec << ";"; + } + + virtual void + traverse_date (member_info& mi) + { + os << b << ".type = mssql::bind::date;" + << b << ".buffer = &" << arg << "." << mi.var << "value;" + << b << ".size_ind = &" << arg << "." << mi.var << "size_ind;"; + } + + virtual void + traverse_time (member_info& mi) + { + os << b << ".type = mssql::bind::time;" + << b << ".buffer = &" << arg << "." << mi.var << "value;" + << b << ".size_ind = &" << arg << "." << mi.var << "size_ind;" + // Encode fractional seconds (scale). + // + << b << ".capacity = " << mi.st->scale << ";"; + } + + virtual void + traverse_datetime (member_info& mi) + { + unsigned short scale (0); + + switch (mi.st->type) + { + case sql_type::DATETIME: + { + // Looks like it is 3 (rounded to 0.000, 0.003, or 0.007). + // + scale = 3; + break; + } + case sql_type::DATETIME2: + { + scale = mi.st->scale; + break; + } + case sql_type::SMALLDATETIME: + { + // No seconds in SMALLDATATIME. Encode it a special precision + // value (8). + // + scale = 8; + break; + } + default: + { + assert (false); + break; + } + } + + os << b << ".type = mssql::bind::datetime;" + << b << ".buffer = &" << arg << "." << mi.var << "value;" + << b << ".size_ind = &" << arg << "." << mi.var << "size_ind;" + // Encode fractional seconds (scale). + // + << b << ".capacity = " << scale << ";"; + } + + virtual void + traverse_datetimeoffset (member_info& mi) + { + os << b << ".type = mssql::bind::datetimeoffset;" + << b << ".buffer = &" << arg << "." << mi.var << "value;" + << b << ".size_ind = &" << arg << "." << mi.var << "size_ind;" + // Encode fractional seconds (scale). + // + << b << ".capacity = " << mi.st->scale << ";"; + } + + virtual void + traverse_uniqueidentifier (member_info& mi) + { + os << b << ".type = mssql::bind::uniqueidentifier;" + << b << ".buffer = &" << arg << "." << mi.var << "value;" + << b << ".size_ind = &" << arg << "." << mi.var << "size_ind;"; + } + + virtual void + traverse_rowversion (member_info& mi) + { + os << b << ".type = mssql::bind::rowversion;" + << b << ".buffer = &" << arg << "." << mi.var << "value;" + << b << ".size_ind = &" << arg << "." << mi.var << "size_ind;"; + } + }; + entry<bind_member> bind_member_; + + // + // init image + // + + struct init_image_member: relational::init_image_member_impl<sql_type>, + member_base + { + init_image_member (base const& x) + : member_base::base (x), // virtual base + member_base::base_impl (x), // virtual base + base_impl (x), + member_base (x) + { + } + + virtual void + set_null (member_info& mi) + { + os << "i." << mi.var << "size_ind = SQL_NULL_DATA;"; + } + + virtual void + check_accessor (member_info& mi, member_access& ma) + { + // We cannot use accessors that return by-value for long data + // members. + // + if (long_data (*mi.st) && ma.by_value) + { + error (ma.loc) << "accessor returning a value cannot be used " + << "for a data member of SQL Server long data " + << "type" << endl; + info (ma.loc) << "accessor returning a const reference is required" + << endl; + info (mi.m.location ()) << "data member is defined here" << endl; + throw operation_failed (); + } + } + + virtual void + traverse_integer (member_info& mi) + { + os << traits << "::set_image (" << endl + << "i." << mi.var << "value, is_null, " << member << ");" + << "i." << mi.var << "size_ind = is_null ? SQL_NULL_DATA : 0;"; + } + + virtual void + traverse_decimal (member_info& mi) + { + os << traits << "::set_image (" << endl + << "i." << mi.var << "value, is_null, " << member << ");" + << "i." << mi.var << "size_ind = is_null ? SQL_NULL_DATA : 0;"; + } + + virtual void + traverse_smallmoney (member_info& mi) + { + os << traits << "::set_image (" << endl + << "i." << mi.var << "value, is_null, " << member << ");" + << "i." << mi.var << "size_ind = is_null ? SQL_NULL_DATA : 4;"; + } + + virtual void + traverse_money (member_info& mi) + { + os << traits << "::set_image (" << endl + << "i." << mi.var << "value, is_null, " << member << ");" + << "i." << mi.var << "size_ind = is_null ? SQL_NULL_DATA : 8;"; + } + + virtual void + traverse_float4 (member_info& mi) + { + os << traits << "::set_image (" << endl + << "i." << mi.var << "value, is_null, " << member << ");" + << "i." << mi.var << "size_ind = is_null ? SQL_NULL_DATA : 0;"; + } + + virtual void + traverse_float8 (member_info& mi) + { + os << traits << "::set_image (" << endl + << "i." << mi.var << "value, is_null, " << member << ");" + << "i." << mi.var << "size_ind = is_null ? SQL_NULL_DATA : 0;"; + } + + virtual void + traverse_string (member_info& mi) + { + os << "std::size_t size (0);" + << traits << "::set_image (" << endl + << "i." << mi.var << "value," << endl + // Don't mention the extra character for the null-terminator. + << "sizeof (i." << mi.var << "value) - 1," << endl + << "size," << endl + << "is_null," << endl + << member << ");" + << "i." << mi.var << "size_ind =" << endl + << " is_null ? SQL_NULL_DATA : static_cast<SQLLEN> (size);"; + } + + virtual void + traverse_long_string (member_info& mi) + { + os << traits << "::set_image (" << endl + << "i." << mi.var << "callback.callback.param," << endl + << "i." << mi.var << "callback.context.param," << endl + << "is_null," << endl + << member << ");" + << "i." << mi.var << "size_ind = is_null ? " << + "SQL_NULL_DATA : SQL_DATA_AT_EXEC;"; + } + + virtual void + traverse_nstring (member_info& mi) + { + os << "std::size_t size (0);" + << traits << "::set_image (" << endl + << "i." << mi.var << "value," << endl + // Don't mention the extra character for the null-terminator. + << "sizeof (i." << mi.var << "value) / 2 - 1," << endl + << "size," << endl + << "is_null," << endl + << member << ");" + << "i." << mi.var << "size_ind =" << endl + << " is_null ? SQL_NULL_DATA : static_cast<SQLLEN> (size * 2);"; + } + + virtual void + traverse_long_nstring (member_info& mi) + { + os << traits << "::set_image (" << endl + << "i." << mi.var << "callback.callback.param," << endl + << "i." << mi.var << "callback.context.param," << endl + << "is_null," << endl + << member << ");" + << "i." << mi.var << "size_ind = is_null ? " << + "SQL_NULL_DATA : SQL_DATA_AT_EXEC;"; + } + + virtual void + traverse_binary (member_info& mi) + { + os << "std::size_t size (0);" + << traits << "::set_image (" << endl + << "i." << mi.var << "value," << endl + << "sizeof (i." << mi.var << "value)," << endl + << "size," << endl + << "is_null," << endl + << member << ");" + << "i." << mi.var << "size_ind =" << endl + << " is_null ? SQL_NULL_DATA : static_cast<SQLLEN> (size);"; + } + + virtual void + traverse_long_binary (member_info& mi) + { + os << traits << "::set_image (" << endl + << "i." << mi.var << "callback.callback.param," << endl + << "i." << mi.var << "callback.context.param," << endl + << "is_null," << endl + << member << ");" + << "i." << mi.var << "size_ind = is_null ? " << + "SQL_NULL_DATA : SQL_DATA_AT_EXEC;"; + } + + virtual void + traverse_date (member_info& mi) + { + os << traits << "::set_image (" << endl + << "i." << mi.var << "value, is_null, " << member << ");" + << "i." << mi.var << "size_ind = is_null ? SQL_NULL_DATA : 0;"; + } + + virtual void + traverse_time (member_info& mi) + { + os << traits << "::set_image (" << endl + << "i." << mi.var << "value, " << mi.st->scale << ", " << + "is_null, " << member << ");" + << "i." << mi.var << "size_ind = is_null" << endl + << " ? SQL_NULL_DATA" << endl + << " : static_cast<SQLLEN> (sizeof (i." << mi.var << "value));"; + } + + virtual void + traverse_datetime (member_info& mi) + { + // The same code as in bind. + // + unsigned short scale (0); + + switch (mi.st->type) + { + case sql_type::DATETIME: + { + scale = 3; + break; + } + case sql_type::DATETIME2: + { + scale = mi.st->scale; + break; + } + case sql_type::SMALLDATETIME: + { + scale = 8; + break; + } + default: + { + assert (false); + break; + } + } + + os << traits << "::set_image (" << endl + << "i." << mi.var << "value, " << scale << ", " << + "is_null, " << member << ");" + << "i." << mi.var << "size_ind = is_null ? SQL_NULL_DATA : 0;"; + } + + virtual void + traverse_datetimeoffset (member_info& mi) + { + os << traits << "::set_image (" << endl + << "i." << mi.var << "value, " << mi.st->scale << ", " << + "is_null, " << member << ");" + << "i." << mi.var << "size_ind = is_null" << endl + << " ? SQL_NULL_DATA" << endl + << " : static_cast<SQLLEN> (sizeof (i." << mi.var << "value));"; + } + + virtual void + traverse_uniqueidentifier (member_info& mi) + { + os << traits << "::set_image (" << endl + << "i." << mi.var << "value, is_null, " << member << ");" + << "i." << mi.var << "size_ind = is_null ? SQL_NULL_DATA : 0;"; + } + + virtual void + traverse_rowversion (member_info& mi) + { + os << traits << "::set_image (" << endl + << "i." << mi.var << "value, is_null, " << member << ");" + << "i." << mi.var << "size_ind = is_null ? SQL_NULL_DATA : 8;"; + } + }; + entry<init_image_member> init_image_member_; + + // + // init value + // + + struct init_value_member: relational::init_value_member_impl<sql_type>, + member_base + { + init_value_member (base const& x) + : member_base::base (x), // virtual base + member_base::base_impl (x), // virtual base + base_impl (x), + member_base (x) + { + } + + virtual void + get_null (string const& var) const + { + os << "i." << var << "size_ind == SQL_NULL_DATA"; + } + + virtual void + check_modifier (member_info& mi, member_access& ma) + { + // We cannot use by-value modifier for long data members. + // + if (long_data (*mi.st) && ma.placeholder ()) + { + error (ma.loc) << "modifier accepting a value cannot be used " + << "for a data member of SQL Server long data " + << "type" << endl; + info (ma.loc) << "modifier returning a non-const reference is " + << "required" << endl; + info (mi.m.location ()) << "data member is defined here" << endl; + throw operation_failed (); + } + } + + virtual void + traverse_integer (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "size_ind == SQL_NULL_DATA);" + << endl; + } + + virtual void + traverse_decimal (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "size_ind == SQL_NULL_DATA);" + << endl; + } + + virtual void + traverse_smallmoney (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "size_ind == SQL_NULL_DATA);" + << endl; + } + + virtual void + traverse_money (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "size_ind == SQL_NULL_DATA);" + << endl; + } + + virtual void + traverse_float4 (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "size_ind == SQL_NULL_DATA);" + << endl; + } + + virtual void + traverse_float8 (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "size_ind == SQL_NULL_DATA);" + << endl; + } + + virtual void + traverse_string (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "static_cast<std::size_t> (i." << mi.var << "size_ind)," << endl + << "i." << mi.var << "size_ind == SQL_NULL_DATA);" + << endl; + } + + virtual void + traverse_long_string (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "callback.callback.result," << endl + << "i." << mi.var << "callback.context.result);" + << endl; + } + + virtual void + traverse_nstring (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "static_cast<std::size_t> (" << + "i." << mi.var << "size_ind / 2)," << endl + << "i." << mi.var << "size_ind == SQL_NULL_DATA);" + << endl; + } + + virtual void + traverse_long_nstring (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "callback.callback.result," << endl + << "i." << mi.var << "callback.context.result);" + << endl; + } + + virtual void + traverse_binary (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "static_cast<std::size_t> (i." << mi.var << "size_ind)," << endl + << "i." << mi.var << "size_ind == SQL_NULL_DATA);" + << endl; + } + + virtual void + traverse_long_binary (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "callback.callback.result," << endl + << "i." << mi.var << "callback.context.result);" + << endl; + } + + virtual void + traverse_date (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "size_ind == SQL_NULL_DATA);" + << endl; + } + + virtual void + traverse_time (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "size_ind == SQL_NULL_DATA);" + << endl; + } + + virtual void + traverse_datetime (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "size_ind == SQL_NULL_DATA);" + << endl; + } + + virtual void + traverse_datetimeoffset (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "size_ind == SQL_NULL_DATA);" + << endl; + } + + virtual void + traverse_uniqueidentifier (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "size_ind == SQL_NULL_DATA);" + << endl; + } + + virtual void + traverse_rowversion (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "size_ind == SQL_NULL_DATA);" + << endl; + } + }; + entry<init_value_member> init_value_member_; + + struct statement_columns_common: context + { + void + process (relational::statement_columns& cols, + statement_kind sk, + bool dynamic) + { + using relational::statement_columns; + + // Long data columns must come last in the SELECT statement. If + // this statement is going to be processed at runtime, then this + // will be taken care of then. + // + if (sk != statement_select || dynamic) + return; + + // Go over the columns list while keeping track of how many + // columns we have examined. If the current column is long data, + // then move it to the back. Stop once we have examined all the + // columns. + // + size_t n (cols.size ()); + for (statement_columns::iterator i (cols.begin ()); n != 0; --n) + { + if (long_data (parse_sql_type (i->type, *i->member))) + { + cols.push_back (*i); + i = cols.erase (i); + } + else + ++i; + } + } + }; + + struct container_traits: relational::container_traits, + statement_columns_common + { + container_traits (base const& x): base (x) {} + + virtual void + cache_result (string const&) + { + // Caching is not necessary since with MARS enabled SQL Server + // can execute several interleaving statements. + // + } + + virtual void + init_value_extra () + { + os << "sts.select_statement ().stream_result ();" + << endl; + } + + virtual void + process_statement_columns (relational::statement_columns& cols, + statement_kind sk, + bool dynamic) + { + statement_columns_common::process (cols, sk, dynamic); + } + }; + entry<container_traits> container_traits_; + + struct section_traits: relational::section_traits, + statement_columns_common + { + section_traits (base const& x): base (x) {} + + virtual void + init_value_extra () + { + os << "st.stream_result ();"; + } + + virtual void + process_statement_columns (relational::statement_columns& cols, + statement_kind sk, + bool dynamic) + { + statement_columns_common::process (cols, sk, dynamic); + } + + virtual string + optimistic_version_increment (semantics::data_member& m) + { + sql_type t (parse_sql_type (column_type (m), m)); + return t.type != sql_type::ROWVERSION + ? "1" + : "version (sts.id_image ())"; + } + + virtual string + update_statement_extra (user_section&) + { + string r; + + semantics::data_member* ver (optimistic (c_)); + + if (ver == 0 || + parse_sql_type (column_type (*ver), *ver).type != + sql_type::ROWVERSION) + return r; + + // ROWVERSION & SQL Server 2005 incompatibility is detected + // in persist_statement_extra. + // + r = "OUTPUT INSERTED." + + convert_from (column_qname (*ver, column_prefix ()), *ver); + + return r; + } + }; + entry<section_traits> section_traits_; + + struct class_: relational::class_, statement_columns_common + { + class_ (base const& x): + base (x), init_version_value_member_id_image_ ("v", "version_") {} + + virtual void + init_image_pre (type& c) + { + if (options.generate_query () && + !(composite (c) || (abstract (c) && !polymorphic (c)))) + { + type* poly_root (polymorphic (c)); + bool poly_derived (poly_root != 0 && poly_root != &c); + + if (poly_derived) + os << "{" + << "root_traits::image_type& ri (root_image (i));" + << endl; + + string i (poly_derived ? "ri" : "i"); + + os << "if (" << i << ".change_callback_.callback != 0)" << endl + << "(" << i << ".change_callback_.callback) (" << + i << ".change_callback_.context);"; + + if (poly_derived) + os << "}"; + else + os << endl; + } + } + + virtual void + init_value_extra () + { + os << "st.stream_result ();"; + } + + virtual string + persist_statement_extra (type& c, + relational::query_parameters&, + persist_position p) + { + string r; + + type* poly_root (polymorphic (c)); + bool poly_derived (poly_root != 0 && poly_root != &c); + + // If we are a derived type in a polymorphic hierarchy, then + // auto id/version are handled by the root. + // + if (poly_derived) + return r; + + // See if we have auto id or ROWVERSION version. + // + data_member_path* id (id_member (c)); + semantics::data_member* ver (optimistic (c)); + + if (id != 0 && !auto_ (*id)) + id = 0; + + if (ver != 0) + { + sql_type t (parse_sql_type (column_type (*ver), *ver)); + if (t.type != sql_type::ROWVERSION) + ver = 0; + } + + if (id == 0 && ver == 0) + return r; + + // SQL Server 2005 has a bug that causes it to fail on an + // INSERT statement with the OUTPUT clause if data for one + // of the inserted columns is supplied at execution (long + // data). To work around this problem we use the less + // efficient batch of INSERT and SELECT statements. + // + if (options.mssql_server_version () <= mssql_version (9, 0)) + { + bool ld (false); + + if (c.count ("mssql-has-long-data")) + ld = c.get<bool> ("mssql-has-long-data"); + else + { + has_long_data t (ld); + t.traverse (c); + c.set ("mssql-has-long-data", ld); + } + + if (ld) + { + if (p == persist_after_values) + { + // SQL Server 2005 has no eqivalent of SCOPE_IDENTITY for + // ROWVERSION. + // + if (ver != 0) + { + error (c.location ()) << "in SQL Server 2005 ROWVERSION " << + "value cannot be retrieved for a persistent class " << + "containing long data" << endl; + throw operation_failed (); + } + + // We also cannot support bulk INSERT. + // + if (c.count ("bulk-persist")) + { + error (c.location ()) << "in SQL Server 2005 bulk " << + "persist operation cannot be implemented for a " << + "persistent class containing long data" << endl; + throw operation_failed (); + } + + r = "; SELECT " + + convert_from ("SCOPE_IDENTITY()", *id->back ()); + } + + return r; + } + } + + if (p == persist_after_columns) + { + r = "OUTPUT "; + + // Top-level auto id column. + // + if (id != 0) + r += "INSERTED." + + convert_from (column_qname (*id), *id->back ()); + + // Top-level version column. + // + if (ver != 0) + { + if (id != 0) + r += ','; + + r += "INSERTED." + convert_from ( + column_qname (*ver, column_prefix ()), *ver); + } + } + + return r; + } + + virtual string + update_statement_extra (type& c) + { + string r; + + type* poly_root (polymorphic (c)); + bool poly_derived (poly_root != 0 && poly_root != &c); + + // If we are a derived type in a polymorphic hierarchy, then + // version is handled by the root. + // + if (poly_derived) + return r; + + semantics::data_member* ver (optimistic (c)); + + if (ver == 0 || + parse_sql_type (column_type (*ver), *ver).type != + sql_type::ROWVERSION) + return r; + + // Long data & SQL Server 2005 incompatibility is detected + // in persist_statement_extra. + // + r = "OUTPUT INSERTED." + + convert_from (column_qname (*ver, column_prefix ()), *ver); + + return r; + } + + virtual void + process_statement_columns (relational::statement_columns& cols, + statement_kind sk, + bool dynamic) + { + statement_columns_common::process (cols, sk, dynamic); + } + + virtual string + optimistic_version_init (semantics::data_member& m, bool index) + { + sql_type t (parse_sql_type (column_type (m), m)); + return t.type != sql_type::ROWVERSION + ? "1" + : (index + ? "version (sts.id_image (i))" + : "version (sts.id_image ())"); + } + + virtual string + optimistic_version_increment (semantics::data_member& m, bool index) + { + sql_type t (parse_sql_type (column_type (m), m)); + return t.type != sql_type::ROWVERSION + ? "1" + : (index + ? "version (sts.id_image (i))" + : "version (sts.id_image ())"); + } + + virtual bool + optimistic_insert_bind_version (semantics::data_member& m) + { + sql_type t (parse_sql_type (column_type (m), m)); + return t.type == sql_type::ROWVERSION; + } + + virtual void + object_extra (type& c) + { + bool abst (abstract (c)); + + type* poly_root (polymorphic (c)); + bool poly (poly_root != 0); + bool poly_derived (poly && poly_root != &c); + + if (poly_derived || (abst && !poly)) + return; + + if (semantics::data_member* m = optimistic (c)) + { + sql_type t (parse_sql_type (column_type (*m), *m)); + if (t.type == sql_type::ROWVERSION) + { + string const& type (class_fq_name (c)); + string traits ("access::object_traits_impl< " + type + ", id_" + + db.string () + " >"); + + os << traits << "::version_type" << endl + << traits << "::" << endl + << "version (const id_image_type& i)" + << "{" + << "version_type v;"; + init_version_value_member_id_image_->traverse (*m); + os << "return v;" + << "}"; + } + } + } + + virtual string + from_trailer (type& c) + { + return c.get<view_query> ("query").for_update + ? " WITH (UPDLOCK)" + : ""; + } + + virtual string + select_trailer (type&) {return "";} + + private: + // Go via the dynamic creation to get access to the constructor. + // + instance<relational::init_value_member> + init_version_value_member_id_image_; + }; + entry<class_> class_entry_; + } + } +} diff --git a/odb/odb/relational/mysql/common.cxx b/odb/odb/relational/mysql/common.cxx new file mode 100644 index 0000000..d049443 --- /dev/null +++ b/odb/odb/relational/mysql/common.cxx @@ -0,0 +1,400 @@ +// file : odb/relational/mysql/common.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <cassert> + +#include <odb/relational/mysql/common.hxx> + +using namespace std; + +namespace relational +{ + namespace mysql + { + // + // member_base + // + + sql_type const& member_base:: + member_sql_type (semantics::data_member& m) + { + return parse_sql_type (column_type (m, key_prefix_), m); + } + + void member_base:: + traverse_simple (member_info& mi) + { + switch (mi.st->type) + { + // Integral types. + // + case sql_type::TINYINT: + case sql_type::SMALLINT: + case sql_type::MEDIUMINT: + case sql_type::INT: + case sql_type::BIGINT: + { + traverse_integer (mi); + break; + } + + // Float types. + // + case sql_type::FLOAT: + case sql_type::DOUBLE: + { + traverse_float (mi); + break; + } + case sql_type::DECIMAL: + { + traverse_decimal (mi); + break; + } + + // Data-time types. + // + case sql_type::DATE: + case sql_type::TIME: + case sql_type::DATETIME: + case sql_type::TIMESTAMP: + case sql_type::YEAR: + { + traverse_date_time (mi); + break; + } + + // String and binary types. + // + case sql_type::CHAR: + case sql_type::VARCHAR: + case sql_type::TINYTEXT: + case sql_type::TEXT: + case sql_type::MEDIUMTEXT: + case sql_type::LONGTEXT: + { + // For string types the limit is in characters rather + // than in bytes. The fixed-length pre-allocated buffer + // optimization can only be used for 1-byte encodings. + // To support this we will need the character encoding + // in sql_type. + // + traverse_long_string (mi); + break; + } + case sql_type::BINARY: + case sql_type::TINYBLOB: + { + // BINARY's range is always 255 or less from MySQL 5.0.3. + // TINYBLOB can only store up to 255 bytes. + // + traverse_short_string (mi); + break; + } + case sql_type::VARBINARY: + case sql_type::BLOB: + case sql_type::MEDIUMBLOB: + case sql_type::LONGBLOB: + { + if (mi.st->range && mi.st->range_value <= 255) + traverse_short_string (mi); + else + traverse_long_string (mi); + + break; + } + + // Other types. + // + case sql_type::BIT: + { + traverse_bit (mi); + break; + } + case sql_type::ENUM: + { + traverse_enum (mi); + break; + } + case sql_type::SET: + { + traverse_set (mi); + break; + } + case sql_type::invalid: + { + assert (false); + break; + } + } + } + + // + // member_image_type + // + + static const char* integer_types[] = + { + "char", + "short", + "int", + "int", + "long long" + }; + + static const char* float_types[] = + { + "float", + "double" + }; + + member_image_type:: + member_image_type (base const& x) + : member_base::base (x), // virtual base + base (x) {} + + member_image_type:: + member_image_type () + : relational::member_base (0, 0, string (), string ()) {} + + member_image_type:: + member_image_type (semantics::type* type, + const custom_cxx_type* ct, + string const& fq_type, + string const& key_prefix) + : relational::member_base (type, ct, fq_type, key_prefix) {} + + string member_image_type:: + image_type (semantics::data_member& m) + { + type_.clear (); + member_base::traverse (m, true); + return type_; + } + + void member_image_type:: + traverse_composite (member_info& mi) + { + type_ = "composite_value_traits< " + mi.fq_type () + + ", id_mysql >::image_type"; + } + + void member_image_type:: + traverse_integer (member_info& mi) + { + if (mi.st->unsign) + type_ = "unsigned "; + else if (mi.st->type == sql_type::TINYINT) + type_ = "signed "; + + type_ += integer_types[mi.st->type - sql_type::TINYINT]; + } + + void member_image_type:: + traverse_float (member_info& mi) + { + type_ = float_types[mi.st->type - sql_type::FLOAT]; + } + + void member_image_type:: + traverse_decimal (member_info&) + { + type_ = "details::buffer"; + } + + void member_image_type:: + traverse_date_time (member_info& mi) + { + if (mi.st->type == sql_type::YEAR) + type_ = "short"; + else + type_ = "MYSQL_TIME"; + } + + void member_image_type:: + traverse_string (member_info&) + { + type_ = "details::buffer"; + } + + void member_image_type:: + traverse_bit (member_info&) + { + type_ = "unsigned char*"; + } + + void member_image_type:: + traverse_enum (member_info& mi) + { + // Represented as either integer or string. + // + type_ = "mysql::value_traits< " + mi.fq_type () + + ", mysql::id_enum >::image_type"; + } + + void member_image_type:: + traverse_set (member_info&) + { + // Represented as string. + // + type_ = "details::buffer"; + } + + entry<member_image_type> member_image_type_; + + // + // member_database_type + // + + static const char* integer_database_id[] = + { + "id_tiny", + "id_utiny", + "id_short", + "id_ushort", + "id_long", // INT24 + "id_ulong", // INT24 UNSIGNED + "id_long", + "id_ulong", + "id_longlong", + "id_ulonglong" + }; + + static const char* float_database_id[] = + { + "id_float", + "id_double" + }; + + static const char* date_time_database_id[] = + { + "id_date", + "id_time", + "id_datetime", + "id_timestamp", + "id_year" + }; + + static const char* char_bin_database_id[] = + { + "id_string", // CHAR + "id_blob", // BINARY, + "id_string", // VARCHAR + "id_blob", // VARBINARY + "id_string", // TINYTEXT + "id_blob", // TINYBLOB + "id_string", // TEXT + "id_blob", // BLOB + "id_string", // MEDIUMTEXT + "id_blob", // MEDIUMBLOB + "id_string", // LONGTEXT + "id_blob" // LONGBLOB + }; + + member_database_type_id:: + member_database_type_id (base const& x) + : member_base::base (x), // virtual base + base (x) {} + + member_database_type_id:: + member_database_type_id () + : member_base::base (0, 0, string (), string ()), // virtual base + base (0, 0, string (), string ()) {} + + member_database_type_id:: + member_database_type_id (semantics::type* type, + const custom_cxx_type* ct, + string const& fq_type, + string const& key_prefix) + : member_base::base (type, ct, fq_type, key_prefix), // virtual base + base (type, ct, fq_type, key_prefix) {} + + string member_database_type_id:: + database_type_id (type& m) + { + type_id_.clear (); + member_base::traverse (m, true); + return type_id_; + } + + void member_database_type_id:: + traverse_composite (member_info&) + { + assert (false); + } + + void member_database_type_id:: + traverse_integer (member_info& mi) + { + size_t i ( + (mi.st->type - sql_type::TINYINT) * 2 + (mi.st->unsign ? 1 : 0)); + type_id_ = string ("mysql::") + integer_database_id[i]; + } + + void member_database_type_id:: + traverse_float (member_info& mi) + { + type_id_ = string ("mysql::") + + float_database_id[mi.st->type - sql_type::FLOAT]; + } + + void member_database_type_id:: + traverse_decimal (member_info&) + { + type_id_ = "mysql::id_decimal"; + } + + void member_database_type_id:: + traverse_date_time (member_info& mi) + { + type_id_ = string ("mysql::") + + date_time_database_id[mi.st->type - sql_type::DATE]; + } + + void member_database_type_id:: + traverse_string (member_info& mi) + { + type_id_ = string ("mysql::") + + char_bin_database_id[mi.st->type - sql_type::CHAR]; + } + + void member_database_type_id:: + traverse_bit (member_info&) + { + type_id_ = "mysql::id_bit"; + } + + void member_database_type_id:: + traverse_enum (member_info&) + { + type_id_ = "mysql::id_enum"; + } + + void member_database_type_id:: + traverse_set (member_info&) + { + type_id_ = "mysql::id_set"; + } + + entry<member_database_type_id> member_database_type_id_; + + // + // query_columns + // + + struct query_columns: relational::query_columns, context + { + query_columns (base const& x): base_impl (x) {} + + virtual string + database_type_id (semantics::data_member& m) + { + return member_database_type_id_.database_type_id (m); + } + + private: + member_database_type_id member_database_type_id_; + }; + entry<query_columns> query_columns_; + } +} diff --git a/odb/odb/relational/mysql/common.hxx b/odb/odb/relational/mysql/common.hxx new file mode 100644 index 0000000..b43dc0d --- /dev/null +++ b/odb/odb/relational/mysql/common.hxx @@ -0,0 +1,171 @@ +// file : odb/relational/mysql/common.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_RELATIONAL_MYSQL_COMMON_HXX +#define ODB_RELATIONAL_MYSQL_COMMON_HXX + +#include <odb/relational/common.hxx> +#include <odb/relational/mysql/context.hxx> + +namespace relational +{ + namespace mysql + { + struct member_base: virtual relational::member_base_impl<sql_type>, context + { + member_base (base const& x): base (x), base_impl (x) {} + + // This c-tor is for the direct use inside the mysql namespace. + // If you do use this c-tor, you should also explicitly call + // relational::member_base (aka base). + // + member_base () {} + + virtual sql_type const& + member_sql_type (semantics::data_member&); + + virtual void + traverse_simple (member_info&); + + virtual void + traverse_integer (member_info&) + { + } + + virtual void + traverse_float (member_info&) + { + } + + virtual void + traverse_decimal (member_info&) + { + } + + virtual void + traverse_date_time (member_info&) + { + } + + virtual void + traverse_string (member_info&) + { + } + + virtual void + traverse_short_string (member_info& mi) + { + traverse_string (mi); + } + + virtual void + traverse_long_string (member_info& mi) + { + traverse_string (mi); + } + + virtual void + traverse_bit (member_info&) + { + } + + virtual void + traverse_enum (member_info&) + { + } + + virtual void + traverse_set (member_info&) + { + } + }; + + struct member_image_type: relational::member_image_type, + member_base + { + member_image_type (base const&); + member_image_type (); + member_image_type (semantics::type* type, + const custom_cxx_type*, + string const& fq_type = string (), + string const& key_prefix = string ()); + virtual string + image_type (semantics::data_member&); + + virtual void + traverse_composite (member_info&); + + virtual void + traverse_integer (member_info&); + + virtual void + traverse_float (member_info&); + + virtual void + traverse_decimal (member_info&); + + virtual void + traverse_date_time (member_info&); + + virtual void + traverse_string (member_info&); + + virtual void + traverse_bit (member_info&); + + virtual void + traverse_enum (member_info&); + + virtual void + traverse_set (member_info&); + + private: + string type_; + }; + + struct member_database_type_id: relational::member_database_type_id, + member_base + { + member_database_type_id (base const&); + member_database_type_id (); + member_database_type_id (semantics::type* type, + const custom_cxx_type*, + string const& fq_type = string (), + string const& key_prefix = string ()); + + virtual string + database_type_id (type&); + + virtual void + traverse_composite (member_info&); + + virtual void + traverse_integer (member_info&); + + virtual void + traverse_float (member_info&); + + virtual void + traverse_decimal (member_info&); + + virtual void + traverse_date_time (member_info&); + + virtual void + traverse_string (member_info&); + + virtual void + traverse_bit (member_info&); + + virtual void + traverse_enum (member_info&); + + virtual void + traverse_set (member_info&); + + private: + string type_id_; + }; + } +} +#endif // ODB_RELATIONAL_MYSQL_COMMON_HXX diff --git a/odb/odb/relational/mysql/context.cxx b/odb/odb/relational/mysql/context.cxx new file mode 100644 index 0000000..8b3d983 --- /dev/null +++ b/odb/odb/relational/mysql/context.cxx @@ -0,0 +1,868 @@ +// file : odb/relational/mysql/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/mysql/context.hxx> +#include <odb/relational/mysql/common.hxx> + +using namespace std; + +namespace relational +{ + namespace mysql + { + 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", "TINYINT(1)", 0, false}, + + {"char", "CHAR(1)", 0, false}, + {"signed char", "TINYINT", 0, false}, + {"unsigned char", "TINYINT UNSIGNED", 0, false}, + + {"short int", "SMALLINT", 0, false}, + {"short unsigned int", "SMALLINT UNSIGNED", 0, false}, + + {"int", "INT", 0, false}, + {"unsigned int", "INT UNSIGNED", 0, false}, + + {"long int", "BIGINT", 0, false}, + {"long unsigned int", "BIGINT UNSIGNED", 0, false}, + + {"long long int", "BIGINT", 0, false}, + {"long long unsigned int", "BIGINT UNSIGNED", 0, false}, + + {"float", "FLOAT", 0, false}, + {"double", "DOUBLE", 0, false}, + + {"::std::string", "TEXT", "VARCHAR(128)", false}, + + {"::size_t", "BIGINT UNSIGNED", 0, false}, + {"::std::size_t", "BIGINT UNSIGNED", 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 = true; + need_alias_as = true; + insert_send_auto_id = true; + delay_freeing_statement_result = false; + need_image_clone = false; + generate_bulk = false; + global_index = false; + global_fkey = true; + data_->bind_vector_ = "MYSQL_BIND*"; + data_->truncated_vector_ = "my_bool*"; + + // 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; + + // Warn if the name is greater than the 64 limit. + // + if (i->size () > 64) + { + cerr << "warning: SQL name '" << *i << "' is longer than " + << "the MySQL name limit of 64 characters and will " + << "be truncated" << endl; + + cerr << "info: consider shortening it using #pragma db " + << "table/column/index or --*-regex options" << endl; + } + + if (f) + f = false; + else + r += '.'; + + r += '`'; + r.append (*i, 0, 64); // Max identifier length is 64. + r += '`'; + } + + return r; + } + + namespace + { + struct has_grow: traversal::class_ + { + has_grow (bool& r, user_section* s) + : r_ (r), section_ (s) + { + *this >> inherits_ >> *this; + } + + virtual void + traverse (type& c) + { + bool view (context::view (c)); + + // Ignore transient bases. + // + if (!(context::object (c) || view || context::composite (c))) + return; + + if (section_ == 0 && c.count ("mysql-grow")) + r_ = c.get<bool> ("mysql-grow"); + else + { + // r_ should be false. + // + if (!view) + inherits (c); + + if (!r_) + names (c); + + if (section_ == 0) + c.set ("mysql-grow", r_); + } + } + + private: + bool& r_; + user_section* section_; + traversal::inherits inherits_; + }; + + struct has_grow_member: member_base + { + has_grow_member (bool& r, user_section* section = 0) + : relational::member_base (0, 0, string (), string (), section), + r_ (r) {} + + has_grow_member (bool& r, + user_section* section, + semantics::type* t, + const custom_cxx_type* ct, + string const& key_prefix = string ()) + : relational::member_base (t, ct, string (), key_prefix, section), + r_ (r) {} + + virtual bool + pre (member_info& mi) + { + // If we have a key prefix (container), then it can't be in a + // section (while mi.m can). The same for top-level -- if we got + // called, then we shouldn't ignore it. + // + return !key_prefix_.empty () || top_level_ || + (section_ == 0 && !separate_load (mi.m)) || + (section_ != 0 && *section_ == section (mi.m)); + } + + virtual void + traverse_composite (member_info& mi) + { + // By calling grow() instead of recursing, we reset any overrides. + // We also don't pass section since they don't apply inside + // composites. + // + r_ = r_ || context::grow (dynamic_cast<semantics::class_&> (mi.t)); + } + + virtual void + traverse_decimal (member_info&) + { + r_ = true; + } + + virtual void + traverse_long_string (member_info&) + { + r_ = true; + } + + virtual void + traverse_short_string (member_info&) + { + r_ = true; // @@ Short string optimization disabled. + } + + virtual void + traverse_enum (member_info&) + { + r_ = true; + } + + virtual void + traverse_set (member_info&) + { + r_ = true; + } + + private: + bool& r_; + }; + } + + bool context:: + grow_impl (semantics::class_& c, user_section* section) + { + if (section == 0 && c.count ("mysql-grow")) + return c.get<bool> ("mysql-grow"); + + bool r (false); + has_grow ct (r, section); + has_grow_member mt (r, section); + traversal::names names; + ct >> names >> mt; + ct.traverse (c); + return r; + } + + bool context:: + grow_impl (semantics::data_member& m) + { + bool r (false); + has_grow_member mt (r); + mt.traverse (m, true); + return r; + } + + bool context:: + grow_impl (semantics::data_member& m, + semantics::type& t, + const custom_cxx_type* ct, + string const& kp) + { + bool r (false); + has_grow_member mt (r, 0, &t, ct, kp); + mt.traverse (m, true); + return r; + } + + string context:: + database_type_impl (semantics::type& t, + semantics::names* hint, + bool id, + bool* null) + { + using semantics::enum_; + using semantics::enumerator; + using semantics::array; + + string r; + + // Enum mapping. + // + if (enum_* e = dynamic_cast<enum_*> (&t)) + { + // We can only map to ENUM if the C++ enumeration is contiguous + // and starts with 0. + // + enum_::enumerates_iterator i (e->enumerates_begin ()), + end (e->enumerates_end ()); + + if (i != end) + { + r += "ENUM("; + + for (unsigned long long j (0); i != end; ++i, ++j) + { + enumerator const& er (i->enumerator ()); + + if (er.value () != j) + break; + + if (j != 0) + r += ", "; + + r += quote_string (er.name ()); + } + + if (i == end) + r += ")"; + else + r.clear (); + } + + if (!r.empty ()) + return r; + } + + r = base_context::database_type_impl (t, hint, id, null); + + if (!r.empty ()) + return r; + + // char[N] mapping. + // + else 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 = "VARCHAR("; + n--; + } + + ostringstream ostr; + ostr << n; + r += ostr.str (); + r += ')'; + } + } + + 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: + // + // NATIONAL CHAR|VARCHAR + // CHAR BYTE (BINARY) + // CHARACTER VARYING (VARCHAR) + // LONG VARBINARY (MEDIUMBLOB) + // LONG VARCHAR (MEDIUMTEXT) + // + // + enum state + { + parse_prefix, + parse_name, + parse_range, + parse_sign, + parse_done + }; + + state s (parse_prefix); + string prefix; + bool flt (false); + + for (sql_token t (l.next ()); + s != parse_done && t.type () != sql_token::t_eos; + t = l.next ()) + { + sql_token::token_type tt (t.type ()); + + switch (s) + { + case parse_prefix: + { + if (tt == sql_token::t_identifier) + { + string const& id (context::upcase (t.identifier ())); + + if (id == "NATIONAL" || + id == "CHAR" || + id == "CHARACTER" || + id == "LONG") + { + prefix = id; + s = parse_name; + continue; + } + } + + s = parse_name; + } + // Fall through. + case parse_name: + { + if (tt == sql_token::t_identifier) + { + bool match (true); + string const& id (context::upcase (t.identifier ())); + + // Numeric types. + // + if (id == "BIT") + { + r.type = sql_type::BIT; + } + else if (id == "TINYINT" || id == "INT1") + { + r.type = sql_type::TINYINT; + } + else if (id == "BOOL" || id == "BOOLEAN") + { + r.type = sql_type::TINYINT; + r.range = true; + r.range_value = 1; + } + else if (id == "SMALLINT" || id == "INT2") + { + r.type = sql_type::SMALLINT; + } + else if (id == "MEDIUMINT" || + id == "INT3" || + id == "MIDDLEINT") + { + r.type = sql_type::MEDIUMINT; + } + else if (id == "INT" || id == "INTEGER" || id == "INT4") + { + r.type = sql_type::INT; + } + else if (id == "BIGINT" || id == "INT8") + { + r.type = sql_type::BIGINT; + } + else if (id == "SERIAL") + { + r.type = sql_type::BIGINT; + r.unsign = true; + } + else if (id == "FLOAT") + { + // Assign a type only once we know the precision of the + // float; it can be either 4 or 8 byte. + // + flt = true; + } + else if (id == "FLOAT4") + { + r.type = sql_type::FLOAT; + } + else if (id == "DOUBLE" || id == "FLOAT8") + { + r.type = sql_type::DOUBLE; + } + else if (id == "DECIMAL" || + id == "DEC" || + id == "NUMERIC" || + id == "FIXED") + { + r.type = sql_type::DECIMAL; + } + // + // Date-time types. + // + else if (id == "DATE") + { + r.type = sql_type::DATE; + } + else if (id == "TIME") + { + r.type = sql_type::TIME; + } + else if (id == "DATETIME") + { + r.type = sql_type::DATETIME; + } + else if (id == "TIMESTAMP") + { + r.type = sql_type::TIMESTAMP; + } + else if (id == "YEAR") + { + r.type = sql_type::YEAR; + } + // + // String and binary types. + // + else if (id == "NCHAR") + { + r.type = sql_type::CHAR; + } + else if (id == "VARCHAR") + { + r.type = prefix == "LONG" + ? sql_type::MEDIUMTEXT + : sql_type::VARCHAR; + } + else if (id == "NVARCHAR") + { + r.type = sql_type::VARCHAR; + } + else if (id == "VARYING" && prefix == "CHARACTER") + { + r.type = sql_type::VARCHAR; + } + else if (id == "BINARY") + { + r.type = sql_type::BINARY; + } + else if (id == "BYTE" && prefix == "CHAR") + { + r.type = sql_type::BINARY; + } + else if (id == "VARBINARY") + { + r.type = prefix == "LONG" + ? sql_type::MEDIUMBLOB + : sql_type::VARBINARY; + } + else if (id == "TINYBLOB") + { + r.type = sql_type::TINYBLOB; + } + else if (id == "TINYTEXT") + { + r.type = sql_type::TINYTEXT; + } + else if (id == "BLOB") + { + r.type = sql_type::BLOB; + } + else if (id == "TEXT") + { + r.type = sql_type::TEXT; + } + else if (id == "MEDIUMBLOB") + { + r.type = sql_type::MEDIUMBLOB; + } + else if (id == "MEDIUMTEXT") + { + r.type = sql_type::MEDIUMTEXT; + } + else if (id == "LONGBLOB") + { + r.type = sql_type::LONGBLOB; + } + else if (id == "LONGTEXT") + { + r.type = sql_type::LONGTEXT; + } + else if (id == "ENUM") + { + r.type = sql_type::ENUM; + } + else if (id == "SET") + { + r.type = sql_type::SET; + } + else + match = false; + + if (match) + { + s = parse_range; + continue; + } + } + + // Some prefixes can also be type names if not followed + // by the actual type name. + // + if (!prefix.empty ()) + { + if (prefix == "CHAR" || prefix == "CHARACTER") + { + r.type = sql_type::CHAR; + } + else if (prefix == "LONG") + { + r.type = sql_type::MEDIUMTEXT; + } + } + + if (r.type == sql_type::invalid) + { + if (tt == sql_token::t_identifier) + { + return error (ct, "unknown MySQL type '" + t.identifier () + + "'"); + } + else + return error (ct, "expected MySQL type name"); + } + + s = parse_range; + } + // Fall through. + case parse_range: + { + if (t.punctuation () == sql_token::p_lparen) + { + t = l.next (); + + // ENUM and SET have a list of members instead of the range. + // + if (r.type == sql_type::ENUM || r.type == sql_type::SET) + { + while (true) + { + if (t.type () != sql_token::t_string_lit) + { + return error (ct, "string literal expected in MySQL " + "ENUM or SET declaration"); + } + + if (r.type == sql_type::ENUM) + r.enumerators.push_back (t.literal ()); + + t = l.next (); + + if (t.punctuation () == sql_token::p_rparen) + break; + else if (t.punctuation () != sql_token::p_comma) + { + return error (ct, "comma expected in MySQL ENUM or " + "SET declaration"); + } + + t = l.next (); + } + } + else + { + if (t.type () != sql_token::t_int_lit) + { + return error (ct, "integer range expected in MySQL type " + "declaration"); + } + + unsigned int v; + istringstream is (t.literal ()); + + if (!(is >> v && is.eof ())) + { + return error (ct, "invalid range value '" + t.literal () + + "' in MySQL type declaration"); + } + + r.range = true; + r.range_value = v; + + t = l.next (); + + if (t.punctuation () == sql_token::p_comma) + { + // We have the second range value. Skip it. + // + // In FLOAT the two-value range means something + // completely different than the single-value. + // Pretend we don't have the range in the former + // case. + // + if (flt) + r.range = false; + + l.next (); + t = l.next (); + } + } + + if (t.punctuation () != sql_token::p_rparen) + { + return error (ct, "expected ')' in MySQL type declaration"); + } + + s = parse_sign; + continue; + } + + s = parse_sign; + } + // Fall through. + case parse_sign: + { + if (tt == sql_token::t_identifier && + context::upcase (t.identifier ()) == "UNSIGNED") + { + r.unsign = true; + } + + s = parse_done; + break; + } + case parse_done: + { + assert (false); + break; + } + } + } + + if (s == parse_name && !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; + } + else if (prefix == "LONG") + { + r.type = sql_type::MEDIUMTEXT; + } + } + + if (flt) + { + r.type = !r.range || r.range_value < 24 + ? sql_type::FLOAT + : sql_type::DOUBLE; + } + + if (r.type == sql_type::invalid) + return error (ct, "incomplete MySQL type declaration"); + + // If range is omitted for CHAR or BIT types, it defaults to 1. + // + if ((r.type == sql_type::CHAR || r.type == sql_type::BIT) && !r.range) + { + r.range = true; + r.range_value = 1; + } + + return r; + } + catch (sql_lexer::invalid_input const& e) + { + return error (ct, "invalid MySQL type declaration: " + e.message); + } + } + } +} diff --git a/odb/odb/relational/mysql/context.hxx b/odb/odb/relational/mysql/context.hxx new file mode 100644 index 0000000..98574f2 --- /dev/null +++ b/odb/odb/relational/mysql/context.hxx @@ -0,0 +1,194 @@ +// file : odb/relational/mysql/context.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_RELATIONAL_MYSQL_CONTEXT_HXX +#define ODB_RELATIONAL_MYSQL_CONTEXT_HXX + +#include <map> +#include <vector> + +#include <odb/relational/context.hxx> + +namespace relational +{ + namespace mysql + { + struct sql_type + { + // Keep the order in each block of types. + // + enum core_type + { + // Integral types. + // + TINYINT, + SMALLINT, + MEDIUMINT, + INT, + BIGINT, + + // Float types. + // + FLOAT, + DOUBLE, + DECIMAL, + + // Data-time types. + // + DATE, + TIME, + DATETIME, + TIMESTAMP, + YEAR, + + // String and binary types. + // + CHAR, + BINARY, + VARCHAR, + VARBINARY, + TINYTEXT, + TINYBLOB, + TEXT, + BLOB, + MEDIUMTEXT, + MEDIUMBLOB, + LONGTEXT, + LONGBLOB, + + // Other types. + // + BIT, + ENUM, + SET, + + // Invalid type. + // + invalid + }; + + sql_type () : type (invalid), unsign (false), range (false) {} + + core_type type; + bool unsign; + bool range; + unsigned int range_value; // MySQL max value is 2^32 - 1 (LONGBLOG/TEXT). + std::vector<std::string> enumerators; // Enumerator strings for ENUM. + + // Conversion expressions for custom database types. + // + std::string to; + std::string from; + }; + + class context: public virtual relational::context + { + public: + sql_type const& + parse_sql_type (string const&, + semantics::data_member&, + bool custom = true); + public: + struct invalid_sql_type + { + invalid_sql_type (string const& message): message_ (message) {} + + string const& + message () const {return message_;} + + private: + string message_; + }; + + // If custom_db_types is NULL, then this function returns + // invalid type instead of throwing in case an unknown type + // is encountered. + // + static sql_type + parse_sql_type (string, custom_db_types const* = 0); + + protected: + virtual string const& + convert_expr (string const&, semantics::data_member&, bool); + + virtual bool + grow_impl (semantics::class_&, user_section*); + + virtual bool + grow_impl (semantics::data_member&); + + virtual bool + grow_impl (semantics::data_member&, + semantics::type&, + const custom_cxx_type*, + string const&); + + protected: + virtual string + quote_id_impl (qname const&) const; + + protected: + virtual string + database_type_impl (semantics::type&, semantics::names*, bool, bool*); + + public: + virtual + ~context (); + context (); + context (std::ostream&, + semantics::unit&, + options_type const&, + features_type&, + sema_rel::model*); + + static context& + current () + { + return *current_; + } + + private: + static context* current_; + + private: + struct data: base_context::data + { + data (std::ostream& os): base_context::data (os) {} + + struct sql_type_cache_entry + { + sql_type_cache_entry () + : custom_cached (false), straight_cached (false) {} + + sql_type const& + cache_custom (sql_type const& t) + { + custom = t; + custom_cached = true; + return custom; + } + + sql_type const& + cache_straight (sql_type const& t) + { + straight = t; + straight_cached = true; + return straight; + } + + sql_type custom; // With custom mapping. + sql_type straight; // Without custom mapping. + + bool custom_cached; + bool straight_cached; + }; + + typedef std::map<string, sql_type_cache_entry> sql_type_cache; + sql_type_cache sql_type_cache_; + }; + data* data_; + }; + } +} + +#endif // ODB_RELATIONAL_MYSQL_CONTEXT_HXX diff --git a/odb/odb/relational/mysql/header.cxx b/odb/odb/relational/mysql/header.cxx new file mode 100644 index 0000000..27bae48 --- /dev/null +++ b/odb/odb/relational/mysql/header.cxx @@ -0,0 +1,136 @@ +// file : odb/relational/mysql/header.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <odb/relational/header.hxx> + +#include <odb/relational/mysql/common.hxx> +#include <odb/relational/mysql/context.hxx> + +namespace relational +{ + namespace mysql + { + namespace header + { + namespace relational = relational::header; + + struct image_member: relational::image_member_impl<sql_type>, + member_base + { + image_member (base const& x) + : member_base::base (x), // virtual base + member_base::base_impl (x), // virtual base + base_impl (x), + member_base (x) {} + + virtual void + traverse_integer (member_info& mi) + { + os << image_type << " " << mi.var << "value;" + << "my_bool " << mi.var << "null;" + << endl; + } + + virtual void + traverse_float (member_info& mi) + { + os << image_type << " " << mi.var << "value;" + << "my_bool " << mi.var << "null;" + << endl; + } + + virtual void + traverse_decimal (member_info& mi) + { + // Exchanged as strings. Can have up to 65 digits not counting + // '-' and '.'. If range is not specified, the default is 10. + // + + /* + @@ Disabled. + os << "char " << mi.var << "value[" << + (t.range ? t.range_value : 10) + 3 << "];" + */ + + os << image_type << " " << mi.var << "value;" + << "unsigned long " << mi.var << "size;" + << "my_bool " << mi.var << "null;" + << endl; + } + + virtual void + traverse_date_time (member_info& mi) + { + os << image_type << " " << mi.var << "value;" + << "my_bool " << mi.var << "null;" + << endl; + + } + + virtual void + traverse_short_string (member_info& mi) + { + // If range is not specified, the default buffer size is 255. + // + /* + @@ Disabled. + os << "char " << mi.var << "value[" << + (t.range ? t.range_value : 255) + 1 << "];" + */ + + os << image_type << " " << mi.var << "value;" + << "unsigned long " << mi.var << "size;" + << "my_bool " << mi.var << "null;" + << endl; + } + + virtual void + traverse_long_string (member_info& mi) + { + os << image_type << " " << mi.var << "value;" + << "unsigned long " << mi.var << "size;" + << "my_bool " << mi.var << "null;" + << endl; + } + + virtual void + traverse_bit (member_info& mi) + { + // Valid range is 1 to 64. + // + unsigned int n (mi.st->range / 8 + (mi.st->range % 8 ? 1 : 0)); + + os << "unsigned char " << mi.var << "value[" << n << "];" + << "unsigned long " << mi.var << "size;" + << "my_bool " << mi.var << "null;" + << endl; + } + + virtual void + traverse_enum (member_info& mi) + { + // Represented as either integer or string. Since we don't know + // at the code generation time which one it is, we have to always + // keep size in case it is a string. + // + os << image_type << " " << mi.var << "value;" + << "unsigned long " << mi.var << "size;" + << "my_bool " << mi.var << "null;" + << endl; + } + + virtual void + traverse_set (member_info& mi) + { + // Represented as string. + // + os << image_type << " " << mi.var << "value;" + << "unsigned long " << mi.var << "size;" + << "my_bool " << mi.var << "null;" + << endl; + } + }; + entry<image_member> image_member_; + } + } +} diff --git a/odb/odb/relational/mysql/inline.cxx b/odb/odb/relational/mysql/inline.cxx new file mode 100644 index 0000000..bfa2c94 --- /dev/null +++ b/odb/odb/relational/mysql/inline.cxx @@ -0,0 +1,42 @@ +// file : odb/relational/mysql/inline.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <odb/relational/inline.hxx> + +#include <odb/relational/mysql/common.hxx> +#include <odb/relational/mysql/context.hxx> + +using namespace std; + +namespace relational +{ + namespace mysql + { + namespace inline_ + { + namespace relational = relational::inline_; + + struct null_member: relational::null_member_impl<sql_type>, + member_base + { + null_member (base const& x) + : member_base::base (x), // virtual base + member_base::base_impl (x), // virtual base + base_impl (x), + member_base (x) + { + } + + virtual void + traverse_simple (member_info& mi) + { + if (get_) + os << "r = r && i." << mi.var << "null;"; + else + os << "i." << mi.var << "null = 1;"; + } + }; + entry<null_member> null_member_; + } + } +} diff --git a/odb/odb/relational/mysql/model.cxx b/odb/odb/relational/mysql/model.cxx new file mode 100644 index 0000000..17ed4c0 --- /dev/null +++ b/odb/odb/relational/mysql/model.cxx @@ -0,0 +1,161 @@ +// file : odb/relational/mysql/model.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <sstream> + +#include <odb/relational/model.hxx> +#include <odb/relational/mysql/context.hxx> + +using namespace std; + +namespace relational +{ + namespace mysql + { + namespace model + { + namespace relational = relational::model; + + struct object_columns: relational::object_columns, context + { + object_columns (base const& x): base (x) {} + + virtual string + default_bool (semantics::data_member&, bool v) + { + // MySQL has TRUE and FALSE as just aliases for 1 and 0. Still + // use them for self-documentation. + // + return v ? "TRUE" : "FALSE"; + } + + virtual string + default_enum (semantics::data_member& m, tree en, string const& name) + { + // Make sure the column is mapped to an ENUM or integer type. + // + sql_type const& t (parse_sql_type (column_type (), m, false)); + + switch (t.type) + { + case sql_type::ENUM: + case sql_type::TINYINT: + case sql_type::SMALLINT: + case sql_type::MEDIUMINT: + case sql_type::INT: + case sql_type::BIGINT: + break; + default: + { + cerr << m.file () << ":" << m.line () << ":" << m.column () + << ": error: column with default value specified as C++ " + << "enumerator must map to MySQL ENUM or integer type" + << endl; + + throw operation_failed (); + } + } + + using semantics::enum_; + using semantics::enumerator; + + enumerator& er (dynamic_cast<enumerator&> (*unit.find (en))); + enum_& e (er.enum_ ()); + + if (t.type == sql_type::ENUM) + { + // Assuming the enumerators in the C++ enum and MySQL ENUM are + // in the same order, calculate the poistion of the C++ + // enumerator and use that as an index in the MySQL ENUM. + // + size_t pos (0); + + for (enum_::enumerates_iterator i (e.enumerates_begin ()), + end (e.enumerates_end ()); i != end; ++i) + { + if (&i->enumerator () == &er) + break; + + pos++; + } + + if (pos < t.enumerators.size ()) + return t.enumerators[pos]; + else + { + cerr << m.file () << ":" << m.line () << ":" << m.column () + << ": error: unable to map C++ enumerator '" << name + << "' to MySQL ENUM value" << endl; + + throw operation_failed (); + } + } + else + { + ostringstream ostr; + + if (e.unsigned_ ()) + ostr << er.value (); + else + ostr << static_cast<long long> (er.value ()); + + return ostr.str (); + } + } + }; + entry<object_columns> object_columns_; + + struct member_create: relational::member_create, context + { + member_create (base const& x): base (x) {} + + virtual string + table_options (semantics::data_member& m, semantics::type& c) + { + string r (relational::member_create::table_options (m, c)); + + string const& engine (options.mysql_engine ()); + if (engine != "default") + { + // Note: MySQL table options can be separated with spaces. + // + if (!r.empty ()) + r += ' '; + + r += "ENGINE="; + r += engine; + } + + return r; + } + }; + entry<member_create> member_create_; + + struct class_: relational::class_, context + { + class_ (base const& x): base (x) {} + + virtual string + table_options (type& c) + { + string r (relational::class_::table_options (c)); + + string const& engine (options.mysql_engine ()); + if (engine != "default") + { + // Note: MySQL table options can be separated with spaces. + // + if (!r.empty ()) + r += ' '; + + r += "ENGINE="; + r += engine; + } + + return r; + } + }; + entry<class_> class__; + } + } +} diff --git a/odb/odb/relational/mysql/schema.cxx b/odb/odb/relational/mysql/schema.cxx new file mode 100644 index 0000000..60dc95b --- /dev/null +++ b/odb/odb/relational/mysql/schema.cxx @@ -0,0 +1,489 @@ +// file : odb/relational/mysql/schema.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <odb/relational/schema.hxx> + +#include <odb/relational/mysql/common.hxx> +#include <odb/relational/mysql/context.hxx> + +using namespace std; + +namespace relational +{ + namespace mysql + { + namespace schema + { + namespace relational = relational::schema; + using relational::table_set; + + // + // Drop. + // + + struct drop_foreign_key: relational::drop_foreign_key, context + { + drop_foreign_key (base const& x): base (x) {} + + virtual void + drop (sema_rel::table& t, sema_rel::foreign_key& fk) + { + /* + // @@ This does not work: in MySQL control statements can only + // be used in stored procedures. It seems the only way to + // implement this is to define, execute, and drop a stored + // procedure, which is just too ugly. + // + // Another option would be to use CREATE TABLE IF NOT EXISTS + // to create a dummy table with a dummy constraint that makes + // the following DROP succeed. Note, however, that MySQL issues + // a notice if the table already exist so would need to suppress + // that as well. Still not sure that the utility of this support + // justifies this kind of a hack. + // + os << "IF EXISTS (SELECT NULL FROM information_schema.TABLE_CONSTRAINTS" << endl + << " WHERE CONSTRAINT_TYPE = " << quote_string ("FOREIGN KEY") << "AND" << endl + << " CONSTRAINT_SCHEMA = DATABASE() AND" << endl + << " CONSTRAINT_NAME = " << quote_string (fk.name ()) << ") THEN" << endl + << " ALTER TABLE " << quote_id (t.name ()) << " DROP FOREIGN KEY " << quote_id (fk.name ()) << ";" << endl + << "END IF;" << endl; + */ + + // So for now we only do this in migration. + // + if (dropped_ == 0) + { + if (fk.not_deferrable ()) + pre_statement (); + else + { + if (format_ != schema_format::sql) + return; + + os << "/*" << endl; + } + + os << "ALTER TABLE " << quote_id (t.name ()) << endl + << " DROP FOREIGN KEY " << quote_id (fk.name ()) << endl; + + if (fk.not_deferrable ()) + post_statement (); + else + os << "*/" << endl + << endl; + } + } + + using base::drop; + + virtual void + traverse (sema_rel::drop_foreign_key& dfk) + { + // Find the foreign key we are dropping in the base model. + // + sema_rel::foreign_key& fk (find<sema_rel::foreign_key> (dfk)); + + if (fk.not_deferrable () || in_comment) + base::traverse (dfk); + else + { + if (format_ != schema_format::sql) + return; + + os << endl + << " /*" + << endl; + + drop (dfk); + + os << endl + << " */"; + } + } + + virtual void + drop_header () + { + os << "DROP FOREIGN KEY "; + } + }; + entry<drop_foreign_key> drop_foreign_key_; + + struct drop_index: relational::drop_index, context + { + drop_index (base const& x): base (x) {} + + virtual void + drop (sema_rel::index& in) + { + sema_rel::table& t (static_cast<sema_rel::table&> (in.scope ())); + + os << "DROP INDEX " << name (in) << " ON " << + quote_id (t.name ()) << endl; + } + }; + entry<drop_index> drop_index_; + + // + // Create. + // + + struct create_column: relational::create_column, context + { + create_column (base const& x): base (x) {} + + virtual void + auto_ (sema_rel::primary_key&) + { + os << " AUTO_INCREMENT"; + } + }; + entry<create_column> create_column_; + + struct create_foreign_key: relational::create_foreign_key, context + { + create_foreign_key (base const& x): base (x) {} + + void + diagnose (sema_rel::foreign_key& fk) + { + if (fk.on_delete () != sema_rel::foreign_key::no_action) + { + cerr << "warning: foreign key '" << fk.name () << "' has " << + "ON DELETE clause but is disabled in MySQL due to lack " + "of deferrable constraint support" << endl; + + cerr << "info: consider using non-deferrable foreign keys (" << + "--fkeys-deferrable-mode)" << endl; + } + } + + virtual void + traverse_create (sema_rel::foreign_key& fk) + { + // MySQL does not support deferrable constraint checking. Output + // such foreign keys as comments, for documentation, unless we + // are generating embedded schema. + // + if (fk.not_deferrable ()) + base::traverse_create (fk); + else + { + diagnose (fk); + + // Don't bloat C++ code with comment strings if we are + // generating embedded schema. + // + if (format_ != schema_format::sql) + return; + + os << endl + << " /*" << endl + << " CONSTRAINT "; + create (fk); + os << endl + << " */"; + } + } + + virtual void + traverse_add (sema_rel::foreign_key& fk) + { + if (fk.not_deferrable () || in_comment) + base::traverse_add (fk); + else + { + diagnose (fk); + + if (format_ != schema_format::sql) + return; + + os << endl + << " /*" + << endl; + + add (fk); + + os << endl + << " */"; + } + } + + virtual void + deferrable (sema_rel::deferrable) + { + // This will still be called to output the comment. + } + }; + entry<create_foreign_key> create_foreign_key_; + + struct create_index: relational::create_index, context + { + create_index (base const& x): base (x) {} + + virtual void + create (sema_rel::index& in) + { + os << "CREATE "; + + if (!in.type ().empty ()) + os << in.type () << ' '; + + os << "INDEX " << name (in); + + if (!in.method ().empty ()) + os << " USING " << in.method (); + + os << endl + << " ON " << table_name (in) << " ("; + + columns (in); + + os << ")" << endl; + + if (!in.options ().empty ()) + os << ' ' << in.options () << endl; + } + }; + entry<create_index> create_index_; + + struct create_table: relational::create_table, context + { + create_table (base const& x): base (x) {} + + // See if there are any undefined foreign keys that are not + // deferrable. + // + bool + check_undefined_fk_deferrable_only (sema_rel::table& t) + { + for (sema_rel::table::names_iterator i (t.names_begin ()); + i != t.names_end (); ++i) + { + using sema_rel::foreign_key; + + if (foreign_key* fk = dynamic_cast<foreign_key*> (&i->nameable ())) + { + if (!fk->count ("mysql-fk-defined") && + fk->not_deferrable ()) + return false; + } + } + return true; + } + + virtual void + traverse (sema_rel::table& t) + { + if (pass_ == 1) + base::traverse (t); + else + { + // Add undefined foreign keys. + // + if (check_undefined_fk (t)) + { + bool deferrable (check_undefined_fk_deferrable_only (t)); + + if (!deferrable || format_ == schema_format::sql) + { + if (deferrable) + { + os << "/*" << endl; + in_comment = true; + } + else + pre_statement (); + + os << "ALTER TABLE " << quote_id (t.name ()); + + instance<create_foreign_key> cfk (*this); + trav_rel::unames n (*cfk); + names (t, n); + os << endl; + + if (deferrable) + { + in_comment = false; + os << "*/" << endl + << endl; + } + else + post_statement (); + } + } + } + } + }; + entry<create_table> create_table_; + + // + // Alter. + // + + struct alter_column: relational::alter_column, context + { + alter_column (base const& x): base (x) {} + + virtual void + alter_header () + { + os << "MODIFY COLUMN "; + } + }; + entry<alter_column> alter_column_; + + struct alter_table_pre: relational::alter_table_pre, context + { + alter_table_pre (base const& x): base (x) {} + + // Check if we are only dropping deferrable foreign keys. + // + bool + check_drop_deferrable_only (sema_rel::alter_table& at) + { + if (check<sema_rel::add_column> (at) || + check_alter_column_null (at, true)) + return false; + + for (sema_rel::alter_table::names_iterator i (at.names_begin ()); + i != at.names_end (); ++i) + { + using sema_rel::foreign_key; + using sema_rel::drop_foreign_key; + + if (drop_foreign_key* dfk = + dynamic_cast<drop_foreign_key*> (&i->nameable ())) + { + foreign_key& fk (find<foreign_key> (*dfk)); + + if (fk.not_deferrable ()) + return false; + } + } + return true; + } + + virtual void + alter (sema_rel::alter_table& at) + { + if (check_drop_deferrable_only (at)) + { + if (format_ != schema_format::sql) + return; + + os << "/*" << endl; + in_comment = true; + + os << "ALTER TABLE " << quote_id (at.name ()); + instance<drop_foreign_key> dfk (*this); + trav_rel::unames n (*dfk); + names (at, n); + os << endl; + + in_comment = false; + os << "*/" << endl + << endl; + } + else + base::alter (at); + } + }; + entry<alter_table_pre> alter_table_pre_; + + struct alter_table_post: relational::alter_table_post, context + { + alter_table_post (base const& x): base (x) {} + + // Check if we are only adding deferrable foreign keys. + // + bool + check_add_deferrable_only (sema_rel::alter_table& at) + { + if (check<sema_rel::drop_column> (at) || + check_alter_column_null (at, false)) + return false; + + for (sema_rel::alter_table::names_iterator i (at.names_begin ()); + i != at.names_end (); ++i) + { + using sema_rel::add_foreign_key; + + if (add_foreign_key* afk = + dynamic_cast<add_foreign_key*> (&i->nameable ())) + { + if (afk->not_deferrable ()) + return false; + } + } + return true; + } + + virtual void + alter (sema_rel::alter_table& at) + { + if (check_add_deferrable_only (at)) + { + if (format_ != schema_format::sql) + return; + + os << "/*" << endl; + in_comment = true; + + os << "ALTER TABLE " << quote_id (at.name ()); + instance<create_foreign_key> cfk (*this); + trav_rel::unames n (*cfk); + names (at, n); + os << endl; + + in_comment = false; + os << "*/" << endl + << endl; + } + else + base::alter (at); + } + }; + entry<alter_table_post> alter_table_post_; + + // + // Schema version table. + // + + struct version_table: relational::version_table, context + { + version_table (base const& x): base (x) {} + + virtual void + create_table () + { + pre_statement (); + + os << "CREATE TABLE IF NOT EXISTS " << qt_ << " (" << endl + << " " << qn_ << " VARCHAR(128) NOT NULL PRIMARY KEY," << endl + << " " << qv_ << " BIGINT UNSIGNED NOT NULL," << endl + << " " << qm_ << " TINYINT(1) NOT NULL)" << endl; + + string const& engine (options.mysql_engine ()); + if (engine != "default") + os << " ENGINE=" << engine << endl; + + post_statement (); + } + + virtual void + create (sema_rel::version v) + { + pre_statement (); + + os << "INSERT IGNORE INTO " << qt_ << " (" << endl + << " " << qn_ << ", " << qv_ << ", " << qm_ << ")" << endl + << " VALUES (" << qs_ << ", " << v << ", 0)" << endl; + + post_statement (); + } + }; + entry<version_table> version_table_; + } + } +} diff --git a/odb/odb/relational/mysql/source.cxx b/odb/odb/relational/mysql/source.cxx new file mode 100644 index 0000000..9131ea7 --- /dev/null +++ b/odb/odb/relational/mysql/source.cxx @@ -0,0 +1,724 @@ +// file : odb/relational/mysql/source.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <odb/relational/source.hxx> + +#include <odb/relational/mysql/common.hxx> +#include <odb/relational/mysql/context.hxx> + +using namespace std; + +namespace relational +{ + namespace mysql + { + namespace source + { + namespace relational = relational::source; + + namespace + { + const char* integer_buffer_types[] = + { + "MYSQL_TYPE_TINY", + "MYSQL_TYPE_SHORT", + "MYSQL_TYPE_LONG", // *_bind_param() doesn't support INT24. + "MYSQL_TYPE_LONG", + "MYSQL_TYPE_LONGLONG" + }; + + const char* float_buffer_types[] = + { + "MYSQL_TYPE_FLOAT", + "MYSQL_TYPE_DOUBLE" + }; + + const char* date_time_buffer_types[] = + { + "MYSQL_TYPE_DATE", + "MYSQL_TYPE_TIME", + "MYSQL_TYPE_DATETIME", + "MYSQL_TYPE_TIMESTAMP", + "MYSQL_TYPE_SHORT" + }; + + const char* char_bin_buffer_types[] = + { + "MYSQL_TYPE_STRING", // CHAR + "MYSQL_TYPE_BLOB", // BINARY, + "MYSQL_TYPE_STRING", // VARCHAR + "MYSQL_TYPE_BLOB", // VARBINARY + "MYSQL_TYPE_STRING", // TINYTEXT + "MYSQL_TYPE_BLOB", // TINYBLOB + "MYSQL_TYPE_STRING", // TEXT + "MYSQL_TYPE_BLOB", // BLOB + "MYSQL_TYPE_STRING", // MEDIUMTEXT + "MYSQL_TYPE_BLOB", // MEDIUMBLOB + "MYSQL_TYPE_STRING", // LONGTEXT + "MYSQL_TYPE_BLOB" // LONGBLOB + }; + } + + // + // + struct object_columns: relational::object_columns, context + { + object_columns (base const& x): base (x) {} + + virtual bool + column (semantics::data_member& m, + string const& table, + string const& column) + { + // When we store a ENUM column in the MySQL database, if we bind + // an integer parameter, then it is treated as an index and if we + // bind a string, then it is treated as a enumerator. Everything + // would have worked well if the same logic applied to the select + // operation. That is, if we bind integer, then the database sends + // the index and if we bind string then the database sends the + // enumerator. Unfortunately, MySQL always sends the enumerator + // and to get the index one has to resort to the enum+0 hack. + // + // This causes the following problem: at code generation time we + // do not yet know which format we want. This is determined at + // C++ compile time by traits (the reason we don't know this is + // because we don't want to drag database-specific runtimes, + // which define the necessary traits, as well as their + // prerequisites into the ODB compilation process). As a result, + // we cannot decide at code generation time whether we need the + // +0 hack or not. One way to overcome this would be to construct + // the SELECT statements at runtime, something along these lines: + // + // "enum" + enum_traits<type>::hack + "," + // + // However, this complicates the code generator quite a bit: we + // either have to move to std::string storage for all the + // statements and all the databases, which is kind of a waste, + // or do some deep per-database customizations, which is hairy. + // So, instead, we are going to use another hack (hey, what the + // hell, right?) by loading both the index and enumerator + // combined into a string: + // + // CONCAT (enum+0, ' ', enum) + // + // For cases where we need the index, everything works since + // MySQL will convert the leading number and stop at the space. + // For cases where we need the enumerator, we do a bit of pre- + // processing (see enum_traits) before handing the value off + // to value_traits. + // + + string const& type (column_type ()); + + if (sk_ != statement_select || + parse_sql_type (type, m).type != sql_type::ENUM) + { + return base::column (m, table, column); + } + + // Qualified column and conversion expression. + // + string qc; + if (!table.empty ()) + { + qc += table; + qc += '.'; + } + qc += column; + qc = convert_from (qc, type, m); + + string r ("CONCAT(" + qc + "+0,' '," + qc + ")"); + + sc_.push_back ( + relational::statement_column (table, r, type, m, key_prefix_)); + return true; + } + }; + entry<object_columns> object_columns_; + + struct view_columns: relational::view_columns, context + { + view_columns (base const& x): base (x) {} + + virtual bool + column (semantics::data_member& m, + string const& table, + string const& column) + { + // The same idea as in object_columns. + // + string const& type (column_type ()); + + if (parse_sql_type (type, m).type != sql_type::ENUM) + { + return base::column (m, table, column); + } + + // Column and conversion expression. + // + string c (convert_from (column, type, m)); + + string r ("CONCAT(" + c + "+0,' '," + c + ")"); + sc_.push_back (relational::statement_column (table, r, type, m)); + return true; + } + }; + entry<view_columns> view_columns_; + + // + // bind + // + + struct bind_member: relational::bind_member_impl<sql_type>, + member_base + { + bind_member (base const& x) + : member_base::base (x), // virtual base + member_base::base_impl (x), // virtual base + base_impl (x), + member_base (x) + { + } + + virtual void + traverse_integer (member_info& mi) + { + // While the is_unsigned should indicate whether the + // buffer variable is unsigned, rather than whether the + // database type is unsigned, in case of the image types, + // this is the same. + // + os << b << ".buffer_type = " << + integer_buffer_types[mi.st->type - sql_type::TINYINT] << ";" + << b << ".is_unsigned = " << (mi.st->unsign ? "1" : "0") << ";" + << b << ".buffer = &" << arg << "." << mi.var << "value;" + << b << ".is_null = &" << arg << "." << mi.var << "null;"; + } + + virtual void + traverse_float (member_info& mi) + { + os << b << ".buffer_type = " << + float_buffer_types[mi.st->type - sql_type::FLOAT] << ";" + << b << ".buffer = &" << arg << "." << mi.var << "value;" + << b << ".is_null = &" << arg << "." << mi.var << "null;"; + } + + virtual void + traverse_decimal (member_info& mi) + { + os << b << ".buffer_type = MYSQL_TYPE_NEWDECIMAL;" + << b << ".buffer = " << arg << "." << mi.var << "value.data ();" + << b << ".buffer_length = static_cast<unsigned long> (" << endl + << arg << "." << mi.var << "value.capacity ());" + << b << ".length = &" << arg << "." << mi.var << "size;" + << b << ".is_null = &" << arg << "." << mi.var << "null;"; + } + + virtual void + traverse_date_time (member_info& mi) + { + os << b << ".buffer_type = " << + date_time_buffer_types[mi.st->type - sql_type::DATE] << ";" + << b << ".buffer = &" << arg << "." << mi.var << "value;"; + + if (mi.st->type == sql_type::YEAR) + os << b << ".is_unsigned = 0;"; + + os << b << ".is_null = &" << arg << "." << mi.var << "null;"; + } + + virtual void + traverse_short_string (member_info& mi) + { + // MySQL documentation is quite confusing about the use of + // buffer_length and length when it comes to input parameters. + // Source code, however, tells us that it uses buffer_length + // only if length is NULL. + // + os << b << ".buffer_type = " << + char_bin_buffer_types[mi.st->type - sql_type::CHAR] << ";" + << b << ".buffer = " << arg << "." << mi.var << "value.data ();" + << b << ".buffer_length = static_cast<unsigned long> (" << endl + << arg << "." << mi.var << "value.capacity ());" + << b << ".length = &" << arg << "." << mi.var << "size;" + << b << ".is_null = &" << arg << "." << mi.var << "null;"; + } + + virtual void + traverse_long_string (member_info& mi) + { + os << b << ".buffer_type = " << + char_bin_buffer_types[mi.st->type - sql_type::CHAR] << ";" + << b << ".buffer = " << arg << "." << mi.var << "value.data ();" + << b << ".buffer_length = static_cast<unsigned long> (" << endl + << arg << "." << mi.var << "value.capacity ());" + << b << ".length = &" << arg << "." << mi.var << "size;" + << b << ".is_null = &" << arg << "." << mi.var << "null;"; + } + + virtual void + traverse_bit (member_info& mi) + { + // Treated as a BLOB. + // + os << b << ".buffer_type = MYSQL_TYPE_BLOB;" + << b << ".buffer = " << arg << "." << mi.var << "value;" + << b << ".buffer_length = static_cast<unsigned long> (" << endl + << "sizeof (" << arg << "." << mi.var << "value));" + << b << ".length = &" << arg << "." << mi.var << "size;" + << b << ".is_null = &" << arg << "." << mi.var << "null;"; + } + + virtual void + traverse_enum (member_info& mi) + { + // Represented as either integer or string. + // + os << "mysql::enum_traits::bind (" << b << "," << endl + << arg << "." << mi.var << "value," << endl + << arg << "." << mi.var << "size," << endl + << "&" << arg << "." << mi.var << "null);"; + } + + virtual void + traverse_set (member_info& mi) + { + // Represented as a string. + // + os << b << ".buffer_type = MYSQL_TYPE_STRING;" + << b << ".buffer = " << arg << "." << mi.var << "value.data ();" + << b << ".buffer_length = static_cast<unsigned long> (" << endl + << arg << "." << mi.var << "value.capacity ());" + << b << ".length = &" << arg << "." << mi.var << "size;" + << b << ".is_null = &" << arg << "." << mi.var << "null;"; + } + }; + entry<bind_member> bind_member_; + + // + // grow + // + + struct grow_member: relational::grow_member_impl<sql_type>, + member_base + { + grow_member (base const& x) + : member_base::base (x), // virtual base + member_base::base_impl (x), // virtual base + base_impl (x), + member_base (x) {} + + virtual void + traverse_integer (member_info&) + { + os << e << " = 0;" + << endl; + } + + virtual void + traverse_float (member_info&) + { + os << e << " = 0;" + << endl; + } + + virtual void + traverse_decimal (member_info& mi) + { + // @@ Optimization disabled. + // + os << "if (" << e << ")" << endl + << "{" + << "i." << mi.var << "value.capacity (i." << mi.var << "size);" + << "grew = true;" + << "}"; + } + + virtual void + traverse_date_time (member_info&) + { + os << e << " = 0;" + << endl; + } + + virtual void + traverse_short_string (member_info& mi) + { + // @@ Optimization disabled. + // + os << "if (" << e << ")" << endl + << "{" + << "i." << mi.var << "value.capacity (i." << mi.var << "size);" + << "grew = true;" + << "}"; + } + + virtual void + traverse_long_string (member_info& mi) + { + os << "if (" << e << ")" << endl + << "{" + << "i." << mi.var << "value.capacity (i." << mi.var << "size);" + << "grew = true;" + << "}"; + } + + virtual void + traverse_bit (member_info&) + { + os << e << " = 0;" + << endl; + } + + virtual void + traverse_enum (member_info& mi) + { + // Represented as either integer or string (and we don't know + // at the code generation time which one it is). + // + os << "if (" << e << ")" << endl + << "{" + << "if (mysql::enum_traits::grow (" << + "i." << mi.var << "value, " << + "i." << mi.var << "size))" << endl + << "grew = true;" // String + << "else" << endl + << e << " = 0;" // Integer. + << "}"; + } + + virtual void + traverse_set (member_info& mi) + { + // Represented as a string. + // + os << "if (" << e << ")" << endl + << "{" + << "i." << mi.var << "value.capacity (i." << mi.var << "size);" + << "grew = true;" + << "}"; + } + }; + entry<grow_member> grow_member_; + + // + // init image + // + + struct init_image_member: relational::init_image_member_impl<sql_type>, + member_base + { + init_image_member (base const& x) + : member_base::base (x), // virtual base + member_base::base_impl (x), // virtual base + base_impl (x), + member_base (x) + { + } + + virtual void + set_null (member_info& mi) + { + os << "i." << mi.var << "null = 1;"; + } + + virtual void + traverse_integer (member_info& mi) + { + os << traits << "::set_image (" << endl + << "i." << mi.var << "value, is_null, " << member << ");" + << "i." << mi.var << "null = is_null;"; + } + + virtual void + traverse_float (member_info& mi) + { + os << traits << "::set_image (" << endl + << "i." << mi.var << "value, is_null, " << member << ");" + << "i." << mi.var << "null = is_null;"; + } + + virtual void + traverse_decimal (member_info& mi) + { + // @@ Optimization: can remove growth check if buffer is fixed. + // + os << "std::size_t size (0);" + << "std::size_t cap (i." << mi.var << "value.capacity ());" + << traits << "::set_image (" << endl + << "i." << mi.var << "value," << endl + << "size," << endl + << "is_null," << endl + << member << ");" + << "i." << mi.var << "null = is_null;" + << "i." << mi.var << "size = static_cast<unsigned long> (size);" + << "grew = grew || (cap != i." << mi.var << "value.capacity ());"; + } + + virtual void + traverse_date_time (member_info& mi) + { + os << traits << "::set_image (" << endl + << "i." << mi.var << "value, is_null, " << member << ");" + << "i." << mi.var << "null = is_null;"; + } + + virtual void + traverse_short_string (member_info& mi) + { + // @@ Optimization: can remove growth check if buffer is fixed. + // + os << "std::size_t size (0);" + << "std::size_t cap (i." << mi.var << "value.capacity ());" + << traits << "::set_image (" << endl + << "i." << mi.var << "value," << endl + << "size," << endl + << "is_null," << endl + << member << ");" + << "i." << mi.var << "null = is_null;" + << "i." << mi.var << "size = static_cast<unsigned long> (size);" + << "grew = grew || (cap != i." << mi.var << "value.capacity ());"; + } + + virtual void + traverse_long_string (member_info& mi) + { + os << "std::size_t size (0);" + << "std::size_t cap (i." << mi.var << "value.capacity ());" + << traits << "::set_image (" << endl + << "i." << mi.var << "value," << endl + << "size," << endl + << "is_null," << endl + << member << ");" + << "i." << mi.var << "null = is_null;" + << "i." << mi.var << "size = static_cast<unsigned long> (size);" + << "grew = grew || (cap != i." << mi.var << "value.capacity ());"; + } + + virtual void + traverse_bit (member_info& mi) + { + // Represented as a BLOB. + // + os << "std::size_t size (0);" + << traits << "::set_image (" << endl + << "i." << mi.var << "value," << endl + << "sizeof (i." << mi.var << "value)," << endl + << "size," << endl + << "is_null," << endl + << member << ");" + << "i." << mi.var << "null = is_null;" + << "i." << mi.var << "size = static_cast<unsigned long> (size);"; + } + + virtual void + traverse_enum (member_info& mi) + { + // Represented as either integer or string. + // + os << "if (mysql::enum_traits::set_image (" << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "size," << endl + << "is_null," << endl + << member << "))" << endl + << "grew = true;" + << endl + << "i." << mi.var << "null = is_null;"; + } + + virtual void + traverse_set (member_info& mi) + { + // Represented as a string. + // + os << "std::size_t size (0);" + << "std::size_t cap (i." << mi.var << "value.capacity ());" + << traits << "::set_image (" << endl + << "i." << mi.var << "value," << endl + << "size," << endl + << "is_null," << endl + << member << ");" + << "i." << mi.var << "null = is_null;" + << "i." << mi.var << "size = static_cast<unsigned long> (size);" + << "grew = grew || (cap != i." << mi.var << "value.capacity ());"; + } + }; + entry<init_image_member> init_image_member_; + + // + // init value + // + + struct init_value_member: relational::init_value_member_impl<sql_type>, + member_base + { + init_value_member (base const& x) + : member_base::base (x), // virtual base + member_base::base_impl (x), // virtual base + base_impl (x), + member_base (x) + { + } + + virtual void + get_null (string const& var) const + { + os << "i." << var << "null"; + } + + virtual void + traverse_integer (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "null);" + << endl; + } + + virtual void + traverse_float (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "null);" + << endl; + } + + virtual void + traverse_decimal (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "size," << endl + << "i." << mi.var << "null);" + << endl; + } + + virtual void + traverse_date_time (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "null);" + << endl; + } + + virtual void + traverse_short_string (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "size," << endl + << "i." << mi.var << "null);" + << endl; + } + + virtual void + traverse_long_string (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "size," << endl + << "i." << mi.var << "null);" + << endl; + } + + virtual void + traverse_bit (member_info& mi) + { + // Represented as a BLOB. + // + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "size," << endl + << "i." << mi.var << "null);" + << endl; + } + + virtual void + traverse_enum (member_info& mi) + { + // Represented as either integer or string. + // + os << "mysql::enum_traits::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "size," << endl + << "i." << mi.var << "null);" + << endl; + } + + virtual void + traverse_set (member_info& mi) + { + // Represented as a string. + // + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "size," << endl + << "i." << mi.var << "null);" + << endl; + } + }; + entry<init_value_member> init_value_member_; + + struct class_: relational::class_, context + { + class_ (base const& x): base (x) {} + + virtual void + init_auto_id (semantics::data_member& m, string const& im) + { + // Don't set the id value to 0 if this is a nullable wrapper. This + // will allow the user to use the NO_AUTO_VALUE_ON_ZERO mode by + // making it NULL when they want the auto semantics: + // + // #pragma db auto + // odb::nullable<int64_t> id; + // + semantics::type& t (utype (m)); + if (wrapper (t) && t.template get<bool> ("wrapper-null-handler")) + return; + + os << im << "value = 0;" + << endl; + } + + virtual string + join_syntax (view_object const& vo) + { + if (vo.join == view_object::full) + { + error (vo.loc) + << "FULL OUTER JOIN is not supported by MySQL" << endl; + throw operation_failed (); + } + + return base::join_syntax (vo); + } + }; + entry<class_> class_entry_; + + struct include: relational::include, context + { + include (base const& x): base (x) {} + + virtual void + extra_post () + { + os << "#include <odb/mysql/enum.hxx>" << endl; + } + }; + entry<include> include_; + } + } +} diff --git a/odb/odb/relational/oracle/common.cxx b/odb/odb/relational/oracle/common.cxx new file mode 100644 index 0000000..7caafc9 --- /dev/null +++ b/odb/odb/relational/oracle/common.cxx @@ -0,0 +1,522 @@ +// file : odb/relational/oracle/common.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <cassert> + +#include <odb/relational/oracle/common.hxx> + +using namespace std; + +namespace relational +{ + namespace oracle + { + // + // member_base + // + + sql_type const& member_base:: + member_sql_type (semantics::data_member& m) + { + return parse_sql_type (column_type (m, key_prefix_), m); + } + + void member_base:: + traverse_simple (member_info& mi) + { + switch (mi.st->type) + { + // Numeric types. + // + case sql_type::NUMBER: + { + const sql_type& st (*mi.st); + + if (st.prec) + { + unsigned short r (st.prec_value); + + if (!st.scale) + { + if (r <= 10) + traverse_int32 (mi); + // Only OCI versions 11.2 and later support insertion and + // extraction into a 64 bit integer. + // + else if ( + (options.oracle_client_version () >= oracle_version (11, 2)) && + (r <= 19 || (r == 20 && unsigned_integer (mi.t)))) + traverse_int64 (mi); + else + traverse_big_int (mi); + } + else + { + // We can calculate the decimal exponent of the normalised + // floating point equivalent of the fixed point number using + // e = p - s, where p is the precision, s is the scale, and + // e the exponent. We can then use this to determine whether + // or not a value of Oracle SQL type NUMBER can be completely + // stored in the native floating point type. + // + + // The maximum decimal precision of a float is 7 significant + // digits. The minimum and maximum decimal exponents + // representable by a float are -37 and 38 respectively. + // + if (r <= 7) + { + int e = r - st.scale_value; + + if (e >= -37 && e <= 38) + traverse_float (mi); + else + traverse_double (mi); + } + + // The maximum decimal precision of a double is 15 significant + // digits. The minimum and maximum decimal exponent representable + // by a double exceeds that of the Oracle NUMBER type. + // + else if (r <= 15) + traverse_double (mi); + else + traverse_big_float (mi); + } + } + else + // If there is no precision, then this is a floating-point number. + // + traverse_double (mi); + + break; + } + case sql_type::FLOAT: + { + // We map FLOAT types based exclusively on their binary precision + // seeing that in 99% of cases it is the precision that is the + // limiting factor and not the exponent. + // + if (mi.st->prec_value <= 24) + traverse_float (mi); + else if (mi.st->prec_value <= 53) + traverse_double (mi); + else + traverse_big_float (mi); + + break; + } + case sql_type::BINARY_FLOAT: + { + traverse_float (mi); + break; + } + case sql_type::BINARY_DOUBLE: + { + traverse_double (mi); + break; + } + // Data-time types. + // + case sql_type::DATE: + { + traverse_date (mi); + break; + } + case sql_type::TIMESTAMP: + { + traverse_timestamp (mi); + break; + } + case sql_type::INTERVAL_YM: + { + traverse_interval_ym (mi); + break; + } + case sql_type::INTERVAL_DS: + { + traverse_interval_ds (mi); + break; + } + // String and binary types. + // + case sql_type::CHAR: + case sql_type::NCHAR: + case sql_type::VARCHAR2: + case sql_type::NVARCHAR2: + case sql_type::RAW: + { + traverse_string (mi); + break; + } + case sql_type::BLOB: + case sql_type::CLOB: + case sql_type::NCLOB: + { + traverse_lob (mi); + break; + } + case sql_type::invalid: + { + assert (false); + break; + } + } + } + + // + // member_image_type + // + + member_image_type:: + member_image_type (base const& x) + : member_base::base (x), // virtual base + base (x) {} + + member_image_type:: + member_image_type () + : relational::member_base (0, 0, string (), string ()) {} + + member_image_type:: + member_image_type (semantics::type* type, + const custom_cxx_type* ct, + string const& fq_type, + string const& key_prefix) + : relational::member_base (type, ct, fq_type, key_prefix) {} + + string member_image_type:: + image_type (semantics::data_member& m) + { + type_.clear (); + member_base::traverse (m, true); + return type_; + } + + void member_image_type:: + traverse_composite (member_info& mi) + { + type_ = "composite_value_traits< " + mi.fq_type () + + ", id_oracle >::image_type"; + } + + void member_image_type:: + traverse_int32 (member_info& mi) + { + if (unsigned_integer (mi.t)) + type_ = "unsigned int"; + else + type_ = "int"; + } + + void member_image_type:: + traverse_int64 (member_info& mi) + { + if (unsigned_integer (mi.t)) + type_ = "unsigned long long"; + else + type_ = "long long"; + } + + void member_image_type:: + traverse_big_int (member_info&) + { + type_ = "char*"; + } + + void member_image_type:: + traverse_float (member_info&) + { + type_ = "float"; + } + + void member_image_type:: + traverse_double (member_info&) + { + type_ = "double"; + } + + void member_image_type:: + traverse_big_float (member_info&) + { + type_ = "char*"; + } + + void member_image_type:: + traverse_date (member_info&) + { + type_ = "char*"; + } + + void member_image_type:: + traverse_timestamp (member_info&) + { + type_ = "oracle::datetime"; + } + + void member_image_type:: + traverse_interval_ym (member_info&) + { + type_ = "oracle::interval_ym"; + } + + void member_image_type:: + traverse_interval_ds (member_info&) + { + type_ = "oracle::interval_ds"; + } + + void member_image_type:: + traverse_string (member_info&) + { + type_ = "char*"; + } + + void member_image_type:: + traverse_lob (member_info&) + { + type_ = "oracle::lob_callback"; + } + + entry<member_image_type> member_image_type_; + + // + // member_database_type + // + + namespace + { + const char* string_bin_database_id[] = + { + "id_string", // CHAR + "id_nstring", // NCHAR + "id_string", // VARCHAR2 + "id_nstring", // NVARCHAR2 + "id_raw" // RAW + }; + + const char* lob_database_id[] = + { + "id_blob", + "id_clob", + "id_nclob" + }; + } + + member_database_type_id:: + member_database_type_id (base const& x) + : member_base::base (x), // virtual base + base (x) {} + + member_database_type_id:: + member_database_type_id () + : member_base::base (0, 0, string (), string ()), // virtual base + base (0, 0, string (), string ()) {} + + member_database_type_id:: + member_database_type_id (semantics::type* type, + const custom_cxx_type* ct, + string const& fq_type, + string const& key_prefix) + : member_base::base (type, ct, fq_type, key_prefix), // virtual base + base (type, ct, fq_type, key_prefix) {} + + string member_database_type_id:: + database_type_id (type& m) + { + type_id_.clear (); + member_base::traverse (m, true); + return type_id_; + } + + void member_database_type_id:: + traverse_composite (member_info&) + { + assert (false); + } + + void member_database_type_id:: + traverse_int32 (member_info&) + { + type_id_ = "oracle::id_int32"; + } + + void member_database_type_id:: + traverse_int64 (member_info&) + { + type_id_ = "oracle::id_int64"; + } + + void member_database_type_id:: + traverse_big_int (member_info&) + { + type_id_ = "oracle::id_big_int"; + } + + void member_database_type_id:: + traverse_float (member_info&) + { + type_id_ = "oracle::id_float"; + } + + void member_database_type_id:: + traverse_double (member_info&) + { + type_id_ = "oracle::id_double"; + } + + void member_database_type_id:: + traverse_big_float (member_info&) + { + type_id_ = "oracle::id_big_float"; + } + + void member_database_type_id:: + traverse_date (member_info&) + { + type_id_ = "oracle::id_date"; + } + + void member_database_type_id:: + traverse_timestamp (member_info&) + { + type_id_ = "oracle::id_timestamp"; + } + + void member_database_type_id:: + traverse_interval_ym (member_info&) + { + type_id_ = "oracle::id_interval_ym"; + } + + void member_database_type_id:: + traverse_interval_ds (member_info&) + { + type_id_ = "oracle::id_interval_ds"; + } + + void member_database_type_id:: + traverse_string (member_info& mi) + { + type_id_ = string ("oracle::") + + string_bin_database_id[mi.st->type - sql_type::CHAR]; + } + + void member_database_type_id:: + traverse_lob (member_info& mi) + { + type_id_ = string ("oracle::") + + lob_database_id[mi.st->type - sql_type::BLOB]; + } + + entry<member_database_type_id> member_database_type_id_; + + // + // query_columns + // + + struct query_columns: relational::query_columns, context + { + query_columns (base const& x): base_impl (x) {} + + void + column_ctor (string const& type, string const& name, string const& base) + { + os << name << " ("; + + if (multi_dynamic) + os << "odb::query_column< " << type << " >& qc," << endl; + + os << "const char* t," << endl + << "const char* c," << endl + << "const char* conv," << endl + << "unsigned short p = 0xFFF," << endl + << "short s = 0xFFF)" << endl + << " : " << base << " (" << (multi_dynamic ? "qc, " : "") << + "t, c, conv, p, s)" + << "{" + << "}"; + } + + virtual void + column_ctor_args_extra (semantics::data_member& m) + { + // For some types we need to pass precision and scale. + // + sql_type const& st (parse_sql_type (column_type (), m)); + + switch (st.type) + { + case sql_type::NUMBER: + { + if (st.prec) + { + os << ", " << st.prec_value; + + if (st.scale) + os << ", " << st.scale_value; + } + break; + } + case sql_type::FLOAT: + { + os << ", " << st.prec_value; + break; + } + case sql_type::TIMESTAMP: + { + os << ", " << st.prec_value; + break; + } + case sql_type::INTERVAL_YM: + { + os << ", " << st.prec_value; + break; + } + case sql_type::INTERVAL_DS: + { + // INTERVAL DAY TO SECOND has two precisions. + // + os << ", " << st.prec_value << ", " << st.scale_value; + break; + } + case sql_type::CHAR: + case sql_type::NCHAR: + case sql_type::VARCHAR2: + case sql_type::NVARCHAR2: + case sql_type::RAW: + { + // The same logic as in header.cxx. + // + size_t n (st.prec ? st.prec_value : 1); + + if (!st.byte_semantics) + n *= 4; + + if (st.type == sql_type::VARCHAR2 || + st.type == sql_type::NVARCHAR2) + n = n > 4000 ? 4000 : n; + else + n = n > 2000 ? 2000 : n; + + os << ", " << n; + break; + } + default: + { + break; + } + } + } + + virtual string + database_type_id (semantics::data_member& m) + { + return member_database_type_id_.database_type_id (m); + } + + private: + member_database_type_id member_database_type_id_; + }; + entry<query_columns> query_columns_; + } +} diff --git a/odb/odb/relational/oracle/common.hxx b/odb/odb/relational/oracle/common.hxx new file mode 100644 index 0000000..1958aab --- /dev/null +++ b/odb/odb/relational/oracle/common.hxx @@ -0,0 +1,203 @@ +// file : odb/relational/oracle/common.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_RELATIONAL_ORACLE_COMMON_HXX +#define ODB_RELATIONAL_ORACLE_COMMON_HXX + +#include <odb/relational/common.hxx> +#include <odb/relational/oracle/context.hxx> + +namespace relational +{ + namespace oracle + { + struct member_base: virtual relational::member_base_impl<sql_type>, context + { + member_base (base const& x): base (x), base_impl (x) {} + + // This c-tor is for the direct use inside the oracle namespace. + // If you do use this c-tor, you should also explicitly call + // relational::member_base (aka base). + // + member_base () {} + + virtual sql_type const& + member_sql_type (semantics::data_member&); + + virtual void + traverse_simple (member_info&); + + virtual void + traverse_int32 (member_info&) + { + } + + virtual void + traverse_int64 (member_info&) + { + } + + virtual void + traverse_big_int (member_info&) + { + } + + virtual void + traverse_float (member_info&) + { + } + + virtual void + traverse_double (member_info&) + { + } + + virtual void + traverse_big_float (member_info&) + { + } + + virtual void + traverse_date (member_info&) + { + } + + virtual void + traverse_timestamp (member_info&) + { + } + + virtual void + traverse_interval_ym (member_info&) + { + } + + virtual void + traverse_interval_ds (member_info&) + { + } + + virtual void + traverse_string (member_info&) + { + } + + virtual void + traverse_lob (member_info&) + { + } + }; + + struct member_image_type: relational::member_image_type, + member_base + { + member_image_type (base const&); + member_image_type (); + member_image_type (semantics::type* type, + const custom_cxx_type*, + string const& fq_type = string (), + string const& key_prefix = string ()); + virtual string + image_type (semantics::data_member&); + + virtual void + traverse_composite (member_info&); + + virtual void + traverse_int32 (member_info&); + + virtual void + traverse_int64 (member_info&); + + virtual void + traverse_big_int (member_info&); + + virtual void + traverse_float (member_info&); + + virtual void + traverse_double (member_info&); + + virtual void + traverse_big_float (member_info&); + + virtual void + traverse_date (member_info&); + + virtual void + traverse_timestamp (member_info&); + + virtual void + traverse_interval_ym (member_info&); + + virtual void + traverse_interval_ds (member_info&); + + virtual void + traverse_string (member_info&); + + virtual void + traverse_lob (member_info&); + + private: + string type_; + }; + + struct member_database_type_id: relational::member_database_type_id, + member_base + { + member_database_type_id (base const&); + member_database_type_id (); + member_database_type_id (semantics::type* type, + const custom_cxx_type*, + string const& fq_type = string (), + string const& key_prefix = string ()); + + virtual string + database_type_id (type&); + + virtual void + traverse_composite (member_info&); + + virtual void + traverse_int32 (member_info&); + + virtual void + traverse_int64 (member_info&); + + virtual void + traverse_big_int (member_info&); + + virtual void + traverse_float (member_info&); + + virtual void + traverse_double (member_info&); + + virtual void + traverse_big_float (member_info&); + + virtual void + traverse_date (member_info&); + + virtual void + traverse_timestamp (member_info&); + + virtual void + traverse_interval_ym (member_info&); + + virtual void + traverse_interval_ds (member_info&); + + virtual void + traverse_string (member_info&); + + virtual void + traverse_lob (member_info&); + + private: + string type_id_; + }; + } +} +#endif // ODB_RELATIONAL_ORACLE_COMMON_HXX 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); + } + } + } +} diff --git a/odb/odb/relational/oracle/context.hxx b/odb/odb/relational/oracle/context.hxx new file mode 100644 index 0000000..6c55853 --- /dev/null +++ b/odb/odb/relational/oracle/context.hxx @@ -0,0 +1,188 @@ +// file : odb/relational/oracle/context.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_RELATIONAL_ORACLE_CONTEXT_HXX +#define ODB_RELATIONAL_ORACLE_CONTEXT_HXX + +#include <map> + +#include <odb/relational/context.hxx> + +namespace relational +{ + namespace oracle + { + struct sql_type + { + // Keep the order in each block of types. + // + enum core_type + { + // Numeric types. + // + NUMBER, + FLOAT, + + // Floating point types. + // + BINARY_FLOAT, + BINARY_DOUBLE, + + // Date-time types. + // + DATE, + TIMESTAMP, + INTERVAL_YM, + INTERVAL_DS, + + // String and binary types. + // + CHAR, + NCHAR, + VARCHAR2, + NVARCHAR2, + RAW, + + // LOB types. + // + BLOB, + CLOB, + NCLOB, + + // Invalid type. + // + invalid + }; + + sql_type () : + type (invalid), prec (false), scale (false), byte_semantics (true) + { + } + + core_type type; + + bool prec; + unsigned short prec_value; // Oracle max value is 4000. + + bool scale; + short scale_value; // Oracle min value is -84. Max value is 127. + + bool byte_semantics; + + // Conversion expressions for custom database types. + // + std::string to; + std::string from; + }; + + class context: public virtual relational::context + { + public: + sql_type const& + parse_sql_type (string const&, + semantics::data_member&, + bool custom = true); + public: + struct invalid_sql_type + { + invalid_sql_type (string const& message): message_ (message) {} + + string const& + message () const {return message_;} + + private: + string message_; + }; + + // If custom_db_types is NULL, then this function returns + // invalid type instead of throwing in case an unknown type + // is encountered. + // + static sql_type + parse_sql_type (string, custom_db_types const* = 0); + + public: + // If necessary, unwraps. + // + static bool + unsigned_integer (semantics::type&); + + public: + // Construct sequence name from a given table name. + // + qname + sequence_name (qname const& table); + + protected: + virtual string const& + convert_expr (string const&, semantics::data_member&, bool); + + virtual string + quote_id_impl (qname const&) const; + + protected: + virtual string + database_type_impl (semantics::type&, semantics::names*, bool, bool*); + + public: + virtual + ~context (); + + context (); + context (std::ostream&, + semantics::unit&, + options_type const&, + features_type&, + sema_rel::model*); + + static context& + current () + { + return *current_; + } + + private: + static context* current_; + + private: + struct data: base_context::data + { + data (std::ostream& os): base_context::data (os) {} + + struct sql_type_cache_entry + { + sql_type_cache_entry () + : custom_cached (false), straight_cached (false) {} + + sql_type const& + cache_custom (sql_type const& t) + { + custom = t; + custom_cached = true; + return custom; + } + + sql_type const& + cache_straight (sql_type const& t) + { + straight = t; + straight_cached = true; + return straight; + } + + sql_type custom; // With custom mapping. + sql_type straight; // Without custom mapping. + + bool custom_cached; + bool straight_cached; + }; + + typedef std::map<string, sql_type_cache_entry> sql_type_cache; + sql_type_cache sql_type_cache_; + }; + data* data_; + }; + } +} + +#endif // ODB_RELATIONAL_ORACLE_CONTEXT_HXX diff --git a/odb/odb/relational/oracle/header.cxx b/odb/odb/relational/oracle/header.cxx new file mode 100644 index 0000000..bf50bb2 --- /dev/null +++ b/odb/odb/relational/oracle/header.cxx @@ -0,0 +1,230 @@ +// file : odb/relational/oracle/header.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <odb/relational/header.hxx> + +#include <odb/relational/oracle/common.hxx> +#include <odb/relational/oracle/context.hxx> + +namespace relational +{ + namespace oracle + { + namespace header + { + namespace relational = relational::header; + + struct image_type: relational::image_type, context + { + image_type (base const& x): base (x) {}; + + virtual void + image_extra (type& c) + { + if (!(composite (c) || (abstract (c) && !polymorphic (c)))) + { + type* poly_root (polymorphic (c)); + + // If this is a polymorphic type, only add callback to the root. + // + if (poly_root == 0 || poly_root == &c) + { + bool gc (options.generate_query ()); + + if (gc) + os << "oracle::change_callback change_callback_;" + << endl; + + os << "oracle::change_callback*" << endl + << "change_callback ()" + << "{"; + + if (gc) + os << "return &change_callback_;"; + else + os << "return 0;"; + + os << "}"; + } + } + } + }; + entry<image_type> image_type_; + + struct image_member: relational::image_member_impl<sql_type>, + member_base + { + image_member (base const& x) + : member_base::base (x), // virtual base + member_base::base_impl (x), // virtual base + base_impl (x), + member_base (x) {} + + virtual void + traverse_int32 (member_info& mi) + { + os << image_type << " " << mi.var << "value;" + << "sb2 " << mi.var << "indicator;" + << endl; + } + + virtual void + traverse_int64 (member_info& mi) + { + os << image_type << " " << mi.var << "value;" + << "sb2 " << mi.var << "indicator;" + << endl; + } + + virtual void + traverse_big_int (member_info& mi) + { + // Each significant base-100 digit requires a byte of storage + // in the manitissa. The default precision is 38 decimal digits, + // which is equivalent to 19 base-100 digits. + // + size_t n (19); + + if (mi.st->prec) + n = mi.st->prec_value / 2 + mi.st->prec_value % 2; + + // We require an additional byte for each of the exponent and + // negative value terminator values. + // + n += 2; + + os << "char " << mi.var << "value[" << n << "];" + << "ub2 " << mi.var << "size;" + << "sb2 " << mi.var << "indicator;" + << endl; + } + + virtual void + traverse_float (member_info& mi) + { + os << image_type << " " << mi.var << "value;" + << "sb2 " << mi.var << "indicator;" + << endl; + } + + virtual void + traverse_double (member_info& mi) + { + os << image_type << " " << mi.var << "value;" + << "sb2 " << mi.var << "indicator;" + << endl; + } + + virtual void + traverse_big_float (member_info& mi) + { + // big_float is mapped to the OCI type SQLT_NUM, which requires 21 + // bytes of storage. + // + os << "char " << mi.var << "value[21];" + << "ub2 " << mi.var << "size;" + << "sb2 " << mi.var << "indicator;" + << endl; + } + + virtual void + traverse_date (member_info& mi) + { + os << "char " << mi.var << "value[7];" + << "sb2 " << mi.var << "indicator;" + << endl; + } + + virtual void + traverse_timestamp (member_info& mi) + { + os << image_type << " " << mi.var << "value;" + << "sb2 " << mi.var << "indicator;" + << endl; + } + + virtual void + traverse_interval_ym (member_info& mi) + { + os << image_type << " " << mi.var << "value;" + << "sb2 " << mi.var << "indicator;" + << endl; + } + + virtual void + traverse_interval_ds (member_info& mi) + { + os << image_type << " " << mi.var << "value;" + << "sb2 " << mi.var << "indicator;" + << endl; + } + + virtual void + traverse_string (member_info& mi) + { + size_t n (mi.st->prec ? mi.st->prec_value : 1); + + // National characters can be either UTF-8 or UTF-16 encoded, + // both of which have a maximum character encoding size of 4 + // bytes. Database character set can also be UTF-8 so if the + // size is specified in characters, then conservatively assume + // each character can take up to 4 bytes. + // + if (!mi.st->byte_semantics) // N*CHAR always has CHAR semantics. + n *= 4; + + if (mi.st->type == sql_type::VARCHAR2 || + mi.st->type == sql_type::NVARCHAR2) + n = n > 4000 ? 4000 : n; + else + n = n > 2000 ? 2000 : n; + + os << "char " << mi.var << "value[" << n << "];" + << "ub2 " << mi.var << "size;" + << "sb2 " << mi.var << "indicator;" + << endl; + } + + virtual void + traverse_lob (member_info& mi) + { + os << "mutable " << image_type << " " << mi.var << "callback;" + << "sb2 " << mi.var << "indicator;" + << "oracle::lob " << mi.var << "lob;" + << endl; + } + }; + entry<image_member> image_member_; + + struct class1: relational::class1 + { + class1 (base const& x): base (x) {} + + virtual void + object_public_extra_pre (type& c) + { + bool abst (abstract (c)); + + type* poly_root (polymorphic (c)); + bool poly (poly_root != 0); + bool poly_derived (poly && poly_root != &c); + + if (poly_derived || (abst && !poly)) + return; + + // Bulk operations batch size. + // + { + unsigned long long b (c.count ("bulk") + ? c.get<unsigned long long> ("bulk") + : 1); + + os << "static const std::size_t batch = " << b << "UL;" + << endl; + } + } + }; + entry<class1> class1_entry_; + } + } +} diff --git a/odb/odb/relational/oracle/inline.cxx b/odb/odb/relational/oracle/inline.cxx new file mode 100644 index 0000000..1b6d606 --- /dev/null +++ b/odb/odb/relational/oracle/inline.cxx @@ -0,0 +1,42 @@ +// file : odb/relational/oracle/inline.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <odb/relational/inline.hxx> + +#include <odb/relational/oracle/common.hxx> +#include <odb/relational/oracle/context.hxx> + +using namespace std; + +namespace relational +{ + namespace oracle + { + namespace inline_ + { + namespace relational = relational::inline_; + + struct null_member: relational::null_member_impl<sql_type>, + member_base + { + null_member (base const& x) + : member_base::base (x), // virtual base + member_base::base_impl (x), // virtual base + base_impl (x), + member_base (x) + { + } + + virtual void + traverse_simple (member_info& mi) + { + if (get_) + os << "r = r && i." << mi.var << "indicator == -1;"; + else + os << "i." << mi.var << "indicator = -1;"; + } + }; + entry<null_member> null_member_; + } + } +} diff --git a/odb/odb/relational/oracle/model.cxx b/odb/odb/relational/oracle/model.cxx new file mode 100644 index 0000000..b65e201 --- /dev/null +++ b/odb/odb/relational/oracle/model.cxx @@ -0,0 +1,64 @@ +// file : odb/relational/oracle/model.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <sstream> + +#include <odb/relational/model.hxx> + +#include <odb/relational/oracle/common.hxx> +#include <odb/relational/oracle/context.hxx> + +using namespace std; + +namespace relational +{ + namespace oracle + { + namespace model + { + namespace relational = relational::model; + + struct object_columns: relational::object_columns, context + { + object_columns (base const& x): base (x) {} + + virtual string + default_enum (semantics::data_member& m, tree en, string const&) + { + // Make sure the column is mapped to Oracle NUMBER. + // + sql_type const& t (parse_sql_type (column_type (), m, false)); + if (t.type != sql_type::NUMBER) + { + cerr << m.file () << ":" << m.line () << ":" << m.column () + << ": error: column with default value specified as C++ " + << "enumerator must map to Oracle NUMBER" << endl; + + throw operation_failed (); + } + + using semantics::enumerator; + + enumerator& e (dynamic_cast<enumerator&> (*unit.find (en))); + + ostringstream ostr; + + if (e.enum_ ().unsigned_ ()) + ostr << e.value (); + else + ostr << static_cast<long long> (e.value ()); + + return ostr.str (); + } + + virtual void + primary_key (sema_rel::primary_key& pk) + { + if (pk.auto_ ()) + pk.extra ()["sequence"] = sequence_name (table_.name ()).string (); + } + }; + entry<object_columns> object_columns_; + } + } +} diff --git a/odb/odb/relational/oracle/schema.cxx b/odb/odb/relational/oracle/schema.cxx new file mode 100644 index 0000000..75100b1 --- /dev/null +++ b/odb/odb/relational/oracle/schema.cxx @@ -0,0 +1,696 @@ +// file : odb/relational/oracle/schema.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <map> +#include <utility> // pair + +#include <odb/diagnostics.hxx> + +#include <odb/relational/schema.hxx> + +#include <odb/relational/oracle/common.hxx> +#include <odb/relational/oracle/context.hxx> + +using namespace std; + +namespace relational +{ + namespace oracle + { + namespace schema + { + namespace relational = relational::schema; + using relational::table_set; + + struct sql_emitter: relational::sql_emitter + { + sql_emitter (const base& x): base (x) {} + + virtual void + line (const std::string& l) + { + // SQLPlus doesn't like empty line in the middle of a statement. + // + if (!l.empty ()) + { + base::line (l); + last_ = l; + } + } + + virtual void + post () + { + if (!first_) // Ignore empty statements. + { + if (last_ == "END;") + os << endl + << '/' << endl + << endl; + + else + os << ';' << endl + << endl; + } + } + + private: + string last_; + }; + entry<sql_emitter> sql_emitter_; + + // + // File. + // + + struct sql_file: relational::sql_file, context + { + sql_file (const base& x): base (x) {} + + virtual void + prologue () + { + // Quiet down SQLPlus and make sure it exits with an error + // code if there is an error. + // + os << "SET FEEDBACK OFF;" << endl + << "WHENEVER SQLERROR EXIT FAILURE;" << endl + << "WHENEVER OSERROR EXIT FAILURE;" << endl + << endl; + } + + virtual void + epilogue () + { + os << "EXIT;" << endl; + } + }; + entry<sql_file> sql_file_; + + // + // Drop. + // + + struct drop_column: relational::drop_column, context + { + drop_column (base const& x): base (x) {} + + virtual void + traverse (sema_rel::drop_column& dc) + { + if (first_) + first_ = false; + else + os << "," << endl + << " "; + + os << quote_id (dc.name ()); + } + }; + entry<drop_column> drop_column_; + + struct drop_foreign_key: relational::drop_foreign_key, context + { + drop_foreign_key (base const& x): base (x) {} + + virtual void + traverse (sema_rel::drop_foreign_key& dfk) + { + os << endl; + drop (dfk); + } + }; + entry<drop_foreign_key> drop_foreign_key_; + + struct drop_index: relational::drop_index, context + { + drop_index (base const& x): base (x) {} + + virtual string + name (sema_rel::index& in) + { + // In Oracle, index names can be qualified with the schema. + // + sema_rel::table& t (static_cast<sema_rel::table&> (in.scope ())); + sema_rel::qname n (t.name ().qualifier ()); + n.append (in.name ()); + return quote_id (n); + } + }; + entry<drop_index> drop_index_; + + struct drop_table: relational::drop_table, context + { + drop_table (base const& x): base (x) {} + + virtual void + drop (sema_rel::table& t, bool migration) + { + using sema_rel::primary_key; + + sema_rel::table::names_iterator i (t.find ("")); // Special name. + primary_key* pk (i != t.names_end () + ? &dynamic_cast<primary_key&> (i->nameable ()) + : 0); + + string qt (quote_id (t.name ())); + string qs (pk != 0 && pk->auto_ () + ? quote_id (qname::from_string (pk->extra ()["sequence"])) + : ""); + + if (migration) + { + pre_statement (); + os << "DROP TABLE " << qt << endl; + post_statement (); + + // Drop the sequence if we have auto primary key. + // + if (!qs.empty ()) + { + pre_statement (); + os << "DROP SEQUENCE " << qs << endl; + post_statement (); + } + } + else + { + // Oracle has no IF EXISTS conditional for dropping objects. The + // PL/SQL approach below seems to be the least error-prone and the + // most widely used of the alternatives. + // + pre_statement (); + os << "BEGIN" << endl + << " BEGIN" << endl + << " EXECUTE IMMEDIATE 'DROP TABLE " << qt << " CASCADE " << + "CONSTRAINTS';" << endl + << " EXCEPTION" << endl + << " WHEN OTHERS THEN" << endl + << " IF SQLCODE != -942 THEN RAISE; END IF;" << endl + << " END;" << endl; + + // Drop the sequence if we have auto primary key. + // + if (!qs.empty ()) + { + os << " BEGIN" << endl + << " EXECUTE IMMEDIATE 'DROP SEQUENCE " << qs << + "';" << endl + << " EXCEPTION" << endl + << " WHEN OTHERS THEN" << endl + << " IF SQLCODE != -2289 THEN RAISE; END IF;" << endl + << " END;" << endl; + } + + os << "END;" << endl; + post_statement (); + } + } + + virtual void + traverse (sema_rel::table& t, bool migration) + { + // For migration drop foreign keys explicitly in pre-migration. + // + if (migration) + { + base::traverse (t, migration); + return; + } + + // For schema creation we use the CASCADE clause to drop foreign + // keys. + // + if (pass_ != 2) + return; + + drop (t, migration); + } + }; + entry<drop_table> drop_table_; + + // + // 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 <typename N> + struct scope + { + typedef std::map<N, pair<N, location> > 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<typename map::iterator, bool> 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<sema_rel::qname> tables; + scope<sema_rel::uname> fkeys; // Global but can't have schema. + scope<sema_rel::qname> indexes; + scope<sema_rel::qname> sequences; + scope<sema_rel::uname> 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<scopes*> (context::extra)) + s->columns.check (c.get<location> ("cxx-location"), c.name ()); + + base::traverse (c); + } + + virtual void + traverse (sema_rel::add_column& ac) + { + if (first_) + first_ = false; + else + os << "," << endl + << " "; + + create (ac); + } + + virtual void + constraints (sema_rel::column& c, sema_rel::primary_key* pk) + { + // Oracle wants DEFAULT before NULL even though we can end + // up with mouthfulls like DEFAULT NULL NULL. + // + if (!c.default_ ().empty ()) + os << " DEFAULT " << c.default_ (); + + null (c); + + // If this is a single-column primary key, generate it inline. + // + if (pk != 0 && pk->contains_size () == 1) + primary_key (); + + if (pk != 0 && pk->auto_ ()) + auto_ (*pk); + } + }; + entry<create_column> create_column_; + + struct create_foreign_key: relational::create_foreign_key, context + { + 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<scopes*> (context::extra)) + s->fkeys.check (fk.get<location> ("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<scopes*> (context::extra)) + s->fkeys.check (fk.get<location> ("cxx-location"), fk.name ()); + + os << endl + << " ADD CONSTRAINT "; + create (fk); + } + }; + entry<create_foreign_key> create_foreign_key_; + + struct create_index: relational::create_index, context + { + create_index (base const& x): base (x) {} + + virtual string + name (sema_rel::index& in) + { + // In Oracle, index names can be qualified with the schema. + // + sema_rel::table& t (static_cast<sema_rel::table&> (in.scope ())); + sema_rel::qname n (t.name ().qualifier ()); + n.append (in.name ()); + + // Check name trunction and conflicts. + // + if (scopes* s = static_cast<scopes*> (context::extra)) + s->indexes.check (in.get<location> ("cxx-location"), n); + + return quote_id (n); + } + }; + entry<create_index> create_index_; + + struct create_table: relational::create_table, context + { + create_table (base const& x): base (x) {} + + void + traverse (sema_rel::table& t) + { + // Check name trunction and conflicts. + // + if (scopes* s = static_cast<scopes*> (context::extra)) + { + if (pass_ == 1) + { + s->tables.check (t.get<location> ("cxx-location"), t.name ()); + s->columns.clear (); + } + } + + base::traverse (t); + + if (pass_ == 1) + { + // Create the sequence if we have auto primary key. + // + using sema_rel::primary_key; + + sema_rel::table::names_iterator i (t.find ("")); // Special name. + primary_key* pk (i != t.names_end () + ? &dynamic_cast<primary_key&> (i->nameable ()) + : 0); + + if (pk != 0 && pk->auto_ ()) + { + // Already qualified with the table's schema, if any. + // + sema_rel::qname n ( + qname::from_string (pk->extra ()["sequence"])); + + if (scopes* s = static_cast<scopes*> (context::extra)) + s->sequences.check (pk->get<location> ("cxx-location"), n); + + pre_statement (); + os_ << "CREATE SEQUENCE " << quote_id (n) << endl + << " START WITH 1 INCREMENT BY 1" << endl; + post_statement (); + } + } + } + }; + entry<create_table> 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> create_model_; + + // + // Alter. + // + + struct alter_column: relational::alter_column, context + { + alter_column (base const& x): base (x) {} + + virtual void + traverse (sema_rel::column& c) + { + // Relax (NULL) in pre and tighten (NOT NULL) in post. + // + if (pre_ != c.null ()) + return; + + if (first_) + first_ = false; + else + os << "," << endl + << " "; + + os << quote_id (c.name ()) << (c.null () ? " NULL" : " NOT NULL"); + } + }; + entry<alter_column> alter_column_; + + struct alter_table_pre: relational::alter_table_pre, context + { + alter_table_pre (base const& x): base (x) {} + + virtual void + alter (sema_rel::alter_table& at) + { + // Oracle can only alter certain kinds of things together but + // grouped one at a time. + // + if (check<sema_rel::drop_foreign_key> (at)) + { + pre_statement (); + + os << "ALTER TABLE " << quote_id (at.name ()); + + instance<drop_foreign_key> dfc (*this); + trav_rel::unames n (*dfc); + names (at, n); + os << endl; + + post_statement (); + } + + if (check<sema_rel::add_column> (at)) + { + pre_statement (); + + os << "ALTER TABLE " << quote_id (at.name ()) << endl + << " ADD ("; + + instance<create_column> cc (*this); + trav_rel::unames n (*cc); + names (at, n); + os << ")" << endl; + + post_statement (); + } + + if (check_alter_column_null (at, true)) + { + pre_statement (); + + os << "ALTER TABLE " << quote_id (at.name ()) << endl + << " MODIFY ("; + + bool tl (true); // (Im)perfect forwarding. + instance<alter_column> ac (*this, tl); + trav_rel::unames n (*ac); + names (at, n); + os << ")" << endl; + + post_statement (); + } + } + }; + entry<alter_table_pre> alter_table_pre_; + + struct alter_table_post: relational::alter_table_post, context + { + alter_table_post (base const& x): base (x) {} + + virtual void + alter (sema_rel::alter_table& at) + { + // Oracle can only alter certain kinds of things together but + // grouped one at a time. + // + if (check<sema_rel::drop_column> (at)) + { + pre_statement (); + + os << "ALTER TABLE " << quote_id (at.name ()) << endl + << " DROP ("; + + instance<drop_column> dc (*this); + trav_rel::unames n (*dc); + names (at, n); + os << ")" << endl; + + post_statement (); + } + + if (check_alter_column_null (at, false)) + { + pre_statement (); + + os << "ALTER TABLE " << quote_id (at.name ()) << endl + << " MODIFY ("; + + bool fl (false); // (Im)perfect forwarding. + instance<alter_column> ac (*this, fl); + trav_rel::unames n (*ac); + names (at, n); + os << ")" << endl; + + post_statement (); + } + + if (check<sema_rel::add_foreign_key> (at)) + { + pre_statement (); + + os << "ALTER TABLE " << quote_id (at.name ()); + + instance<create_foreign_key> cfc (*this); + trav_rel::unames n (*cfc); + names (at, n); + os << endl; + + post_statement (); + } + } + }; + entry<alter_table_post> alter_table_post_; + + // + // Schema version table. + // + + struct version_table: relational::version_table, context + { + version_table (base const& x) + : base (x) + { + // If the schema name is empty, replace it with a single space + // to workaround the VARCHAR2 empty/NULL issue. + // + if (qs_ == "''") + qs_ = "' '"; + } + + virtual void + create_table () + { + pre_statement (); + + os << "BEGIN" << endl + << " EXECUTE IMMEDIATE 'CREATE TABLE " << qt_ << " (" << endl + << " " << qn_ << " VARCHAR2(512) NOT NULL PRIMARY KEY," << endl + << " " << qv_ << " NUMBER(20) NOT NULL," << endl + << " " << qm_ << " NUMBER(1) NOT NULL)';" << endl + << "EXCEPTION" << endl + << " WHEN OTHERS THEN" << endl + << " IF SQLCODE != -955 THEN RAISE; END IF;" << endl + << "END;" << endl; + + post_statement (); + } + + virtual void + create (sema_rel::version v) + { + pre_statement (); + + os << "MERGE INTO " << qt_ << " USING DUAL ON (" << qn_ << " = " << + qs_ << ")" << endl + << " WHEN NOT MATCHED THEN INSERT (" << endl + << " " << qn_ << ", " << qv_ << ", " << qm_ << ")" << endl + << " VALUES (" << qs_ << ", " << v << ", 0)" << endl; + + post_statement (); + } + }; + entry<version_table> version_table_; + } + } +} diff --git a/odb/odb/relational/oracle/source.cxx b/odb/odb/relational/oracle/source.cxx new file mode 100644 index 0000000..adf9864 --- /dev/null +++ b/odb/odb/relational/oracle/source.cxx @@ -0,0 +1,646 @@ +// file : odb/relational/oracle/source.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <odb/relational/source.hxx> + +#include <odb/relational/oracle/common.hxx> +#include <odb/relational/oracle/context.hxx> + +using namespace std; + +namespace relational +{ + namespace oracle + { + namespace source + { + namespace relational = relational::source; + + struct query_parameters: relational::query_parameters, context + { + query_parameters (base const& x): base (x), i_ (0) {} + + virtual string + next (semantics::data_member&, const string&, const string&) + { + ostringstream ss; + ss << ":" << ++i_; + + return ss.str (); + } + + virtual string + auto_id (semantics::data_member&, const string&, const string&) + { + return quote_id (sequence_name (table_)) + ".nextval"; + } + + private: + size_t i_; + }; + entry<query_parameters> query_parameters_; + + namespace + { + const char* string_buffer_types[] = + { + "oracle::bind::string", // CHAR + "oracle::bind::nstring", // NCHAR + "oracle::bind::string", // VARCHAR2 + "oracle::bind::nstring", // NVARCHAR2 + "oracle::bind::raw" // RAW + }; + + const char* lob_buffer_types[] = + { + "oracle::bind::blob", + "oracle::bind::clob", + "oracle::bind::nclob" + }; + } + + // + // bind + // + + struct bind_member: relational::bind_member_impl<sql_type>, + member_base + { + bind_member (base const& x) + : member_base::base (x), // virtual base + member_base::base_impl (x), // virtual base + base_impl (x), + member_base (x) + { + } + + virtual void + traverse_int32 (member_info& mi) + { + os << b << ".type = oracle::bind::" << + (unsigned_integer (mi.t) ? "uinteger" : "integer") << ";" + << b << ".buffer = &" << arg << "." << mi.var << "value;" + << b << ".capacity = 4;" + << b << ".size = 0;" + << b << ".indicator = &" << arg << "." << mi.var << "indicator;"; + } + + virtual void + traverse_int64 (member_info& mi) + { + os << b << ".type = oracle::bind::" << + (unsigned_integer (mi.t) ? "uinteger" : "integer") << ";" + << b << ".buffer= &" << arg << "." << mi.var << "value;" + << b << ".capacity = 8;" + << b << ".size = 0;" + << b << ".indicator = &" << arg << "." << mi.var << "indicator;"; + } + + virtual void + traverse_big_int (member_info& mi) + { + os << b << ".type = oracle::bind::number;" + << b << ".buffer = " << arg << "." << mi.var << "value;" + << b << ".capacity = static_cast<ub4> (sizeof (" << arg << + "." << mi.var << "value));" + << b << ".size = &" << arg << "." << mi.var << "size;" + << b << ".indicator = &" << arg << "." << mi.var << "indicator;"; + } + + virtual void + traverse_float (member_info& mi) + { + os << b << ".type = oracle::bind::binary_float;" + << b << ".buffer= &" << arg << "." << mi.var << "value;" + << b << ".capacity = 4;" + << b << ".size = 0;" + << b << ".indicator = &" << arg << "." << mi.var << "indicator;"; + } + + virtual void + traverse_double (member_info& mi) + { + os << b << ".type = oracle::bind::binary_double;" + << b << ".buffer= &" << arg << "." << mi.var << "value;" + << b << ".capacity = 8;" + << b << ".size = 0;" + << b << ".indicator = &" << arg << "." << mi.var << "indicator;"; + } + + virtual void + traverse_big_float (member_info& mi) + { + os << b << ".type = oracle::bind::number;" + << b << ".buffer = " << arg << "." << mi.var << "value;" + << b << ".capacity = static_cast<ub4> (sizeof (" << arg << "." << + mi.var << "value));" + << b << ".size = &" << arg << "." << mi.var << "size;" + << b << ".indicator = &" << arg << "." << mi.var << "indicator;"; + } + + virtual void + traverse_date (member_info& mi) + { + os << b << ".type = oracle::bind::date;" + << b << ".buffer = " << arg << "." << mi.var << "value;" + << b << ".capacity = static_cast<ub4> (sizeof (" << arg << "." << + mi.var << "value));" + << b << ".size = 0;" + << b << ".indicator = &" << arg << "." << mi.var << "indicator;"; + } + + virtual void + traverse_timestamp (member_info& mi) + { + os << b << ".type = oracle::bind::timestamp;" + << b << ".buffer = &" << arg << "." << mi.var << "value;" + << b << ".indicator = &" << arg << "." << mi.var << "indicator;"; + } + + virtual void + traverse_interval_ym (member_info& mi) + { + os << b << ".type = oracle::bind::interval_ym;" + << b << ".buffer = &" << arg << "." << mi.var << "value;" + << b << ".indicator = &" << arg << "." << mi.var << "indicator;"; + } + + virtual void + traverse_interval_ds (member_info& mi) + { + os << b << ".type = oracle::bind::interval_ds;" + << b << ".buffer = &" << arg << "." << mi.var << "value;" + << b << ".indicator = &" << arg << "." << mi.var << "indicator;"; + } + + virtual void + traverse_string (member_info& mi) + { + os << b << ".type = " << + string_buffer_types[mi.st->type - sql_type::CHAR] << ";" + << b << ".buffer = " << arg << "." << mi.var << "value;" + << b << ".capacity = static_cast<ub4> (sizeof (" << arg << + "." << mi.var << "value));" + << b << ".size = &" << arg << "." << mi.var << "size;" + << b << ".indicator = &" << arg << "." << mi.var << "indicator;"; + } + + virtual void + traverse_lob (member_info& mi) + { + os << b << ".type = " << + lob_buffer_types[mi.st->type - sql_type::BLOB] << ";" + << b << ".buffer = &" << arg << "." << mi.var << "lob;" + << b << ".indicator = &" << arg << "." << mi.var << "indicator;" + << b << ".callback = &" << arg << "." << mi.var << "callback;" + << endl; + } + }; + entry<bind_member> bind_member_; + + // + // init image + // + + struct init_image_member: relational::init_image_member_impl<sql_type>, + member_base + { + init_image_member (base const& x) + : member_base::base (x), // virtual base + member_base::base_impl (x), // virtual base + base_impl (x), + member_base (x) + { + } + + virtual void + check_accessor (member_info& mi, member_access& ma) + { + // We cannot use accessors that return by-value for LOB + // members. + // + if ((mi.st->type == sql_type::BLOB || + mi.st->type == sql_type::CLOB || + mi.st->type == sql_type::NCLOB) && + ma.by_value) + { + error (ma.loc) << "accessor returning a value cannot be used " + << "for a data member of Oracle LOB type" << endl; + info (ma.loc) << "accessor returning a const reference is required" + << endl; + info (mi.m.location ()) << "data member is defined here" << endl; + throw operation_failed (); + } + } + + virtual void + set_null (member_info& mi) + { + os << "i." << mi.var << "indicator = -1;"; + } + + virtual void + traverse_int32 (member_info& mi) + { + os << traits << "::set_image (" << endl + << "i." << mi.var << "value, is_null, " << member << ");" + << "i." << mi.var << "indicator = is_null ? -1 : 0;"; + } + + virtual void + traverse_int64 (member_info& mi) + { + os << traits << "::set_image (" << endl + << "i." << mi.var << "value, is_null, " << member << ");" + << "i." << mi.var << "indicator = is_null ? -1 : 0;"; + } + + virtual void + traverse_big_int (member_info& mi) + { + os << "std::size_t size (0);" + << traits << "::set_image (" << endl + << "i." << mi.var << "value," << endl + << "size," << endl + << "is_null," << endl + << member << ");" + << "i." << mi.var << "indicator = is_null ? -1 : 0;" + << "i." << mi.var << "size = static_cast<ub2> (size);"; + } + + virtual void + traverse_float (member_info& mi) + { + os << traits << "::set_image (" << endl + << "i." << mi.var << "value, is_null, " << member << ");" + << "i." << mi.var << "indicator = is_null ? -1 : 0;"; + } + + virtual void + traverse_double (member_info& mi) + { + os << traits << "::set_image (" << endl + << "i." << mi.var << "value, is_null, " << member << ");" + << "i." << mi.var << "indicator = is_null ? -1 : 0;"; + } + + virtual void + traverse_big_float (member_info& mi) + { + os << "std::size_t size (0);" + << traits << "::set_image (" << endl + << "i." << mi.var << "value," << endl + << "sizeof (i." << mi.var << "value)," << endl + << "size," << endl + << "is_null," << endl + << member << ");" + << "i." << mi.var << "indicator = is_null ? -1 : 0;" + << "i." << mi.var << "size = static_cast<ub2> (size);"; + } + + virtual void + traverse_date (member_info& mi) + { + os << traits << "::set_image (" << endl + << "i." << mi.var << "value, is_null, " << member << ");" + << "i." << mi.var << "indicator = is_null ? -1 : 0;"; + } + + virtual void + traverse_timestamp (member_info& mi) + { + os << traits << "::set_image (" << endl + << "i." << mi.var << "value, is_null, " << member << ");" + << "i." << mi.var << "indicator = is_null ? -1 : 0;"; + } + + virtual void + traverse_interval_ym (member_info& mi) + { + os << traits << "::set_image (" << endl + << "i." << mi.var << "value, is_null, " << member << ");" + << "i." << mi.var << "indicator = is_null ? -1 : 0;"; + } + + virtual void + traverse_interval_ds (member_info& mi) + { + os << traits << "::set_image (" << endl + << "i." << mi.var << "value, is_null, " << member << ");" + << "i." << mi.var << "indicator = is_null ? -1 : 0;"; + } + + virtual void + traverse_string (member_info& mi) + { + os << "std::size_t size (0);" + << traits << "::set_image (" << endl + << "i." << mi.var << "value," << endl + << "sizeof (i." << mi.var << "value)," << endl + << "size," << endl + << "is_null," << endl + << member << ");" + << "i." << mi.var << "indicator = is_null ? -1 : 0;" + << "i." << mi.var << "size = static_cast<ub2> (size);"; + } + + virtual void + traverse_lob (member_info& mi) + { + os << "i." << mi.var << "lob.position = 0;" + << traits << "::set_image (" << endl + << "i." << mi.var << "callback.callback.param," << endl + << "i." << mi.var << "callback.context.param," << endl + << "is_null," << endl + << member << ");" + << "i." << mi.var << "indicator = is_null ? -1 : 0;"; + } + }; + entry<init_image_member> init_image_member_; + + // + // init value + // + + struct init_value_member: relational::init_value_member_impl<sql_type>, + member_base + { + init_value_member (base const& x) + : member_base::base (x), // virtual base + member_base::base_impl (x), // virtual base + base_impl (x), + member_base (x) + { + } + + virtual void + get_null (string const& var) const + { + os << "i." << var << "indicator == -1"; + } + + virtual void + check_modifier (member_info& mi, member_access& ma) + { + // We cannot use by-value modifier for LOB members. + // + if ((mi.st->type == sql_type::BLOB || + mi.st->type == sql_type::CLOB || + mi.st->type == sql_type::NCLOB) && + ma.placeholder ()) + { + error (ma.loc) << "modifier accepting a value cannot be used " + << "for a data member of Oracle LOB type" << endl; + info (ma.loc) << "modifier returning a non-const reference is " + << "required" << endl; + info (mi.m.location ()) << "data member is defined here" << endl; + throw operation_failed (); + } + } + + virtual void + traverse_int32 (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "indicator == -1);" + << endl; + } + + virtual void + traverse_int64 (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "indicator == -1);" + << endl; + } + + virtual void + traverse_big_int (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "size," << endl + << "i." << mi.var << "indicator == -1);" + << endl; + } + + virtual void + traverse_float (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "indicator == -1);" + << endl; + } + + virtual void + traverse_double (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "indicator == -1);" + << endl; + } + + virtual void + traverse_big_float (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "size," << endl + << "i." << mi.var << "indicator == -1);" + << endl; + } + + virtual void + traverse_date (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "indicator == -1);" + << endl; + } + + virtual void + traverse_timestamp (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "indicator == -1);" + << endl; + } + + virtual void + traverse_interval_ym (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "indicator == -1);" + << endl; + } + + virtual void + traverse_interval_ds (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "indicator == -1);" + << endl; + } + + virtual void + traverse_string (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "size," << endl + << "i." << mi.var << "indicator == -1);" + << endl; + } + + virtual void + traverse_lob (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "callback.callback.result," << endl + << "i." << mi.var << "callback.context.result," << endl + << "i." << mi.var << "indicator == -1);" + << endl; + } + }; + entry<init_value_member> init_value_member_; + + struct container_traits: relational::container_traits, context + { + container_traits (base const& x): base (x) {} + + virtual void + cache_result (string const&) + { + // Caching is not necessary since Oracle can execute several + // interleaving statements. + // + } + + virtual void + init_value_extra () + { + os << "sts.select_statement ().stream_result ();" + << endl; + } + }; + entry<container_traits> container_traits_; + + struct section_traits: relational::section_traits, context + { + section_traits (base const& x): base (x) {} + + virtual void + init_value_extra () + { + os << "st.stream_result ();"; + } + }; + entry<section_traits> section_traits_; + + struct class_: relational::class_, context + { + class_ (base const& x): base (x) {} + + virtual void + init_image_pre (type& c) + { + if (options.generate_query () && + !(composite (c) || (abstract (c) && !polymorphic (c)))) + { + type* poly_root (polymorphic (c)); + bool poly_derived (poly_root != 0 && poly_root != &c); + + if (poly_derived) + os << "{" + << "root_traits::image_type& ri (root_image (i));" + << endl; + + string i (poly_derived ? "ri" : "i"); + + os << "if (" << i << ".change_callback_.callback != 0)" << endl + << "(" << i << ".change_callback_.callback) (" << + i << ".change_callback_.context);"; + + if (poly_derived) + os << "}"; + else + os << endl; + } + } + + virtual void + init_value_extra () + { + os << "st.stream_result ();"; + } + + virtual string + persist_statement_extra (type& c, + relational::query_parameters& qp, + persist_position p) + { + string r; + + if (p == persist_after_values) + { + data_member_path* id (id_member (c)); + + type* poly_root (polymorphic (c)); + bool poly_derived (poly_root != 0 && poly_root != &c); + + // Top-level auto id. + // + if (id != 0 && !poly_derived && auto_ (*id)) + { + semantics::data_member& idb (*id->back ()); + + const string& name (column_qname (*id)); + const string& type (column_type (idb)); + + r = "RETURNING " + convert_from (name, type, idb) + + " INTO " + qp.next (idb, name, type); + } + } + + return r; + } + + virtual string + select_trailer (type& c) + { + view_query const& vq (c.get<view_query> ("query")); + + if (vq.for_update && vq.distinct) + { + error (vq.loc) + << "Oracle does not support FOR UPDATE with DISTINCT" << endl; + throw operation_failed (); + } + + return base::select_trailer (c); + } + }; + entry<class_> class_entry_; + } + } +} diff --git a/odb/odb/relational/pgsql/common.cxx b/odb/odb/relational/pgsql/common.cxx new file mode 100644 index 0000000..6a59954 --- /dev/null +++ b/odb/odb/relational/pgsql/common.cxx @@ -0,0 +1,351 @@ +// file : odb/relational/pgsql/common.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <cassert> + +#include <odb/relational/pgsql/common.hxx> + +using namespace std; + +namespace relational +{ + namespace pgsql + { + // + // member_base + // + + sql_type const& member_base:: + member_sql_type (semantics::data_member& m) + { + return parse_sql_type (column_type (m, key_prefix_), m); + } + + void member_base:: + traverse_simple (member_info& mi) + { + switch (mi.st->type) + { + // Integral types. + // + case sql_type::BOOLEAN: + case sql_type::SMALLINT: + case sql_type::INTEGER: + case sql_type::BIGINT: + { + traverse_integer (mi); + break; + } + + // Float types. + // + case sql_type::REAL: + case sql_type::DOUBLE: + { + traverse_float (mi); + break; + } + case sql_type::NUMERIC: + { + traverse_numeric (mi); + break; + } + + // Data-time types. + // + case sql_type::DATE: + case sql_type::TIME: + case sql_type::TIMESTAMP: + { + traverse_date_time (mi); + break; + } + + // String and binary types. + // + case sql_type::CHAR: + case sql_type::VARCHAR: + case sql_type::TEXT: + case sql_type::BYTEA: + { + traverse_string (mi); + break; + } + case sql_type::BIT: + { + traverse_bit (mi); + break; + } + case sql_type::VARBIT: + { + traverse_varbit (mi); + break; + } + // Other types. + // + case sql_type::UUID: + { + traverse_uuid (mi); + break; + } + case sql_type::invalid: + { + assert (false); + break; + } + } + } + + // + // member_image_type + // + + namespace + { + const char* integer_types[] = + { + "bool", + "short", + "int", + "long long" + }; + + const char* float_types[] = + { + "float", + "double" + }; + + const char* date_time_types[] = + { + "int", + "long long", + "long long" + }; + } + + member_image_type:: + member_image_type (base const& x) + : member_base::base (x), // virtual base + base (x) + { + } + + member_image_type:: + member_image_type () + : relational::member_base (0, 0, string (), string ()) {} + + member_image_type:: + member_image_type (semantics::type* type, + const custom_cxx_type* ct, + string const& fq_type, + string const& key_prefix) + : relational::member_base (type, ct, fq_type, key_prefix) {} + + string member_image_type:: + image_type (semantics::data_member& m) + { + type_.clear (); + member_base::traverse (m, true); + return type_; + } + + void member_image_type:: + traverse_composite (member_info& mi) + { + type_ = "composite_value_traits< " + mi.fq_type () + + ", id_pgsql >::image_type"; + } + + void member_image_type:: + traverse_integer (member_info& mi) + { + type_ += integer_types[mi.st->type - sql_type::BOOLEAN]; + } + + void member_image_type:: + traverse_float (member_info& mi) + { + type_ = float_types[mi.st->type - sql_type::REAL]; + } + + void member_image_type:: + traverse_numeric (member_info&) + { + type_ = "details::buffer"; + } + + void member_image_type:: + traverse_date_time (member_info& mi) + { + type_ = date_time_types[mi.st->type - sql_type::DATE]; + } + + void member_image_type:: + traverse_string (member_info&) + { + type_ = "details::buffer"; + } + + void member_image_type:: + traverse_bit (member_info&) + { + type_ = "unsigned char*"; + } + + void member_image_type:: + traverse_varbit (member_info&) + { + type_ = "details::ubuffer"; + } + + void member_image_type:: + traverse_uuid (member_info&) + { + type_ = "unsigned char*"; + } + + entry<member_image_type> member_image_type_; + + // + // member_database_type + // + + namespace + { + const char* integer_database_id[] = + { + "id_boolean", + "id_smallint", + "id_integer", + "id_bigint" + }; + + const char* float_database_id[] = + { + "id_real", + "id_double" + }; + + const char* date_time_database_id[] = + { + "id_date", + "id_time", + "id_timestamp" + }; + + const char* char_bin_database_id[] = + { + "id_string", // CHAR + "id_string", // VARCHAR + "id_string", // TEXT, + "id_bytea" // BYTEA + }; + } + + member_database_type_id:: + member_database_type_id (base const& x) + : member_base::base (x), // virtual base + base (x) {} + + member_database_type_id:: + member_database_type_id () + : member_base::base (0, 0, string (), string ()), // virtual base + base (0, 0, string (), string ()) {} + + member_database_type_id:: + member_database_type_id (semantics::type* type, + const custom_cxx_type* ct, + string const& fq_type, + string const& key_prefix) + : member_base::base (type, ct, fq_type, key_prefix), // virtual base + base (type, ct, fq_type, key_prefix) {} + + string member_database_type_id:: + database_type_id (type& m) + { + type_id_.clear (); + member_base::traverse (m, true); + return type_id_; + } + + void member_database_type_id:: + traverse_composite (member_info&) + { + assert (false); + } + + void member_database_type_id:: + traverse_integer (member_info& mi) + { + type_id_ = string ("pgsql::") + + integer_database_id[mi.st->type - sql_type::BOOLEAN]; + } + + void member_database_type_id:: + traverse_float (member_info& mi) + { + type_id_ = string ("pgsql::") + + float_database_id[mi.st->type - sql_type::REAL]; + } + + void member_database_type_id:: + traverse_numeric (member_info&) + { + type_id_ = "pgsql::id_numeric"; + } + + void member_database_type_id:: + traverse_date_time (member_info& mi) + { + type_id_ = string ("pgsql::") + + date_time_database_id[mi.st->type - sql_type::DATE]; + } + + void member_database_type_id:: + traverse_string (member_info& mi) + { + type_id_ = string ("pgsql::") + + char_bin_database_id[mi.st->type - sql_type::CHAR]; + } + + void member_database_type_id:: + traverse_bit (member_info&) + { + type_id_ = "pgsql::id_bit"; + } + + void member_database_type_id:: + traverse_varbit (member_info&) + { + type_id_ = "pgsql::id_varbit"; + } + + void member_database_type_id:: + traverse_uuid (member_info&) + { + type_id_ = "pgsql::id_uuid"; + } + + entry<member_database_type_id> member_database_type_id_; + + // + // query_columns + // + + struct query_columns: relational::query_columns, context + { + query_columns (base const& x): base_impl (x) {} + + virtual string + database_type_id (semantics::data_member& m) + { + return member_database_type_id_.database_type_id (m); + } + + private: + member_database_type_id member_database_type_id_; + }; + entry<query_columns> query_columns_; + } +} diff --git a/odb/odb/relational/pgsql/common.hxx b/odb/odb/relational/pgsql/common.hxx new file mode 100644 index 0000000..1d383bf --- /dev/null +++ b/odb/odb/relational/pgsql/common.hxx @@ -0,0 +1,159 @@ +// file : odb/relational/pgsql/common.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_RELATIONAL_PGSQL_COMMON_HXX +#define ODB_RELATIONAL_PGSQL_COMMON_HXX + +#include <odb/relational/common.hxx> +#include <odb/relational/pgsql/context.hxx> + +namespace relational +{ + namespace pgsql + { + struct member_base: virtual relational::member_base_impl<sql_type>, context + { + member_base (base const& x): base (x), base_impl (x) {} + + // This c-tor is for the direct use inside the pgsql namespace. + // If you do use this c-tor, you should also explicitly call + // relational::member_base (aka base). + // + member_base () {} + + virtual sql_type const& + member_sql_type (semantics::data_member&); + + virtual void + traverse_simple (member_info&); + + virtual void + traverse_integer (member_info&) + { + } + + virtual void + traverse_float (member_info&) + { + } + + virtual void + traverse_numeric (member_info&) + { + } + + virtual void + traverse_date_time (member_info&) + { + } + + virtual void + traverse_string (member_info&) + { + } + + virtual void + traverse_bit (member_info&) + { + } + + virtual void + traverse_varbit (member_info&) + { + } + + virtual void + traverse_uuid (member_info&) + { + } + }; + + struct member_image_type: relational::member_image_type, + member_base + { + member_image_type (base const&); + member_image_type (); + member_image_type (semantics::type* type, + const custom_cxx_type*, + string const& fq_type = string (), + string const& key_prefix = string ()); + virtual string + image_type (semantics::data_member&); + + virtual void + traverse_composite (member_info&); + + virtual void + traverse_integer (member_info&); + + virtual void + traverse_float (member_info&); + + virtual void + traverse_numeric (member_info&); + + virtual void + traverse_date_time (member_info&); + + virtual void + traverse_string (member_info&); + + virtual void + traverse_bit (member_info&); + + virtual void + traverse_varbit (member_info&); + + virtual void + traverse_uuid (member_info&); + + private: + string type_; + }; + + struct member_database_type_id: relational::member_database_type_id, + member_base + { + member_database_type_id (base const&); + member_database_type_id (); + member_database_type_id (semantics::type* type, + const custom_cxx_type*, + string const& fq_type = string (), + string const& key_prefix = string ()); + + virtual string + database_type_id (type&); + + virtual void + traverse_composite (member_info&); + + virtual void + traverse_integer (member_info&); + + virtual void + traverse_float (member_info&); + + virtual void + traverse_numeric (member_info&); + + virtual void + traverse_date_time (member_info&); + + virtual void + traverse_string (member_info&); + + virtual void + traverse_bit (member_info&); + + virtual void + traverse_varbit (member_info&); + + virtual void + traverse_uuid (member_info&); + + private: + string type_id_; + }; + } +} +#endif // ODB_RELATIONAL_PGSQL_COMMON_HXX diff --git a/odb/odb/relational/pgsql/context.cxx b/odb/odb/relational/pgsql/context.cxx new file mode 100644 index 0000000..7f99f5d --- /dev/null +++ b/odb/odb/relational/pgsql/context.cxx @@ -0,0 +1,786 @@ +// file : odb/relational/pgsql/context.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <cassert> +#include <sstream> + +#include <odb/diagnostics.hxx> + +#include <odb/sql-token.hxx> +#include <odb/sql-lexer.hxx> + +#include <odb/relational/pgsql/context.hxx> +#include <odb/relational/pgsql/common.hxx> + +using namespace std; + +namespace relational +{ + namespace pgsql + { + 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", "BOOLEAN", 0, false}, + + {"char", "CHAR(1)", 0, false}, + {"signed char", "SMALLINT", 0, false}, + {"unsigned char", "SMALLINT", 0, false}, + + {"short int", "SMALLINT", 0, false}, + {"short unsigned int", "SMALLINT", 0, false}, + + {"int", "INTEGER", 0, false}, + {"unsigned int", "INTEGER", 0, false}, + + {"long int", "BIGINT", 0, false}, + {"long unsigned int", "BIGINT", 0, false}, + + {"long long int", "BIGINT", 0, false}, + {"long long unsigned int", "BIGINT", 0, false}, + + {"float", "REAL", 0, false}, + {"double", "DOUBLE PRECISION", 0, false}, + + {"::std::string", "TEXT", 0, false}, + + {"::size_t", "BIGINT", 0, false}, + {"::std::size_t", "BIGINT", 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 = true; + need_alias_as = true; + insert_send_auto_id = false; + delay_freeing_statement_result = false; + need_image_clone = false; + generate_bulk = true; + global_index = true; + global_fkey = false; + data_->bind_vector_ = "pgsql::bind*"; + data_->truncated_vector_ = "bool*"; + + // 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_) + { + } + + namespace + { + struct has_grow: traversal::class_ + { + has_grow (bool& r, user_section* s) + : r_ (r), section_ (s) + { + *this >> inherits_ >> *this; + } + + virtual void + traverse (type& c) + { + // Ignore transient bases. + // + if (!(context::object (c) || context::composite (c))) + return; + + if (section_ == 0 && c.count ("pgsql-grow")) + r_ = c.get<bool> ("pgsql-grow"); + else + { + // r_ should be false. + // + inherits (c); + + if (!r_) + names (c); + + if (section_ == 0) + c.set ("pgsql-grow", r_); + } + } + + private: + bool& r_; + user_section* section_; + traversal::inherits inherits_; + }; + + struct has_grow_member: member_base + { + has_grow_member (bool& r, user_section* section = 0) + : relational::member_base (0, 0, string (), string (), section), + r_ (r) {} + + has_grow_member (bool& r, + user_section* section, + semantics::type* t, + const custom_cxx_type* ct, + string const& key_prefix = string ()) + : relational::member_base (t, ct, string (), key_prefix, section), + r_ (r) {} + + virtual bool + pre (member_info& mi) + { + // If we have a key prefix (container), then it can't be in a + // section (while mi.m can). The same for top-level -- if we got + // called, then we shouldn't ignore it. + // + return !key_prefix_.empty () || top_level_ || + (section_ == 0 && !separate_load (mi.m)) || + (section_ != 0 && *section_ == section (mi.m)); + } + + virtual void + traverse_composite (member_info& mi) + { + // By calling grow() instead of recursing, we reset any overrides. + // We also don't pass section since they don't apply inside + // composites. + // + r_ = r_ || context::grow (dynamic_cast<semantics::class_&> (mi.t)); + } + + virtual void + traverse_numeric (member_info&) + { + r_ = true; + } + + virtual void + traverse_string (member_info&) + { + r_ = true; + } + + virtual void + traverse_varbit (member_info&) + { + r_ = true; + } + + private: + bool& r_; + }; + } + + bool context:: + grow_impl (semantics::class_& c, user_section* section) + { + if (section == 0 && c.count ("pgsql-grow")) + return c.get<bool> ("pgsql-grow"); + + bool r (false); + has_grow ct (r, section); + has_grow_member mt (r, section); + traversal::names names; + ct >> names >> mt; + ct.traverse (c); + return r; + } + + bool context:: + grow_impl (semantics::data_member& m) + { + bool r (false); + has_grow_member mt (r); + mt.traverse (m, true); + return r; + } + + bool context:: + grow_impl (semantics::data_member& m, + semantics::type& t, + const custom_cxx_type* ct, + string const& kp) + { + bool r (false); + has_grow_member mt (r, 0, &t, ct, kp); + mt.traverse (m, true); + return r; + } + + 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; + + // Warn if the name is greater than the (NAMEDATALEN - 1) limit, + // which is 63 in the default PG build. + // + if (i->size () > 63) + { + cerr << "warning: SQL name '" << *i << "' is longer than " + << "the default PostgreSQL name limit of 63 characters " + << "and may be truncated" << endl; + + cerr << "info: consider shortening it using #pragma db " + << "table/column/index or --*-regex options" << endl; + } + + if (f) + f = false; + else + r += '.'; + + r += '"'; + r += *i; + r += '"'; + } + + return r; + } + + string context:: + statement_name (string const& type, string const& name, semantics::node& n) + { + // Put the type first so that in the case of truncation it + // remains thus lowering the chance of a clash. + // + string r (type); + r += '_'; + r += name; + + r = transform_name (r, sql_name_statement); + + // Warn if the name is greater than the (NAMEDATALEN - 1) limit, + // which is 63 in the default PG build. + // + // Note that we have to do it in addition to the above since this + // name doesn't go through quote_id(). + // + if (r.size () > 63) + { + location const& l (n.location ()); + + warn (l) << "prepared statement name '" << r << "' is longer than " + << "the default PostgreSQL name limit of 63 characters " + << "and may be truncated" << endl; + + info (l) << "consider shortening the corresponding namespace " + << "name, class name, or data member name" << endl; + + info (l) << "or shortening the statement name itself using the " + << "--statement-regex option" << endl; + } + + 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 = "VARCHAR("; + n--; + } + + ostringstream ostr; + ostr << n; + r += ostr.str (); + r += ')'; + } + } + + 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: + // + // BIT VARYING (VARBIT) + // CHARACTER VARYING (VARRCHAR) + // DOUBLE PRECISION (DOUBLE) + // TIME WITH TIME ZONE (not currently supported) + // TIMESTAMP WITH TIME ZONE (not currently supported) + // + + enum state + { + parse_prefix, + parse_name, + parse_range, + parse_suffix, + parse_done + }; + + state s (parse_prefix); + string prefix; + bool flt (false); + + for (sql_token t (l.next ()); + s != parse_done && t.type () != sql_token::t_eos; + t = l.next ()) + { + sql_token::token_type tt (t.type ()); + + switch (s) + { + case parse_prefix: + { + if (tt == sql_token::t_identifier) + { + string const& id (context::upcase (t.identifier ())); + + if (id == "BIT" || + id == "CHARACTER" || + id == "DOUBLE") + { + prefix = id; + s = parse_name; + continue; + } + } + + s = parse_name; + } + // Fall through. + case parse_name: + { + if (tt == sql_token::t_identifier) + { + bool match (true); + string const& id (context::upcase (t.identifier ())); + + // + // Numeric types. + // + if (id == "BOOL" || id == "BOOLEAN") + { + r.type = sql_type::BOOLEAN; + } + else if (id == "SMALLINT" || id == "INT2") + { + r.type = sql_type::SMALLINT; + } + else if (id == "INT" || + id == "INTEGER" || + id == "INT4") + { + r.type = sql_type::INTEGER; + } + else if (id == "BIGINT") + { + r.type = sql_type::BIGINT; + } + else if (id == "REAL" || id == "FLOAT4") + { + r.type = sql_type::REAL; + } + else if ((id == "PRECISION" && prefix == "DOUBLE") || + id == "FLOAT8") + { + r.type = sql_type::DOUBLE; + } + else if (id == "FLOAT") + { + // Assign a type only once we know the precision of the + // float. + // + flt = true; + } + else if (id == "NUMERIC" || id == "DECIMAL") + { + r.type = sql_type::NUMERIC; + } + // + // Date-time types. + // + else if (id == "DATE") + { + r.type = sql_type::DATE; + } + else if (id == "TIME") + { + r.type = sql_type::TIME; + } + else if (id == "TIMETZ") + { + return error (ct, "PostgreSQL time zones are not currently " + "supported"); + } + else if (id == "TIMESTAMP") + { + r.type = sql_type::TIMESTAMP; + } + else if (id == "TIMESTAMPTZ") + { + return error (ct, "PostgreSQL time zones are not currently " + "supported"); + } + // + // String and binary types. + // + else if (id == "CHAR") + { + r.type = sql_type::CHAR; + } + else if (id == "VARCHAR") + { + r.type = sql_type::VARCHAR; + } + else if (id == "TEXT") + { + r.type = sql_type::TEXT; + } + else if (id == "VARYING") + { + if (prefix == "BIT") + r.type = sql_type::VARBIT; + else if (prefix == "CHARACTER") + r.type = sql_type::VARCHAR; + } + else if (id == "BYTEA") + { + r.type = sql_type::BYTEA; + } + else if (id == "VARBIT") + { + r.type = sql_type::VARBIT; + } + // + // Other types. + // + else if (id == "UUID") + { + r.type = sql_type::UUID; + } + else + match = false; + + if (match) + { + s = parse_range; + continue; + } + } + + // Some prefixes can also be type names if not followed + // by the actual type name. + // + if (!prefix.empty ()) + { + if (prefix == "BIT") + { + r.type = sql_type::BIT; + } + else if (prefix == "CHARACTER") + { + r.type = sql_type::CHAR; + } + } + + if (r.type == sql_type::invalid) + { + return error ( + ct, + tt == sql_token::t_identifier + ? "unknown PostgreSQL type '" + t.identifier () + "'" + : "expected PostgreSQL type name"); + } + + s = parse_range; + } + // Fall through. + case parse_range: + { + if (t.punctuation () == sql_token::p_lparen) + { + t = l.next (); + + if (t.type () != sql_token::t_int_lit) + { + return error (ct, "integer range expected in PostgreSQL " + "type declaration"); + } + + unsigned int v; + istringstream is (t.literal ()); + + if (!(is >> v && is.eof ())) + { + return error (ct, "invalid range value '" + t.literal () + + "' in PostgreSQL type declaration"); + } + + r.range = true; + r.range_value = v; + + t = l.next (); + + if (t.punctuation () == sql_token::p_comma) + { + // We have the second range value. Skip it. + // + l.next (); + t = l.next (); + } + + if (t.punctuation () != sql_token::p_rparen) + { + return error (ct, "expected ')' in PostgreSQL type " + "declaration"); + } + + s = parse_suffix; + continue; + } + + s = parse_suffix; + } + // Fall through. + case parse_suffix: + { + if (r.type == sql_type::TIME || r.type == sql_type::TIMESTAMP) + { + string const& id1 (context::upcase (t.identifier ())); + + if (id1 == "WITH") + { + t = l.next (); + tt = t.type (); + + if (tt == sql_token::t_identifier) + { + string const& id2 (context::upcase (t.identifier ())); + + if (id2 == "TIME") + { + t = l.next (); + tt = t.type (); + + if (tt == sql_token::t_identifier) + { + string const& id3 (context::upcase (t.identifier ())); + + if (id3 == "ZONE") + { + // This code shall not fall through. + // + return error (ct, "PostgreSQL time zones are not " + "currently supported"); + } + } + } + } + } + } + + return error ( + ct, + tt == sql_token::t_identifier + ? "unknown PostgreSQL type '" + t.identifier () + "'" + : "unknown PostgreSQL type"); + } + case parse_done: + { + assert (false); + break; + } + } + } + + if (s == parse_name && !prefix.empty ()) + { + // Some prefixes can also be type names if not followed + // by the actual type name. + // + if (prefix == "BIT") + { + r.type = sql_type::BIT; + } + else if (prefix == "CHARACTER") + { + r.type = sql_type::CHAR; + } + } + + if (flt) + { + r.type = r.range && r.range_value < 25 ? + sql_type::REAL : + sql_type::DOUBLE; + } + + if (r.type == sql_type::invalid) + return error (ct, "incomplete PostgreSQL type declaration"); + + // If range is omitted for CHAR or BIT types, it defaults to 1. + // + if ((r.type == sql_type::CHAR || r.type == sql_type::BIT) && !r.range) + { + r.range = true; + r.range_value = 1; + } + + return r; + } + catch (sql_lexer::invalid_input const& e) + { + return error (ct, "invalid PostgreSQL type declaration: " + e.message); + } + } + } +} diff --git a/odb/odb/relational/pgsql/context.hxx b/odb/odb/relational/pgsql/context.hxx new file mode 100644 index 0000000..64e0b1a --- /dev/null +++ b/odb/odb/relational/pgsql/context.hxx @@ -0,0 +1,192 @@ +// file : odb/relational/pgsql/context.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_RELATIONAL_PGSQL_CONTEXT_HXX +#define ODB_RELATIONAL_PGSQL_CONTEXT_HXX + +#include <map> + +#include <odb/relational/context.hxx> + +namespace relational +{ + namespace pgsql + { + struct sql_type + { + // Keep the order in each block of types. + // + enum core_type + { + // Integral types. + // + BOOLEAN, + SMALLINT, + INTEGER, + BIGINT, + + // Float types. + // + REAL, + DOUBLE, + NUMERIC, + + // Data-time types. + // + DATE, + TIME, + TIMESTAMP, + + // String and binary types. + // + CHAR, + VARCHAR, + TEXT, + BYTEA, + BIT, + VARBIT, + + // Other types. + // + UUID, + + // Invalid type. + // + invalid + }; + + sql_type () : type (invalid), range (false) {} + + core_type type; + + // VARBIT maximum length is 2^31 - 1 bit. String types can hold a + // maximum of 1GB of data. + // + bool range; + unsigned int range_value; + + // Conversion expressions for custom database types. + // + std::string to; + std::string from; + }; + + class context: public virtual relational::context + { + public: + sql_type const& + parse_sql_type (string const&, + semantics::data_member&, + bool custom = true); + public: + struct invalid_sql_type + { + invalid_sql_type (string const& message): message_ (message) {} + + string const& + message () const {return message_;} + + private: + string message_; + }; + + // If custom_db_types is NULL, then this function returns + // invalid type instead of throwing in case an unknown type + // is encountered. + // + static sql_type + parse_sql_type (string, custom_db_types const* = 0); + + public: + // Construct statement name from a given type and name. + // + string + statement_name (string const& type, + string const& name, + semantics::node&); + + protected: + virtual string const& + convert_expr (string const&, semantics::data_member&, bool); + + virtual string + quote_id_impl (qname const&) const; + + virtual bool + grow_impl (semantics::class_&, user_section*); + + virtual bool + grow_impl (semantics::data_member&); + + virtual bool + grow_impl (semantics::data_member&, + semantics::type&, + const custom_cxx_type*, + string const&); + + protected: + virtual string + database_type_impl (semantics::type&, semantics::names*, bool, bool*); + + public: + virtual + ~context (); + + context (); + context (std::ostream&, + semantics::unit&, + options_type const&, + features_type&, + sema_rel::model*); + + static context& + current () + { + return *current_; + } + + private: + static context* current_; + + private: + struct data: base_context::data + { + data (std::ostream& os): base_context::data (os) {} + + struct sql_type_cache_entry + { + sql_type_cache_entry () + : custom_cached (false), straight_cached (false) {} + + sql_type const& + cache_custom (sql_type const& t) + { + custom = t; + custom_cached = true; + return custom; + } + + sql_type const& + cache_straight (sql_type const& t) + { + straight = t; + straight_cached = true; + return straight; + } + + sql_type custom; // With custom mapping. + sql_type straight; // Without custom mapping. + + bool custom_cached; + bool straight_cached; + }; + + typedef std::map<string, sql_type_cache_entry> sql_type_cache; + sql_type_cache sql_type_cache_; + }; + data* data_; + }; + } +} + +#endif // ODB_RELATIONAL_PGSQL_CONTEXT_HXX diff --git a/odb/odb/relational/pgsql/header.cxx b/odb/odb/relational/pgsql/header.cxx new file mode 100644 index 0000000..c3efc3e --- /dev/null +++ b/odb/odb/relational/pgsql/header.cxx @@ -0,0 +1,285 @@ +// file : odb/relational/pgsql/header.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <odb/relational/header.hxx> + +#include <odb/relational/pgsql/common.hxx> +#include <odb/relational/pgsql/context.hxx> + +namespace relational +{ + namespace pgsql + { + namespace header + { + namespace relational = relational::header; + + struct class1: relational::class1 + { + class1 (base const& x): base (x) {} + + virtual void + object_public_extra_post (type& c) + { + bool abst (abstract (c)); + + type* poly_root (polymorphic (c)); + bool poly (poly_root != 0); + bool poly_derived (poly && poly_root != &c); + + if (abst && !poly) + return; + + data_member_path* id (id_member (c)); + semantics::data_member* optimistic (context::optimistic (c)); + + column_count_type const& cc (column_count (c)); + + size_t update_columns ( + cc.total - cc.id - cc.inverse - cc.readonly - cc.separate_update); + + // Statement names. + // + os << "static const char persist_statement_name[];"; + + if (id != 0) + { + if (poly_derived) + os << "static const char* const find_statement_names[" << + (abst ? "1" : "depth") << "];"; + else + os << "static const char find_statement_name[];"; + + if (poly && !poly_derived) + os << "static const char find_discriminator_statement_name[];"; + + if (update_columns != 0) + os << "static const char update_statement_name[];"; + + os << "static const char erase_statement_name[];"; + + if (optimistic != 0) + os << "static const char optimistic_erase_statement_name[];"; + } + + // Query statement name. + // + if (options.generate_query ()) + os << "static const char query_statement_name[];" + << "static const char erase_query_statement_name[];"; + + os << endl; + + // Statement types. + // + os << "static const unsigned int persist_statement_types[];"; + + if (id != 0) + { + os << "static const unsigned int find_statement_types[];"; + + if (update_columns != 0) + os << "static const unsigned int update_statement_types[];"; + + if (optimistic != 0) + os << "static const unsigned int " << + "optimistic_erase_statement_types[];"; + } + + os << endl; + + if (poly_derived) + return; + + // Bulk operations batch size. + // + { + unsigned long long b (c.count ("bulk") + ? c.get<unsigned long long> ("bulk") + : 1); + + os << "static const std::size_t batch = " << b << "UL;" + << endl; + } + } + + virtual void + view_public_extra_post (type&) + { + // Statement names. + // + os << "static const char query_statement_name[];" + << endl; + } + }; + entry<class1> class1_entry_; + + struct container_traits: relational::container_traits, context + { + container_traits (base const& x): base (x) {} + + virtual void + container_public_extra_pre (semantics::data_member& m, + semantics::type& t) + { + if (!object (c_) || (abstract (c_) && !polymorphic (c_))) + return; + + bool smart (!inverse (m, "value") && !unordered (m) && + container_smart (t)); + + // Container statement names. + // + os << "static const char select_name[];" + << "static const char insert_name[];"; + + if (smart) + os << "static const char update_name[];"; + + os << "static const char delete_name[];" + << endl; + + // Container statement types. + // + os << "static const unsigned int insert_types[];"; + + if (smart) + os << "static const unsigned int update_types[];" + << "static const unsigned int delete_types[];"; + + os << endl; + } + }; + entry<container_traits> container_traits_; + + struct section_traits: relational::section_traits, context + { + section_traits (base const& x): base (x) {} + + virtual void + section_public_extra_post (user_section& s) + { + semantics::class_* poly_root (polymorphic (c_)); + bool poly (poly_root != 0); + + if (!poly && (abstract (c_) || + s.special == user_section::special_version)) + return; + + bool load (s.total != 0 && s.separate_load ()); + bool load_opt (s.optimistic () && s.separate_load ()); + + bool update (s.total != s.inverse + s.readonly); // Always separate. + bool update_opt (s.optimistic () && (s.readwrite_containers || poly)); + + // Statement names. + // + if (load || load_opt) + os << "static const char select_name[];" + << endl; + + if (update || update_opt) + os << "static const char update_name[];" + << endl; + + // Statement types. + // + if (update || update_opt) + os << "static const unsigned int update_types[];"; + } + }; + entry<section_traits> section_traits_; + + struct image_member: relational::image_member_impl<sql_type>, + member_base + { + image_member (base const& x) + : member_base::base (x), // virtual base + member_base::base_impl (x), // virtual base + base_impl (x), + member_base (x) {} + + virtual void + traverse_integer (member_info& mi) + { + os << image_type << " " << mi.var << "value;" + << "bool " << mi.var << "null;" + << endl; + } + + virtual void + traverse_float (member_info& mi) + { + os << image_type << " " << mi.var << "value;" + << "bool " << mi.var << "null;" + << endl; + } + + virtual void + traverse_numeric (member_info& mi) + { + // Exchanged as strings. Can have up to 1000 digits not counting + // '-' and '.'. + // + + os << image_type << " " << mi.var << "value;" + << "std::size_t " << mi.var << "size;" + << "bool " << mi.var << "null;" + << endl; + } + + virtual void + traverse_date_time (member_info& mi) + { + os << image_type << " " << mi.var << "value;" + << "bool " << mi.var << "null;" + << endl; + } + + virtual void + traverse_string (member_info& mi) + { + os << image_type << " " << mi.var << "value;" + << "std::size_t " << mi.var << "size;" + << "bool " << mi.var << "null;" + << endl; + } + + virtual void + traverse_bit (member_info& mi) + { + // Additional 4 bytes at the beginning of the array specify + // the number of significant bits in the image. This number + // is stored in network byte order. + // + unsigned int n (4 + mi.st->range / 8 + (mi.st->range % 8 ? 1 : 0)); + + os << "unsigned char " << mi.var << "value[" << n << "];" + << "std::size_t " << mi.var << "size;" + << "bool " << mi.var << "null;" + << endl; + } + + virtual void + traverse_varbit (member_info& mi) + { + os << image_type << " " << mi.var << "value;" + << "std::size_t " << mi.var << "size;" + << "bool " << mi.var << "null;" + << endl; + } + + virtual void + traverse_uuid (member_info& mi) + { + // UUID is a 16-byte sequence. + // + os << "unsigned char " << mi.var << "value[16];" + << "bool " << mi.var << "null;" + << endl; + } + }; + entry<image_member> image_member_; + } + } +} diff --git a/odb/odb/relational/pgsql/inline.cxx b/odb/odb/relational/pgsql/inline.cxx new file mode 100644 index 0000000..08688c3 --- /dev/null +++ b/odb/odb/relational/pgsql/inline.cxx @@ -0,0 +1,42 @@ +// file : odb/relational/pgsql/inline.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <odb/relational/inline.hxx> + +#include <odb/relational/pgsql/common.hxx> +#include <odb/relational/pgsql/context.hxx> + +using namespace std; + +namespace relational +{ + namespace pgsql + { + namespace inline_ + { + namespace relational = relational::inline_; + + struct null_member: relational::null_member_impl<sql_type>, + member_base + { + null_member (base const& x) + : member_base::base (x), // virtual base + member_base::base_impl (x), // virtual base + base_impl (x), + member_base (x) + { + } + + virtual void + traverse_simple (member_info& mi) + { + if (get_) + os << "r = r && i." << mi.var << "null;"; + else + os << "i." << mi.var << "null = true;"; + } + }; + entry<null_member> null_member_; + } + } +} diff --git a/odb/odb/relational/pgsql/model.cxx b/odb/odb/relational/pgsql/model.cxx new file mode 100644 index 0000000..092f8bb --- /dev/null +++ b/odb/odb/relational/pgsql/model.cxx @@ -0,0 +1,101 @@ +// file : odb/relational/pgsql/model.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <sstream> + +#include <odb/diagnostics.hxx> + +#include <odb/relational/model.hxx> + +#include <odb/relational/pgsql/common.hxx> +#include <odb/relational/pgsql/context.hxx> + +using namespace std; + +namespace relational +{ + namespace pgsql + { + namespace model + { + namespace relational = relational::model; + + struct object_columns: relational::object_columns, context + { + object_columns (base const& x): base (x) {} + + virtual void + traverse_object (semantics::class_& c) + { + base::traverse_object (c); + + if (context::top_object == &c) + { + // Make sure that the auto id type is INTEGER or BIGINT. + // + if (pkey_ != 0 && pkey_->auto_ ()) + { + // Should be a single column. + // + sema_rel::column& c (pkey_->contains_begin ()->column ()); + + // This should never fail since we have already parsed this. + // + sql_type const& t (parse_sql_type (c.type ())); + + if (t.type != sql_type::INTEGER && t.type != sql_type::BIGINT) + { + location const& l (c.get<location> ("cxx-location")); + error (l) << "automatically assigned object id must map " + << "to PostgreSQL INTEGER or BIGINT" << endl; + throw operation_failed (); + } + } + } + } + + virtual string + default_bool (semantics::data_member&, bool v) + { + return v ? "TRUE" : "FALSE"; + } + + virtual string + default_enum (semantics::data_member& m, tree en, string const&) + { + // Make sure the column is mapped to an integer type. + // + switch (parse_sql_type (column_type (), m, false).type) + { + case sql_type::SMALLINT: + case sql_type::INTEGER: + case sql_type::BIGINT: + break; + default: + { + cerr << m.file () << ":" << m.line () << ":" << m.column () + << ": error: column with default value specified as C++ " + << "enumerator must map to PostgreSQL integer type" << endl; + + throw operation_failed (); + } + } + + using semantics::enumerator; + + enumerator& e (dynamic_cast<enumerator&> (*unit.find (en))); + + ostringstream ostr; + + if (e.enum_ ().unsigned_ ()) + ostr << e.value (); + else + ostr << static_cast<long long> (e.value ()); + + return ostr.str (); + } + }; + entry<object_columns> object_columns_; + } + } +} diff --git a/odb/odb/relational/pgsql/schema.cxx b/odb/odb/relational/pgsql/schema.cxx new file mode 100644 index 0000000..b9c3f2e --- /dev/null +++ b/odb/odb/relational/pgsql/schema.cxx @@ -0,0 +1,266 @@ +// file : odb/relational/pgsql/schema.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <odb/relational/schema.hxx> + +#include <odb/relational/pgsql/common.hxx> +#include <odb/relational/pgsql/context.hxx> + +using namespace std; + +namespace relational +{ + namespace pgsql + { + namespace schema + { + namespace relational = relational::schema; + using relational::table_set; + + // + // Drop. + // + + struct drop_table: relational::drop_table, context + { + drop_table (base const& x): base (x) {} + + virtual void + traverse (sema_rel::table& t, bool migration) + { + // For migration drop foreign keys explicitly in pre-migration. + // + if (migration) + { + base::traverse (t, migration); + return; + } + + // For schema creation we use the CASCADE clause to drop foreign + // keys. + // + if (pass_ != 2) + return; + + pre_statement (); + os << "DROP TABLE " << (migration ? "" : "IF EXISTS ") << + quote_id (t.name ()) << " CASCADE" << endl; + post_statement (); + } + }; + entry<drop_table> drop_table_; + + // + // Create. + // + + struct create_column: relational::create_column, context + { + create_column (base const& x): base (x) {} + + virtual void + type (sema_rel::column& c, bool auto_) + { + if (auto_) + { + // This should never fail since we have already parsed this. + // + sql_type const& t (parse_sql_type (c.type ())); + + // The model creation code makes sure it is one of these type. + // + if (t.type == sql_type::INTEGER) + os << "SERIAL"; + else if (t.type == sql_type::BIGINT) + os << "BIGSERIAL"; + } + else + base::type (c, auto_); + } + }; + entry<create_column> create_column_; + + struct create_foreign_key: relational::create_foreign_key, context + { + create_foreign_key (base const& x): base (x) {} + + virtual void + deferrable (sema_rel::deferrable d) + { + os << endl + << " INITIALLY " << d; + } + }; + entry<create_foreign_key> create_foreign_key_; + + struct create_index: relational::create_index, context + { + create_index (base const& x): base (x) {} + + virtual void + create (sema_rel::index& in) + { + os << "CREATE "; + + if (!in.type ().empty ()) + { + // Handle the CONCURRENTLY keyword. + // + string const& t (in.type ()); + + if (t == "concurrently" || t == "CONCURRENTLY") + { + os << "INDEX " << t; + } + else + { + size_t p (t.rfind (' ')); + string s (t, (p != string::npos ? p + 1 : 0), string::npos); + + if (s == "concurrently" || s == "CONCURRENTLY") + os << string (t, 0, p) << " INDEX " << s; + else + os << t << " INDEX"; + } + } + else + os << "INDEX"; + + os << " " << name (in) << endl + << " ON " << table_name (in); + + if (!in.method ().empty ()) + os << " USING " << in.method (); + + os << " ("; + columns (in); + os << ")" << endl; + + if (!in.options ().empty ()) + os << ' ' << in.options () << endl; + } + }; + entry<create_index> create_index_; + + // + // Alter. + // + + struct alter_column: relational::alter_column, context + { + alter_column (base const& x): base (x) {} + + virtual void + alter (sema_rel::column& c) + { + os << quote_id (c.name ()) << " " << + (c.null () ? "DROP" : "SET") << " NOT NULL"; + } + }; + entry<alter_column> alter_column_; + + // + // Schema version table. + // + + struct version_table: relational::version_table, context + { + version_table (base const& x): base (x) {} + + // PostgreSQL prior to 9.1 doesn't support IF NOT EXISTS in + // CREATE TABLE. We also cannot use IF-ELSE construct in plain + // SQL. To make it at least work for a single schema, we are + // going to drop the schema version table after the DROP + // statements and then unconditionally create it after CREATE. + // + virtual void + create_table () + { + if (options.pgsql_server_version () >= pgsql_version (9, 1)) + { + pre_statement (); + + os << "CREATE TABLE IF NOT EXISTS " << qt_ << " (" << endl + << " " << qn_ << " TEXT NOT NULL PRIMARY KEY," << endl + << " " << qv_ << " BIGINT NOT NULL," << endl + << " " << qm_ << " BOOLEAN NOT NULL)" << endl; + + post_statement (); + } + } + + virtual void + drop () + { + pre_statement (); + + if (options.pgsql_server_version () >= pgsql_version (9, 1)) + os << "DELETE FROM " << qt_ << endl + << " WHERE " << qn_ << " = " << qs_ << endl; + else + os << "DROP TABLE IF EXISTS " << qt_ << endl; + + post_statement (); + } + + virtual void + create (sema_rel::version v) + { + pre_statement (); + + if (options.pgsql_server_version () >= pgsql_version (9, 1)) + { + os << "INSERT INTO " << qt_ << " (" << endl + << " " << qn_ << ", " << qv_ << ", " << qm_ << ")" << endl + << " SELECT " << qs_ << ", " << v << ", FALSE" << endl + << " WHERE NOT EXISTS (" << endl + << " SELECT 1 FROM " << qt_ << " WHERE " << qn_ << " = " << + qs_ << ")" << endl; + } + else + { + os << "CREATE TABLE " << qt_ << " (" << endl + << " " << qn_ << " TEXT NOT NULL PRIMARY KEY," << endl + << " " << qv_ << " BIGINT NOT NULL," << endl + << " " << qm_ << " BOOLEAN NOT NULL)" << endl; + + post_statement (); + pre_statement (); + + os << "INSERT INTO " << qt_ << " (" << endl + << " " << qn_ << ", " << qv_ << ", " << qm_ << ")" << endl + << " VALUES (" << qs_ << ", " << v << ", FALSE)" << endl; + } + + post_statement (); + } + + virtual void + migrate_pre (sema_rel::version v) + { + pre_statement (); + + os << "UPDATE " << qt_ << endl + << " SET " << qv_ << " = " << v << ", " << qm_ << " = TRUE" << endl + << " WHERE " << qn_ << " = " << qs_ << endl; + + post_statement (); + } + + virtual void + migrate_post () + { + pre_statement (); + + os << "UPDATE " << qt_ << endl + << " SET " << qm_ << " = FALSE" << endl + << " WHERE " << qn_ << " = " << qs_ << endl; + + post_statement (); + } + + }; + entry<version_table> version_table_; + } + } +} diff --git a/odb/odb/relational/pgsql/source.cxx b/odb/odb/relational/pgsql/source.cxx new file mode 100644 index 0000000..b881e48 --- /dev/null +++ b/odb/odb/relational/pgsql/source.cxx @@ -0,0 +1,1140 @@ +// file : odb/relational/pgsql/source.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <sstream> + +#include <odb/relational/source.hxx> + +#include <odb/relational/pgsql/common.hxx> +#include <odb/relational/pgsql/context.hxx> + +using namespace std; + +namespace relational +{ + namespace pgsql + { + namespace source + { + namespace relational = relational::source; + + struct query_parameters: relational::query_parameters + { + query_parameters (base const& x): base (x), i_ (0) {} + + virtual string + next (semantics::data_member&, const string&, const string&) + { + ostringstream ss; + ss << "$" << ++i_; + + return ss.str (); + } + + virtual string + auto_id (semantics::data_member&, const string&, const string&) + { + return "DEFAULT"; + } + + private: + size_t i_; + }; + entry<query_parameters> query_parameters_; + + namespace + { + const char* integer_buffer_types[] = + { + "pgsql::bind::boolean_", + "pgsql::bind::smallint", + "pgsql::bind::integer", + "pgsql::bind::bigint" + }; + + const char* float_buffer_types[] = + { + "pgsql::bind::real", + "pgsql::bind::double_" + }; + + const char* char_bin_buffer_types[] = + { + "pgsql::bind::text", // CHAR + "pgsql::bind::text", // VARCHAR + "pgsql::bind::text", // TEXT + "pgsql::bind::bytea" // BYTEA + }; + + const char* date_time_buffer_types[] = + { + "pgsql::bind::date", + "pgsql::bind::time", + "pgsql::bind::timestamp" + }; + + const char* oids[] = + { + "pgsql::bool_oid", // BOOLEAN + "pgsql::int2_oid", // SMALLINT + "pgsql::int4_oid", // INTEGER + "pgsql::int8_oid", // BIGINT + "pgsql::float4_oid", // REAL + "pgsql::float8_oid", // DOUBLE + "pgsql::numeric_oid", // NUMERIC + "pgsql::date_oid", // DATE + "pgsql::time_oid", // TIME + "pgsql::timestamp_oid", // TIMESTAMP + "pgsql::text_oid", // CHAR + "pgsql::text_oid", // VARCHAR + "pgsql::text_oid", // TEXT + "pgsql::bytea_oid", // BYTEA + "pgsql::bit_oid", // BIT + "pgsql::varbit_oid", // VARBIT + "pgsql::uuid_oid" // UUID + }; + } + + struct statement_oids: object_columns_base, context + { + statement_oids (statement_kind sk, + bool first = true, + object_section* section = 0) + : object_columns_base (first, column_prefix (), section), sk_ (sk) + { + } + + virtual bool + section_test (data_member_path const& mp) + { + object_section& s (section (mp)); + + // Include eager loaded members into the main section for + // SELECT statements. + // + return section_ == 0 || + *section_ == s || + (sk_ == statement_select && + *section_ == main_section && + !s.separate_load ()); + } + + virtual void + traverse_pointer (semantics::data_member& m, semantics::class_& c) + { + // Ignore certain columns depending on what kind statement we are + // generating. See object_columns in common source generator for + // details. + // + if (!(inverse (m, key_prefix_) && sk_ != statement_select)) + object_columns_base::traverse_pointer (m, c); + } + + virtual bool + traverse_column (semantics::data_member& m, + string const&, + bool first) + { + // Ignore certain columns depending on what kind statement we are + // generating. See object_columns in common source generator for + // details. + // + if (id ()) + { + if (sk_ == statement_update || + (sk_ == statement_insert && auto_ (m))) + return false; + } + + if (sk_ == statement_update && + readonly (member_path_, member_scope_)) + return false; + + if ((sk_ == statement_insert || sk_ == statement_update) && + version (m)) + return false; + + if (!first) + os << ',' << endl; + + os << oids[parse_sql_type (column_type (), m).type]; + + return true; + } + + private: + statement_kind sk_; + }; + + // + // bind + // + + struct bind_member: relational::bind_member_impl<sql_type>, + member_base + { + bind_member (base const& x) + : member_base::base (x), // virtual base + member_base::base_impl (x), // virtual base + base_impl (x), + member_base (x) + { + } + + virtual void + traverse_integer (member_info& mi) + { + os << b << ".type = " << + integer_buffer_types[mi.st->type - sql_type::BOOLEAN] << ";" + << b << ".buffer = &" << arg << "." << mi.var << "value;" + << b << ".is_null = &" << arg << "." << mi.var << "null;"; + } + + virtual void + traverse_float (member_info& mi) + { + os << b << ".type = " << + float_buffer_types[mi.st->type - sql_type::REAL] << ";" + << b << ".buffer = &" << arg << "." << mi.var << "value;" + << b << ".is_null = &" << arg << "." << mi.var << "null;"; + } + + virtual void + traverse_numeric (member_info& mi) + { + os << b << ".type = pgsql::bind::numeric;" + << b << ".buffer = " << arg << "." << mi.var << "value.data_ptr ();" + << b << ".capacity = " << arg << "." << mi.var << + "value.capacity ();" + << b << ".size = &" << arg << "." << mi.var << "size;" + << b << ".is_null = &" << arg << "." << mi.var << "null;"; + } + + virtual void + traverse_date_time (member_info& mi) + { + os << b << ".type = " << + date_time_buffer_types[mi.st->type - sql_type::DATE] << ";" + << b << ".buffer = &" << arg << "." << mi.var << "value;" + << b << ".is_null = &" << arg << "." << mi.var << "null;"; + } + + virtual void + traverse_string (member_info& mi) + { + os << b << ".type = " << + char_bin_buffer_types[mi.st->type - sql_type::CHAR] << ";" + << b << ".buffer = " << arg << "." << mi.var << "value.data_ptr ();" + << b << ".capacity = " << arg << "." << mi.var << + "value.capacity ();" + << b << ".size = &" << arg << "." << mi.var << "size;" + << b << ".is_null = &" << arg << "." << mi.var << "null;"; + } + + virtual void + traverse_bit (member_info& mi) + { + os << b << ".type = pgsql::bind::bit;" + << b << ".buffer = " << arg << "." << mi.var << "value;" + << b << ".capacity = sizeof (" << arg << "." << mi.var << "value);" + << b << ".size = &" << arg << "." << mi.var << "size;" + << b << ".is_null = &" << arg << "." << mi.var << "null;"; + } + + virtual void + traverse_varbit (member_info& mi) + { + os << b << ".type = pgsql::bind::varbit;" + << b << ".buffer = " << arg << "." << mi.var << "value.data_ptr ();" + << b << ".capacity = " << arg << "." << mi.var << + "value.capacity ();" + << b << ".size = &" << arg << "." << mi.var << "size;" + << b << ".is_null = &" << arg << "." << mi.var << "null;"; + } + + virtual void + traverse_uuid (member_info& mi) + { + os << b << ".type = pgsql::bind::uuid;" + << b << ".buffer = " << arg << "." << mi.var << "value;" + << b << ".is_null = &" << arg << "." << mi.var << "null;"; + } + }; + entry<bind_member> bind_member_; + + // + // grow + // + + struct grow_member: relational::grow_member_impl<sql_type>, + member_base + { + grow_member (base const& x) + : member_base::base (x), // virtual base + member_base::base_impl (x), // virtual base + base_impl (x), + member_base (x) {} + + virtual void + traverse_integer (member_info&) + { + os << e << " = 0;" + << endl; + } + + virtual void + traverse_float (member_info&) + { + os << e << " = 0;" + << endl; + } + + virtual void + traverse_numeric (member_info& mi) + { + os << "if (" << e << ")" << endl + << "{" + << "i." << mi.var << "value.capacity (i." << mi.var << "size);" + << "grew = true;" + << "}"; + } + + virtual void + traverse_date_time (member_info&) + { + os << e << " = 0;" + << endl; + } + + virtual void + traverse_string (member_info& mi) + { + os << "if (" << e << ")" << endl + << "{" + << "i." << mi.var << "value.capacity (i." << mi.var << "size);" + << "grew = true;" + << "}"; + } + + virtual void + traverse_bit (member_info&) + { + os << e << " = 0;" + << endl; + } + + virtual void + traverse_varbit (member_info& mi) + { + os << "if (" << e << ")" << endl + << "{" + << "i." << mi.var << "value.capacity (i." << mi.var << "size);" + << "grew = true;" + << "}"; + } + + virtual void + traverse_uuid (member_info&) + { + os << e << " = 0;" + << endl; + } + }; + entry<grow_member> grow_member_; + + // + // init image + // + + struct init_image_member: relational::init_image_member_impl<sql_type>, + member_base + { + init_image_member (base const& x) + : member_base::base (x), // virtual base + member_base::base_impl (x), // virtual base + base_impl (x), + member_base (x) + { + } + + virtual void + set_null (member_info& mi) + { + os << "i." << mi.var << "null = true;"; + } + + virtual void + traverse_integer (member_info& mi) + { + os << traits << "::set_image (" << endl + << "i." << mi.var << "value, is_null, " << member << ");" + << "i." << mi.var << "null = is_null;"; + } + + virtual void + traverse_float (member_info& mi) + { + os << traits << "::set_image (" << endl + << "i." << mi.var << "value, is_null, " << member << ");" + << "i." << mi.var << "null = is_null;"; + } + + virtual void + traverse_numeric (member_info& mi) + { + // @@ Optimization: can remove growth check if buffer is fixed. + // + os << "std::size_t size (0);" + << "std::size_t cap (i." << mi.var << "value.capacity ());" + << traits << "::set_image (" << endl + << "i." << mi.var << "value," << endl + << "size," << endl + << "is_null," << endl + << member << ");" + << "i." << mi.var << "null = is_null;" + << "i." << mi.var << "size = size;" + << "grew = grew || (cap != i." << mi.var << "value.capacity ());"; + } + + virtual void + traverse_date_time (member_info& mi) + { + os << traits << "::set_image (" << endl + << "i." << mi.var << "value, is_null, " << member << ");" + << "i." << mi.var << "null = is_null;"; + } + + virtual void + traverse_string (member_info& mi) + { + os << "std::size_t size (0);" + << "std::size_t cap (i." << mi.var << "value.capacity ());" + << traits << "::set_image (" << endl + << "i." << mi.var << "value," << endl + << "size," << endl + << "is_null," << endl + << member << ");" + << "i." << mi.var << "null = is_null;" + << "i." << mi.var << "size = size;" + << "grew = grew || (cap != i." << mi.var << "value.capacity ());"; + } + + virtual void + traverse_bit (member_info& mi) + { + os << "std::size_t size (0);" + << traits << "::set_image (" << endl + << "i." << mi.var << "value," << endl + << "sizeof (i." << mi.var << "value)," << endl + << "size," << endl + << "is_null," << endl + << member << ");" + << "i." << mi.var << "null = is_null;" + << "i." << mi.var << "size = size;"; + } + + virtual void + traverse_varbit (member_info& mi) + { + os << "std::size_t size (0);" + << "std::size_t cap (i." << mi.var << "value.capacity ());" + << traits << "::set_image (" << endl + << "i." << mi.var << "value," << endl + << "size," << endl + << "is_null," << endl + << member << ");" + << "i." << mi.var << "null = is_null;" + << "i." << mi.var << "size = size;" + << "grew = grew || (cap != i." << mi.var << "value.capacity ());"; + } + + virtual void + traverse_uuid (member_info& mi) + { + os << traits << "::set_image (" << endl + << "i." << mi.var << "value, is_null, " << member << ");" + << "i." << mi.var << "null = is_null;"; + } + }; + entry<init_image_member> init_image_member_; + + // + // init value + // + + struct init_value_member: relational::init_value_member_impl<sql_type>, + member_base + { + init_value_member (base const& x) + : member_base::base (x), // virtual base + member_base::base_impl (x), // virtual base + base_impl (x), + member_base (x) + { + } + + virtual void + get_null (string const& var) const + { + os << "i." << var << "null"; + } + + virtual void + traverse_integer (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "null);" + << endl; + } + + virtual void + traverse_float (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "null);" + << endl; + } + + virtual void + traverse_numeric (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "size," << endl + << "i." << mi.var << "null);" + << endl; + } + + virtual void + traverse_date_time (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "null);" + << endl; + } + + virtual void + traverse_string (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "size," << endl + << "i." << mi.var << "null);" + << endl; + } + + virtual void + traverse_bit (member_info& mi) + { + // Presented as byte. + // + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "size," << endl + << "i." << mi.var << "null);" + << endl; + } + + virtual void + traverse_varbit (member_info& mi) + { + // Presented as bytea. + // + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "size," << endl + << "i." << mi.var << "null);" + << endl; + } + + virtual void + traverse_uuid (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "null);" + << endl; + } + }; + entry<init_value_member> init_value_member_; + + struct class_: relational::class_, context + { + class_ (base const& x): base (x) {} + + virtual string + persist_statement_extra (type& c, + relational::query_parameters&, + persist_position p) + { + string r; + + if (p == persist_after_values) + { + data_member_path* id (id_member (c)); + + type* poly_root (polymorphic (c)); + bool poly_derived (poly_root != 0 && poly_root != &c); + + // Top-level auto id. + // + if (id != 0 && !poly_derived && auto_ (*id)) + r = "RETURNING " + + convert_from (column_qname (*id), *id->back ()); + } + + return r; + } + + virtual void + object_extra (type& c) + { + bool abst (abstract (c)); + + type* poly_root (polymorphic (c)); + bool poly (poly_root != 0); + bool poly_derived (poly && poly_root != &c); + + if (abst && !poly) + return; + + data_member_path* id (id_member (c)); + semantics::data_member* optimistic (context::optimistic (c)); + + column_count_type const& cc (column_count (c)); + + size_t update_columns ( + cc.total - cc.id - cc.inverse - cc.readonly - cc.separate_update); + + string const& n (class_fq_name (c)); + string const& fn (flat_name (n)); + string traits ("access::object_traits_impl< " + n + ", id_pgsql >"); + + os << "const char " << traits << "::" << endl + << "persist_statement_name[] = " << + strlit (statement_name ("persist", fn, c)) << ";" + << endl; + + if (id != 0) + { + if (poly_derived) + { + os << "const char* const " << traits << "::" << endl + << "find_statement_names[] =" + << "{"; + + for (size_t i (0), n (abst ? 1 : polymorphic_depth (c)); + i < n; + ++i) + { + if (i != 0) + os << "," << endl; + + ostringstream ostr; + ostr << "find_" << i; + os << strlit (statement_name (ostr.str (), fn, c)); + } + + os << "};"; + } + else + os << "const char " << traits << "::" << endl + << "find_statement_name[] = " << + strlit (statement_name ("find", fn, c)) << ";" + << endl; + + if (poly && !poly_derived) + os << "const char " << traits << "::" << endl + << "find_discriminator_statement_name[] = " << + strlit (statement_name ("find_discriminator", fn, c)) << ";" + << endl; + + if (update_columns != 0) + os << "const char " << traits << "::" << endl + << "update_statement_name[] = " << + strlit (statement_name ("update", fn, c)) << ";" + << endl; + + os << "const char " << traits << "::" << endl + << "erase_statement_name[] = " << + strlit (statement_name ("erase", fn, c)) << ";" + << endl; + + if (optimistic != 0) + os << "const char " << traits << "::" << endl + << "optimistic_erase_statement_name[] = " << + strlit (statement_name ("erase_optimistic", fn, c)) << ";" + << endl; + } + + // Query statement name. + // + if (options.generate_query ()) + { + os << "const char " << traits << "::" << endl + << "query_statement_name[] = " << + strlit (statement_name ("query", fn, c)) << ";" + << endl + << "const char " << traits << "::" << endl + << "erase_query_statement_name[] = " << + strlit (statement_name ("erase_query", fn, c)) << ";" + << endl; + } + + // Statement types. + // + + // persist_statement_types. + // + { + os << "const unsigned int " << traits << "::" << endl + << "persist_statement_types[] =" + << "{"; + + statement_oids st (statement_insert); + st.traverse (c); + + // Empty array is not portable. So add a dummy member if we + // are not sending anything with the insert statement. + // + if (cc.total == cc.inverse + cc.optimistic_managed + + (id != 0 && !poly_derived && auto_ (*id) ? cc.id : 0)) + os << "0"; + + os << "};"; + } + + // find_statement_types. + // + if (id != 0) + { + os << "const unsigned int " << traits << "::" << endl + << "find_statement_types[] =" + << "{"; + + statement_oids st (statement_select, true); + st.traverse (*id); + + os << "};"; + } + + // update_statement_types. + // + if (id != 0 && update_columns != 0) + { + os << "const unsigned int " << traits << "::" << endl + << "update_statement_types[] =" + << "{"; + + { + statement_oids st (statement_update, true, &main_section); + st.traverse (c); + } + + // Not the same as update_columns. + // + bool first (cc.total == cc.id + cc.inverse + cc.readonly + + cc.separate_update + cc.optimistic_managed); + + statement_oids st (statement_where, first); + st.traverse (*id); + + if (optimistic != 0) + st.traverse (*optimistic); + + os << "};"; + } + + if (id != 0 && optimistic != 0) + { + os << "const unsigned int " << traits << "::" << endl + << "optimistic_erase_statement_types[] =" + << "{"; + + statement_oids st (statement_where); + st.traverse (*id); + st.traverse (*optimistic); + + os << "};"; + } + } + + virtual void + extra_statement_cache_extra_args (bool c, bool s) + { + bool u (c || s); + + os << "," << endl + << db << "::native_binding&" << (u ? " idn" : "") << "," << endl + << "const unsigned int*" << (u ? " idt" : ""); + } + + virtual void + view_extra (type& c) + { + string const& n (class_fq_name (c)); + string const& fn (flat_name (n)); + string traits ("access::view_traits_impl< " + n + ", id_pgsql >"); + + os << "const char " << traits << "::" << endl + << "query_statement_name[] = " << + strlit (statement_name ("query", fn, c)) << ";" + << endl; + } + + virtual void + object_query_statement_ctor_args (type&, + string const& q, + bool process, + bool prep) + { + os << "sts.connection ()," << endl; + + if (prep) + os << "n," << endl; + else + os << "query_statement_name," << endl; + + os << "text," << endl + << process << "," << endl // Process. + << "true," << endl // Optimize. + << q << ".parameter_types ()," << endl + << q << ".parameter_count ()," << endl + << q << ".parameters_binding ()," << endl + << "imb"; + } + + virtual void + object_erase_query_statement_ctor_args (type&) + { + os << "conn," << endl + << "erase_query_statement_name," << endl + << "text," << endl + << "q.parameter_types ()," << endl + << "q.parameter_count ()," << endl + << "q.parameters_binding ()"; + } + + virtual void + view_query_statement_ctor_args (type&, + string const& q, + bool process, + bool prep) + { + os << "sts.connection ()," << endl; + + if (prep) + os << "n," << endl; + else + os << "query_statement_name," << endl; + + os << q << ".clause ()," << endl + << process << "," << endl // Process. + << "true," << endl // Optimize. + << q << ".parameter_types ()," << endl + << q << ".parameter_count ()," << endl + << q << ".parameters_binding ()," << endl + << "imb"; + } + + virtual void + post_query_ (type&, bool once_off) + { + if (once_off) + os << "st->deallocate ();"; + } + }; + entry<class_> class_entry_; + + struct container_traits : relational::container_traits, context + { + container_traits (base const& x): base (x) {} + + virtual void + container_extra (semantics::data_member& m, semantics::type& t) + { + if (!object (c_) || (abstract (c_) && !polymorphic (c_))) + return; + + container_kind_type ck (container_kind (t)); + + string const& pn (public_name (m)); + string scope (scope_ + "::" + flat_prefix_ + pn + "_traits"); + + data_member_path* imp (inverse (m, "value")); + bool inv (imp != 0); + + bool smart (!inv && !unordered (m) && container_smart (t)); + + // Statment names. + // + + // Prefix top-object name to avoid conflicts with inherited + // member statement names. + // + string fn ( + flat_name ( + class_fq_name (*top_object) + "_" + flat_prefix_ + pn)); + + os << "const char " << scope << "::" << endl + << "select_name[] = " << + strlit (statement_name ("select", fn, m)) << ";" + << endl + << "const char " << scope << "::" << endl + << "insert_name[] = " << + strlit (statement_name ("insert", fn, m)) << ";" + << endl; + + if (smart) + os << "const char " << scope << "::" << endl + << "update_name[] = " << + strlit (statement_name ("update", fn, m)) << ";" + << endl; + + os << "const char " << scope << "::" << endl + << "delete_name[] = " << + strlit (statement_name ("delete", fn, m)) << ";" + << endl; + + // Statement types. + // + + semantics::type& vt (container_vt (m)); + semantics::type& idt (container_idt (m)); + + // insert statement types. + // + { + os << "const unsigned int " << scope << "::" << endl + << "insert_types[] =" + << "{"; + + if (!inv) + { + statement_oids so (statement_insert); + + so.traverse (m, idt, "id", "object_id"); + + switch (ck) + { + case ck_ordered: + { + if (!unordered (m)) + so.traverse (m, container_it (m), "index", "index"); + break; + } + case ck_map: + case ck_multimap: + { + so.traverse (m, container_kt (m), "key", "key"); + break; + } + case ck_set: + case ck_multiset: + { + break; + } + } + + so.traverse (m, vt, "value", "value"); + } + else + // MSVC does not allow zero length arrays or uninitialized + // non-extern const values. + // + os << "0"; + + os << "};"; + } + + // update statement types. + // + if (smart) + { + os << "const unsigned int " << scope << "::" << endl + << "update_types[] =" + << "{"; + + { + // Use insert instead of update to include read-only members. + // + statement_oids so (statement_insert); + so.traverse (m, vt, "value", "value"); + } + + statement_oids so (statement_where, false); + so.traverse (m, idt, "id", "object_id"); + + switch (ck) + { + case ck_ordered: + { + if (!unordered (m)) + so.traverse (m, container_it (m), "index", "index"); + break; + } + case ck_map: + case ck_multimap: + { + //so.traverse (m, container_kt (t), "key", "key"); + break; + } + case ck_set: + case ck_multiset: + { + //so.traverse (m, vt, "value", "value"); + break; + } + } + + os << "};"; + } + + // delete statement types. + // + if (smart) + { + os << "const unsigned int " << scope << "::" << endl + << "delete_types[] =" + << "{"; + + statement_oids so (statement_where); + so.traverse (m, idt, "id", "object_id"); + + switch (ck) + { + case ck_ordered: + { + if (!unordered (m)) + so.traverse (m, container_it (m), "index", "index"); + break; + } + case ck_map: + case ck_multimap: + { + //so.traverse (m, container_kt (t), "key", "key"); + break; + } + case ck_set: + case ck_multiset: + { + //so.traverse (m, vt, "value", "value"); + break; + } + } + + os << "};"; + } + } + }; + entry<container_traits> container_traits_; + + struct section_traits : relational::section_traits, context + { + section_traits (base const& x): base (x) {} + + virtual void + section_extra (user_section& s) + { + semantics::class_* poly_root (polymorphic (c_)); + bool poly (poly_root != 0); + + if (!poly && (abstract (c_) || + s.special == user_section::special_version)) + return; + + semantics::data_member* opt (optimistic (c_)); + + bool load (s.total != 0 && s.separate_load ()); + bool load_opt (s.optimistic () && s.separate_load ()); + + bool update (s.total != s.inverse + s.readonly); // Always separate. + bool update_opt (s.optimistic () && (s.readwrite_containers || poly)); + + string name (public_name (*s.member)); + string scope (scope_ + "::" + name + "_traits"); + + // Statment names. + // + + // Prefix object name to avoid conflicts with inherited member + // statement names. + // + string fn (flat_name (class_fq_name (c_) + "_" + name)); + + if (load || load_opt) + os << "const char " << scope << "::" << endl + << "select_name[] = " << + strlit (statement_name ("select", fn, *s.member)) << ";" + << endl; + + if (update || update_opt) + os << "const char " << scope << "::" << endl + << "update_name[] = " << + strlit (statement_name ("update", fn, *s.member)) << ";" + << endl; + + // Statement types. + // + if (update || update_opt) + { + os << "const unsigned int " << scope << "::" << endl + << "update_types[] =" + << "{"; + + { + statement_oids st (statement_update, true, &s); + st.traverse (c_); + } + + statement_oids st (statement_where, !update); + st.traverse (*id_member (c_)); + + if (s.optimistic ()) // Note: not update_opt. + st.traverse (*opt); + + os << "};"; + } + } + }; + entry<section_traits> section_traits_; + + struct container_cache_init_members: + relational::container_cache_init_members + { + container_cache_init_members (base const& x): base (x) {} + + virtual void + extra_members () + { + os << ", idn, idt"; + } + }; + entry<container_cache_init_members> container_cache_init_members_; + + struct section_cache_init_members: + relational::section_cache_init_members + { + section_cache_init_members (base const& x): base (x) {} + + virtual void + extra_members () + { + os << ", idn, idt"; + } + }; + entry<section_cache_init_members> section_cache_init_members_; + } + } +} diff --git a/odb/odb/relational/processor.cxx b/odb/odb/relational/processor.cxx new file mode 100644 index 0000000..0f60359 --- /dev/null +++ b/odb/odb/relational/processor.cxx @@ -0,0 +1,1564 @@ +// file : odb/relational/processor.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <odb/gcc.hxx> + +#include <vector> +#include <algorithm> + +#include <odb/diagnostics.hxx> +#include <odb/lookup.hxx> +#include <odb/cxx-lexer.hxx> +#include <odb/common.hxx> + +#include <odb/relational/context.hxx> +#include <odb/relational/processor.hxx> + +using namespace std; + +namespace relational +{ + namespace + { + // Indirect (dynamic) context values. + // + static string + id_column_type () + { + context& c (context::current ()); + data_member_path& id (*context::id_member (*c.top_object)); + return id.back ()->get<string> ("column-id-type"); + } + + struct data_member: traversal::data_member, context + { + virtual void + traverse (semantics::data_member& m) + { + if (transient (m)) + return; + + semantics::names* hint; + semantics::type& t (utype (m, hint)); + + semantics::type* wt; + semantics::names* whint (0); + if ((wt = wrapper (t, whint))) + wt = &utype (*wt, whint); + + // Determine the member kind. + // + enum {simple, composite, container, unknown} kind (unknown); + + // See if this is a composite value type. + // + if (composite_wrapper (t)) + kind = composite; + + // If not, see if it is a simple value. + // + if (kind == unknown) + { + string type, id_type; + + if (m.count ("id-type")) + id_type = m.get<string> ("id-type"); + + if (m.count ("type")) + { + type = m.get<string> ("type"); + + if (id_type.empty ()) + id_type = type; + } + + if (semantics::class_* c = object_pointer (t)) + { + // An object pointer in view doesn't really have a "column" + // so pretend that it has already been handled. + // + if (view_member (m)) + kind = simple; + else + { + // This is an object pointer. The column type is the pointed-to + // object id type. + // + semantics::data_member& id (*id_member (*c)->back ()); + + semantics::names* idhint; + semantics::type& idt (utype (id, idhint)); + + // The id type can be a composite value type. + // + if (composite_wrapper (idt)) + kind = composite; + else + { + semantics::type* wt; + semantics::names* whint (0); + if ((wt = wrapper (idt, whint))) + wt = &utype (*wt, whint); + + if (type.empty () && id.count ("id-type")) + type = id.get<string> ("id-type"); + + if (type.empty () && id.count ("type")) + type = id.get<string> ("type"); + + // The rest should be identical to the code for the id_type in + // the else block. + // + if (type.empty () && idt.count ("id-type")) + type = idt.get<string> ("id-type"); + + if (type.empty () && wt != 0 && wt->count ("id-type")) + type = wt->get<string> ("id-type"); + + if (type.empty () && idt.count ("type")) + type = idt.get<string> ("type"); + + if (type.empty () && wt != 0 && wt->count ("type")) + type = wt->get<string> ("type"); + + if (type.empty ()) + type = database_type (idt, idhint, true); + + if (type.empty () && wt != 0) + type = database_type (*wt, whint, true); + + id_type = type; + } + } + } + else + { + if (id_type.empty () && t.count ("id-type")) + id_type = t.get<string> ("id-type"); + + if (id_type.empty () && wt != 0 && wt->count ("id-type")) + id_type = wt->get<string> ("id-type"); + + if (type.empty () && t.count ("type")) + type = t.get<string> ("type"); + + if (type.empty () && wt != 0 && wt->count ("type")) + type = wt->get<string> ("type"); + + if (id_type.empty ()) + id_type = type; + + if (id_type.empty ()) + id_type = database_type (t, hint, true); + + if (id_type.empty () && wt != 0) + id_type = database_type (*wt, whint, true); + + bool null (false); + if (type.empty ()) + type = database_type (t, hint, false, &null); + + if (type.empty () && wt != 0) + type = database_type (*wt, whint, false, &null); + + // Use id mapping for discriminators. + // + if (id (m) || discriminator (m)) + type = id_type; + // Allow NULL if requested by the default mapping. + // + else if (null && !m.count ("not-null")) + m.set ("null", true); + } + + if (kind == unknown && !type.empty ()) + { + m.set ("column-type", type); + m.set ("column-id-type", id_type); + + // Issue a warning if we are relaxing null-ness. + // + if (m.count ("null") && t.count ("not-null")) + { + os << m.file () << ":" << m.line () << ":" << m.column () << ":" + << " warning: data member declared null while its type is " + << "declared not null" << endl; + } + + kind = simple; + } + } + + // If not a simple value, see if this is a container. + // + if (kind == unknown && context::container (m)) + { + process_container (m, (wt != 0 ? *wt : t)); + kind = container; + } + + // If it is none of the above then we have an error. + // + if (kind == unknown) + { + os << m.file () << ":" << m.line () << ":" << m.column () << ":" + << " error: unable to map C++ type '" << t.fq_name (hint) + << "' used in data member '" << m.name () << "' to a " + << db.name () << " database type" << endl; + + os << m.file () << ":" << m.line () << ":" << m.column () << ":" + << " info: use '#pragma db type' to specify the database type" + << endl; + + throw operation_failed (); + } + + if (m.count ("polymorphic-ref")) + { + // Copy the column name from the root's id member, if specified. + // + { + semantics::class_& r (*object_pointer (t)); + semantics::data_member& id (*id_member (r)->front ()); + + if (id.count ("column")) + m.set ("column", id.get<table_column> ("column")); + } + + m.set ("not-null", true); + m.set ("deferrable", + sema_rel::deferrable (sema_rel::deferrable::not_deferrable)); + m.set ("on-delete", sema_rel::foreign_key::cascade); + } + + process_index (m); + } + + // Convert index/unique specifiers to the index entry in the object. + // + void + process_index (semantics::data_member& m) + { + bool ip (m.count ("index")); + bool up (m.count ("unique")); + + if (ip || up) + { + using semantics::class_; + class_& c (dynamic_cast<class_&> (m.scope ())); + + indexes& ins (c.count ("index") + ? c.get<indexes> ("index") + : c.set ("index", indexes ())); + + index in; + in.loc = m.get<location_t> ( + ip ? "index-location" : "unique-location"); + + if (up) + in.type = "UNIQUE"; + + index::member im; + im.loc = in.loc; + im.name = m.name (); + im.path.push_back (&m); + in.members.push_back (im); + + // Insert it in the location order. + // + ins.insert ( + lower_bound (ins.begin (), ins.end (), in, index_comparator ()), + in); + } + } + + void + process_container_value (semantics::type& t, + semantics::names* hint, + semantics::data_member& m, + string const& prefix, + bool obj_ptr) + { + if (composite_wrapper (t)) + return; + + semantics::names* wh (0); + semantics::type* wt (wrapper (t, wh)); + + string type; + semantics::type& ct (utype (m)); + + // Custom mapping can come from these places (listed in the order + // of priority): member, container type, value type. To complicate + // things a bit, for object references, it can also come from the + // member and value type of the id member. + // + if (m.count (prefix + "-type")) + type = m.get<string> (prefix + "-type"); + + if (type.empty () && ct.count (prefix + "-type")) + type = ct.get<string> (prefix + "-type"); + + semantics::class_* c; + if (obj_ptr && (c = object_pointer (t))) + { + // This is an object pointer. The column type is the pointed-to + // object id type. + // + semantics::data_member& id (*id_member (*c)->back ()); + + semantics::names* idhint; + semantics::type& idt (utype (id, idhint)); + + // Nothing to do if this is a composite value type. + // + if (composite_wrapper (idt)) + return; + + semantics::type* wt (0); + semantics::names* whint (0); + if ((wt = wrapper (idt, whint))) + wt = &utype (*wt, whint); + + if (type.empty () && id.count ("id-type")) + type = id.get<string> ("id-type"); + + if (type.empty () && id.count ("type")) + type = id.get<string> ("type"); + + // The rest of the code is identical to the else block except here + // we have to check for "id-type" before checking for "type". + // + + if (type.empty () && idt.count ("id-type")) + type = idt.get<string> ("id-type"); + + if (type.empty () && wt != 0 && wt->count ("id-type")) + type = wt->get<string> ("id-type"); + + if (type.empty () && idt.count ("type")) + type = idt.get<string> ("type"); + + if (type.empty () && wt != 0 && wt->count ("type")) + type = wt->get<string> ("type"); + + if (type.empty ()) + type = database_type (idt, idhint, true); + + if (type.empty () && wt != 0) + type = database_type (*wt, whint, true); + } + else + { + if (type.empty () && t.count ("type")) + type = t.get<string> ("type"); + + if (type.empty () && wt != 0 && wt->count ("type")) + type = wt->get<string> ("type"); + + bool null (false); + if (type.empty ()) + type = database_type (t, hint, false, &null); + + if (type.empty () && wt != 0) + type = database_type (*wt, wh, false, &null); + + // Allow NULL if requested by the default mapping. + // + if (null && !m.count (prefix + "-not-null")) + m.set (prefix + "-null", true); + } + + if (!type.empty ()) + { + m.set (prefix + "-column-type", type); + m.set (prefix + "-column-id-type", type); + return; + } + + // We do not support nested containers so skip that test. + // + + // If it is none of the above then we have an error. + // + string fq_type (t.fq_anonymous () ? "<anonymous>" : t.fq_name ()); + + os << m.file () << ":" << m.line () << ":" << m.column () << ":" + << " error: unable to map C++ type '" << fq_type << "' used in " + << "data member '" << m.name () << "' to a " << db.name () + << " database type" << endl; + + os << m.file () << ":" << m.line () << ":" << m.column () << ":" + << " info: use '#pragma db " << prefix << "_type' to specify the " + << "database type" << endl; + + throw operation_failed (); + } + + void + process_container (semantics::data_member& m, semantics::type& t) + { + container_kind_type ck (t.get<container_kind_type> ("container-kind")); + + semantics::names* vh (0); + semantics::names* ih (0); + semantics::names* kh (0); + + semantics::type* vt (&utype (m, vh, "value")); + semantics::type* it (ck == ck_ordered ? &utype (m, ih, "index") : 0); + semantics::type* kt (ck == ck_map || ck == ck_multimap + ? &utype (m, kh, "key") + : 0); + + // Process member data. + // + m.set ("id-column-type", &id_column_type); + + process_container_value (*vt, vh, m, "value", true); + + if (it != 0) + process_container_value (*it, ih, m, "index", false); + + if (kt != 0) + process_container_value (*kt, kh, m, "key", true); + } + }; + + // + // + struct view_data_member: traversal::data_member, context + { + view_data_member (semantics::class_& c) + : view_ (c), + query_ (c.get<view_query> ("query")), + amap_ (c.get<view_alias_map> ("alias-map")), + omap_ (c.get<view_object_map> ("object-map")) + { + } + + struct assoc_member + { + semantics::data_member* m; + view_object* vo; + }; + + typedef vector<assoc_member> assoc_members; + + virtual void + traverse (semantics::data_member& m) + { + using semantics::data_member; + + if (transient (m)) + return; + + semantics::type& t (utype (m)); + + // Object pointers are associated with objects. + // + if (object_pointer (t)) + return; + + data_member* src_m (0); // Source member. + + // Resolve member references in column expressions. + // + if (m.count ("column")) + { + // Column literal. + // + if (query_.kind != view_query::condition) + { + warn (m.get<location_t> ("column-location")) + << "db pragma column ignored in a view with " + << (query_.kind == view_query::runtime ? "runtime" : "complete") + << " query" << endl; + } + + return; + } + else if (m.count ("column-expr")) + { + column_expr& e (m.get<column_expr> ("column-expr")); + + if (query_.kind != view_query::condition) + { + warn (e.loc) + << "db pragma column ignored in a view with " + << (query_.kind == view_query::runtime ? "runtime" : "complete") + << " query" << endl; + return; + } + + for (column_expr::iterator i (e.begin ()); i != e.end (); ++i) + { + // This code is quite similar to translate_expression in the + // source generator. + // + try + { + using semantics::scope; + using semantics::class_; + + if (i->kind != column_expr_part::reference) + continue; + + lex_.start (i->value); + + string tl; + tree tn; + cpp_ttype tt (lex_.next (tl, &tn)); + + data_member* m (0); + view_object* vo (0); + + // Check if this is an alias. + // + if (tt == CPP_NAME) + { + view_alias_map::iterator j (amap_.find (tl)); + + if (j != amap_.end ()) + { + vo = j->second; + + // Skip '::'. + // + if (lex_.next (tl, &tn) != CPP_SCOPE) + { + error (i->loc) << "member name expected after an alias " << + "in db pragma column" << endl; + throw operation_failed (); + } + + if (lex_.next (tl, &tn) != CPP_NAME) + throw lookup::invalid_name (); + + m = &vo->obj->lookup<data_member> ( + tl, scope::include_hidden); + + tt = lex_.next (tl, &tn); + } + } + + // If it is not an alias, do the normal lookup. + // + if (vo == 0) + { + // Also get the object type. We need to do it so that + // we can get the correct (derived) table name (the + // member itself can come from a base class). + // + scope* s; + string name; + cpp_ttype ptt; // Not used. + m = &lookup::resolve_scoped_name<data_member> ( + lex_, tt, tl, tn, ptt, + dynamic_cast<scope&> (*unit.find (i->scope)), + name, + false, + &s); + + view_object_map::iterator j ( + omap_.find (dynamic_cast<class_*> (s))); + + if (j == omap_.end ()) + { + error (i->loc) << "name '" << name << "' in db pragma " << + "column does not refer to a data member of a " << + "persistent class that is used in this view" << endl; + throw operation_failed (); + } + + vo = j->second; + } + + i->member_path.push_back (m); + + // Figure out the table name/alias for this member. + // + if (class_* root = polymorphic (*vo->obj)) + { + // If the object is polymorphic, then figure out which of the + // bases this member comes from and use the corresponding + // table. + // + class_* c (&static_cast<class_&> (m->scope ())); + + // If this member's class is not polymorphic (root uses reuse + // inheritance), then use the root table. + // + if (!polymorphic (*c)) + c = root; + + // In a polymorphic hierarchy we have several tables and the + // provided alias is used as a prefix together with the table + // name to form the actual alias. + // + qname const& t (table_name (*c)); + + if (vo->alias.empty ()) + i->table = t; + else + i->table = qname (vo->alias + "_" + t.uname ()); + } + else + i->table = vo->alias.empty () + ? table_name (*vo->obj) + : qname (vo->alias); + + // Finally, resolve nested members if any. + // + for (; tt == CPP_DOT; tt = lex_.next (tl, &tn)) + { + lex_.next (tl, &tn); // Get CPP_NAME. + + // Check that the outer member is composite and also + // unwrap it while at it. + // + class_* comp (composite_wrapper (utype (*m))); + if (comp == 0) + { + error (i->loc) << "data member '" << m->name () << "' " << + "specified in db pragma column is not composite" << endl; + throw operation_failed (); + } + + m = &comp->lookup<data_member> (tl, class_::include_hidden); + i->member_path.push_back (m); + } + + // If the expression is just this reference, then we have + // a source member. + // + if (e.size () == 1) + src_m = m; + } + catch (lookup::invalid_name const&) + { + error (i->loc) << "invalid name in db pragma column" << endl; + throw operation_failed (); + } + catch (semantics::unresolved const& e) + { + if (e.type_mismatch) + error (i->loc) << "name '" << e.name << "' in db pragma " << + "column does not refer to a data member" << endl; + else + error (i->loc) << "unable to resolve data member '" << + e.name << "' specified with db pragma column" << endl; + + throw operation_failed (); + } + catch (semantics::ambiguous const& e) + { + error (i->loc) << "data member name '" << e.first.name () << + "' specified with db pragma column is ambiguous" << endl; + + info (e.first.named ().location ()) << "could resolve to " << + "this data member" << endl; + + info (e.second.named ().location ()) << "or could resolve " << + "to this data member" << endl; + + throw operation_failed (); + } + } + + // Check that the source member is not transient or inverse. Also + // check that the C++ types are the same (sans cvr-qualification + // and wrapping) and issue a warning if they differ. In rare cases + // where this is not a mistake, the user can use a phony expression + // (e.g., "" + person:name) to disable the warning. Note that in + // this case there will be no type pragma copying, which is probably + // ok seeing that the C++ types are different. + // + // + if (src_m != 0) + { + string reason; + + if (transient (*src_m)) + reason = "transient"; + else if (inverse (*src_m)) + reason = "inverse"; + + if (!reason.empty ()) + { + error (e.loc) + << "object data member '" << src_m->name () << "' specified " + << "in db pragma column is " << reason << endl; + throw operation_failed (); + } + + if (!member_resolver::check_types (utype (*src_m), utype (m))) + { + warn (e.loc) + << "object data member '" << src_m->name () << "' specified " + << "in db pragma column has a different type compared to the " + << "view data member" << endl; + + info (src_m->file (), src_m->line (), src_m->column ()) + << "object data member is defined here" << endl; + + info (m.file (), m.line (), m.column ()) + << "view data member is defined here" << endl; + } + } + } + // This member has no column information. If we are generating our + // own query, try to find a member with the same (or similar) name + // in one of the associated objects. + // + else if (query_.kind == view_query::condition) + { + view_objects& objs (view_.get<view_objects> ("objects")); + + assoc_members exact_members, pub_members; + member_resolver resolver (exact_members, pub_members, m); + + for (view_objects::iterator i (objs.begin ()); i != objs.end (); ++i) + { + if (i->kind == view_object::object) + resolver.traverse (*i); + } + + assoc_members& members ( + !exact_members.empty () ? exact_members : pub_members); + + // Issue diagnostics if we didn't find any or found more + // than one. + // + if (members.empty ()) + { + error (m.file (), m.line (), m.column ()) + << "unable to find a corresponding data member for '" + << m.name () << "' in any of the associated objects" << endl; + + info (m.file (), m.line (), m.column ()) + << "use db pragma column to specify the corresponding data " + << "member or column name" << endl; + + throw operation_failed (); + } + else if (members.size () > 1) + { + error (m.file (), m.line (), m.column ()) + << "corresponding data member for '" << m.name () << "' is " + << "ambiguous" << endl; + + info (m.file (), m.line (), m.column ()) + << "candidates are:" << endl; + + for (assoc_members::const_iterator i (members.begin ()); + i != members.end (); + ++i) + { + info (i->m->file (), i->m->line (), i->m->column ()) + << " '" << i->m->name () << "' in object '" + << i->vo->name () << "'" << endl; + } + + info (m.file (), m.line (), m.column ()) + << "use db pragma column to resolve this ambiguity" << endl; + + throw operation_failed (); + } + + // Synthesize the column expression for this member. + // + assoc_member const& am (members.back ()); + + column_expr& e (m.set ("column-expr", column_expr ())); + e.push_back (column_expr_part ()); + column_expr_part& ep (e.back ()); + + ep.kind = column_expr_part::reference; + + + // If this object is polymorphic, then figure out which of the + // bases this member comes from and use the corresponding table. + // + using semantics::class_; + + if (class_* root = polymorphic (*am.vo->obj)) + { + class_* c (&static_cast<class_&> (am.m->scope ())); + + // If this member's class is not polymorphic (root uses reuse + // inheritance), then use the root table. + // + if (!polymorphic (*c)) + c = root; + + // In a polymorphic hierarchy we have several tables and the + // provided alias is used as a prefix together with the table + // name to form the actual alias. + // + qname const& t (table_name (*c)); + + if (am.vo->alias.empty ()) + ep.table = t; + else + ep.table = qname (am.vo->alias + "_" + t.uname ()); + } + else + ep.table = am.vo->alias.empty () + ? table_name (*am.vo->obj) + : qname (am.vo->alias); + + ep.member_path.push_back (am.m); + + src_m = am.m; + } + + // If we have the source member and don't have the type pragma of + // our own, but the source member does, then copy the columnt type + // over. In case the source member is a pointer, also check the id + // member. + // + if (src_m != 0 && !m.count ("type")) + { + if (src_m->count ("type")) + m.set ("column-type", src_m->get<string> ("column-type")); + else if (semantics::class_* c = object_pointer (utype (*src_m))) + { + semantics::data_member& id (*id_member (*c)->back ()); + + if (id.count ("type")) + m.set ("column-type", id.get<string> ("column-type")); + } + } + + // Check the return statements above if you add any extra logic + // here. + } + + struct member_resolver: traversal::class_ + { + member_resolver (assoc_members& members, + assoc_members& pub_members, + semantics::data_member& m) + : member_ (members, pub_members, m) + { + *this >> names_ >> member_; + *this >> inherits_ >> *this; + } + + void + traverse (view_object& vo) + { + member_.vo_ = &vo; + + // First look for an exact match. + // + { + member_.exact_ = true; + member_.found_ = false; + traverse (*vo.obj); + } + + // If we didn't find an exact match, then look for a public + // name match. + // + if (!member_.found_) + { + member_.exact_ = false; + traverse (*vo.obj); + } + } + + virtual void + traverse (type& c) + { + if (!object (c)) + return; // Ignore transient bases. + + names (c); + + // If we already found a match in one of the derived classes, + // don't go into bases to get the standard "hiding" behavior. + // + if (!member_.found_) + inherits (c); + } + + public: + static bool + check_types (semantics::type& ot, semantics::type& vt) + { + using semantics::type; + + // Require that the types be the same sans the wrapping and + // cvr-qualification. If the object member type is a pointer, + // use the id type of the pointed-to object. + // + type* t1; + + if (semantics::class_* c = object_pointer (ot)) + t1 = &utype (*id_member (*c)); + else + t1 = &ot; + + type* t2 (&vt); + + if (type* wt1 = context::wrapper (*t1)) + t1 = &utype (*wt1); + + if (type* wt2 = context::wrapper (*t2)) + t2 = &utype (*wt2); + + if (t1 != t2) + return false; + + return true; + } + + private: + struct data_member: traversal::data_member + { + data_member (assoc_members& members, + assoc_members& pub_members, + semantics::data_member& m) + : members_ (members), + pub_members_ (pub_members), + name_ (m.name ()), + pub_name_ (context::current ().public_name (m)), + type_ (utype (m)) + { + } + + virtual void + traverse (type& m) + { + if (exact_) + { + if (name_ == m.name () && check (m)) + { + assoc_member am; + am.m = &m; + am.vo = vo_; + members_.push_back (am); + found_ = true; + } + } + else + { + if (pub_name_ == context::current ().public_name (m) && + check (m)) + { + assoc_member am; + am.m = &m; + am.vo = vo_; + pub_members_.push_back (am); + found_ = true; + } + } + } + + bool + check (semantics::data_member& m) + { + // Make sure that the found node can possibly match. + // + if (context::transient (m) || + context::inverse (m) || + m.count ("polymorphic-ref")) + return false; + + return check_types (utype (m), type_); + } + + assoc_members& members_; + assoc_members& pub_members_; + + string name_; + string pub_name_; + semantics::type& type_; + + view_object* vo_; + bool exact_; + bool found_; + }; + + traversal::names names_; + data_member member_; + traversal::inherits inherits_; + }; + + private: + semantics::class_& view_; + view_query& query_; + view_alias_map& amap_; + view_object_map& omap_; + cxx_string_lexer lex_; + }; + + struct class_: traversal::class_, context + { + class_ () + : typedefs_ (true) + { + *this >> defines_ >> *this; + *this >> typedefs_ >> *this; + + member_names_ >> member_; + } + + virtual void + traverse (type& c) + { + class_kind_type k (class_kind (c)); + + if (k == class_other) + return; + + names (c); // Process nested classes. + names (c, member_names_); + + if (k == class_object) + traverse_object (c); + else if (k == class_view) + traverse_view (c); + } + + // + // Object. + // + + virtual void + traverse_object (type& c) + { + // Remove the bulk pragma if this database doesn't support bulk + // operations. + // + if (c.count ("bulk") && !generate_bulk) + c.remove ("bulk"); + + // Process indexes. Here we need to do two things: resolve member + // names to member paths and assign names to unnamed indexes. We + // are also going to handle the special container indexes. + // + indexes& ins (c.count ("index") + ? c.get<indexes> ("index") + : c.set ("index", indexes ())); + + for (indexes::iterator i (ins.begin ()); i != ins.end ();) + { + index& in (*i); + + // This should never happen since a db index pragma without + // the member specifier will be treated as a member pragma. + // + assert (!in.members.empty ()); + + // First resolve member names. + // + index::members_type::iterator j (in.members.begin ()); + for (; j != in.members.end (); ++j) + { + index::member& im (*j); + + if (!im.path.empty ()) + continue; // Already resolved. + + im.path = resolve_data_members (c, im.name, im.loc, lex_); + + if (container (*im.path.back ())) + break; + } + + // Add the table prefix if this database has global index names. + // + if (!in.name.empty () && global_index) + in.name = table_name_prefix (class_scope (c)) + in.name; + + // Handle container indexes. + // + if (j != in.members.end ()) + { + // Do some sanity checks. + // + if (in.members.size () != 1) + { + error (in.loc) << "multiple data members specified for a " + << "container index" << endl; + throw operation_failed (); + } + + string tl; + if (lex_.next (tl) != CPP_DOT || lex_.next (tl) != CPP_NAME || + (tl != "id" && tl != "index")) + { + error (j->loc) << ".id or .index special member expected in a " + << "container index" << endl; + throw operation_failed (); + } + + string n (tl); + + if (lex_.next (tl) != CPP_EOF) + { + error (j->loc) << "unexpected text after ." << n << " in " + << "db pragma member" << endl; + throw operation_failed (); + } + + // Move this index to the container member. + // + j->path.back ()->set (n + "-index", *i); + i = ins.erase (i); + continue; + } + + // Now assign the name if the index is unnamed. We have to + // add table name as a prefix here since there is not way + // to distinguish between user-assigned and auto-derived + // names in the model. + // + if (in.name.empty ()) + { + // Make sure there is only one member. + // + if (in.members.size () > 1) + { + error (in.loc) << "unnamed index with more than one data " + << "member" << endl; + throw operation_failed (); + } + + // Generally, we want the index name to be based on the column + // name. This is straightforward for single-column members. In + // case of a composite member, we will need to use the column + // prefix which is based on the data member name, unless + // overridden by the user. In the latter case the prefix can + // be empty, in which case we will just fall back on the + // member's public name. + // + string n (column_prefix (in.members.front ().path, true).prefix); + + if (n.empty ()) + n = public_name_db (*in.members.front ().path.back ()); + else if (n[n.size () - 1] == '_') + n.resize (n.size () - 1); // Remove trailing underscore. + + in.name = index_name (table_name (c), n); + } + + ++i; + } + } + + // + // View. + // + + struct relationship + { + semantics::data_member* member; + string name; + view_object* pointer; + view_object* pointee; + }; + + typedef vector<relationship> relationships; + + virtual void + traverse_view (type& c) + { + bool has_q (c.count ("query")); + bool has_o (c.count ("objects")); + + // Determine the kind of query template we've got. + // + view_query& vq (has_q + ? c.get<view_query> ("query") + : c.set ("query", view_query ())); + if (has_q) + { + if (!vq.literal.empty ()) + { + string q (upcase (vq.literal)); + + //@@ We need to recognize database-specific list of prefixes. For + // example, PG has WITH. Alternatively (or in addition) we could + // do the same comment trick (e.g., /*SELECT*/ to treat it as a + // SELECT-like queiry). + // + if (q.compare (0, 7, "SELECT ") == 0) + vq.kind = view_query::complete_select; + else if (q.compare (0, 5, "EXEC ") == 0 || + q.compare (0, 5, "CALL ") == 0 || + q.compare (0, 8, "EXECUTE ") == 0) + vq.kind = view_query::complete_execute; + // + // Hint for databases that use SELECT for stored procedure + // calls (e.g., PostgreSQL). + // + else if (q.compare (0, 8, "/*CALL*/") == 0) + { + vq.literal = string (vq.literal, q[8] == ' ' ? 9 : 8); + vq.kind = view_query::complete_execute; + } + else + vq.kind = view_query::condition; + } + else if (!vq.expr.empty ()) + { + // If the first token in the expression is a string and + // it starts with "SELECT " or is equal to "SELECT" or + // one of the stored procedure call keywords, then we + // have a complete query. + // + if (vq.expr.front ().type == CPP_STRING) + { + string q (upcase (vq.expr.front ().literal)); + + if (q.compare (0, 7, "SELECT ") == 0 || q == "SELECT") + vq.kind = view_query::complete_select; + else if (q.compare (0, 5, "EXEC ") == 0 || q == "EXEC" || + q.compare (0, 5, "CALL ") == 0 || q == "CALL" || + q.compare (0, 8, "EXECUTE ") == 0 || q == "EXECUTE") + vq.kind = view_query::complete_execute; + else if (q.compare (0, 8, "/*CALL*/") == 0) + { + vq.expr.front ().literal = + string (vq.expr.front ().literal, q[8] == ' ' ? 9 : 8); + vq.kind = view_query::complete_execute; + } + else + vq.kind = view_query::condition; + } + else + vq.kind = view_query::condition; + } + else + vq.kind = (vq.distinct || vq.for_update) + ? view_query::condition // The query(distinct) case. + : view_query::runtime; + } + else + vq.kind = has_o ? view_query::condition : view_query::runtime; + + if ((vq.distinct || vq.for_update) && vq.kind != view_query::condition) + { + error (vq.loc) + << "result modifier specified for " + << (vq.kind == view_query::runtime ? "runtime" : "native") + << " query" << endl; + + throw operation_failed (); + } + + // We cannot have an incomplete query if there are not objects + // to derive the rest from. + // + if (vq.kind == view_query::condition && !has_o) + { + error (c.file (), c.line (), c.column ()) + << "view '" << class_fq_name (c) << "' has an incomplete query " + << "template and no associated objects" << endl; + + info (c.file (), c.line (), c.column ()) + << "use db pragma query to provide a complete query template" + << endl; + + info (c.file (), c.line (), c.column ()) + << "or use db pragma object to associate one or more objects " + << "with the view" + << endl; + + throw operation_failed (); + } + + // Process join conditions. + // + if (has_o) + { + view_objects& objs (c.get<view_objects> ("objects")); + + for (view_objects::iterator i (objs.begin ()); i != objs.end (); ++i) + { + if (i == objs.begin () && i->join != view_object::left) + { + error (i->loc) + << "no join type can be specified for the first associated " + << (i->kind == view_object::object ? "object" : "table") + << endl; + throw operation_failed (); + } + + if (i->kind != view_object::object) + { + // Make sure we have join conditions for tables unless it + // is the first entry. + // + if (i != objs.begin () && i->cond.empty ()) + { + error (i->loc) + << "missing join condition in db pragma table" << endl; + + throw operation_failed (); + } + + continue; + } + + // If we have to generate the query and there was no JOIN + // condition specified by the user, try to come up with one + // automatically based on object relationships. CROSS JOIN + // has no condition. + // + if (vq.kind == view_query::condition && + i->cond.empty () && + i != objs.begin () && + i->join != view_object::cross) + { + relationships rs; + + // Check objects specified prior to this one for any + // relationships. We don't examine objects that were + // specified after this one because that would require + // rearranging the JOIN order. + // + for (view_objects::iterator j (objs.begin ()); j != i; ++j) + { + if (j->kind != view_object::object) + continue; // Skip tables. + + // First see if any of the objects that were specified + // prior to this object point to it. + // + { + relationship_resolver r (rs, *i, true); + r.traverse (*j); + } + + // Now see if this object points to any of the objects + // specified prior to it. + // + { + relationship_resolver r (rs, *j, false); + r.traverse (*i); + } + } + + // Issue diagnostics if we didn't find any or found more + // than one. + // + if (rs.empty ()) + { + error (i->loc) + << "unable to find an object relationship involving " + << "object '" << i->name () << "' and any of the previously " + << "associated objects" << endl; + + info (i->loc) + << "use the join condition clause in db pragma object " + << "to specify a custom join condition" << endl; + + throw operation_failed (); + } + else if (rs.size () > 1) + { + error (i->loc) + << "object relationship for object '" << i->name () << "' " + << "is ambiguous" << endl; + + info (i->loc) + << "candidates are:" << endl; + + for (relationships::const_iterator j (rs.begin ()); + j != rs.end (); + ++j) + { + semantics::data_member& m (*j->member); + + info (m.file (), m.line (), m.column ()) + << " '" << j->name << "' " + << "in object '" << j->pointer->name () << "' " + << "pointing to '" << j->pointee->name () << "'" + << endl; + } + + info (i->loc) + << "use the join condition clause in db pragma object " + << "to resolve this ambiguity" << endl; + + throw operation_failed (); + } + + // Synthesize the condition. + // + relationship const& r (rs.back ()); + + string name (r.pointer->alias.empty () + ? class_fq_name (*r.pointer->obj) + : r.pointer->alias); + name += "::"; + name += r.name; + + lex_.start (name); + + string t; + for (cpp_ttype tt (lex_.next (t)); + tt != CPP_EOF; + tt = lex_.next (t)) + { + i->cond.push_back (cxx_token (lex_.location (), tt, t)); + } + } + } + } + + // Handle forced versioning. When versioning is forced, ignore + // it for native views. + // + if (force_versioned && vq.kind == view_query::condition) + c.set ("versioned", true); + + // Handle data members. + // + { + view_data_member t (c); + traversal::names n (t); + names (c, n); + } + } + + struct relationship_resolver: object_members_base + { + relationship_resolver (relationships& rs, + view_object& pointee, + bool forward) + // Look in polymorphic bases only for previously-associated + // objects since backward pointers from bases will result in + // the pathological case (we will have to join the base table + // first, which means we will get both bases and derived objects + // instead of just derived). + // + : object_members_base (false, false, true, forward), + relationships_ (rs), + // Ignore self-references if we are looking for backward + // pointers since they were already added to the list in + // the previous pass. + // + self_pointer_ (forward), + pointer_ (0), + pointee_ (pointee) + { + } + + void + traverse (view_object& pointer) + { + pointer_ = &pointer; + object_members_base::traverse (*pointer.obj); + } + + using object_members_base::traverse; // Unhide. + + virtual void + traverse_pointer (semantics::data_member& m, semantics::class_& c) + { + // Ignore polymorphic id references. + // + if (m.count ("polymorphic-ref")) + return; + + // Ignore inverse sides of the same relationship to avoid + // phony conflicts caused by the direct side that will end + // up in the relationship list as well. Unless the inverse + // member is in the polymorphic base in which case we will + // miss it since we don't examine inside poly bases on the + // backwards scan (see above). + // + if (data_member_path* imp = inverse (m)) + { + if (&imp->front ()->scope () == &c) // Direct member. + return; + } + + // Ignore self-pointers if requested. + // + if (!self_pointer_ && pointer_->obj == &c) + return; + + if (pointee_.obj == &c) + { + relationships_.push_back (relationship ()); + relationships_.back ().member = &m; + relationships_.back ().name = member_prefix_ + m.name (); + relationships_.back ().pointer = pointer_; + relationships_.back ().pointee = &pointee_; + } + } + + virtual void + traverse_container (semantics::data_member& m, semantics::type&) + { + if (semantics::class_* c = + object_pointer (context::container_vt (m))) + { + if (inverse (m, "value")) + return; + + // Ignore self-pointers if requested. + // + if (!self_pointer_ && pointer_->obj == c) + return; + + if (pointee_.obj == c) + { + relationships_.push_back (relationship ()); + relationships_.back ().member = &m; + relationships_.back ().name = member_prefix_ + m.name (); + relationships_.back ().pointer = pointer_; + relationships_.back ().pointee = &pointee_; + } + } + } + + private: + relationships& relationships_; + bool self_pointer_; + view_object* pointer_; + view_object& pointee_; + }; + + private: + cxx_string_lexer lex_; + + traversal::defines defines_; + typedefs typedefs_; + + data_member member_; + traversal::names member_names_; + }; + } + + void + process () + { + context ctx; + + traversal::unit unit; + traversal::defines unit_defines; + typedefs unit_typedefs (true); + traversal::namespace_ ns; + class_ c; + + unit >> unit_defines >> ns; + unit_defines >> c; + unit >> unit_typedefs >> c; + + traversal::defines ns_defines; + typedefs ns_typedefs (true); + + ns >> ns_defines >> ns; + ns_defines >> c; + ns >> ns_typedefs >> c; + + unit.dispatch (ctx.unit); + } +} diff --git a/odb/odb/relational/processor.hxx b/odb/odb/relational/processor.hxx new file mode 100644 index 0000000..71b8643 --- /dev/null +++ b/odb/odb/relational/processor.hxx @@ -0,0 +1,15 @@ +// file : odb/relational/processor.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_RELATIONAL_PROCESSOR_HXX +#define ODB_RELATIONAL_PROCESSOR_HXX + +namespace relational +{ + // Issues diagnostics and throws operation_failed in case of an error. + // + void + process (); +} + +#endif // ODB_RELATIONAL_PROCESSOR_HXX diff --git a/odb/odb/relational/schema-source.cxx b/odb/odb/relational/schema-source.cxx new file mode 100644 index 0000000..5659485 --- /dev/null +++ b/odb/odb/relational/schema-source.cxx @@ -0,0 +1,281 @@ +// file : odb/relational/schema-source.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <odb/relational/schema-source.hxx> +#include <odb/relational/generate.hxx> + +using namespace std; + +namespace relational +{ + namespace schema + { + void + generate_source (sema_rel::changelog* log) + { + context ctx; + ostream& os (ctx.os); + database db (ctx.db); + options const& ops (ctx.options); + sema_rel::model& model (*ctx.model); + string const& schema_name (ops.schema_name ()[db]); + + if (log != 0 && ops.suppress_migration ()) + log = 0; + + bool schema_version ( + model.version () != 0 && !ctx.options.suppress_schema_version ()); + + instance<cxx_emitter> emitter; + emitter_ostream emitter_os (*emitter); + schema_format format (schema_format::embedded); + + if (!model.names_empty () || log != 0 || schema_version) + os << "namespace odb" + << "{"; + + // Schema. + // + if (!model.names_empty () || schema_version) + { + os << "static bool" << endl + << "create_schema (database& db, unsigned short pass, bool drop)" + << "{" + << "ODB_POTENTIALLY_UNUSED (db);" + << "ODB_POTENTIALLY_UNUSED (pass);" + << "ODB_POTENTIALLY_UNUSED (drop);" + << endl; + + // Drop. + // + if (!ops.omit_drop ()) + { + bool close (false); + + os << "if (drop)" + << "{"; + + instance<drop_model> dmodel (*emitter, emitter_os, format); + instance<drop_table> dtable (*emitter, emitter_os, format); + trav_rel::qnames names; + dmodel >> names >> dtable; + + for (unsigned short pass (1); pass < 3; ++pass) + { + emitter->pass (pass); + dmodel->pass (pass); + dtable->pass (pass); + + dmodel->traverse (model); + + close = close || !emitter->empty (); + } + + if (schema_version) + { + instance<version_table> vt (*emitter, emitter_os, format); + vt->create_table (); + vt->drop (); + close = true; + } + + if (close) // Close the last case and the switch block. + os << "return false;" + << "}" // case + << "}"; // switch + + os << "}"; + } + + // Create. + // + if (!ops.omit_create ()) + { + bool close (false); + + if (ops.omit_drop ()) + os << "if (!drop)"; + else + os << "else"; + + os << "{"; + + instance<create_model> cmodel (*emitter, emitter_os, format); + instance<create_table> ctable (*emitter, emitter_os, format); + trav_rel::qnames names; + cmodel >> names >> ctable; + + for (unsigned short pass (1); pass < 3; ++pass) + { + emitter->pass (pass); + cmodel->pass (pass); + ctable->pass (pass); + + cmodel->traverse (model); + + close = close || !emitter->empty (); + } + + if (schema_version) + { + instance<version_table> vt (*emitter, emitter_os, format); + vt->create_table (); + vt->create (model.version ()); + close = true; + } + + if (close) // Close the last case and the switch block. + os << "return false;" + << "}" // case + << "}"; // switch + + os << "}"; + } + + os << "return false;" + << "}"; + + os << "static const schema_catalog_create_entry" << endl + << "create_schema_entry_ (" << endl + << "id_" << db << "," << endl + << context::strlit (schema_name) << "," << endl + << "&create_schema);" + << endl; + } + + // Migration. + // + if (log != 0) + { + // Create NULL migration entry for the base version so that we + // get the complete version range (base, current) at runtime. + // Code in schema_catalog relies on this. + // + os << "static const schema_catalog_migrate_entry" << endl + << "migrate_schema_entry_" << log->model ().version () << + "_ (" << endl + << "id_" << db << "," << endl + << context::strlit (schema_name) << "," << endl + << log->model ().version () << "ULL," << endl + << "0);" + << endl; + + for (sema_rel::changelog::contains_changeset_iterator i ( + log->contains_changeset_begin ()); + i != log->contains_changeset_end (); ++i) + { + sema_rel::changeset& cs (i->changeset ()); + + os << "static bool" << endl + << "migrate_schema_" << cs.version () << " (database& db, " << + "unsigned short pass, bool pre)" + << "{" + << "ODB_POTENTIALLY_UNUSED (db);" + << "ODB_POTENTIALLY_UNUSED (pass);" + << "ODB_POTENTIALLY_UNUSED (pre);" + << endl; + + // Pre. + // + { + bool close (false); + + os << "if (pre)" + << "{"; + + instance<changeset_pre> changeset (*emitter, emitter_os, format); + instance<create_table> ctable (*emitter, emitter_os, format); + instance<alter_table_pre> atable (*emitter, emitter_os, format); + trav_rel::qnames names; + changeset >> names; + names >> ctable; + names >> atable; + + for (unsigned short pass (1); pass < 3; ++pass) + { + emitter->pass (pass); + changeset->pass (pass); + ctable->pass (pass); + atable->pass (pass); + + changeset->traverse (cs); + + close = close || !emitter->empty (); + } + + if (!ctx.options.suppress_schema_version ()) + { + instance<version_table> vt (*emitter, emitter_os, format); + vt->migrate_pre (cs.version ()); + close = true; + } + + if (close) // Close the last case and the switch block. + os << "return false;" + << "}" // case + << "}"; // switch + + os << "}"; + } + + // Post. + // + { + bool close (false); + + os << "else" + << "{"; + + instance<changeset_post> changeset (*emitter, emitter_os, format); + instance<drop_table> dtable (*emitter, emitter_os, format); + instance<alter_table_post> atable (*emitter, emitter_os, format); + trav_rel::qnames names; + changeset >> names; + names >> dtable; + names >> atable; + + for (unsigned short pass (1); pass < 3; ++pass) + { + emitter->pass (pass); + changeset->pass (pass); + dtable->pass (pass); + atable->pass (pass); + + changeset->traverse (cs); + + close = close || !emitter->empty (); + } + + if (!ctx.options.suppress_schema_version ()) + { + instance<version_table> vt (*emitter, emitter_os, format); + vt->migrate_post (); + close = true; + } + + if (close) // Close the last case and the switch block. + os << "return false;" + << "}" // case + << "}"; // switch + + os << "}"; + } + + os << "return false;" + << "}"; + + os << "static const schema_catalog_migrate_entry" << endl + << "migrate_schema_entry_" << cs.version () << "_ (" << endl + << "id_" << db << "," << endl + << context::strlit (schema_name) << "," << endl + << cs.version () << "ULL," << endl + << "&migrate_schema_" << cs.version () << ");" + << endl; + } + } + + if (!model.names_empty () || log != 0 || schema_version) + os << "}"; // namespace odb + } + } +} diff --git a/odb/odb/relational/schema-source.hxx b/odb/odb/relational/schema-source.hxx new file mode 100644 index 0000000..d2235f5 --- /dev/null +++ b/odb/odb/relational/schema-source.hxx @@ -0,0 +1,126 @@ +// file : odb/relational/schema-source.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_RELATIONAL_SCHEMA_SOURCE_HXX +#define ODB_RELATIONAL_SCHEMA_SOURCE_HXX + +#include <odb/diagnostics.hxx> + +#include <odb/emitter.hxx> +#include <odb/relational/context.hxx> +#include <odb/relational/schema.hxx> + +namespace relational +{ + namespace schema + { + struct cxx_emitter: emitter, virtual context + { + typedef cxx_emitter base; + + void + pass (unsigned short p) + { + empty_ = true; + pass_ = p; + new_pass_ = true; + + if (pass_ == 1) + empty_passes_ = 0; // New set of passes. + + // Assume this pass is empty. + // + empty_passes_++; + } + + // Did this pass produce anything? + // + bool + empty () const + { + return empty_; + } + + virtual void + pre () + { + first_ = true; + } + + virtual void + line (const string& l) + { + if (l.empty ()) + return; // Ignore empty lines. + + if (first_) + { + first_ = false; + + // If this line starts a new pass, then output the switch/case + // blocks. + // + if (new_pass_) + { + new_pass_ = false; + empty_ = false; + empty_passes_--; // This pass is not empty. + + // Output case statements for empty preceeding passes, if any. + // + if (empty_passes_ != 0) + { + unsigned short s (pass_ - empty_passes_); + + if (s == 1) + os << "switch (pass)" + << "{"; + else + os << "return true;" // One more pass. + << "}"; + + for (; s != pass_; ++s) + os << "case " << s << ":" << endl; + + os << "{"; + empty_passes_ = 0; + } + + if (pass_ == 1) + os << "switch (pass)" + << "{"; + else + os << "return true;" // One more pass. + << "}"; + + os << "case " << pass_ << ":" << endl + << "{"; + } + + os << "db.execute ("; + } + else + os << strlit (line_ + '\n') << endl; + + line_ = l; + } + + virtual void + post () + { + if (!first_) // Ignore empty statements. + os << strlit (line_) << ");"; + } + + private: + std::string line_; + bool first_; + bool empty_; + bool new_pass_; + unsigned short pass_; + unsigned short empty_passes_; // Number of preceding empty passes. + }; + } +} + +#endif // ODB_RELATIONAL_SCHEMA_SOURCE_HXX diff --git a/odb/odb/relational/schema.cxx b/odb/odb/relational/schema.cxx new file mode 100644 index 0000000..dd70bfa --- /dev/null +++ b/odb/odb/relational/schema.cxx @@ -0,0 +1,174 @@ +// file : odb/relational/schema.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <cassert> +#include <limits> +#include <sstream> + +#include <odb/relational/schema.hxx> +#include <odb/relational/generate.hxx> + +using namespace std; + +namespace relational +{ + namespace schema + { + void + generate_prologue () + { + instance<sql_file> file; + file->prologue (); + } + + void + generate_epilogue () + { + instance<sql_file> file; + file->epilogue (); + } + + void + generate_drop () + { + context ctx; + instance<sql_emitter> em; + emitter_ostream emos (*em); + + schema_format f (schema_format::sql); + + instance<drop_model> model (*em, emos, f); + instance<drop_table> table (*em, emos, f); + trav_rel::qnames names; + + model >> names >> table; + + // Pass 1 and 2. + // + for (unsigned short pass (1); pass < 3; ++pass) + { + model->pass (pass); + table->pass (pass); + + model->traverse (*ctx.model); + } + + if (ctx.model->version () != 0 && + !ctx.options.suppress_schema_version ()) + { + instance<version_table> vt (*em, emos, f); + vt->create_table (); + vt->drop (); + } + } + + void + generate_create () + { + context ctx; + instance<sql_emitter> em; + emitter_ostream emos (*em); + + schema_format f (schema_format::sql); + + instance<create_model> model (*em, emos, f); + instance<create_table> table (*em, emos, f); + trav_rel::qnames names; + + model >> names >> table; + + // Pass 1 and 2. + // + for (unsigned short pass (1); pass < 3; ++pass) + { + model->pass (pass); + table->pass (pass); + + model->traverse (*ctx.model); + } + + if (ctx.model->version () != 0 && + !ctx.options.suppress_schema_version ()) + { + instance<version_table> vt (*em, emos, f); + + if (ctx.options.omit_drop ()) + vt->create_table (); + + vt->create (ctx.model->version ()); + } + } + + void + generate_migrate_pre (sema_rel::changeset& cs) + { + context ctx; + instance<sql_emitter> em; + emitter_ostream emos (*em); + + schema_format f (schema_format::sql); + + instance<changeset_pre> changeset (*em, emos, f); + instance<create_table> ctable (*em, emos, f); + instance<alter_table_pre> atable (*em, emos, f); + trav_rel::qnames names; + + changeset >> names; + names >> ctable; + names >> atable; + + // Pass 1 and 2. + // + for (unsigned short pass (1); pass < 3; ++pass) + { + changeset->pass (pass); + ctable->pass (pass); + atable->pass (pass); + + changeset->traverse (cs); + } + + if (!ctx.options.suppress_schema_version ()) + { + instance<version_table> vt (*em, emos, f); + vt->migrate_pre (cs.version ()); + } + } + + void + generate_migrate_post (sema_rel::changeset& cs) + { + context ctx; + instance<sql_emitter> em; + emitter_ostream emos (*em); + + schema_format f (schema_format::sql); + + instance<changeset_post> changeset (*em, emos, f); + instance<drop_table> dtable (*em, emos, f); + instance<alter_table_post> atable (*em, emos, f); + trav_rel::qnames names; + + changeset >> names; + names >> dtable; + names >> atable; + + // Pass 1 and 2. + // + for (unsigned short pass (1); pass < 3; ++pass) + { + changeset->pass (pass); + dtable->pass (pass); + atable->pass (pass); + + changeset->traverse (cs); + } + + if (!ctx.options.suppress_schema_version ()) + { + instance<version_table> vt (*em, emos, f); + vt->migrate_post (); + } + } + } +} diff --git a/odb/odb/relational/schema.hxx b/odb/odb/relational/schema.hxx new file mode 100644 index 0000000..cd975b7 --- /dev/null +++ b/odb/odb/relational/schema.hxx @@ -0,0 +1,1606 @@ +// file : odb/relational/schema.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_RELATIONAL_SCHEMA_HXX +#define ODB_RELATIONAL_SCHEMA_HXX + +#include <set> +#include <vector> +#include <cassert> + +#include <odb/emitter.hxx> +#include <odb/relational/common.hxx> +#include <odb/relational/context.hxx> + +namespace relational +{ + namespace schema + { + typedef std::set<qname> table_set; + + struct common: virtual context + { + typedef ::emitter emitter_type; + + common (emitter_type& e, ostream& os, schema_format f) + : e_ (e), os_ (os), format_ (f) {} + + void + pre_statement () + { + e_.pre (); + diverge (os_); + } + + void + post_statement () + { + restore (); + e_.post (); + } + + emitter_type& + emitter () const + { + return e_; + } + + ostream& + stream () const + { + return os_; + } + + public: + // Find an entity corresponding to the drop node in alter_table. + // + template <typename T, typename D> + T& + find (D& d) + { + using sema_rel::model; + using sema_rel::changeset; + using sema_rel::table; + using sema_rel::alter_table; + + alter_table& at (dynamic_cast<alter_table&> (d.scope ())); + changeset& cs (dynamic_cast<changeset&> (at.scope ())); + model& bm (cs.base_model ()); + table* bt (bm.find<table> (at.name ())); + assert (bt != 0); + T* b (bt->find<T> (d.name ())); + assert (b != 0); + return *b; + } + + protected: + emitter_type& e_; + ostream& os_; + schema_format format_; + }; + + // + // Drop. + // + + // Only used in migration. + // + struct drop_column: trav_rel::drop_column, common + { + typedef drop_column base; + + drop_column (common const& c, bool* first = 0) + : common (c), + first_ (first != 0 ? *first : first_data_), + first_data_ (true) + { + } + + drop_column (drop_column const& c) + : root_context (), // @@ -Wextra + context (), + common (c), + first_ (&c.first_ != &c.first_data_ ? c.first_ : first_data_), + first_data_ (c.first_data_) + { + } + + virtual void + drop_header () + { + // By default ADD COLUMN though some databases use just ADD. + // + os << "DROP COLUMN "; + } + + virtual void + traverse (sema_rel::drop_column& dc) + { + if (first_) + first_ = false; + else + os << ","; + + os << endl + << " "; + drop_header (); + os << quote_id (dc.name ()); + } + + protected: + bool& first_; + bool first_data_; + }; + + // Normally only used in migration but some databases use it as a + // base to also drop foreign keys in schema. + // + struct drop_foreign_key: trav_rel::foreign_key, + trav_rel::drop_foreign_key, + trav_rel::add_foreign_key, // Override. + common + { + typedef drop_foreign_key base; + + // Schema constructor. + // + drop_foreign_key (common const& c, table_set& dropped, bool* first = 0) + : common (c), + dropped_ (&dropped), + first_ (first != 0 ? *first : first_data_), + first_data_ (true) + { + } + + // Migration constructor. + // + drop_foreign_key (common const& c, bool* first = 0) + : common (c), + dropped_ (0), + first_ (first != 0 ? *first : first_data_), + first_data_ (true) + { + } + + drop_foreign_key (drop_foreign_key const& c) + : root_context (), // @@ -Wextra + context (), + common (c), + dropped_ (c.dropped_), + first_ (&c.first_ != &c.first_data_ ? c.first_ : first_data_), + first_data_ (c.first_data_) + { + } + + virtual void + drop_header () + { + os << "DROP CONSTRAINT "; + } + + virtual void + traverse (sema_rel::foreign_key& fk) + { + // If the table which we reference is droped before us, then + // we need to drop the constraint first. Similarly, if the + // referenced table is not part if this model, then assume + // it is dropped before us. In migration we always do this + // first. + // + sema_rel::table& t (dynamic_cast<sema_rel::table&> (fk.scope ())); + + if (dropped_ != 0) + { + sema_rel::qname const& rt (fk.referenced_table ()); + sema_rel::model& m (dynamic_cast<sema_rel::model&> (t.scope ())); + + if (dropped_->find (rt) == dropped_->end () && + m.find (rt) != m.names_end ()) + return; + } + + drop (t, fk); + } + + virtual void + drop (sema_rel::table& t, sema_rel::foreign_key& fk) + { + // When generating schema we would need to check if the key exists. + // So this implementation will need to be customize on the per- + // database level. + // + pre_statement (); + + os << "ALTER TABLE " << quote_id (t.name ()) << endl + << " "; + drop_header (); + os << quote_id (fk.name ()) << endl; + + post_statement (); + } + + virtual void + traverse (sema_rel::drop_foreign_key& dfk) + { + if (first_) + first_ = false; + else + os << ","; + + os << endl; + drop (dfk); + } + + virtual void + drop (sema_rel::drop_foreign_key& dfk) + { + os << " "; + drop_header (); + os << quote_id (dfk.name ()); + } + + protected: + table_set* dropped_; + bool& first_; + bool first_data_; + }; + + // Currently only used in migration. + // + struct drop_index: trav_rel::drop_index, common + { + typedef drop_index base; + + enum index_type {unique, non_unique, all}; + + drop_index (common const& c, index_type t = all) + : common (c), type_ (t) {} + + virtual void + traverse (sema_rel::drop_index& di) + { + // Find the index we are dropping in the base model. + // + traverse (find<sema_rel::index> (di)); + } + + virtual void + traverse (sema_rel::index& in) + { + if (type_ == unique && + in.type ().find ("UNIQUE") == string::npos && + in.type ().find ("unique") == string::npos) + return; + + if (type_ == non_unique && ( + in.type ().find ("UNIQUE") != string::npos || + in.type ().find ("unique") != string::npos)) + return; + + pre_statement (); + drop (in); + post_statement (); + } + + virtual string + name (sema_rel::index& in) + { + return quote_id (in.name ()); + } + + virtual void + drop (sema_rel::index& in) + { + os << "DROP INDEX " << name (in) << endl; + } + + protected: + index_type type_; + }; + + struct drop_table: trav_rel::table, + trav_rel::drop_table, + trav_rel::add_table, // Override. + trav_rel::alter_table, // Override. + common + { + typedef drop_table base; + + drop_table (emitter_type& e, ostream& os, schema_format f) + : common (e, os, f) {} + + virtual void + drop (sema_rel::table& t, bool migration) + { + pre_statement (); + os << "DROP TABLE " << (migration ? "" : "IF EXISTS ") << + quote_id (t.name ()) << endl; + post_statement (); + } + + virtual void + delete_ (sema_rel::qname const& rtable, + sema_rel::qname const& dtable, + sema_rel::primary_key& rkey, + sema_rel::primary_key& dkey) + { + pre_statement (); + + // This might not be the most efficient way for every database. + // + os << "DELETE FROM " << quote_id (rtable) << endl + << " WHERE EXISTS (SELECT 1 FROM " << quote_id (dtable) << endl + << " WHERE "; + + for (size_t i (0); i != rkey.contains_size (); ++i) + { + if (i != 0) + os << endl + << " AND "; + + os << quote_id (rtable) << "." << + quote_id (rkey.contains_at (i).column ().name ()) << " = " << + quote_id (dtable) << "." << + quote_id (dkey.contains_at (i).column ().name ()); + } + + os << ")" << endl; + + post_statement (); + } + + virtual void + traverse (sema_rel::table& t, bool migration) + { + // By default drop foreign keys referencing tables that would + // have already been dropped on the first pass. + // + if (pass_ == 1) + { + // Drop constraints. In migration this is always done on pass 1. + // + if (migration) + { + instance<drop_foreign_key> dfk (*this); + trav_rel::unames n (*dfk); + names (t, n); + } + else + { + dropped_.insert (t.name ()); // Add it before to cover self-refs. + + instance<drop_foreign_key> dfk (*this, dropped_); + trav_rel::unames n (*dfk); + names (t, n); + } + } + else + { + if (migration && t.extra ()["kind"] == "polymorphic derived object") + { + // If we are dropping a polymorphic derived object, then we + // also have to clean the base tables. Note that this won't + // trigger cascade deletion since we have dropped all the + // keys on pass 1. But we still need to do this in the base + // to root order in order not to trigger other cascades. + // + using sema_rel::model; + using sema_rel::table; + using sema_rel::primary_key; + using sema_rel::foreign_key; + + model& m (dynamic_cast<model&> (t.scope ())); + + table* p (&t); + do + { + // The polymorphic link is the first primary key. + // + for (table::names_iterator i (p->names_begin ()); + i != p->names_end (); ++i) + { + if (foreign_key* fk = dynamic_cast<foreign_key*> ( + &i->nameable ())) + { + p = m.find<table> (fk->referenced_table ()); + assert (p != 0); // Base table should be there. + break; + } + } + + primary_key& rkey (*p->find<primary_key> ("")); + primary_key& dkey (*t.find<primary_key> ("")); + assert (rkey.contains_size () == dkey.contains_size ()); + delete_ (p->name (), t.name (), rkey, dkey); + } + while (p->extra ()["kind"] != "polymorphic root object"); + } + + drop (t, migration); + } + } + + virtual void + traverse (sema_rel::table& t) + { + traverse (t, false); + } + + virtual void + traverse (sema_rel::drop_table& dt) + { + using sema_rel::model; + using sema_rel::changeset; + using sema_rel::table; + + // Find the table we are dropping in the base model. + // + changeset& cs (dynamic_cast<changeset&> (dt.scope ())); + model& bm (cs.base_model ()); + table* t (bm.find<table> (dt.name ())); + assert (t != 0); + traverse (*t, true); + } + + using add_table::traverse; // Unhide. + using alter_table::traverse; // Unhide. + + using table::names; + + void + pass (unsigned short p) + { + pass_ = p; + } + + protected: + unsigned short pass_; + table_set dropped_; + }; + + struct drop_model: trav_rel::model, common + { + typedef drop_model base; + + drop_model (emitter_type& e, ostream& os, schema_format f) + : common (e, os, f) + { + } + + virtual void + traverse (sema_rel::model& m) + { + // Traverse named entities in the reverse order. This way we + // drop them in the order opposite to creating. + // + for (sema_rel::model::names_iterator begin (m.names_begin ()), + end (m.names_end ()); begin != end;) + dispatch (*--end); + } + + void + pass (unsigned short p) + { + pass_ = p; + } + + protected: + unsigned short pass_; + }; + + // + // Create. + // + + struct create_column: trav_rel::column, + trav_rel::add_column, + trav_rel::alter_column, // Override. + common + { + typedef create_column base; + + create_column (common const& c, + bool override_null = true, + bool* first = 0) + : common (c), + override_null_ (override_null), + first_ (first != 0 ? *first : first_data_), + first_data_ (true) + { + } + + create_column (create_column const& c) + : root_context (), // @@ -Wextra + context (), + common (c), + override_null_ (c.override_null_), + first_ (&c.first_ != &c.first_data_ ? c.first_ : first_data_), + first_data_ (c.first_data_) + { + } + + virtual void + traverse (sema_rel::column& c) + { + if (first_) + first_ = false; + else + os << ","; + + os << endl + << " "; + create (c); + } + + virtual void + add_header () + { + // By default ADD COLUMN though some databases use just ADD. + // + os << "ADD COLUMN "; + } + + virtual void + traverse (sema_rel::add_column& ac) + { + if (first_) + first_ = false; + else + os << ","; + + os << endl + << " "; + add_header (); + create (ac); + } + + virtual void + create (sema_rel::column& c) + { + using sema_rel::column; + + // See if this column is (part of) a primary key. + // + sema_rel::primary_key* pk (0); + + for (column::contained_iterator i (c.contained_begin ()); + i != c.contained_end (); + ++i) + { + if ((pk = dynamic_cast<sema_rel::primary_key*> (&i->key ()))) + break; + } + + os << quote_id (c.name ()) << " "; + + type (c, pk != 0 && pk->auto_ ()); + constraints (c, pk); + + if (!c.options ().empty ()) + os << " " << c.options (); + } + + virtual void + constraints (sema_rel::column& c, sema_rel::primary_key* pk) + { + null (c); + + if (!c.default_ ().empty ()) + os << " DEFAULT " << c.default_ (); + + // If this is a single-column primary key, generate it inline. + // + if (pk != 0 && pk->contains_size () == 1) + primary_key (); + + if (pk != 0 && pk->auto_ ()) + auto_ (*pk); + } + + virtual void + type (sema_rel::column& c, bool /*auto*/) + { + os << c.type (); + } + + virtual void + null (sema_rel::column& c) + { + bool n (c.null ()); + + // If we are adding a new column that doesn't allow NULL nor has + // a default value, add it as NULL. Later, after migration, we + // will convert it to NOT NULL. + // + if (override_null_ && c.is_a<sema_rel::add_column> () && + !n && c.default_ ().empty ()) + n = true; + + // Specify both cases explicitly for better readability, + // especially in ALTER COLUMN clauses. + // + os << (n ? " NULL" : " NOT NULL"); + } + + virtual void + primary_key () + { + os << " PRIMARY KEY"; + } + + virtual void + auto_ (sema_rel::primary_key&) + { + } + + protected: + bool override_null_; // Override NOT NULL in add_column. + bool& first_; + bool first_data_; + bool add_; + }; + + struct create_primary_key: trav_rel::primary_key, common + { + typedef create_primary_key base; + + create_primary_key (common const& c): common (c) {} + + virtual void + traverse (sema_rel::primary_key& pk) + { + // Single-column primary keys are generated inline in the + // column declaration. + // + if (pk.contains_size () == 1) + return; + + // We will always follow a column. + // + os << "," << endl; + + create (pk); + } + + virtual void + create (sema_rel::primary_key& pk) + { + using sema_rel::primary_key; + + // By default we create unnamed primary key constraint. + // + + os << " PRIMARY KEY ("; + + for (primary_key::contains_iterator i (pk.contains_begin ()); + i != pk.contains_end (); + ++i) + { + if (i != pk.contains_begin ()) + os << "," << endl + << " "; + + os << quote_id (i->column ().name ()); + } + + os << ")"; + } + }; + + struct create_foreign_key: trav_rel::foreign_key, + trav_rel::add_foreign_key, + common + { + typedef create_foreign_key base; + + // Schema constructor, pass 1. + // + create_foreign_key (common const& c, table_set& created, bool* first = 0) + : common (c), + created_ (&created), + first_ (first != 0 ? *first : first_data_), + first_data_ (true) + { + } + + // Schema constructor, pass 2 and migration constructor. + // + create_foreign_key (common const& c, bool* first = 0) + : common (c), + created_ (0), + first_ (first != 0 ? *first : first_data_), + first_data_ (true) + { + } + + create_foreign_key (create_foreign_key const& c) + : root_context (), // @@ -Wextra + context (), + common (c), + created_ (c.created_), + first_ (&c.first_ != &c.first_data_ ? c.first_ : first_data_), + first_data_ (c.first_data_) + { + } + + virtual void + traverse (sema_rel::foreign_key& fk) + { + if (created_ != 0) + { + // Pass 1. + // + // If the referenced table has already been defined, do the + // foreign key definition in the table definition. Otherwise + // postpone it until pass 2 where we do it via ALTER TABLE. + // + if (created_->find (fk.referenced_table ()) != created_->end ()) + { + traverse_create (fk); + fk.set (db.string () + "-fk-defined", true); // Mark it as defined. + } + } + else + { + // Pass 2. + // + if (!fk.count (db.string () + "-fk-defined")) + traverse_add (fk); + } + } + + virtual void + traverse_create (sema_rel::foreign_key& fk) + { + if (first_) + first_ = false; + else + os << ","; + + os << endl + << " CONSTRAINT "; + create (fk); + } + + virtual void + traverse_add (sema_rel::foreign_key& fk) + { + if (first_) + first_ = false; + else + os << ","; + + os << endl; + add (fk); + } + + virtual void + traverse (sema_rel::add_foreign_key& afk) + { + traverse_add (afk); + } + + virtual void + add_header () + { + os << "ADD CONSTRAINT "; + } + + virtual void + add (sema_rel::foreign_key& fk) + { + os << " "; + add_header (); + create (fk); + } + + virtual void + create (sema_rel::foreign_key& fk) + { + using sema_rel::foreign_key; + + os << name (fk) << endl + << " FOREIGN KEY ("; + + for (foreign_key::contains_iterator i (fk.contains_begin ()); + i != fk.contains_end (); + ++i) + { + if (i != fk.contains_begin ()) + os << "," << endl + << " "; + + os << quote_id (i->column ().name ()); + } + + string tn (table_name (fk)); + string tn_pad (tn.size (), ' '); + + os << ")" << endl + << " REFERENCES " << tn << " ("; + + foreign_key::columns const& refs (fk.referenced_columns ()); + for (foreign_key::columns::const_iterator i (refs.begin ()); + i != refs.end (); + ++i) + { + if (i != refs.begin ()) + os << "," << endl + << " " << tn_pad; + + os << quote_id (*i); + } + + os << ")"; + + if (fk.on_delete () != foreign_key::no_action) + on_delete (fk.on_delete ()); + + if (!fk.not_deferrable ()) + deferrable (fk.deferrable ()); + } + + virtual string + name (sema_rel::foreign_key& fk) + { + return quote_id (fk.name ()); + } + + virtual string + table_name (sema_rel::foreign_key& fk) + { + return quote_id (fk.referenced_table ()); + } + + virtual void + on_delete (sema_rel::foreign_key::action_type a) + { + using sema_rel::foreign_key; + + switch (a) + { + case foreign_key::no_action: + break; + case foreign_key::cascade: + { + os << endl + << " ON DELETE CASCADE"; + break; + } + case foreign_key::set_null: + { + os << endl + << " ON DELETE SET NULL"; + break; + } + } + } + + virtual void + deferrable (sema_rel::deferrable d) + { + os << endl + << " DEFERRABLE INITIALLY " << d; + } + + protected: + table_set* created_; + bool& first_; + bool first_data_; + }; + + struct create_index: trav_rel::index, common + { + typedef create_index base; + + enum index_type {unique, non_unique, all}; + + create_index (common const& c, index_type t = all) + : common (c), type_ (t) {} + + virtual void + traverse (sema_rel::index& in) + { + if (type_ == unique && + in.type ().find ("UNIQUE") == string::npos && + in.type ().find ("unique") == string::npos) + return; + + if (type_ == non_unique && ( + in.type ().find ("UNIQUE") != string::npos || + in.type ().find ("unique") != string::npos)) + return; + + pre_statement (); + create (in); + post_statement (); + } + + virtual string + name (sema_rel::index& in) + { + return quote_id (in.name ()); + } + + virtual string + table_name (sema_rel::index& in) + { + return quote_id (static_cast<sema_rel::table&> (in.scope ()).name ()); + } + + virtual void + columns (sema_rel::index& in) + { + using sema_rel::index; + + for (index::contains_iterator i (in.contains_begin ()); + i != in.contains_end (); + ++i) + { + if (in.contains_size () > 1) + { + if (i != in.contains_begin ()) + os << ","; + + os << endl + << " "; + } + + os << quote_id (i->column ().name ()); + + if (!i->options ().empty ()) + os << ' ' << i->options (); + } + } + + virtual void + create (sema_rel::index& in) + { + // Default implementation that ignores the method. + // + os << "CREATE "; + + if (!in.type ().empty ()) + os << in.type () << ' '; + + os << "INDEX " << name (in) << endl + << " ON " << table_name (in) << " ("; + + columns (in); + + os << ")" << endl; + + if (!in.options ().empty ()) + os << ' ' << in.options () << endl; + } + + protected: + index_type type_; + }; + + struct create_table: trav_rel::table, + trav_rel::alter_table, // Override. + common + { + typedef create_table base; + + using trav_rel::table::names; + + create_table (emitter_type& e, ostream& os, schema_format f) + : common (e, os, f) {} + + virtual void + create_pre (sema_rel::qname const& table) + { + os << "CREATE TABLE " << quote_id (table) << " ("; + } + + virtual void + create_post (sema_rel::table& t) + { + os << ")" << endl; + + if (!t.options ().empty ()) + os << " " << t.options () << endl; + } + + virtual void + create (sema_rel::table& t) + { + pre_statement (); + create_pre (t.name ()); + + instance<create_column> c (*this); + instance<create_primary_key> pk (*this); + + // We will always follow a column, so set first to false. + // + bool f (false); // (Im)perfect forwarding. + bool* pf (&f); // (Im)perfect forwarding. + instance<create_foreign_key> fk (*this, created_, pf); + + trav_rel::unames n; + n >> c; + n >> pk; + n >> fk; + + names (t, n); + + create_post (t); + post_statement (); + + // Create indexes. + // + { + instance<create_index> in (*this); + trav_rel::unames n (*in); + names (t, n); + } + } + + // See if there are any undefined foreign keys that we need to + // add with ALTER TABLE. + // + bool + check_undefined_fk (sema_rel::table& t) + { + for (sema_rel::table::names_iterator i (t.names_begin ()); + i != t.names_end (); ++i) + { + if (i->nameable ().is_a<sema_rel::foreign_key> () && + !i->nameable ().count (db.string () + "-fk-defined")) + return true; + } + return false; + } + + virtual void + traverse (sema_rel::table& t) + { + // By default add foreign keys referencing tables that haven't + // yet been defined on the second pass. + // + if (pass_ == 1) + { + // In migration we always add foreign keys on pass 2. + // + if (!t.is_a<sema_rel::add_table> ()) + created_.insert (t.name ()); // Add it before to cover self-refs. + + create (t); + } + else + { + // Add undefined foreign keys. + // + if (check_undefined_fk (t)) + { + pre_statement (); + os << "ALTER TABLE " << quote_id (t.name ()); + + instance<create_foreign_key> cfk (*this); + trav_rel::unames n (*cfk); + names (t, n); + os << endl; + + post_statement (); + } + } + } + + void + pass (unsigned short p) + { + pass_ = p; + } + + protected: + unsigned short pass_; + table_set created_; + }; + + struct create_model: trav_rel::model, common + { + typedef create_model base; + + create_model (emitter_type& e, ostream& os, schema_format f) + : common (e, os, f) {} + + void + pass (unsigned short p) + { + pass_ = p; + } + + protected: + unsigned short pass_; + }; + + // + // Alter. + // + + struct alter_column: trav_rel::alter_column, + trav_rel::add_column, + common + { + typedef alter_column base; + + alter_column (common const& c, bool pre, bool* first = 0) + : common (c), + pre_ (pre), + first_ (first != 0 ? *first : first_data_), + first_data_ (true), + fl_ (false), + def_ (c, fl_) + { + } + + alter_column (alter_column const& c) + : root_context (), // @@ -Wextra + context (), + common (c), + pre_ (c.pre_), + first_ (&c.first_ != &c.first_data_ ? c.first_ : first_data_), + first_data_ (c.first_data_), + fl_ (false), + def_ (c, fl_) + { + } + + virtual void + alter_header () + { + os << "ALTER COLUMN "; + } + + virtual void + alter (sema_rel::column& c) + { + // By default use the whole definition. + // + def_->create (c); + } + + virtual void + traverse (sema_rel::column& c) + { + // Relax (NULL) in pre and tighten (NOT NULL) in post. + // + if (pre_ != c.null ()) + return; + + if (first_) + first_ = false; + else + os << ","; + + os << endl + << " "; + alter_header (); + alter (c); + } + + virtual void + traverse (sema_rel::alter_column& ac) + { + assert (ac.null_altered ()); + traverse (static_cast<sema_rel::column&> (ac)); + } + + virtual void + traverse (sema_rel::add_column& ac) + { + // We initially add NOT NULL columns without default values as + // NULL. Now, after the migration, we convert them to NOT NULL. + // + if (!ac.null () && ac.default_ ().empty ()) + traverse (static_cast<sema_rel::column&> (ac)); + } + + protected: + bool pre_; + bool& first_; + bool first_data_; + bool fl_; // (Im)perfect forwarding. + instance<create_column> def_; + }; + + struct alter_table_common: trav_rel::alter_table, common + { + alter_table_common (emitter_type& e, ostream& os, schema_format f) + : common (e, os, f) {} + + template <typename T> + T* + check (sema_rel::alter_table& at) + { + for (sema_rel::alter_table::names_iterator i (at.names_begin ()); + i != at.names_end (); ++i) + { + if (T* x = dynamic_cast<T*> (&i->nameable ())) + return x; + } + return 0; + } + + sema_rel::column* + check_alter_column_null (sema_rel::alter_table& at, bool v) + { + for (sema_rel::alter_table::names_iterator i (at.names_begin ()); + i != at.names_end (); ++i) + { + using sema_rel::add_column; + using sema_rel::alter_column; + + if (alter_column* ac = dynamic_cast<alter_column*> (&i->nameable ())) + { + if (ac->null_altered () && ac->null () == v) + return ac; + } + + // If we are testing for NOT NULL, also look for new columns that + // we initially add as NULL and later convert to NOT NULL. + // + if (!v) + { + if (add_column* ac = dynamic_cast<add_column*> (&i->nameable ())) + { + if (!ac->null () && ac->default_ ().empty ()) + return ac; + } + } + } + return 0; + } + + void + pass (unsigned short p) + { + pass_ = p; + } + + protected: + unsigned short pass_; + }; + + struct alter_table_pre: alter_table_common + { + typedef alter_table_pre base; + + alter_table_pre (emitter_type& e, ostream& os, schema_format f) + : alter_table_common (e, os, f) {} + + // Check if there will be any clauses in ALTER TABLE. + // + using alter_table_common::check; + + virtual bool + check (sema_rel::alter_table& at) + { + // If changing the below test, make sure to also update tests + // in database-specific code. + // + return + check<sema_rel::drop_foreign_key> (at) || + check<sema_rel::add_column> (at) || + check_alter_column_null (at, true); + } + + virtual void + alter (sema_rel::alter_table& at) + { + // By default we generate all the alterations in a single ALTER TABLE + // statement. Quite a few databases don't support this. + // + pre_statement (); + os << "ALTER TABLE " << quote_id (at.name ()); + + bool f (true); // Shared first flag. + bool* pf (&f); // (Im)perfect forwarding. + bool tl (true); // (Im)perfect forwarding. + instance<create_column> cc (*this, tl, pf); + instance<alter_column> ac (*this, tl, pf); + instance<drop_foreign_key> dfk (*this, pf); + trav_rel::unames n; + n >> cc; + n >> ac; + n >> dfk; + names (at, n); + os << endl; + + post_statement (); + } + + virtual void + traverse (sema_rel::alter_table& at) + { + if (pass_ == 1) + { + // Drop unique indexes. + // + { + drop_index::index_type it (drop_index::unique); + instance<drop_index> in (*this, it); + trav_rel::unames n (*in); + names (at, n); + } + + if (check (at)) + alter (at); + } + else + { + // Add non-unique indexes. + // + { + create_index::index_type it (create_index::non_unique); + instance<create_index> in (*this, it); + trav_rel::unames n (*in); + names (at, n); + } + } + } + }; + + struct changeset_pre: trav_rel::changeset, common + { + typedef changeset_pre base; + + changeset_pre (emitter_type& e, ostream& os, schema_format f) + : common (e, os, f) {} + + void + pass (unsigned short p) + { + pass_ = p; + } + + protected: + unsigned short pass_; + }; + + struct alter_table_post: alter_table_common + { + typedef alter_table_post base; + + alter_table_post (emitter_type& e, ostream& os, schema_format f) + : alter_table_common (e, os, f) {} + + // Check if there will be any clauses in ALTER TABLE. + // + using alter_table_common::check; + + virtual bool + check (sema_rel::alter_table& at) + { + // If changing the below test, make sure to also update tests + // in database-specific code. + // + return + check<sema_rel::add_foreign_key> (at) || + check<sema_rel::drop_column> (at) || + check_alter_column_null (at, false); + } + + virtual void + alter (sema_rel::alter_table& at) + { + // By default we generate all the alterations in a single ALTER TABLE + // statement. Quite a few databases don't support this. + // + pre_statement (); + os << "ALTER TABLE " << quote_id (at.name ()); + + bool f (true); // Shared first flag. + bool* pf (&f); // (Im)perfect forwarding. + bool fl (false); // (Im)perfect forwarding. + instance<drop_column> dc (*this, pf); + instance<alter_column> ac (*this, fl, pf); + instance<create_foreign_key> fk (*this, pf); + + trav_rel::unames n; + n >> dc; + n >> ac; + n >> fk; + names (at, n); + os << endl; + + post_statement (); + } + + virtual void + traverse (sema_rel::alter_table& at) + { + if (pass_ == 1) + { + // Drop non-unique indexes. + // + { + drop_index::index_type it (drop_index::non_unique); + instance<drop_index> in (*this, it); + trav_rel::unames n (*in); + names (at, n); + } + } + else + { + if (check (at)) + alter (at); + + // Add unique indexes. + // + { + create_index::index_type it (create_index::unique); + instance<create_index> in (*this, it); + trav_rel::unames n (*in); + names (at, n); + } + } + } + }; + + struct changeset_post: trav_rel::changeset, common + { + typedef changeset_post base; + + changeset_post (emitter_type& e, ostream& os, schema_format f) + : common (e, os, f) {} + + virtual void + traverse (sema_rel::changeset& m) + { + // Traverse named entities in the reverse order. This way we + // drop them in the order opposite to creating. + // + for (sema_rel::changeset::names_iterator begin (m.names_begin ()), + end (m.names_end ()); begin != end;) + dispatch (*--end); + } + + void + pass (unsigned short p) + { + pass_ = p; + } + + protected: + unsigned short pass_; + }; + + // + // Schema version table. + // + + struct version_table: common + { + typedef version_table base; + + version_table (emitter_type& e, ostream& os, schema_format f) + : common (e, os, f), + table_ (options.schema_version_table ()[db]), + qt_ (quote_id (table_)), + qs_ (quote_string (options.schema_name ()[db])), + qn_ (quote_id ("name")), + qv_ (quote_id ("version")), + qm_ (quote_id ("migration")) + { + } + + // Create the version table if it doesn't exist. + // + virtual void + create_table () {} + + // Remove the version entry. Called after the DROP statements. + // + virtual void + drop () + { + pre_statement (); + + os << "DELETE FROM " << qt_ << endl + << " WHERE " << qn_ << " = " << qs_ << endl; + + post_statement (); + } + + // Set the version. Called after the CREATE statements. + // + virtual void + create (sema_rel::version) {} + + // Set the version and migration state to true. Called after + // the pre migration statements. + // + virtual void + migrate_pre (sema_rel::version v) + { + pre_statement (); + + os << "UPDATE " << qt_ << endl + << " SET " << qv_ << " = " << v << ", " << qm_ << " = 1" << endl + << " WHERE " << qn_ << " = " << qs_ << endl; + + post_statement (); + } + + // Set migration state to false. Called after the post migration + // statements. + // + virtual void + migrate_post () + { + pre_statement (); + + os << "UPDATE " << qt_ << endl + << " SET " << qm_ << " = 0" << endl + << " WHERE " << qn_ << " = " << qs_ << endl; + + post_statement (); + } + + protected: + sema_rel::qname table_; + string qt_; // Quoted table. + string qs_; // Quoted schema name string. + string qn_; // Quoted name column. + string qv_; // Quoted version column. + string qm_; // Quoted migration column. + }; + + // + // SQL output. + // + + struct sql_emitter: emitter, virtual context + { + typedef sql_emitter base; + + virtual void + pre () + { + first_ = true; + } + + virtual void + line (const std::string& l) + { + if (first_ && !l.empty ()) + first_ = false; + else + os << endl; + + os << l; + } + + virtual void + post () + { + if (!first_) // Ignore empty statements. + os << ';' << endl + << endl; + } + + protected: + bool first_; + }; + + struct sql_file: virtual context + { + typedef sql_file base; + + virtual void + prologue () + { + } + + virtual void + epilogue () + { + } + }; + } +} + +#endif // ODB_RELATIONAL_SCHEMA_HXX diff --git a/odb/odb/relational/source.cxx b/odb/odb/relational/source.cxx new file mode 100644 index 0000000..e00626a --- /dev/null +++ b/odb/odb/relational/source.cxx @@ -0,0 +1,6343 @@ +// file : odb/relational/source.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <map> + +#include <odb/gcc.hxx> + +#include <odb/lookup.hxx> +#include <odb/cxx-lexer.hxx> + +#include <odb/relational/source.hxx> +#include <odb/relational/generate.hxx> + +using namespace std; + +void relational::source::class_:: +traverse_object (type& c) +{ + using semantics::data_member; + + data_member_path* id (id_member (c)); + data_member* idf (id ? id->front () : 0); + data_member* idb (id ? id->back () : 0); + bool auto_id (id && auto_ (*id)); + bool base_id (id && &idf->scope () != &c); // Comes from base. + + data_member* opt (optimistic (c)); + + type* poly_root (polymorphic (c)); + bool poly (poly_root != 0); + bool poly_derived (poly && poly_root != &c); + type* poly_base (poly_derived ? &polymorphic_base (c) : 0); + size_t poly_depth (poly_derived ? polymorphic_depth (c) : 1); + data_member* discriminator (poly ? context::discriminator (*poly_root) : 0); + + bool abst (abstract (c)); + bool reuse_abst (abst && !poly); + bool readonly (context::readonly (c)); + + bool grow (false); + bool grow_id (false); + + if (generate_grow) + { + grow = context::grow (c); + grow_id = (id ? context::grow (*idb) : false) || + (opt ? context::grow (*opt) : false); + } + + column_count_type const& cc (column_count (c)); + bool versioned (context::versioned (c)); + + // Schema name as a string literal or empty. + // + string schema_name (options.schema_name ()[db]); + if (!schema_name.empty ()) + schema_name = strlit (schema_name); + + string const& type (class_fq_name (c)); + string traits ("access::object_traits_impl< " + type + ", id_" + + db.string () + " >"); + + user_sections& uss (c.get<user_sections> ("user-sections")); + user_sections* buss (poly_base != 0 + ? &poly_base->get<user_sections> ("user-sections") + : 0); + + os << "// " << class_name (c) << endl + << "//" << endl + << endl; + + object_extra (c); + + // + // Query (abstract and concrete). + // + + // query_columns + // + if (options.generate_query ()) + query_columns_type_->traverse (c); + + // Statement cache (definition). + // + if (!reuse_abst && id != 0) + { + bool sections (false); + bool containers (has_a (c, test_container)); + + os << "struct " << traits << "::extra_statement_cache_type" + << "{"; + + instance<container_cache_members> cm; + cm->traverse (c); + + if (containers) + os << endl; + + instance<section_cache_members> sm; + for (user_sections::iterator i (uss.begin ()); i != uss.end (); ++i) + { + // Skip the special version update section in reuse inheritance (we + // always treat it as abstract). + // + if (i->special == user_section::special_version && !poly) + continue; + + // Generate an entry for a readonly section in optimistic + // polymorphic root since it can be overridden and we may + // need to update the version. + // + if (!i->empty () || (poly && i->optimistic ())) + { + sm->traverse (*i); + sections = true; + } + } + + if (sections) + os << endl; + + os << "extra_statement_cache_type (" << endl + << db << "::connection&" << (containers || sections ? " c" : "") << + "," << endl + << "image_type&" << (sections ? " im" : "") << "," << endl + << "id_image_type&" << (sections ? " idim" : "") << "," << endl + << db << "::binding&" << (containers || sections ? " id" : "") << + "," << endl + << db << "::binding&" << (sections ? " idv" : ""); + + extra_statement_cache_extra_args (containers, sections); + + os << ")"; + + instance<container_cache_init_members> cim; + cim->traverse (c); + + instance<section_cache_init_members> sim (!containers); + for (user_sections::iterator i (uss.begin ()); i != uss.end (); ++i) + { + if (i->special == user_section::special_version && !poly) + continue; + + if (!i->empty () || (poly && i->optimistic ())) + sim->traverse (*i); + } + + os << "{" + << "}" + << "};"; + } + + // + // Containers (abstract and concrete). + // + + { + instance<container_traits> t (c); + t->traverse (c); + } + + // + // Sections (abstract and concrete). + // + + for (user_sections::iterator i (uss.begin ()); i != uss.end (); ++i) + { + instance<section_traits> t (c); + t->traverse (*i); + } + + // + // Functions (abstract and concrete). + // + + // id(), version() + // + if (!poly_derived && id != 0 && !base_id) + { + // id (id_image_type) + // + if (auto_id) + { + os << traits << "::id_type" << endl + << traits << "::" << endl + << "id (const id_image_type& i)" + << "{" + << db << "::database* db (0);" + << "ODB_POTENTIALLY_UNUSED (db);" + << endl + << "id_type id;"; + init_id_value_member_id_image_->traverse (*idb); + os << "return id;" + << "}"; + } + + // id (image) + // + if (options.generate_query ()) + { + os << traits << "::id_type" << endl + << traits << "::" << endl + << "id (const image_type& i)" + << "{" + << db << "::database* db (0);" + << "ODB_POTENTIALLY_UNUSED (db);" + << endl + << "id_type id;"; + + // Handle nested id. + // + if (id->size () > 1) + { + string var; + + for (data_member_path::const_iterator i (id->begin ()); + i != id->end (); + ++i) + { + // The same logic as in member_base. + // + if (!var.empty ()) + var += "value."; // Composite. + + string const& name ((*i)->name ()); + var += name; + + if (name[name.size () - 1] != '_') + var += '_'; + } + + instance<init_value_member> t ("id", var); + t->traverse (*idb); + } + else + init_id_value_member_->traverse (*idb); + + os << "return id;" + << "}"; + } + + // version (image) + // + if (opt != 0) + { + os << traits << "::version_type" << endl + << traits << "::" << endl + << "version (const image_type& i)" + << "{" + << "version_type v;"; + init_version_value_member_->traverse (*opt); + os << "return v;" + << "}"; + } + } + + // discriminator(image) + // + if (poly && !poly_derived) + { + os << traits << "::discriminator_type" << endl + << traits << "::" << endl + << "discriminator (const image_type& i)" + << "{" + << db << "::database* db (0);" + << "ODB_POTENTIALLY_UNUSED (db);" + << endl + << "discriminator_type d;"; + init_discriminator_value_member_->traverse (*discriminator); + os << "return d;" + << "}"; + } + + // grow () + // + if (generate_grow) + { + os << "bool " << traits << "::" << endl + << "grow (image_type& i," << endl + << truncated_vector << " t"; + + if (versioned) + os << "," << endl + << "const schema_version_migration& svm"; + + if (poly_derived) + os << "," << endl + << "std::size_t d"; + + os << ")" + << "{" + << "ODB_POTENTIALLY_UNUSED (i);" + << "ODB_POTENTIALLY_UNUSED (t);"; + + if (versioned) + os << "ODB_POTENTIALLY_UNUSED (svm);"; + + os << endl + << "bool grew (false);" + << endl; + + index_ = 0; + + if (poly_derived) + { + // Select column count for this class. + // + size_t cols (cc.total - cc.id); + + os << "// " << class_name (*poly_base) << " base" << endl + << "//" << endl + << "if (--d != 0)" + << "{" + << "if (base_traits::grow (*i.base, " << + "t + " << cols << "UL" << + (context::versioned (*poly_base) ? ", svm" : "") << + (poly_base != poly_root ? ", d" : "") << "))" << endl + << "i.base->version++;" + << "}"; + } + else + inherits (c, grow_base_inherits_); + + names (c, grow_member_names_); + + os << "return grew;" + << "}"; + } + + // bind (image_type) + // + os << "void " << traits << "::" << endl + << "bind (" << bind_vector << " b," << endl; + + // If we are a derived type in a polymorphic hierarchy, then + // we get the external id binding. + // + if (poly_derived) + os << "const " << bind_vector << " id," << endl + << "std::size_t id_size," << endl; + + os << "image_type& i," << endl + << db << "::statement_kind sk"; + + if (versioned) + os << "," << endl + << "const schema_version_migration& svm"; + + os << ")" + << "{" + << "ODB_POTENTIALLY_UNUSED (sk);"; + + if (versioned) + os << "ODB_POTENTIALLY_UNUSED (svm);"; + + os << endl + << "using namespace " << db << ";" + << endl; + + if (readonly) + os << "assert (sk != statement_update);" + << endl; + + os << "std::size_t n (0);" + << endl; + + if (poly_derived) + { + // The id reference comes first in the insert statement. + // + os << "// " << idf->name () << endl + << "//" << endl + << "if (sk == statement_insert)" + << "{" + << "if (id != 0)" << endl + << "std::memcpy (&b[n], id, id_size * sizeof (id[0]));" + << "n += id_size;" // Not in if for "id unchanged" optimization. + << "}"; + } + else + inherits (c, bind_base_inherits_); + + names (c, bind_member_names_); + + if (poly_derived) + { + // The id reference comes last in the update statement. + // + if (!readonly) + os << "// " << idf->name () << endl + << "//" << endl + << "if (sk == statement_update)" + << "{" + << "if (id != 0)" << endl + << "std::memcpy (&b[n], id, id_size * sizeof (id[0]));" + << "n += id_size;" // Not in if for "id unchanged" optimization. + << "}"; + + // Bind the image chain for the select statement. Seeing that + // this is the last statement in the function, we don't care + // about updating n. + // + os << "// " << class_name (*poly_base) << " base" << endl + << "//" << endl + << "if (sk == statement_select)" << endl + << "base_traits::bind (b + n, "; + + if (poly_base != poly_root) + os << "id, id_size, "; + + os << "*i.base, sk" << + (context::versioned (*poly_base) ? ", svm" : "") << ");"; + } + + os << "}"; + + // bind (id_image_type) + // + if (!poly_derived && id != 0 && !base_id) + { + os << "void " << traits << "::" << endl + << "bind (" << bind_vector << " b, id_image_type& i" << + (opt != 0 ? ", bool bv" : "") << ")" + << "{" + << "std::size_t n (0);"; + + if (composite_wrapper (utype (*id))) + os << db << "::statement_kind sk (" << db << "::statement_select);"; + + bind_id_member_->traverse (*idb); + + if (opt != 0) + { + os << "if (bv)" + << "{" + << "n += " << column_count (c).id << ";" + << endl; + + bind_version_member_->traverse (*opt); + os << "}"; + } + + os << "}"; + } + + // init (image, object) + // + os << (generate_grow ? "bool " : "void ") << traits << "::" << endl + << "init (image_type& i," << endl + << "const object_type& o," << endl + << db << "::statement_kind sk"; + + if (versioned) + os << "," << endl + << "const schema_version_migration& svm"; + + os << ")" + << "{" + << "ODB_POTENTIALLY_UNUSED (i);" + << "ODB_POTENTIALLY_UNUSED (o);" + << "ODB_POTENTIALLY_UNUSED (sk);"; + + if (versioned) + os << "ODB_POTENTIALLY_UNUSED (svm);"; + + os << endl + << "using namespace " << db << ";" + << endl; + + if (readonly) + os << "assert (sk != statement_update);" + << endl; + + init_image_pre (c); + + if (generate_grow) + os << "bool grew (false);" + << endl; + + if (!poly_derived) + inherits (c, init_image_base_inherits_); + + names (c, init_image_member_names_); + + if (generate_grow) + os << "return grew;"; + + os << "}"; + + // init (object, image) + // + os << "void " << traits << "::" << endl + << "init (object_type& o," << endl + << "const image_type& i," << endl + << "database* db"; + + if (versioned) + os << "," << endl + << "const schema_version_migration& svm"; + + if (poly_derived) + os << "," << endl + << "std::size_t d"; + + os << ")" + << "{" + << "ODB_POTENTIALLY_UNUSED (o);" + << "ODB_POTENTIALLY_UNUSED (i);" + << "ODB_POTENTIALLY_UNUSED (db);"; + + if (versioned) + os << "ODB_POTENTIALLY_UNUSED (svm);"; + + os << endl; + + if (poly_derived) + { + os << "// " << class_name (*poly_base) << " base" << endl + << "//" << endl + << "if (--d != 0)" << endl + << "base_traits::init (o, *i.base, db" << + (context::versioned (*poly_base) ? ", svm" : "") << + (poly_base != poly_root ? ", d" : "") << ");" + << endl; + } + else + inherits (c, init_value_base_inherits_); + + names (c, init_value_member_names_); + + os << "}"; + + // init (id_image, id) + // + if (id != 0 && !base_id) + { + os << "void " << traits << "::" << endl + << "init (id_image_type& i, const id_type& id" << + (opt != 0 ? ", const version_type* v" : "") << ")" + << "{"; + + if (grow_id) + os << "bool grew (false);"; + + if (composite_wrapper (utype (*id))) + os << db << "::statement_kind sk (" << db << "::statement_select);"; + + init_id_image_member_->traverse (*idb); + + if (opt != 0) + { + // Here we rely on the fact that init_image_member + // always wraps the statements in a block. + // + os << "if (v != 0)"; + init_version_image_member_->traverse (*opt); + } + + if (grow_id) + os << "if (grew)" << endl + << "i.version++;"; + + os << "}"; + } + + // The rest does not apply to reuse-abstract objects. + // + if (reuse_abst) + return; + + // + // Containers (concrete). + // + + // + // Sections (concrete). + // + + // Polymorphic map. + // + if (poly) + { + if (!poly_derived) + os << traits << "::map_type*" << endl + << traits << "::map;" + << endl; + + // Sections. Do we have any new sections or overrides? + // + bool sections ( + uss.count (user_sections::count_new | + user_sections::count_override | + user_sections::count_load | + user_sections::count_update | + user_sections::count_special_version | + (poly ? user_sections::count_optimistic : 0)) != 0); + if (sections) + { + string const& fn (flat_name (type)); + + size_t n (uss.count (user_sections::count_total | + user_sections::count_all | + user_sections::count_special_version)); + + map<size_t, user_section*> m; + for (user_sections::iterator i (uss.begin ()); i != uss.end (); ++i) + m[i->index] = &*i; + + os << "static const "<< traits << "::" << (abst ? "abstract_" : "") << + "info_type::section_functions" << endl + << "section_functions_for_" << fn << "[] =" + << "{"; + + for (size_t i (0); i != n; ++i) + { + os << (i != 0 ? "," : "") << "{"; + + map<size_t, user_section*>::iterator j (m.find (i)); + + if (j != m.end ()) + { + user_section& s (*j->second); + string n (public_name (*s.member)); + + // load + // + if (s.load_empty ()) + os << "0"; + else + os << "&odb::section_load_impl< " << type << ", id_" << db << + ", " << traits << "::" << n << "_traits >"; + + os << "," << endl; + + // update + // + // Generate an entry for a readonly section in optimistic + // polymorphic root since it can be overridden and we may + // need to update the version. + // + if (s.update_empty () && !(poly && s.optimistic ())) + os << "0"; + else + os << "&odb::section_update_impl< " << type << ", id_" << db << + ", " << traits << "::" << n << "_traits >"; + } + else + os << "0," << endl + << "0"; + + os << "}"; + } + + os << "};"; + + os << "static const "<< traits << "::" << (abst ? "abstract_" : "") << + "info_type::section_list" << endl + << "section_list_for_" << fn << " =" + << "{" + << n << "UL," << endl + << "section_functions_for_" << fn + << "};"; + } + + // + // + os << "const " << traits << "::" << (abst ? "abstract_" : "") << + "info_type" << endl + << traits << "::info (" << endl + << "typeid (" << type << ")," << endl; + + if (poly_derived) + os << "&object_traits_impl< " << class_fq_name (*poly_base) << + ", id_" << db << " >::info," << endl; + else + os << "0," << endl; + + // Sections. + // + if (sections) + os << "§ion_list_for_" << flat_name (type); + else + os << "0"; + + if (!abst) + { + os << "," << endl + << strlit (string (type, 2, string::npos)) << "," << endl + << "&odb::create_impl< " << type << " >," << endl + << "&odb::dispatch_impl< " << type << ", id_" << db << " >," << endl; + + if (poly_derived) + os << "&statements_type::delayed_loader"; + else + os << "0"; + } + + os << ");" + << endl; + + if (!abst) + os << "static const " << traits << "::entry_type" << endl + << "polymorphic_entry_for_" << flat_name (type) << ";" + << endl; + } + + // + // Statements. + // + bool update_columns ( + cc.total != cc.id + cc.inverse + cc.readonly + cc.separate_update); + + qname table (table_name (c)); + string qtable (quote_id (table)); + + // persist_statement + // + { + string sep (versioned ? "\n" : " "); + + statement_columns sc; + { + statement_kind sk (statement_insert); // Imperfect forwarding. + instance<object_columns> ct (sk, sc); + ct->traverse (c); + process_statement_columns (sc, statement_insert, versioned); + } + + bool dv (sc.empty ()); // The DEFAULT VALUES syntax. + + os << "const char " << traits << "::persist_statement[] =" << endl + << strlit ("INSERT INTO " + qtable + sep) << endl; + + for (statement_columns::const_iterator b (sc.begin ()), i (b), + e (sc.end ()); i != e;) + { + string s; + + if (i == b) + s += '('; + s += i->column; + s += (++i != e ? ',' : ')'); + s += sep; + + os << strlit (s) << endl; + } + + instance<query_parameters> qp (statement_insert, table); + + string extra (persist_statement_extra (c, *qp, persist_after_columns)); + + if (!extra.empty ()) + os << strlit (extra + sep) << endl; + + string values; + if (!dv) + { + os << strlit ("VALUES" + sep) << endl; + + values += '('; + instance<persist_statement_params> pt (values, *qp, sep); + pt->traverse (c); + values += ')'; + } + else + values = "DEFAULT VALUES"; + + extra = persist_statement_extra (c, *qp, persist_after_values); + + if (!extra.empty ()) + values += sep; + + os << strlit (values); + + if (!extra.empty ()) + os << endl + << strlit (extra); + + os << ";" + << endl; + } + + // Index of the first empty SELECT statement in poly-derived + // statement list. All subsequent statements are also empty. + // The first statement can never be empty (contains id and + // type_id). + // + size_t empty_depth (0); + + if (id != 0) + { + string sep (versioned ? "\n" : " "); + + instance<object_columns_list> id_cols; + id_cols->traverse (*id); + + std::vector<size_t> find_column_counts (abst ? 1 : poly_depth); + + // find_statement + // + if (poly_derived) + os << "const char* const " << traits << "::find_statements[] =" + << "{"; + else + os << "const char " << traits << "::find_statement[] =" << endl; + + for (size_t d (poly_depth); d != 0;) + { + statement_columns sc; + { + statement_kind sk (statement_select); // Imperfect forwarding. + object_section* s (&main_section); // Imperfect forwarding. + instance<object_columns> t (qtable, sk, sc, d, s); + t->traverse (c); + process_statement_columns (sc, statement_select, versioned); + find_column_counts[poly_depth - d] = sc.size (); + } + + if (sc.size () != 0) + { + os << strlit ("SELECT" + sep) << endl; + + for (statement_columns::const_iterator i (sc.begin ()), + e (sc.end ()); i != e;) + { + string const& c (i->column); + os << strlit (c + (++i != e ? "," : "") + sep) << endl; + } + + os << strlit ("FROM " + qtable + sep) << endl; + + if (d != 1) + { + bool f (false); // @@ (im)perfect forwarding + size_t d1 (d - 1); //@@ (im)perfect forward. + instance<polymorphic_object_joins> j (c, f, d1); + j->traverse (polymorphic_base (c)); + + for (strings::const_iterator i (j->begin ()); i != j->end (); ++i) + os << strlit (*i + sep) << endl; + } + + { + // For the find statement, don't join section members. + // + bool f (false); // @@ (im)perfect forwarding + object_section* s (&main_section); // @@ (im)perfect forwarding + instance<object_joins> j (c, f, d, s); + j->traverse (c); + + for (strings::const_iterator i (j->begin ()); i != j->end (); ++i) + os << strlit (*i + sep) << endl; + } + + string where ("WHERE "); + instance<query_parameters> qp (statement_select, table); + for (object_columns_list::iterator b (id_cols->begin ()), i (b); + i != id_cols->end (); ++i) + { + if (i != b) + where += " AND "; + + where += qtable + "." + quote_id (i->name) + "=" + + convert_to (qp->next (*i), i->type, *i->member); + } + + os << strlit (where); + } + else + os << strlit (""); // Empty SELECT statement. + + if (abst) + break; + + if (--d != 0) + os << "," << endl + << endl; + } + + if (poly_derived) + os << "};"; + else + os << ";" + << endl; + + // find_column_counts + // + if (poly_derived) + { + os << "const std::size_t " << traits << "::find_column_counts[] =" + << "{"; + + for (std::vector<size_t>::iterator b (find_column_counts.begin ()), + i (b), e (find_column_counts.end ()); i != e;) + { + os << *i << "UL"; + + if (*i == 0 && empty_depth == 0) + empty_depth = i - b; + + if (++i != e) + os << ',' << endl; + } + + os << "};"; + } + + // find_discriminator_statement + // + if (poly && !poly_derived) + { + statement_columns sc; + { + statement_kind sk (statement_select); // Imperfect forwarding. + instance<object_columns> t (qtable, sk, sc); + t->traverse (*discriminator); + + if (opt != 0) + t->traverse (*opt); + + process_statement_columns (sc, statement_select, false); + } + + os << "const char " << traits << "::" << endl + << "find_discriminator_statement[] =" << endl + << strlit ("SELECT ") << endl; + + for (statement_columns::const_iterator i (sc.begin ()), + e (sc.end ()); i != e;) + { + string const& c (i->column); + os << strlit (c + (++i != e ? ", " : " ")) << endl; + } + + os << strlit ("FROM " + qtable + " ") << endl; + + string where ("WHERE "); + instance<query_parameters> qp (statement_select, table); + for (object_columns_list::iterator b (id_cols->begin ()), i (b); + i != id_cols->end (); ++i) + { + if (i != b) + where += " AND "; + + where += qtable + "." + quote_id (i->name) + "=" + + convert_to (qp->next (*i), i->type, *i->member); + } + + os << strlit (where) << ";" + << endl; + } + + // update_statement + // + if (update_columns) + { + string sep (versioned ? "\n" : " "); + + instance<query_parameters> qp (statement_update, table); + + statement_columns sc; + { + query_parameters* p (qp.get ()); // Imperfect forwarding. + statement_kind sk (statement_update); // Imperfect forwarding. + object_section* s (&main_section); // Imperfect forwarding. + instance<object_columns> t (sk, sc, p, s); + t->traverse (c); + process_statement_columns (sc, statement_update, versioned); + } + + os << "const char " << traits << "::update_statement[] =" << endl + << strlit ("UPDATE " + qtable + sep) << endl + << strlit ("SET" + sep) << endl; + + for (statement_columns::const_iterator i (sc.begin ()), + e (sc.end ()); i != e;) + { + string const& c (i->column); + os << strlit (c + (++i != e ? "," : "") + sep) << endl; + } + + // This didn't work out: cannot change the identity column. + // + //if (sc.empty ()) + //{ + // // We can end up with nothing to set if we need to "touch" a row + // // in order to increment its optimistic concurrency version. In + // // this case just do a dummy assignment based on the id column. + // // + // string const& c (quote_id (id_cols->begin ()->name)); + // os << strlit (c + "=" + c) << endl; + //} + + string extra (update_statement_extra (c)); + + if (!extra.empty ()) + os << strlit (extra + sep) << endl; + + string where ("WHERE "); + for (object_columns_list::iterator b (id_cols->begin ()), i (b); + i != id_cols->end (); ++i) + { + if (i != b) + where += " AND "; + + where += quote_id (i->name) + "=" + + convert_to (qp->next (*i), i->type, *i->member); + } + + // Top-level version column. + // + if (opt != 0 && !poly_derived) + { + string name (column_qname (*opt, column_prefix ())); + string type (column_type (*opt)); + + where += " AND " + name + "=" + + convert_to (qp->next (*opt, name, type), type, *opt); + } + + os << strlit (where) << ";" + << endl; + } + + // erase_statement + // + { + instance<query_parameters> qp (statement_delete, table); + os << "const char " << traits << "::erase_statement[] =" << endl + << strlit ("DELETE FROM " + qtable + " ") << endl; + + string where ("WHERE "); + for (object_columns_list::iterator b (id_cols->begin ()), i (b); + i != id_cols->end (); ++i) + { + if (i != b) + where += " AND "; + + where += quote_id (i->name) + "=" + + convert_to (qp->next (*i), i->type, *i->member); + } + + os << strlit (where) << ";" + << endl; + } + + if (opt != 0 && !poly_derived) + { + instance<query_parameters> qp (statement_delete, table); + + os << "const char " << traits << "::optimistic_erase_statement[] " << + "=" << endl + << strlit ("DELETE FROM " + qtable + " ") << endl; + + string where ("WHERE "); + for (object_columns_list::iterator b (id_cols->begin ()), i (b); + i != id_cols->end (); ++i) + { + if (i != b) + where += " AND "; + + where += quote_id (i->name) + "=" + + convert_to (qp->next (*i), i->type, *i->member); + } + + // Top-level version column. + // + string name (column_qname (*opt, column_prefix ())); + string type (column_type (*opt)); + + where += " AND " + name + "=" + + convert_to (qp->next (*opt, name, type), type, *opt); + + os << strlit (where) << ";" + << endl; + } + } + + // query_statement + // + bool query_optimize (false); + if (options.generate_query ()) + { + // query_statement + // + strings joins; + if (poly_depth != 1) + { + bool t (true); //@@ (im)perfect forwarding + size_t d (poly_depth - 1); //@@ (im)perfect forward. + instance<polymorphic_object_joins> j (c, t, d); + j->traverse (polymorphic_base (c)); + joins = j->joins; + } + + if (id != 0) + { + // For the query statement we also join section members so that they + // can be used in the WHERE clause. + // + bool t (true); //@@ (im)perfect forwarding + instance<object_joins> j (c, t, poly_depth); + j->traverse (c); + joins.insert (joins.end (), j->begin (), j->end ()); + } + + query_optimize = !joins.empty (); + + statement_columns sc; + { + statement_kind sk (statement_select); //@@ Imperfect forwarding. + object_section* s (&main_section); //@@ Imperfect forwarding. + instance<object_columns> oc (qtable, sk, sc, poly_depth, s); + oc->traverse (c); + process_statement_columns ( + sc, statement_select, versioned || query_optimize); + } + + string sep (versioned || query_optimize ? "\n" : " "); + + os << "const char " << traits << "::query_statement[] =" << endl + << strlit ("SELECT" + sep) << endl; + + for (statement_columns::const_iterator i (sc.begin ()), + e (sc.end ()); i != e;) + { + string const& c (i->column); + os << strlit (c + (++i != e ? "," : "") + sep) << endl; + } + + string prev ("FROM " + qtable); + + for (strings::const_iterator i (joins.begin ()); i != joins.end (); ++i) + { + os << strlit (prev + sep) << endl; + prev = *i; + } + + os << strlit (prev) << ";" + << endl; + + // erase_query_statement + // + os << "const char " << traits << "::erase_query_statement[] =" << endl + << strlit ("DELETE FROM " + qtable) << ";" + << endl; + + // table_name + // + os << "const char " << traits << "::table_name[] =" << endl + << strlit (qtable) << ";" // Use quoted name. + << endl; + } + + // persist () + // + size_t persist_containers (has_a (c, test_straight_container)); + bool persist_versioned_containers ( + persist_containers > + has_a (c, + test_straight_container | + exclude_deleted | exclude_added | exclude_versioned)); + + os << "void " << traits << "::" << endl + << "persist (database& db, " << (auto_id ? "" : "const ") << + "object_type& obj"; + + if (poly) + os << ", bool top, bool dyn"; + + os << ")" + << "{"; + + if (poly) + os << "ODB_POTENTIALLY_UNUSED (top);" + << endl; + + os << "using namespace " << db << ";" + << endl; + + if (poly) + os << "if (dyn)" << endl + << "{" + << "const std::type_info& t (typeid (obj));" + << endl + << "if (t != info.type)" + << "{" + << "const info_type& pi (root_traits::map->find (t));" + << "pi.dispatch (info_type::call_persist, db, &obj, 0);" + << "return;" + << "}" + << "}"; + + // If we are database-poly-abstract but not C++-abstract, then make + // sure we are not trying to persist an instance of an abstract class. + // + if (abst && !c.abstract ()) + os << "if (top)" << endl + << "throw abstract_class ();" + << endl; + + os << db << "::connection& conn (" << endl + << db << "::transaction::current ().connection (db));" + << "statements_type& sts (" << endl + << "conn.statement_cache ().find_object<object_type> ());"; + + if (versioned || + persist_versioned_containers || + uss.count (user_sections::count_new | + user_sections::count_all | + user_sections::count_versioned_only) != 0) + os << "const schema_version_migration& svm (" << + "sts.version_migration (" << schema_name << "));"; + + os << endl; + + // Call callback (pre_persist). + // + if (!abst) // If we are poly-abstract, then top will always be false. + { + if (poly) + os << "if (top)" << endl; + + os << "callback (db," << endl + << (auto_id ? "static_cast<const object_type&> (obj)," : "obj,") << endl + << "callback_event::pre_persist);" + << endl; + } + + // Call our base if we are a derived type in a polymorphic + // hierarchy. + // + if (poly_derived) + os << "base_traits::persist (db, obj, false, false);" + << endl; + + os << "image_type& im (sts.image ());" + << "binding& imb (sts.insert_image_binding ());"; + + if (poly_derived) + os << "const binding& idb (sts.id_image_binding ());"; + + os << endl; + + if (generate_grow) + os << "if ("; + + os << "init (im, obj, statement_insert" << (versioned ? ", svm" : "") << ")"; + + if (generate_grow) + os << ")" << endl + << "im.version++"; + + os << ";" + << endl; + + if (!poly_derived && auto_id && insert_send_auto_id) + { + string const& n (idf->name ()); + string var ("im." + n + (n[n.size () - 1] == '_' ? "" : "_")); + init_auto_id (*idf, var); // idf == idb, since auto + } + + os << "if ("; + + if (poly_derived) + os << "idb.version != sts.insert_id_binding_version () ||" << endl; + + os << "im.version != sts.insert_image_version () ||" << endl + << "imb.version == 0)" + << "{" + << "bind (imb.bind, "; + + if (poly_derived) + os << "idb.bind, idb.count, "; + + os << "im, statement_insert" << (versioned ? ", svm" : "") << ");"; + + if (poly_derived) + os << "sts.insert_id_binding_version (idb.version);"; + + os << "sts.insert_image_version (im.version);" + << "imb.version++;" + << "}"; + + // Bind id image since that's where the returned id and/or version will + // be extracted. + // + if (!poly_derived) + { + bool bv (opt != 0 && optimistic_insert_bind_version (*opt)); + if (bv || auto_id) + { + os << "{" + << "id_image_type& i (sts.id_image ());" + << "binding& b (sts.id_image_binding ());" + << "if (i.version != sts.id_image_version () || b.version == 0)" + << "{" + << "bind (b.bind, i);" + << "sts.id_image_version (i.version);" + << "b.version++;"; + if (opt != 0) + os << "sts.optimistic_id_image_binding ().version++;"; + os << "}" + << "}"; + } + } + + os << "insert_statement& st (sts.persist_statement ());" + << "if (!st.execute ())" << endl + << "throw object_already_persistent ();" + << endl; + + if (!poly_derived && auto_id) // idf == idb since auto + { + set_member (*idf, "obj", "id (sts.id_image ())", "db", "id_type"); + os << endl; + } + + // Set the optimistic concurrency version in the object member. + // + if (opt != 0 && !poly_derived) + { + // If we don't have auto id, then obj is a const reference. + // + set_member (*opt, + (auto_id ? "obj" : "const_cast<object_type&> (obj)"), + optimistic_version_init (*opt), + "", // No database. + "version_type"); + os << endl; + } + + // Initialize id_image and binding if we are a root of a polymorphic + // hierarchy or if we have non-inverse containers. + // + if (!poly_derived && (poly || persist_containers)) + { + // If this is a polymorphic root without containers, then we only + // need to do this if we are not a top-level call. If we are poly- + // abstract, then top will always be false. + // + if (poly && !persist_containers && !abst) + os << "if (!top)" + << "{"; + + os << "id_image_type& i (sts.id_image ());" + << "init (i, id (obj));" + << endl + << "binding& idb (sts.id_image_binding ());" + << "if (i.version != sts.id_image_version () || idb.version == 0)" + << "{" + << "bind (idb.bind, i);" + << "sts.id_image_version (i.version);" + << "idb.version++;"; + if (opt != 0) + os << "sts.optimistic_id_image_binding ().version++;"; + os << "}"; + + if (poly && !persist_containers && !abst) + os << "}"; + } + + if (persist_containers) + { + os << "extra_statement_cache_type& esc (sts.extra_statement_cache ());" + << endl; + + instance<container_calls> t (container_calls::persist_call); + t->traverse (c); + } + + // Reset sections: loaded, unchanged. + // + for (user_sections::iterator i (uss.begin ()); i != uss.end (); ++i) + { + // Skip special sections. + // + if (i->special == user_section::special_version) + continue; + + // Skip overridden sections; they have been reset by the base. + // + if (i->base != 0 && poly_derived) + continue; + + data_member& m (*i->member); + + // If the section is soft- added or deleted, check the version. + // + unsigned long long av (added (m)); + unsigned long long dv (deleted (m)); + if (av != 0 || dv != 0) + { + os << "if ("; + + if (av != 0) + os << "svm >= schema_version_migration (" << av << "ULL, true)"; + + if (av != 0 && dv != 0) + os << " &&" << endl; + + if (dv != 0) + os << "svm <= schema_version_migration (" << dv << "ULL, true)"; + + os << ")" << endl; + } + + // Section access is always by reference. + // + member_access& ma (m.get<member_access> ("get")); + if (!ma.synthesized) + os << "// From " << location_string (ma.loc, true) << endl; + + os << ma.translate ("obj") << ".reset (true, false);" + << endl; + } + + // Call callback (post_persist). + // + if (!abst) // If we are poly-abstract, then top will always be false. + { + if (poly) + os << "if (top)" << endl; + + os << "callback (db," << endl + << (auto_id ? "static_cast<const object_type&> (obj)," : "obj,") << endl + << "callback_event::post_persist);"; + } + + os << "}"; + + // persist() bulk + // + if (c.count ("bulk-persist")) + { + os << "void " << traits << "::" << endl + << "persist (database& db," << endl + << (auto_id ? "" : "const ") << "object_type** objs," << endl + << "std::size_t n," << endl + << "multiple_exceptions& mex)" + << "{" + << "using namespace " << db << ";" + << endl; + + os << db << "::connection& conn (" << endl + << db << "::transaction::current ().connection (db));" + << "statements_type& sts (" << endl + << "conn.statement_cache ().find_object<object_type> ());"; + + if (versioned || + uss.count (user_sections::count_new | + user_sections::count_all | + user_sections::count_versioned_only) != 0) + os << "const schema_version_migration& svm (" << + "sts.version_migration (" << schema_name << "));"; + + os << endl; + + os << "for (std::size_t i (0); i != n; ++i)" + << "{" + << "const object_type& obj (*objs[i]);" + << "callback (db, obj, callback_event::pre_persist);" + //@@ assumption: generate_grow is false or it only affects select (like + // in pgsql) so all we have to do is to increment image + // version if it grew. + //@@ assumption: insert_send_auto_id is false + << "image_type& im (sts.image (i));"; + + if (generate_grow) + os << "if ("; + + os << "init (im, obj, statement_insert" << (versioned ? ", svm" : "") << ")"; + + if (generate_grow) + os << " && i == 0)" << endl + << "im.version++"; + + os << ";" + << "}"; + + //@@ assumption: generate_grow: as above + os << "binding& imb (sts.insert_image_binding ());" + << "if (imb.version == 0)" + << "{" + << "bind (imb.bind, sts.image (), statement_insert" << + (versioned ? ", svm" : "") << ");" + << "imb.version++;" + << "}"; + + // Bind id image since that's where the returned id and/or version will + // be extracted. + // + bool bv (opt != 0 && optimistic_insert_bind_version (*opt)); + if (bv || auto_id) + { + os << "binding& idb (sts.id_image_binding ());" + //@@ assumption: generate_grow: as above + << "if (idb.version == 0)" + << "{" + << "bind (idb.bind, sts.id_image ());" + << "idb.version++;"; + if (opt != 0) + os << "sts.optimistic_id_image_binding ().version++;"; + os << "}"; + } + + os << "insert_statement& st (sts.persist_statement ());" + << "n = st.execute (n, mex);" // Actual number of rows attempted. + << endl; + + os << "for (std::size_t i (0); i != n; ++i)" + << "{" + << "bool r (st.result (i));" // Sets current in mex. + << endl + << "if (mex[i] != 0)" << endl // Pending exception for this position. + << "continue;" + << endl + << "if (!r)" + << "{" + << "mex.insert (i, object_already_persistent ());" + << "continue;" + << "}" + << "if (mex.fatal ())" << endl // Don't do any extra work. + << "continue;" + << endl + << (auto_id ? "" : "const ") << "object_type& obj (*objs[i]);" + << endl; + + // Extract auto id. + // + if (auto_id) // idb == idf, since auto + { + set_member (*idf, "obj", "id (sts.id_image (i))", "db", "id_type"); + os << endl; + } + + // Set the optimistic concurrency version. + // + if (opt != 0) + { + // If we don't have auto id, then obj is a const reference. + // + set_member (*opt, + (auto_id ? "obj" : "const_cast<object_type&> (obj)"), + optimistic_version_init (*opt, true), + "", // No database. + "version_type"); + os << endl; + } + + // Reset sections: loaded, unchanged. + // + for (user_sections::iterator i (uss.begin ()); i != uss.end (); ++i) + { + // Skip special sections. + // + if (i->special == user_section::special_version) + continue; + + data_member& m (*i->member); + + // If the section is soft- added or deleted, check the version. + // + unsigned long long av (added (m)); + unsigned long long dv (deleted (m)); + if (av != 0 || dv != 0) + { + os << "if ("; + + if (av != 0) + os << "svm >= schema_version_migration (" << av << "ULL, true)"; + + if (av != 0 && dv != 0) + os << " &&" << endl; + + if (dv != 0) + os << "svm <= schema_version_migration (" << dv << "ULL, true)"; + + os << ")" << endl; + } + + // Section access is always by reference. + // + member_access& ma (m.get<member_access> ("get")); + if (!ma.synthesized) + os << "// From " << location_string (ma.loc, true) << endl; + + os << ma.translate ("obj") << ".reset (true, false);" + << endl; + } + + os << "callback (db," << endl + << (auto_id ? "static_cast<const object_type&> (obj)," : "obj,") << endl + << "callback_event::post_persist);" + << "}" // for + << "}"; // persist () + } + + // update () + // + if (id != 0 && (!readonly || poly)) + { + size_t update_containers ( + has_a (c, test_readwrite_container, &main_section)); + + bool update_versioned_containers ( + update_containers > + has_a (c, + test_readwrite_container | + exclude_deleted | exclude_added | exclude_versioned, + &main_section)); + + // See if we have any sections that we might have to update. + // + bool sections (false); + bool versioned_sections (false); + for (user_sections::iterator i (uss.begin ()); i != uss.end (); ++i) + { + // This test will automatically skip the special version update section. + // + if (i->update_empty () || i->update == user_section::update_manual) + continue; + + sections = true; + + if (added (*i->member) || deleted (*i->member)) + versioned_sections = true; + + // For change-updated sections, also check if we have any + // versioned smart containers. + // + if (i->update != user_section::update_change) + continue; + + if (has_a (c, test_smart_container, &*i) != + has_a (c, test_smart_container | exclude_deleted | exclude_added, &*i)) + versioned_sections = true; + } + + os << "void " << traits << "::" << endl + << "update (database& db, const object_type& obj"; + + if (poly) + os << ", bool top, bool dyn"; + + os << ")" + << "{" + << "ODB_POTENTIALLY_UNUSED (db);"; + + if (poly) + os << "ODB_POTENTIALLY_UNUSED (top);"; + + os << endl + << "using namespace " << db << ";" + << "using " << db << "::update_statement;" + << endl; + + if (poly) + os << "if (dyn)" << endl + << "{" + << "const std::type_info& t (typeid (obj));" + << endl + << "if (t != info.type)" + << "{" + << "const info_type& pi (root_traits::map->find (t));" + << "pi.dispatch (info_type::call_update, db, &obj, 0);" + << "return;" + << "}" + << "}"; + + // If we are database-poly-abstract but not C++-abstract, then make + // sure we are not trying to update an instance of an abstract class. + // + if (abst && !c.abstract ()) + os << "if (top)" << endl + << "throw abstract_class ();" + << endl; + + // If we are readonly, then there is nothing else to do. + // + if (!readonly) + { + // Call callback (pre_update). + // + if (!abst) // If we are poly-abstract then top will always be false. + { + if (poly) + os << "if (top)" << endl; + + os << "callback (db, obj, callback_event::pre_update);" + << endl; + } + + bool sts (false); + + if ((versioned && update_columns) || + update_versioned_containers || + versioned_sections) + { + sts = true; + os << db << "::transaction& tr (" << db << + "::transaction::current ());" + << db << "::connection& conn (tr.connection (db));" + << "statements_type& sts (" << endl + << "conn.statement_cache ().find_object<object_type> ());" + << endl; + + os << "const schema_version_migration& svm (" << + "sts.version_migration (" << schema_name << "));" + << endl; + } + + // If we have change-updated sections that contain change-tracking + // containers, then mark such sections as changed if any of the + // containers were changed. Do this before calling the base so that + // we account for the whole hierarchy before we actually start + // updating any sections (important for optimistic concurrency + // version). + // + for (user_sections::iterator i (uss.begin ()); i != uss.end (); ++i) + { + user_section& s (*i); + + if (s.update != user_section::update_change) + continue; + + if (!has_a (c, test_smart_container, &s)) + continue; + + data_member& m (*s.member); + + os << "// " << m.name () << endl + << "//" << endl; + + // If the section is soft- added or deleted, check the version. + // + unsigned long long av (added (m)); + unsigned long long dv (deleted (m)); + if (av != 0 || dv != 0) + { + os << "if ("; + + if (av != 0) + os << "svm >= schema_version_migration (" << av << "ULL, true)"; + + if (av != 0 && dv != 0) + os << " &&" << endl; + + if (dv != 0) + os << "svm <= schema_version_migration (" << dv << "ULL, true)"; + + os << ")"; + } + + os << "{"; + + // Section access is always by reference. + // + member_access& ma (m.get<member_access> ("get")); + if (!ma.synthesized) + os << "// From " << location_string (ma.loc, true) << endl; + + // VC++ cannot grok the constructor syntax. + // + os << "const odb::section& s = " << ma.translate ("obj") << ";" + << endl; + + os << "if ("; + + // Unless we are always loaded, test for being loaded. + // + if (s.load != user_section::load_eager) + os << "s.loaded () && "; + + // And for not already being changed. + // + os << "!s.changed ())" + << "{"; + + instance<container_calls> t (container_calls::section_call, &s); + t->traverse (c); + + os << "}" // if + << "}"; + } + + if (poly_derived) + { + bool readonly_base (context::readonly (*poly_base)); + + if (!sts && (readonly_base || + update_columns || + update_containers || + sections)) + { + os << db << "::transaction& tr (" << db << + "::transaction::current ());" + << db << "::connection& conn (tr.connection (db));" + << "statements_type& sts (" << endl + << "conn.statement_cache ().find_object<object_type> ());" + << endl; + } + + // Unless our base is readonly, call it first. + // + if (!readonly_base) + { + os << "base_traits::update (db, obj, false, false);" + << endl; + } + else + { + // Otherwise, we have to initialize the id image ourselves. If + // we don't have any columns, containers or sections to update, + // then we only have to do it if this is not a top-level call. + // If we are abstract, then top is always false. + // + if (!update_columns && !update_containers && !sections && !abst) + os << "if (!top)"; + + os << "{" + << "id_image_type& i (sts.id_image ());" + << "init (i, id (obj));" + << endl; + + os << "binding& idb (sts.id_image_binding ());" + << "if (i.version != sts.id_image_version () || idb.version == 0)" + << "{" + << "bind (idb.bind, i);" + << "sts.id_image_version (i.version);" + << "idb.version++;" + // Optimistic poly base cannot be readonly. + << "}" + << "}"; + } + + if (update_columns) + { + // Initialize the object image. + // + os << "image_type& im (sts.image ());"; + + if (generate_grow) + os << "if ("; + + os << "init (im, obj, statement_update" << + (versioned ? ", svm" : "") << ")"; + + if (generate_grow) + os << ")" << endl + << "im.version++"; + + os << ";" + << endl; + + os << "const binding& idb (sts.id_image_binding ());" + << "binding& imb (sts.update_image_binding ());" + << "if (idb.version != sts.update_id_binding_version () ||" << endl + << "im.version != sts.update_image_version () ||" << endl + << "imb.version == 0)" + << "{" + << "bind (imb.bind, idb.bind, idb.count, im, statement_update" << + (versioned ? ", svm" : "") << ");" + << "sts.update_id_binding_version (idb.version);" + << "sts.update_image_version (im.version);" + << "imb.version++;" + << "}"; + + os << "update_statement& st (sts.update_statement ());" + << "if ("; + + if (versioned) + os << "!st.empty () && "; + + os << "st.execute () == 0)" << endl + << "throw object_not_persistent ();" + << endl; + } + + // Otherwise, nothing else to do here if we don't have any columns + // to update. + } + else if (update_columns) + { + if (!sts) + os << db << "::transaction& tr (" << db << + "::transaction::current ());" + << db << "::connection& conn (tr.connection (db));" + << "statements_type& sts (" << endl + << "conn.statement_cache ().find_object<object_type> ());" + << endl; + + // Initialize id image. + // + if (opt != 0) + os << "const version_type& v (version (obj));"; + + os << "id_image_type& idi (sts.id_image ());" + << "init (idi, id (obj)" << (opt != 0 ? ", &v" : "") << ");" + << endl; + + // Initialize object image. + // + os << "image_type& im (sts.image ());"; + + if (generate_grow) + os << "if ("; + + os << "init (im, obj, statement_update" << + (versioned ? ", svm" : "") << ")"; + + if (generate_grow) + os << ")" << endl + << "im.version++"; + + os << ";" + << endl; + + // The update binding is bound to two images (object and id) + // so we have to track both versions. + // + os << "bool u (false);" // Avoid incrementing version twice. + << "binding& imb (sts.update_image_binding ());" + << "if (im.version != sts.update_image_version () ||" << endl + << "imb.version == 0)" + << "{" + << "bind (imb.bind, im, statement_update" << + (versioned ? ", svm" : "") << ");" + << "sts.update_image_version (im.version);" + << "imb.version++;" + << "u = true;" + << "}"; + + // To update the id part of the update binding we have to do + // it indirectly via the id binding, which just points to the + // suffix of the update bind array (see object_statements). + // + os << "binding& idb (sts.id_image_binding ());" + << "if (idi.version != sts.update_id_image_version () ||" << endl + << "idb.version == 0)" + << "{" + // If the id binding is up-to-date, then that means update + // binding is too and we just need to update the versions. + // + << "if (idi.version != sts.id_image_version () ||" << endl + << "idb.version == 0)" + << "{" + << "bind (idb.bind, idi);" + // Update the id binding versions since we may use them later + // to update containers. + // + << "sts.id_image_version (idi.version);" + << "idb.version++;"; + if (opt != 0) + os << "sts.optimistic_id_image_binding ().version++;"; + os << "}" + << "sts.update_id_image_version (idi.version);" + << endl + << "if (!u)" << endl + << "imb.version++;" + << "}"; + + os << "update_statement& st (sts.update_statement ());" + << "if ("; + + if (versioned) + os << "!st.empty () && "; + + os << "st.execute () == 0)" << endl; + + if (opt == 0) + os << "throw object_not_persistent ();"; + else + os << "throw object_changed ();"; + + os << endl; + } + else if (poly || update_containers || sections) + { + // We don't have any columns to update but we may still need + // to initialize the id image if we are polymorphic or have + // any containers or sections. + // + if (!sts) + os << db << "::transaction& tr (" << db << + "::transaction::current ());" + << db << "::connection& conn (tr.connection (db));" + << "statements_type& sts (" << endl + << "conn.statement_cache ().find_object<object_type> ());" + << endl; + + // The same rationale as a couple of screens up. + // + if (poly && !update_containers && !sections && !abst) + os << "if (!top)"; + + os << "{" + << "id_image_type& i (sts.id_image ());" + << "init (i, id (obj));" + << endl; + + os << "binding& idb (sts.id_image_binding ());" + << "if (i.version != sts.id_image_version () || idb.version == 0)" + << "{" + << "bind (idb.bind, i);" + << "sts.id_image_version (i.version);" + << "idb.version++;" + // Cannot be optimistic. + << "}" + << "}"; + } + + if (update_containers || sections) + os << "extra_statement_cache_type& esc (sts.extra_statement_cache ());" + << endl; + + if (update_containers) + { + instance<container_calls> t (container_calls::update_call, + &main_section); + t->traverse (c); + } + + // Update the optimistic concurrency version in the object member. + // Do it before updating sections since they will update it as well. + // The same code as in section_traits. + // + if (opt != 0 && !poly_derived) + { + // Object is passed as const reference so we need to cast away + // constness. + // + const char* obj ("const_cast<object_type&> (obj)"); + string inc (optimistic_version_increment (*opt)); + + if (inc == "1") + inc_member (*opt, obj, "obj", "version_type"); + else + set_member (*opt, obj, inc, "", "version_type"); + + os << endl; + } + + // Update sections that are loaded and need updating. + // + for (user_sections::iterator i (uss.begin ()); i != uss.end (); ++i) + { + if (i->update_empty () || i->update == user_section::update_manual) + continue; + + // Here is the deal: for polymorphic section overrides, we could + // have used the same approach as in load_() where the class that + // defines the section data member initiates the section update. + // However, if we used this approach, then we would get what can + // be called "interleaved" update statements. That is, a section + // update would update the section data in "derived" tables before + // updating the main section in these derived tables. While this + // does not feel right, it can also cause more real problems if, + // for example, there are constraints or some such. Generally, + // we always want to update the main section data first for each + // table in the hierarchy. + // + // So what we are going to do instead is call section traits + // update at each override point (and indicate to it not to + // call base). One tricky aspect is who is going to reset the + // changed state. We have to do it at the top since otherwise + // overrides won't know the section has changed. Finding out + // whether we are the final override (in section terms, not + // object terms) is tricky. + // + data_member& m (*i->member); + + // If the section is soft- added or deleted, check the version. + // + unsigned long long av (added (m)); + unsigned long long dv (deleted (m)); + if (av != 0 || dv != 0) + { + os << "if ("; + + if (av != 0) + os << "svm >= schema_version_migration (" << av << "ULL, true)"; + + if (av != 0 && dv != 0) + os << " &&" << endl; + + if (dv != 0) + os << "svm <= schema_version_migration (" << dv << "ULL, true)"; + + os << ")" + << "{"; + } + + // Section access is always by reference. + // + member_access& ma (m.get<member_access> ("get")); + if (!ma.synthesized) + os << "// From " << location_string (ma.loc, true) << endl; + + os << "if ("; + + // Unless we are always loaded, test for being loaded. + // + string sep; + if (i->load != user_section::load_eager) + { + os << ma.translate ("obj") << ".loaded ()"; + sep = " && "; + } + + // If change-updated section, check that it has changed. + // + if (i->update == user_section::update_change) + os << sep << ma.translate ("obj") << ".changed ()"; + + os << ")" + << "{"; + + // Re-initialize the id+ver image with new version which may + // have changed. Here we take a bit of a shortcut and not + // re-bind the image since we know only version may have + // changed and that is always an integer (cannot grow). + // + if (opt != 0) + { + os << "const version_type& v (version (obj));" + << "init (sts.id_image (), id (obj), &v);" + << endl; + } + + os << public_name (m) << "_traits::update (esc, obj"; + + if (poly_derived && i->base != 0) + { + // Normally we don't want to call base (base object's update() + // would have called it; see comment above). There is one + // exception, however, and that is a readonly base section + // in optimistic class. In this case, base object's update() + // won't call it (it is readonly) but it needs to be called + // in order to increment the version. + // + bool base (false); + for (user_section* b (i->base); !base && b != 0; b = b->base) + { + if (b->total != b->inverse + b->readonly || + b->readwrite_containers) + break; // We have a readwrite base. + else if (b->optimistic ()) + base = true; // We have a readonly optimistic root. + } + + os << ", " << base; + } + + os << ");"; + + // Clear the change flag and arm the transaction rollback callback. + // + if (i->update == user_section::update_change) + { + // If polymorphic, do it only if we are the final override. + // + if (poly) + os << "if (root_traits::map->find (typeid (obj))." << + "final_section_update (info, " << i->index << "UL))" << endl; + + os << ma.translate ("obj") << ".reset (true, false, &tr);"; + } + + os << "}"; + + if (av != 0 || dv != 0) + os << "}"; + } + + // Call callback (post_update). + // + if (!abst) // If we are poly-abstract, then top will always be false. + { + if (poly) + os << "if (top)" + << "{"; + + os << "callback (db, obj, callback_event::post_update);" + << "pointer_cache_traits::update (db, obj);"; + + if (poly) + os << "}"; + } + } // readonly + + os << "}"; + } + + // update () bulk + // + if (id != 0 && !readonly && c.count ("bulk-update")) + { + os << "void " << traits << "::" << endl + << "update (database& db," << endl + << "const object_type** objs," << endl + << "std::size_t n," << endl + << "multiple_exceptions& mex)" + << "{" + << "using namespace " << db << ";" + << "using " << db << "::update_statement;" + << endl + << db << "::connection& conn (" << endl + << db << "::transaction::current ().connection (db));" + << "statements_type& sts (" << endl + << "conn.statement_cache ().find_object<object_type> ());"; + + if (versioned) + os << "const schema_version_migration& svm (" << + "sts.version_migration (" << schema_name << "));"; + + os << endl + << "for (std::size_t i (0); i != n; ++i)" + << "{" + << "const object_type& obj (*objs[i]);" + << "callback (db, obj, callback_event::pre_update);"; + + if (opt != 0) + os << "const version_type& v (version (obj));"; + + os << "init (sts.id_image (i), id (obj)" << (opt != 0 ? ", &v" : "") << ");"; + + //@@ assumption: generate_grow is false or it only affects select (like + // in pgsql) so all we have to do is to increment image + // version if it grew. + + os << "image_type& im (sts.image (i));"; + + if (generate_grow) + os << "if ("; + + os << "init (im, obj, statement_update" << (versioned ? ", svm" : "") << ")"; + + if (generate_grow) + os << " && i == 0)" << endl + << "im.version++"; + + os << ";" + << "}"; + + // Update bindings. + // + os << "binding& idb (sts.id_image_binding ());" + << "binding& imb (sts.update_image_binding ());" + << endl; + + //@@ assumption: generate_grow: as above + // + os << "bool u (false);" // Avoid incrementing version twice. + << "if (imb.version == 0)" + << "{" + << "bind (imb.bind, sts.image (), statement_update" << + (versioned ? ", svm" : "") << ");" + << "imb.version++;" + << "u = true;" + << "}"; + + //@@ assumption: generate_grow: as above + // + os << "if (idb.version == 0)" + << "{" + << "bind (idb.bind, sts.id_image ());" + << "idb.version++;"; + if (opt != 0) + os << "sts.optimistic_id_image_binding ().version++;"; + os << endl + << "if (!u)" << endl + << "imb.version++;" + << "}"; + + // We don't need the versioned check here since the statement cannot + // be empty; otherwise it would have been an empty object which is + // illegal. + // + os << "update_statement& st (sts.update_statement ());" + << "n = st.execute (n, mex);"; // Actual number of rows attempted. + + os << endl + << "for (std::size_t i (0); i != n; ++i)" + << "{" + << "unsigned long long r (st.result (i));" // Also sets current in mex. + << endl + << "if (mex[i] != 0)" << endl // Pending exception from result(). + << "continue;" + << endl + << "if (r != 1)" + << "{" + << "mex.insert (i," << endl + //@@ assumption: result_unknown + << "(r == update_statement::result_unknown)," << endl + << (opt == 0 ? "object_not_persistent" : "object_changed") << " ());" + << "continue;" + << "}" + << "if (mex.fatal ())" << endl // Don't do any extra work. + << "continue;" + << endl + << "const object_type& obj (*objs[i]);"; + + // Update the optimistic concurrency version in the object member. + // + if (opt != 0) + { + // Object is passed as const reference so we need to cast away + // constness. + // + const char* obj ("const_cast<object_type&> (obj)"); + string inc (optimistic_version_increment (*opt)); + + if (inc == "1") + inc_member (*opt, obj, "obj", "version_type"); + else + set_member (*opt, obj, inc, "", "version_type"); + + os << endl; + } + + os << "callback (db, obj, callback_event::post_update);" + << "pointer_cache_traits::update (db, obj);" + << "}" // for + << "}"; // update() + } + + // erase (id) + // + size_t erase_containers (has_a (c, test_straight_container)); + bool erase_versioned_containers ( + erase_containers > + has_a (c, test_straight_container | exclude_deleted | exclude_added)); + + if (id != 0) + { + os << "void " << traits << "::" << endl + << "erase (database& db, const id_type& id"; + + if (poly) + os << ", bool top, bool dyn"; + + os << ")" + << "{" + << "using namespace " << db << ";"; + + if (poly) + os << endl + << "ODB_POTENTIALLY_UNUSED (top);"; + + os << endl + << db << "::connection& conn (" << endl + << db << "::transaction::current ().connection (db));" + << "statements_type& sts (" << endl + << "conn.statement_cache ().find_object<object_type> ());" + << endl; + + // Get the discriminator and determine the dynamic type of + // this object. + // + if (poly) + { + os << "if (dyn)" << endl + << "{" + << "discriminator_type d;" + << "root_traits::discriminator_ (sts.root_statements (), id, &d);"; + + if (!abst) + os << endl + << "if (d != info.discriminator)" + << "{"; + + os << "const info_type& pi (root_traits::map->find (d));" + << endl + // Check that the dynamic type is derived from the static type. + // + << "if (!pi.derived (info))" << endl + << "throw object_not_persistent ();" + << endl + << "pi.dispatch (info_type::call_erase, db, 0, &id);" + << "return;"; + + if (!abst) + os << "}"; + + os << "}"; + } + + // Initialize id image. + // + if (!abst) // If we are poly-abstract, then top will always be false. + { + if (poly) + os << "if (top)" + << "{"; + + os << "id_image_type& i (sts.id_image ());" + << "init (i, id);" + << endl; + + os << "binding& idb (sts.id_image_binding ());" + << "if (i.version != sts.id_image_version () || idb.version == 0)" + << "{" + << "bind (idb.bind, i);" + << "sts.id_image_version (i.version);" + << "idb.version++;"; + if (opt != 0) + os << "sts.optimistic_id_image_binding ().version++;"; + os << "}"; + + if (poly) + os << "}"; + } + + // Erase containers first so that there are no reference + // violations (we don't want to rely on ON DELETE CASCADE + // here since in case of a custom schema, it might not be + // there). + // + if (erase_containers) + { + os << "extra_statement_cache_type& esc (sts.extra_statement_cache ());"; + + if (erase_versioned_containers) + os << "const schema_version_migration& svm (" << + "sts.version_migration (" << schema_name << "));"; + + os << endl; + + instance<container_calls> t (container_calls::erase_id_call); + t->traverse (c); + } + + os << "if (sts.erase_statement ().execute () != 1)" << endl + << "throw object_not_persistent ();" + << endl; + + if (poly_derived) + { + // Call our base last (we erase polymorphic objects from base + // to derived in order not to trigger cascading deletes). + // + os << "base_traits::erase (db, id, false, false);" + << endl; + } + + // Remove from the object cache. + // + if (!abst) // If we are poly-abstract, then top will always be false. + { + if (poly) + os << "if (top)" << endl; + + os << "pointer_cache_traits::erase (db, id);"; + } + + os << "}"; + } + + // erase (id) bulk + // + if (id != 0 && c.count ("bulk-erase")) + { + os << "std::size_t " << traits << "::" << endl + << "erase (database& db," << endl + << "const id_type** ids," << endl + << "std::size_t n," << endl + << "multiple_exceptions& mex)" + << "{" + << "using namespace " << db << ";" + << endl + << db << "::connection& conn (" << endl + << db << "::transaction::current ().connection (db));" + << "statements_type& sts (" << endl + << "conn.statement_cache ().find_object<object_type> ());" + << endl + << "for (std::size_t i (0); i != n; ++i)" << endl + << "init (sts.id_image (i), *ids[i]);" + << endl + << "binding& idb (sts.id_image_binding ());" + //@@ assumption: generate_grow is false or it only affects select (like + // in pgsql). + << "if (idb.version == 0)" + << "{" + << "bind (idb.bind, sts.id_image ());" + << "idb.version++;" + << (opt != 0 ? "sts.optimistic_id_image_binding ().version++;" : "") + << "}" + << "delete_statement& st (sts.erase_statement ());" + << "n = st.execute (n, mex);" // Set to actual number of rows attempted. + << endl + << "for (std::size_t i (0); i != n; ++i)" + << "{" + << "unsigned long long r (st.result (i));" // Sets current in mex. + << endl + << "if (mex[i] != 0)" << endl // Pending exception from result(). + << "continue;" + << endl + << "if (r != 1)" + << "{" + << "mex.insert (i," << endl + //@@ assumption: result_unknown + << "(r == delete_statement::result_unknown)," << endl + << "object_not_persistent ());" + << "continue;" + << "}" + << "if (mex.fatal ())" << endl // Don't do any extra work. + << "continue;" + << "pointer_cache_traits::erase (db, *ids[i]);" + << "}" // for + << "return n;" + << "}"; // erase() + } + + // erase (object) + // + bool erase_smart_containers (has_a (c, test_smart_container)); + + if (id != 0 && (poly || opt != 0 || erase_smart_containers)) + { + os << "void " << traits << "::" << endl + << "erase (database& db, const object_type& obj"; + + if (poly) + os << ", bool top, bool dyn"; + + os << ")" + << "{" + << "ODB_POTENTIALLY_UNUSED (db);"; + + if (poly) + os << "ODB_POTENTIALLY_UNUSED (top);"; + + os << endl; + + if (poly) + os << "if (dyn)" << endl + << "{" + << "const std::type_info& t (typeid (obj));" + << endl + << "if (t != info.type)" + << "{" + << "const info_type& pi (root_traits::map->find (t));" + << "pi.dispatch (info_type::call_erase, db, &obj, 0);" + << "return;" + << "}" + << "}"; + + // If we are database-poly-abstract but not C++-abstract, then make + // sure we are not trying to erase an instance of an abstract class. + // + if (abst && !c.abstract ()) + os << "if (top)" << endl + << "throw abstract_class ();" + << endl; + + if (opt != 0 || erase_smart_containers) + { + string rsts (poly_derived ? "rsts" : "sts"); + + os << "using namespace " << db << ";" + << endl + << db << "::connection& conn (" << endl + << db << "::transaction::current ().connection (db));" + << "statements_type& sts (" << endl + << "conn.statement_cache ().find_object<object_type> ());"; + + if (poly_derived) + os << endl + << "root_statements_type& rsts (sts.root_statements ());" + << "ODB_POTENTIALLY_UNUSED (rsts);"; + + os << endl; + + // Call callback (pre_erase). + // + if (!abst) // If we are poly-abstract, then top will always be false. + { + if (poly) + os << "if (top)" << endl; + + os << "callback (db, obj, callback_event::pre_erase);" + << endl; + } + + if (!abst || erase_containers) + { + os << "const id_type& id (object_traits_impl::id (obj));" + << endl; + } + + // Smart containers case. + // + if (opt == 0) + { + // Initialize id image. + // + if (!abst) // If we are poly-abstract, then top will always be false. + { + if (poly) + os << "if (top)" + << "{"; + + os << "id_image_type& i (" << rsts << ".id_image ());" + << "init (i, id);" + << endl; + + os << "binding& idb (" << rsts << ".id_image_binding ());" + << "if (i.version != " << rsts << ".id_image_version () || " << + "idb.version == 0)" + << "{" + << "bind (idb.bind, i);" + << rsts << ".id_image_version (i.version);" + << "idb.version++;" + // Cannot be optimistic. + << "}"; + + if (poly) + os << "}"; + } + + // Erase containers first so that there are no reference + // violations (we don't want to rely on ON DELETE CASCADE + // here since in case of a custom schema, it might not be + // there). + // + + if (erase_containers) + { + os << "extra_statement_cache_type& esc (" << + "sts.extra_statement_cache ());"; + + if (erase_versioned_containers) + os << "const schema_version_migration& svm (" << + "sts.version_migration (" << schema_name << "));"; + + os << endl; + + instance<container_calls> t (container_calls::erase_obj_call); + t->traverse (c); + } + + os << "if (sts.erase_statement ().execute () != 1)" << endl + << "throw object_not_persistent ();" + << endl; + } + // Optimistic case. + // + else + { + // Initialize id + managed column image. + // + if (!abst) // If we are poly-abstract, then top will always be false. + { + if (poly) + os << "if (top)" + << "{"; + + os << "const version_type& v (version (obj));" + << "id_image_type& i (" << rsts << ".id_image ());" + << "init (i, id, &v);" + << endl; + + os << "binding& idb (" << rsts << ".id_image_binding ());" + << "if (i.version != " << rsts << ".id_image_version () ||" << endl + << "idb.version == 0)" + << "{" + << "bind (idb.bind, i);" + << rsts << ".id_image_version (i.version);" + << "idb.version++;" + << rsts << ".optimistic_id_image_binding ().version++;" + << "}"; + + if (poly) + os << "}"; // if (top) + } + + // If this is a derived type in a polymorphic hierarchy, then we + // need to check the version (stored in root) before we go ahead + // and start deleting things. Also use the same code for root with + // containers since it is more efficient than the find_() method + // below. + // + bool svm (false); + if (poly_derived || (poly && erase_containers)) + { + // Only do the check in the top-level call. + // + if (!abst) // If we are poly-abstract, then top will always be false. + { + os << "if (top)" + << "{" + << "version_type v;" + << "root_traits::discriminator_ (" << rsts << ", id, 0, &v);" + << endl; + + os << "if (v != version (obj))" << endl + << "throw object_changed ();" + << "}"; + } + } + else if (erase_containers) + { + // Things get complicated here: we don't want to trash the + // containers and then find out that the versions don't match + // and we therefore cannot delete the object. After all, there + // is no guarantee that the user will abort the transaction. + // In fact, a perfectly reasonable scenario is to reload the + // object, re-check the condition, decide not to delete the + // object, and then commit the transaction. + // + // There doesn't seem to be anything better than first making + // sure we can delete the object, then deleting the container + // data, and then deleting the object. To check that we can + // delete the object we are going to use find_() and then + // compare the versions. A special-purpose SELECT query would + // have been more efficient but it would complicated and bloat + // things significantly. + // + + if (versioned) + { + os << "const schema_version_migration& svm (" << + "sts.version_migration (" << schema_name << "));" + << endl; + svm = true; + } + + os << "if (!find_ (sts, &id" << + (versioned ? ", svm" : "") << "))" << endl + << "throw object_changed ();" + << endl; + + if (delay_freeing_statement_result) + os << "sts.find_statement ().free_result ();" + << endl; + + os << "if (version (sts.image ()) != version (obj))" << endl + << "throw object_changed ();" + << endl; + } + + // Erase containers first so that there are no reference + // violations (we don't want to rely on ON DELETE CASCADE + // here since in case of a custom schema, it might not be + // there). + // + if (erase_containers) + { + os << "extra_statement_cache_type& esc (" << + "sts.extra_statement_cache ());"; + + if (erase_versioned_containers && !svm) + os << "const schema_version_migration& svm (" << + "sts.version_migration (" << schema_name << "));"; + + os << endl; + + instance<container_calls> t (container_calls::erase_obj_call); + t->traverse (c); + } + + const char* st ( + poly_derived ? "erase_statement" : "optimistic_erase_statement"); + + os << "if (sts." << st << " ().execute () != 1)" << endl + << "throw object_changed ();" + << endl; + } + + if (poly_derived) + { + // Call our base last (we erase polymorphic objects from base + // to derived in order not to trigger cascading deletes). + // + os << "base_traits::erase (db, obj, false, false);" + << endl; + } + + if (!abst) // If we are poly-abstract, then top will always be false. + { + if (poly) + os << "if (top)" + << "{"; + + // Note that we don't reset sections since the object is now + // transient and the state of a section in a transient object + // is undefined. + + // Remove from the object cache. + // + os << "pointer_cache_traits::erase (db, id);"; + + // Call callback (post_erase). + // + os << "callback (db, obj, callback_event::post_erase);"; + + if (poly) + os << "}"; + } + } + else + { + os << "callback (db, obj, callback_event::pre_erase);" + << "erase (db, id (obj), true, false);" + << "callback (db, obj, callback_event::post_erase);"; + } + + os << "}"; + } + + // erase (object) bulk + // + if (id != 0 && c.count ("bulk-erase")) + { + os << "void " << traits << "::" << endl + << "erase (database& db," << endl + << "const object_type** objs," << endl + << "std::size_t n," << endl + << "multiple_exceptions& mex)" + << "{"; + + // In non-optimistic case delegate to erase(id). + // + if (opt == 0) + { + os << "id_type a[batch];" + << "const id_type* p[batch];" + << endl + << "for (std::size_t i (0); i != n; ++i)" + << "{" + << "const object_type& obj (*objs[i]);" + << "callback (db, obj, callback_event::pre_erase);" + << "a[i] = id (obj);" + << "p[i] = a + i;" + << "}" + << "n = erase (db, p, n, mex);" + << endl + << "if (mex.fatal ())" << endl // Don't do any extra work. + << "return;" + << endl + << "for (std::size_t i (0); i != n; ++i)" + << "{" + << "if (mex[i] == 0)" << endl // No pending exception. + << "callback (db, *objs[i], callback_event::post_erase);" + << "}"; // for + } + else + { + os << "using namespace " << db << ";" + << endl + << db << "::connection& conn (" << endl + << db << "::transaction::current ().connection (db));" + << "statements_type& sts (" << endl + << "conn.statement_cache ().find_object<object_type> ());" + << endl + << "for (std::size_t i (0); i != n; ++i)" + << "{" + << "const object_type& obj (*objs[i]);" + << "callback (db, obj, callback_event::pre_erase);" + << "const version_type& v (version (obj));" + << "init (sts.id_image (i), id (obj), &v);" + << "}"; + + os << "binding& idb (sts.id_image_binding ());" + //@@ assumption: generate_grow is false or it only affects select + // (like in pgsql). + << "if (idb.version == 0)" + << "{" + << "bind (idb.bind, sts.id_image ());" + << "idb.version++;" + << "sts.optimistic_id_image_binding ().version++;" + << "}" + << "delete_statement& st (sts.optimistic_erase_statement ());" + << "n = st.execute (n, mex);" // Set to actual number of attempted. + << endl + << "for (std::size_t i (0); i != n; ++i)" + << "{" + << "unsigned long long r (st.result (i));" // Sets current in mex. + << endl + << "if (mex[i] != 0)" << endl // Pending exception from result(). + << "continue;" + << endl + << "if (r != 1)" + << "{" + << "mex.insert (i," << endl + //@@ assumption: result_unknown + << "(r == delete_statement::result_unknown)," << endl + << "object_changed ());" + << "continue;" + << "}" + << "if (mex.fatal ())" << endl // Don't do any extra work. + << "continue;" + << endl + << "const object_type& obj (*objs[i]);" + << "pointer_cache_traits::erase (db, id (obj));" + << "callback (db, obj, callback_event::post_erase);" + << "}"; // for + } + + os << "}"; // erase() + } + + // find (id) + // + if (id != 0 && c.default_ctor ()) + { + string rsts (poly_derived ? "rsts" : "sts"); + + os << traits << "::pointer_type" << endl + << traits << "::" << endl + << "find (database& db, const id_type& id)" + << "{" + << "using namespace " << db << ";" + << endl; + + // First check the session. + // + os << "{"; + + if (poly_derived) + os << "root_traits::pointer_type rp (pointer_cache_traits::find (" << + "db, id));" + << endl + << "if (!root_traits::pointer_traits::null_ptr (rp))" << endl + << "return" << endl + << " root_traits::pointer_traits::dynamic_pointer_cast<" << + "object_type> (rp);"; + else + os << "pointer_type p (pointer_cache_traits::find (db, id));" + << endl + << "if (!pointer_traits::null_ptr (p))" << endl + << "return p;"; + + os << "}"; + + os << db << "::connection& conn (" << endl + << db << "::transaction::current ().connection (db));" + << "statements_type& sts (" << endl + << "conn.statement_cache ().find_object<object_type> ());"; + + if (versioned) + os << "const schema_version_migration& svm (" << + "sts.version_migration (" << schema_name << "));"; + + if (poly_derived) + os << "root_statements_type& rsts (sts.root_statements ());"; + + os << endl + << "statements_type::auto_lock l (" << rsts << ");"; + + if (delay_freeing_statement_result) + os << "auto_result ar;"; + + if (poly) + os << "root_traits::discriminator_type d;"; + + os << endl + << "if (l.locked ())" + << "{" + << "if (!find_ (sts, &id" << (versioned ? ", svm" : "") << "))" << endl + << "return pointer_type ();"; + + if (delay_freeing_statement_result) + os << endl + << "ar.set (sts.find_statement (" << (poly_derived ? "depth" : "") << + "));"; + + if (poly) + os << "d = root_traits::discriminator (" << rsts << ".image ());"; + + os << "}"; + + if (poly) + { + // If statements are locked, then get the discriminator by + // executing a special SELECT statement. We need it to be + // able to create an object of the correct dynamic type + // which will be loaded later. + // + os << "else" << endl + << "root_traits::discriminator_ (" << rsts << ", id, &d);" + << endl; + + if (abst) + os << "const info_type& pi (root_traits::map->find (d));" + << endl; + else + os << "const info_type& pi (" << endl + << "d == info.discriminator ? info : root_traits::map->find (d));" + << endl; + } + + // Create the object. + // + if (poly_derived) + { + os << "root_traits::pointer_type rp (pi.create ());" + << "pointer_type p (" << endl + << "root_traits::pointer_traits::static_pointer_cast<object_type> " << + "(rp));" + << "pointer_traits::guard pg (p);" + << endl; + + // Insert it as a root pointer (for non-unique pointers, rp should + // still be valid and for unique pointers this is a no-op). + // + os << "pointer_cache_traits::insert_guard ig (" << endl + << "pointer_cache_traits::insert (db, id, rp));" + << endl; + } + else + { + if (poly) + os << "pointer_type p (pi.create ());"; + else + os << "pointer_type p (" << endl + << "access::object_factory<object_type, pointer_type>::create ());"; + + os << "pointer_traits::guard pg (p);" + << endl; + + os << "pointer_cache_traits::insert_guard ig (" << endl + << "pointer_cache_traits::insert (db, id, p));" + << endl; + } + + os << "object_type& obj (pointer_traits::get_ref (p));" + << endl + << "if (l.locked ())" + << "{" + << "select_statement& st (sts.find_statement (" << + (poly_derived ? "depth" : "") << "));" + << "ODB_POTENTIALLY_UNUSED (st);" + << endl; + + if (poly) + os << "callback_event ce (callback_event::pre_load);" + << "pi.dispatch (info_type::call_callback, db, &obj, &ce);"; + else + os << "callback (db, obj, callback_event::pre_load);"; + + os << "init (obj, sts.image (), &db" << (versioned ? ", svm" : "") << ");"; + + init_value_extra (); + + if (delay_freeing_statement_result) + os << "ar.free ();"; + + os << "load_ (sts, obj, false" << (versioned ? ", svm" : "") << ");"; + + if (poly) + // Load the dynamic part of the object unless static and dynamic + // types are the same. + // + os << endl + << "if (&pi != &info)" + << "{" + << "std::size_t d (depth);" + << "pi.dispatch (info_type::call_load, db, &obj, &d);" + << "}"; + + os << rsts << ".load_delayed (" << (versioned ? "&svm" : "0") << ");" + << "l.unlock ();"; + + if (poly) + os << "ce = callback_event::post_load;" + << "pi.dispatch (info_type::call_callback, db, &obj, &ce);"; + else + os << "callback (db, obj, callback_event::post_load);"; + + os << "pointer_cache_traits::load (ig.position ());" + << "}" + << "else" << endl + << rsts << ".delay_load (id, obj, ig.position ()" << + (poly ? ", pi.delayed_loader" : "") << ");" + << endl; + + os << "ig.release ();" + << "pg.release ();" + << "return p;" + << "}"; + } + + // find (id, obj) + // + if (id != 0) + { + string rsts (poly_derived ? "rsts" : "sts"); + + os << "bool " << traits << "::" << endl + << "find (database& db, const id_type& id, object_type& obj"; + + if (poly) + os << ", bool dyn"; + + os << ")" + << "{"; + + if (poly) + os << "ODB_POTENTIALLY_UNUSED (dyn);" + << endl; + + os << "using namespace " << db << ";" + << endl; + + if (poly) + { + if (!abst) + os << "if (dyn)" << endl + << "{"; + + os << "const std::type_info& t (typeid (obj));"; + + if (!abst) + os << endl + << "if (t != info.type)" + << "{"; + + os << "const info_type& pi (root_traits::map->find (t));" + << "return pi.dispatch (info_type::call_find, db, &obj, &id);"; + + if (!abst) + os << "}" + << "}"; + } + + if (!abst) + { + os << db << "::connection& conn (" << endl + << db << "::transaction::current ().connection (db));" + << "statements_type& sts (" << endl + << "conn.statement_cache ().find_object<object_type> ());"; + + if (versioned) + os << "const schema_version_migration& svm (" << + "sts.version_migration (" << schema_name << "));"; + + if (poly_derived) + os << "root_statements_type& rsts (sts.root_statements ());"; + + // This can only be top-level call so auto_lock must succeed. + // + os << endl + << "statements_type::auto_lock l (" << rsts << ");" + << "assert (l.locked ()) /* Must be a top-level call. */;" + << endl; + + os << "if (!find_ (sts, &id" << + (versioned ? ", svm" : "") << "))" << endl + << "return false;" + << endl; + + os << "select_statement& st (sts.find_statement (" << + (poly_derived ? "depth" : "") << "));" + << "ODB_POTENTIALLY_UNUSED (st);" + << endl; + + if (delay_freeing_statement_result) + os << "auto_result ar (st);"; + + os << "reference_cache_traits::position_type pos (" << endl + << "reference_cache_traits::insert (db, id, obj));" + << "reference_cache_traits::insert_guard ig (pos);" + << endl + << "callback (db, obj, callback_event::pre_load);" + << "init (obj, sts.image (), &db" << + (versioned ? ", svm" : "") << ");"; + + init_value_extra (); + + if (delay_freeing_statement_result) + os << "ar.free ();"; + + os << "load_ (sts, obj, false" << (versioned ? ", svm" : "") << ");" + << rsts << ".load_delayed (" << (versioned ? "&svm" : "0") << ");" + << "l.unlock ();" + << "callback (db, obj, callback_event::post_load);" + << "reference_cache_traits::load (pos);" + << "ig.release ();" + << "return true;"; + } + + os << "}"; + } + + // reload () + // + if (id != 0) + { + string rsts (poly_derived ? "rsts" : "sts"); + + // This implementation is almost exactly the same as find(id, obj) + // above except that it doesn't interract with the object cache and, + // in case of optimistic concurrency, checks if the object actually + // needs to be reloaded. + // + os << "bool " << traits << "::" << endl + << "reload (database& db, object_type& obj"; + + if (poly) + os << ", bool dyn"; + + os << ")" + << "{"; + + if (poly) + os << "ODB_POTENTIALLY_UNUSED (dyn);" + << endl; + + os << "using namespace " << db << ";" + << endl; + + if (poly) + { + if (!abst) + os << "if (dyn)" << endl + << "{"; + + os << "const std::type_info& t (typeid (obj));"; + + if (!abst) + os << endl + << "if (t != info.type)" + << "{"; + + os << "const info_type& pi (root_traits::map->find (t));" + << "return pi.dispatch (info_type::call_reload, db, &obj, 0);"; + + if (!abst) + os << "}" + << "}"; + } + + if (!abst) + { + os << db << "::connection& conn (" << endl + << db << "::transaction::current ().connection (db));" + << "statements_type& sts (" << endl + << "conn.statement_cache ().find_object<object_type> ());"; + + if (versioned) + os << "const schema_version_migration& svm (" << + "sts.version_migration (" << schema_name << "));"; + + if (poly_derived) + os << "root_statements_type& rsts (sts.root_statements ());"; + + // This can only be top-level call so auto_lock must succeed. + // + os << endl + << "statements_type::auto_lock l (" << rsts << ");" + << "assert (l.locked ()) /* Must be a top-level call. */;" + << endl; + + os << "const id_type& id (object_traits_impl::id (obj));" + << "if (!find_ (sts, &id" << + (versioned ? ", svm" : "") << "))" << endl + << "return false;" + << endl; + + os << "select_statement& st (sts.find_statement (" << + (poly_derived ? "depth" : "") << "));" + << "ODB_POTENTIALLY_UNUSED (st);" + << endl; + + if (delay_freeing_statement_result) + os << "auto_result ar (st);" + << endl; + + if (opt != 0) + { + const char* tr (poly_derived ? "root_traits::" : ""); + + os << "if (" << tr << "version (" << rsts << ".image ()) == " << + tr << "version (obj))" << endl + << "return true;" + << endl; + } + + os << "callback (db, obj, callback_event::pre_load);" + << "init (obj, sts.image (), &db" << + (versioned ? ", svm" : "") << ");"; + + init_value_extra (); + + if (delay_freeing_statement_result) + os << "ar.free ();"; + + os << "load_ (sts, obj, true" << (versioned ? ", svm" : "") << ");" + << rsts << ".load_delayed (" << (versioned ? "&svm" : "0") << ");" + << "l.unlock ();" + << "callback (db, obj, callback_event::post_load);" + << "return true;"; + } + + os << "}"; + } + + // load (section) [non-thunk version] + // + if (uss.count (user_sections::count_new | + user_sections::count_load | + (poly ? user_sections::count_load_empty : 0)) != 0) + { + string rsts (poly_derived ? "rsts" : "sts"); + + os << "bool " << traits << "::" << endl + << "load (connection& conn, object_type& obj, section& s" << + (poly ? ", const info_type* pi" : "") << ")" + << "{" + << "using namespace " << db << ";" + << endl; + + if (poly) + { + // Resolve type information if we are doing indirect calls. + // + os << "bool top (pi == 0);" // Top-level call. + << "if (top)" + << "{" + << "const std::type_info& t (typeid (obj));"; + + if (!abst) + os << endl + << "if (t == info.type)" << endl + << "pi = &info;" + << "else" << endl; + + os << "pi = &root_traits::map->find (t);" + << "}"; + } + + // Lock the statements for the object itself. We need to do this since we + // are using the id image and loading of object pointers can overwrite it. + // + os << db << "::connection& c (static_cast<" << db << + "::connection&> (conn));" + << "statements_type& sts (c.statement_cache ()." << + "find_object<object_type> ());"; + + if (versioned) + os << "const schema_version_migration& svm (" << + "sts.version_migration (" << schema_name << "));"; + + if (poly_derived) + os << "root_statements_type& rsts (sts.root_statements ());"; + + os << endl + << "statements_type::auto_lock l (" << rsts << ");"; + + // It this is a top-level call, then auto_lock must succeed. + // + if (poly) + os << "if (top)" << endl; + + os << "assert (l.locked ()) /* Must be a top-level call. */;" + << endl; + + os << "bool r (false);" + << endl; + + // If our poly-base has load sections, then call the base version + // first. Besides checking for sections, it will also initialize + // the id image. + // + if (poly_derived && + buss->count (user_sections::count_total | + user_sections::count_load | + user_sections::count_load_empty) != 0) + { + os << "if (base_traits::load (conn, obj, s, pi))" << endl + << "r = true;" + << endl; + } + else + { + // Initialize id image (all this is equivalent to using rsts). + // + os << "id_image_type& i (sts.id_image ());" + << "init (i, id (obj));" + << endl; + + os << "binding& idb (sts.id_image_binding ());" + << "if (i.version != sts.id_image_version () || idb.version == 0)" + << "{" + << "bind (idb.bind, i);" + << "sts.id_image_version (i.version);" + << "idb.version++;"; + if (opt != 0) + os << "sts.optimistic_id_image_binding ().version++;"; + os << "}"; + } + + // Resolve extra statements if we are doing direct calls. + // + if (!poly) + os << "extra_statement_cache_type& esc (sts.extra_statement_cache ());" + << endl; + + // Dispatch. + // + for (user_sections::iterator i (uss.begin ()); i != uss.end (); ++i) + { + // Skip special sections. + // + if (i->special == user_section::special_version) + continue; + + if (i->load == user_section::load_eager) + continue; + + // Overridden sections are handled by the base. + // + if (poly_derived && i->base != 0) + continue; + + data_member& m (*i->member); + + // Section access is always by reference. + // + member_access& ma (m.get<member_access> ("get")); + if (!ma.synthesized) + os << "// From " << location_string (ma.loc, true) << endl; + + os << "if (!r && &s == &" << ma.translate ("obj") << ")" + << "{"; + + if (!poly) + os << public_name (m) << "_traits::load (esc, obj);"; + else + { + // If this is an empty section, then there may not be any + // overrides. + // + os << "info_type::section_load sl (" << + "pi->find_section_load (" << i->index << "UL));"; + + if (i->load_empty ()) + os << "if (sl != 0)" << endl; + + os << "sl (conn, obj, true);"; + } + + os << "r = true;" + << "}"; + } + + if (poly) + os << "if (top)" + << "{"; + + os << rsts << ".load_delayed (" << (versioned ? "&svm" : "0") << ");" + << "l.unlock ();"; + + if (poly) + os << "}"; + + os << "return r;" + << "}"; + } + + // update (section) [non-thunk version] + // + if (uss.count (user_sections::count_new | + user_sections::count_update | + (poly ? user_sections::count_update_empty : 0)) != 0) + { + os << "bool " << traits << "::" << endl + << "update (connection& conn, const object_type& obj, " << + "const section& s" << (poly ? ", const info_type* pi" : "") << ")" + << "{" + << "using namespace " << db << ";" + << endl; + + if (poly) + { + // Resolve type information if we are doing indirect calls. + // + os << "if (pi == 0)" // Top-level call. + << "{" + << "const std::type_info& t (typeid (obj));"; + + if (!abst) + os << endl + << "if (t == info.type)" << endl + << "pi = &info;" + << "else" << endl; + + os << "pi = &root_traits::map->find (t);" + << "}"; + } + + // If our poly-base has update sections, then call the base version + // first. Besides checking for sections, it will also initialize the + // id image. + // + if (poly_derived && + buss->count (user_sections::count_total | + user_sections::count_update | + user_sections::count_update_empty) != 0) + { + os << "if (base_traits::update (conn, obj, s, pi))" << endl + << "return true;" + << endl; + } + else + { + // Resolve extra statements if we are doing direct calls. + // + os << db << "::connection& c (static_cast<" << db << + "::connection&> (conn));" + << "statements_type& sts (c.statement_cache ()." << + "find_object<object_type> ());"; + + if (!poly) + os << "extra_statement_cache_type& esc (sts.extra_statement_cache ());"; + + os << endl; + + // Initialize id image. This is not necessarily the root of the + // polymorphic hierarchy. + // + if (opt != 0) + os << "const version_type& v (version (obj));"; + + os << "id_image_type& i (sts.id_image ());"; + + os << "init (i, id (obj)" << (opt != 0 ? ", &v" : "") << ");" + << endl; + + os << "binding& idb (sts.id_image_binding ());" + << "if (i.version != sts.id_image_version () || idb.version == 0)" + << "{" + << "bind (idb.bind, i);" + << "sts.id_image_version (i.version);" + << "idb.version++;"; + if (opt != 0) + os << "sts.optimistic_id_image_binding ().version++;"; + os << "}"; + } + + // Dispatch. + // + bool e (false); + for (user_sections::iterator i (uss.begin ()); i != uss.end (); ++i) + { + // Skip special sections. + // + if (i->special == user_section::special_version) + continue; + + // Overridden sections are handled by the base. + // + if (poly_derived && i->base != 0) + continue; + + // Skip read-only sections. Polymorphic sections are always + // (potentially) read-write. + // + if (!poly && i->update_empty ()) + continue; + + data_member& m (*i->member); + + // Section access is always by reference. + // + member_access& ma (m.get<member_access> ("get")); + if (!ma.synthesized) + os << "// From " << location_string (ma.loc, true) << endl; + + if (e) + os << "else "; + else + e = true; + + os << "if (&s == &" << ma.translate ("obj") << ")"; + + if (!poly) + os << public_name (m) << "_traits::update (esc, obj);"; + else + { + // If this is a readonly section, then there may not be any + // overrides. + // + os << "{" + << "info_type::section_update su (" << + "pi->find_section_update (" << i->index << "UL));"; + + if (i->update_empty ()) + { + // For optimistic section, also check that we are not the + // final override, since otherwise we will increment the + // version without updating anything. + // + if (i->optimistic ()) + os << "if (su != 0 && su != info.sections->functions[" << + i->index << "UL].update)" << endl; + else + os << "if (su != 0)" << endl; + } + + os << "su (conn, obj);" + << "}"; + } + } + + os << "else" << endl + << "return false;" + << endl + << "return true;" + << "}"; + } + + // find_ () + // + if (id != 0) + { + os << "bool " << traits << "::" << endl + << "find_ (statements_type& sts," << endl + << "const id_type* id"; + + if (versioned) + os << "," << endl + << "const schema_version_migration& svm"; + + if (poly_derived && !abst) + os << "," << endl + << "std::size_t d"; + + os << ")" + << "{" + << "using namespace " << db << ";" + << endl; + + // Initialize id image. + // + if (poly_derived && !abst) + os << "if (d == depth)" + << "{"; + + os << "id_image_type& i (sts.id_image ());" + << "init (i, *id);" + << endl; + + os << "binding& idb (sts.id_image_binding ());" + << "if (i.version != sts.id_image_version () || idb.version == 0)" + << "{" + << "bind (idb.bind, i);" + << "sts.id_image_version (i.version);" + << "idb.version++;"; + if (opt != 0) + os << "sts.optimistic_id_image_binding ().version++;"; + os << "}"; + + if (poly_derived && !abst) + os << "}"; + + // Rebind data image. + // + os << "image_type& im (sts.image ());" + << "binding& imb (sts.select_image_binding (" << + (poly_derived ? (abst ? "depth" : "d") : "") << "));" + << endl; + + if (poly_derived) + { + os << "if (imb.version == 0 ||" << endl + << "check_version (sts.select_image_versions (), im))" + << "{" + << "bind (imb.bind, 0, 0, im, statement_select" << + (versioned ? ", svm" : "") << ");" + << "update_version (sts.select_image_versions ()," << endl + << "im," << endl + << "sts.select_image_bindings ());" + << "}"; + } + else + { + os << "if (im.version != sts.select_image_version () ||" << endl + << "imb.version == 0)" + << "{" + << "bind (imb.bind, im, statement_select" << + (versioned ? ", svm" : "") << ");" + << "sts.select_image_version (im.version);" + << "imb.version++;" + << "}"; + } + + os << "select_statement& st (sts.find_statement (" << + (poly_derived ? (abst ? "depth" : "d") : "") << "));" + << endl; + + // The dynamic loader SELECT statement can be dynamically empty. + // + if (versioned && poly_derived && !abst) + os << "if (st.empty ())" << endl + << "return true;" + << endl; + + os << "st.execute ();" + << "auto_result ar (st);" + << "select_statement::result r (st.fetch ());" + << endl; + + if (grow) + { + os << "if (r == select_statement::truncated)" + << "{" + << "if (grow (im, sts.select_image_truncated ()" << + (versioned ? ", svm" : "") << + (poly_derived ? (abst ? ", depth" : ", d") : "") << "))" << endl + << "im.version++;" + << endl; + + if (poly_derived) + { + os << "if (check_version (sts.select_image_versions (), im))" + << "{" + << "bind (imb.bind, 0, 0, im, statement_select" << + (versioned ? ", svm" : "") << ");" + << "update_version (sts.select_image_versions ()," << endl + << "im," << endl + << "sts.select_image_bindings ());" + << "st.refetch ();" + << "}"; + } + else + { + os << "if (im.version != sts.select_image_version ())" + << "{" + << "bind (imb.bind, im, statement_select" << + (versioned ? ", svm" : "") << ");" + << "sts.select_image_version (im.version);" + << "imb.version++;" + << "st.refetch ();" + << "}"; + } + + os << "}"; + } + + // If we are delaying, only free the result if it is empty. + // + if (delay_freeing_statement_result) + os << "if (r != select_statement::no_data)" + << "{" + << "ar.release ();" + << "return true;" + << "}" + << "else" << endl + << "return false;"; + else + os << "return r != select_statement::no_data;"; + + os << "}"; + } + + // load_() + // + // Load containers, reset/reload sections. + // + size_t load_containers ( + has_a (c, test_container | include_eager_load, &main_section)); + + if (poly_derived || + load_containers || + uss.count (user_sections::count_new | + user_sections::count_load | + (poly ? user_sections::count_load_empty : 0)) != 0) + { + bool load_versioned_containers ( + load_containers > + has_a (c, + test_container | include_eager_load | + exclude_deleted | exclude_added | exclude_versioned, + &main_section)); + + os << "void " << traits << "::" << endl + << "load_ (statements_type& sts," << endl + << "object_type& obj," << endl + << "bool reload"; + + if (versioned) + os << "," << endl + << "const schema_version_migration& svm"; + + if (poly_derived) + os << "," << endl + << "std::size_t d"; + + os << ")" + << "{" + << "ODB_POTENTIALLY_UNUSED (reload);"; + if (versioned) + os << "ODB_POTENTIALLY_UNUSED (svm);"; + os << endl; + + if (poly_derived) + os << "if (--d != 0)" << endl + << "base_traits::load_ (sts.base_statements (), obj, reload" << + (context::versioned (*poly_base) ? ", svm" : "") << + (poly_base != poly_root ? ", d" : "") << ");" + << endl; + + if (load_containers || + (!poly && uss.count (user_sections::count_new | + user_sections::count_load) != 0)) + os << "extra_statement_cache_type& esc (sts.extra_statement_cache ());" + << endl; + + if (!versioned && ( + load_versioned_containers || + uss.count (user_sections::count_new | + user_sections::count_all | + user_sections::count_versioned_only) != 0)) + { + os << "const schema_version_migration& svm (" << + "sts.version_migration (" << schema_name << "));" + << endl; + } + + if (load_containers) + { + instance<container_calls> t (container_calls::load_call, &main_section); + t->traverse (c); + } + + for (user_sections::iterator i (uss.begin ()); i != uss.end (); ++i) + { + // Skip special sections. + // + if (i->special == user_section::special_version) + continue; + + // Skip overridden sections; they are handled by the base. + // + if (i->base != 0 && poly_derived) + continue; + + data_member& m (*i->member); + + // If the section is soft- added or deleted, check the version. + // + unsigned long long av (added (m)); + unsigned long long dv (deleted (m)); + if (av != 0 || dv != 0) + { + os << "if ("; + + if (av != 0) + os << "svm >= schema_version_migration (" << av << "ULL, true)"; + + if (av != 0 && dv != 0) + os << " &&" << endl; + + if (dv != 0) + os << "svm <= schema_version_migration (" << dv << "ULL, true)"; + + os << ")" + << "{"; + } + + // Section access is always by reference. + // + member_access& ma (m.get<member_access> ("get")); + if (!ma.synthesized) + os << "// From " << location_string (ma.loc, true) << endl; + + if (i->load == user_section::load_eager) + { + // Mark an eager section as loaded. + // + os << ma.translate ("obj") << ".reset (true, false);" + << endl; + continue; + } + + os << "if (reload)" + << "{" + // Reload sections that have been loaded, clear change state. + // + << "if (" << ma.translate ("obj") << ".loaded ())" + << "{"; + + if (!poly) + os << public_name (m) << "_traits::load (esc, obj);"; + else + { + os << "info_type::section_load sl (" << endl + << "root_traits::map->find (typeid (obj)).find_section_load (" << + i->index << "UL));"; + + if (i->load_empty ()) + os << "if (sl != 0)" << endl; + + os << "sl (sts.connection (), obj, true);"; + } + + os << ma.translate ("obj") << ".reset (true, false);" + << "}" + << "}" + << "else" << endl + // Reset to unloaded, unchanged state. + << ma.translate ("obj") << ".reset ();"; + + if (av != 0 || dv != 0) + os << "}"; + else + os << endl; + } + + os << "}"; + } + + // load_() + // + // Load the dynamic part of the object. We don't need it if we are + // poly-abstract. + // + if (poly_derived && !abst) + { + os << "void " << traits << "::" << endl + << "load_ (database& db, root_type& r, std::size_t d)" + << "{" + << "using namespace " << db << ";" + << endl + << "object_type& obj (static_cast<object_type&> (r));" + << db << "::connection& conn (" << endl + << db << "::transaction::current ().connection (db));" + << "statements_type& sts (" << endl + << "conn.statement_cache ().find_object<object_type> ());" + << endl + << "d = depth - d;" // Convert to distance from derived. + << endl; + + if (versioned) + os << "const schema_version_migration& svm (" << + "sts.version_migration (" << schema_name << "));" + << endl; + + // Avoid trying to execute an empty SELECT statement. + // + if (empty_depth != 0) + os << "if (d > " << (poly_depth - empty_depth) << "UL)" + << "{"; + + os << "if (!find_ (sts, 0" << (versioned ? ", svm" : "") << ", d))" << endl + << "throw object_not_persistent ();" // Database inconsistency. + << endl; + + os << "select_statement& st (sts.find_statement (d));" + << "ODB_POTENTIALLY_UNUSED (st);" + << endl; + + // The resulting SELECT statement may end up being dynamically empty. + // + if (versioned) + os << "if (!st.empty ())" + << "{"; + + if (delay_freeing_statement_result) + os << "auto_result ar (st);"; + + os << "init (obj, sts.image (), &db" << (versioned ? ", svm" : "") << + ", d);"; + + init_value_extra (); + + if (delay_freeing_statement_result) + os << "ar.free ();"; + + if (versioned) + os << "}"; + + if (empty_depth != 0) + os << "}"; + + os << "load_ (sts, obj, false, " << (versioned ? "svm, " : "") << "d);" + << "}"; + } + + // discriminator_ () + // + if (poly && !poly_derived) + { + os << "void " << traits << "::" << endl + << "discriminator_ (statements_type& sts," << endl + << "const id_type& id," << endl + << "discriminator_type* pd"; + + if (opt != 0) + os << "," << endl + << "version_type* pv"; + + os << ")" + << "{" + << "using namespace " << db << ";" + << endl; + + // Initialize id image. + // + os << "id_image_type& idi (sts.discriminator_id_image ());" + << "init (idi, id);" + << endl; + + os << "binding& idb (sts.discriminator_id_image_binding ());" + << "if (idi.version != sts.discriminator_id_image_version () ||" << endl + << "idb.version == 0)" + << "{" + << "bind (idb.bind, idi" << (opt != 0 ? ", false" : "") << ");" + << "sts.discriminator_id_image_version (idi.version);" + << "idb.version++;" + << "}"; + + // Rebind data image. + // + os << "discriminator_image_type& i (sts.discriminator_image ());" + << "binding& imb (sts.discriminator_image_binding ());" + << endl + << "if (i.version != sts.discriminator_image_version () ||" << endl + << "imb.version == 0)" + << "{" + // Generate bind code inline. For now discriminator is simple + // value so we don't need statement kind (sk). + // + << bind_vector << " b (imb.bind);" + << "std::size_t n (0);" + << "{"; + bind_discriminator_member_->traverse (*discriminator); + os << "}"; + + if (opt != 0) + { + os << "n++;" // For now discriminator is a simple value. + << "{"; + bind_version_member_->traverse (*opt); + os << "}"; + } + + os << "sts.discriminator_image_version (i.version);" + << "imb.version++;" + << "}"; + + os << "{" + << "select_statement& st (sts.find_discriminator_statement ());" + << "st.execute ();" + << "auto_result ar (st);" + << "select_statement::result r (st.fetch ());" + << endl + << "if (r == select_statement::no_data)" + << "{"; + + if (opt != 0) + os << "if (pv != 0)" << endl + << "throw object_changed ();" + << "else" << endl; + + os << "throw object_not_persistent ();" + << "}"; + + if (generate_grow && + (context::grow (*discriminator) || + (opt != 0 && context::grow (*opt)))) + { + os << "else if (r == select_statement::truncated)" + << "{"; + + // Generate grow code inline. + // + os << "bool grew (false);" + << truncated_vector << " t (sts.discriminator_image_truncated ());" + << endl; + + index_ = 0; + grow_discriminator_member_->traverse (*discriminator); + + if (opt != 0) + grow_version_member_->traverse (*opt); + + os << "if (grew)" << endl + << "i.version++;" + << endl; + + os << "if (i.version != sts.discriminator_image_version ())" + << "{" + // Generate bind code inline. The same code as above. + // + << bind_vector << " b (imb.bind);" + << "std::size_t n (0);" + << "{"; + bind_discriminator_member_->traverse (*discriminator); + os << "}"; + + if (opt != 0) + { + os << "n++;" // For now discriminator is a simple value. + << "{"; + bind_version_member_->traverse (*opt); + os << "}"; + } + + os << "sts.discriminator_image_version (i.version);" + << "imb.version++;" + << "st.refetch ();" + << "}" + << "}"; + } + + // Discriminator cannot be long data (no streaming). + // + os << "}"; + + // Initialize value inline instead of generating a separate + // init() function. For now discriminator is simple value so + // we don't need the database (db). + // + os << "if (pd != 0)" + << "{" + << "discriminator_type& d (*pd);"; + init_named_discriminator_value_member_->traverse (*discriminator); + os << "}"; + + if (opt != 0) + { + os << "if (pv != 0)" + << "{" + << "version_type& v (*pv);"; + init_named_version_value_member_->traverse (*opt); + os << "}"; + } + + os << "}"; + } + + if (options.generate_query ()) + { + char const* result_type; + if (poly) + result_type = "polymorphic_object_result_impl<object_type>"; + else if (id != 0) + result_type = "object_result_impl<object_type>"; + else + result_type = "no_id_object_result_impl<object_type>"; + + string sep (versioned || query_optimize ? "\n" : " "); + + // Unprepared. + // + if (!options.omit_unprepared ()) + { + // query () + // + os << "result< " << traits << "::object_type >" << endl + << traits << "::" << endl + << "query (database& db, const query_base_type& q)" + << "{" + << "using namespace " << db << ";" + << "using odb::details::shared;" + << "using odb::details::shared_ptr;" + << endl; + + os << db << "::connection& conn (" << endl + << db << "::transaction::current ().connection (db));" + << endl + << "statements_type& sts (" << endl + << "conn.statement_cache ().find_object<object_type> ());"; + + if (versioned) + os << "const schema_version_migration& svm (" << + "sts.version_migration (" << schema_name << "));"; + + os << endl; + + // Rebind the image if necessary. + // + os << "image_type& im (sts.image ());" + << "binding& imb (sts.select_image_binding (" << + (poly_derived ? "depth" : "") << "));" + << endl; + + if (poly_derived) + { + os << "if (imb.version == 0 ||" << endl + << "check_version (sts.select_image_versions (), im))" + << "{" + << "bind (imb.bind, 0, 0, im, statement_select" << + (versioned ? ", svm" : "") << ");" + << "update_version (sts.select_image_versions ()," << endl + << "im," << endl + << "sts.select_image_bindings ());" + << "}"; + } + else + { + os << "if (im.version != sts.select_image_version () ||" << endl + << "imb.version == 0)" + << "{" + << "bind (imb.bind, im, statement_select" << + (versioned ? ", svm" : "") << ");" + << "sts.select_image_version (im.version);" + << "imb.version++;" + << "}"; + } + + os << "std::string text (query_statement);" + << "if (!q.empty ())" + << "{" + << "text += " << strlit (sep) << ";" + << "text += q.clause ();" + << "}"; + + os << "q.init_parameters ();" + << "shared_ptr<select_statement> st (" << endl + << "new (shared) select_statement (" << endl; + object_query_statement_ctor_args ( + c, "q", versioned || query_optimize, false); + os << "));" << endl + << "st->execute ();"; + + post_query_ (c, true); + + os << endl + << "shared_ptr< odb::" << result_type << " > r (" << endl + << "new (shared) " << db << "::" << result_type << " (" << endl + << "q, st, sts, " << (versioned ? "&svm" : "0") << "));" + << endl + << "return result<object_type> (r);" + << "}"; + + // query(odb::query_base) + // + if (multi_dynamic) + os << "result< " << traits << "::object_type >" << endl + << traits << "::" << endl + << "query (database& db, const odb::query_base& q)" + << "{" + << "return query (db, query_base_type (q));" + << "}"; + } + + // erase_query + // + os << "unsigned long long " << traits << "::" << endl + << "erase_query (database& db, const query_base_type& q)" + << "{" + << "using namespace " << db << ";" + << endl + << db << "::connection& conn (" << endl + << db << "::transaction::current ().connection (db));" + << endl + << "std::string text (erase_query_statement);" + << "if (!q.empty ())" + << "{" + << "text += ' ';" + << "text += q.clause ();" + << "}" + << "q.init_parameters ();" + << "delete_statement st (" << endl; + object_erase_query_statement_ctor_args (c); + os << ");" + << endl + << "return st.execute ();" + << "}"; + + // erase_query(odb::query_base) + // + if (multi_dynamic) + os << "unsigned long long " << traits << "::" << endl + << "erase_query (database& db, const odb::query_base& q)" + << "{" + << "return erase_query (db, query_base_type (q));" + << "}"; + + // Prepared. Very similar to unprepared but has some annoying variations + // that make it difficult to factor out something common. + // + if (options.generate_prepared ()) + { + // prepare_query + // + os << "odb::details::shared_ptr<prepared_query_impl>" << endl + << traits << "::" << endl + << "prepare_query (connection& c, const char* n, " << + "const query_base_type& q)" + << "{" + << "using namespace " << db << ";" + << "using odb::details::shared;" + << "using odb::details::shared_ptr;" + << endl; + + os << db << "::connection& conn (" << endl + << "static_cast<" << db << "::connection&> (c));" + << endl + << "statements_type& sts (" << endl + << "conn.statement_cache ().find_object<object_type> ());"; + + if (versioned) + os << "const schema_version_migration& svm (" << + "sts.version_migration (" << schema_name << "));"; + + os << endl; + + // Rebind the image if necessary. + // + os << "image_type& im (sts.image ());" + << "binding& imb (sts.select_image_binding (" << + (poly_derived ? "depth" : "") << "));" + << endl; + + if (poly_derived) + { + os << "if (imb.version == 0 ||" << endl + << "check_version (sts.select_image_versions (), im))" + << "{" + << "bind (imb.bind, 0, 0, im, statement_select" << + (versioned ? ", svm" : "") << ");" + << "update_version (sts.select_image_versions ()," << endl + << "im," << endl + << "sts.select_image_bindings ());" + << "}"; + } + else + { + os << "if (im.version != sts.select_image_version () ||" << endl + << "imb.version == 0)" + << "{" + << "bind (imb.bind, im, statement_select" << + (versioned ? ", svm" : "") << ");" + << "sts.select_image_version (im.version);" + << "imb.version++;" + << "}"; + } + + os << "std::string text (query_statement);" + << "if (!q.empty ())" + << "{" + << "text += " << strlit (sep) << ";" + << "text += q.clause ();" + << "}"; + + os << "shared_ptr<" << db << "::prepared_query_impl> r (" << endl + << "new (shared) " << db << "::prepared_query_impl (conn));" + << "r->name = n;" + << "r->execute = &execute_query;" + << "r->query = q;" + << "r->stmt.reset (" << endl + << "new (shared) select_statement (" << endl; + object_query_statement_ctor_args ( + c, "r->query", versioned || query_optimize, true); + os << "));" + << endl + << "return r;" + << "}"; + + // prepare_query(odb::query_base) + // + if (multi_dynamic) + os << "odb::details::shared_ptr<prepared_query_impl>" << endl + << traits << "::" << endl + << "prepare_query (connection& c, const char* n, " << + "const odb::query_base& q)" + << "{" + << "return prepare_query (c, n, query_base_type (q));" + << "}"; + + // execute_query + // + os << "odb::details::shared_ptr<result_impl>" << endl + << traits << "::" << endl + << "execute_query (prepared_query_impl& q)" + << "{" + << "using namespace " << db << ";" + << "using odb::details::shared;" + << "using odb::details::shared_ptr;" + << endl + << db << "::prepared_query_impl& pq (" << endl + << "static_cast<" << db << "::prepared_query_impl&> (q));" + << "shared_ptr<select_statement> st (" << endl + << "odb::details::inc_ref (" << endl + << "static_cast<select_statement*> (pq.stmt.get ())));" + << endl; + + os << db << "::transaction& tr (" << db << "::transaction::current ());" + << endl + << "// The connection used by the current transaction and the" << endl + << "// one used to prepare this statement must be the same." << endl + << "//" << endl + << "assert (q.verify_connection (tr));" + << endl + << "statements_type& sts (" << endl + << "st->connection ().statement_cache ().find_object<object_type> ());"; + + if (versioned) + os << "const schema_version_migration& svm (" << + "sts.version_migration (" << schema_name << "));"; + + os << endl; + + // Rebind the image if necessary. + // + os << "image_type& im (sts.image ());" + << "binding& imb (sts.select_image_binding (" << + (poly_derived ? "depth" : "") << "));" + << endl; + + if (poly_derived) + { + os << "if (imb.version == 0 ||" << endl + << "check_version (sts.select_image_versions (), im))" + << "{" + << "bind (imb.bind, 0, 0, im, statement_select" << + (versioned ? ", svm" : "") << ");" + << "update_version (sts.select_image_versions ()," << endl + << "im," << endl + << "sts.select_image_bindings ());" + << "}"; + } + else + { + os << "if (im.version != sts.select_image_version () ||" << endl + << "imb.version == 0)" + << "{" + << "bind (imb.bind, im, statement_select" << + (versioned ? ", svm" : "") << ");" + << "sts.select_image_version (im.version);" + << "imb.version++;" + << "}"; + } + + os << "pq.query.init_parameters ();" + << "st->execute ();"; + post_query_ (c, false); + + os << endl + << "return shared_ptr<result_impl> (" << endl + << "new (shared) " << db << "::" << result_type << " (" << endl + << "pq.query, st, sts, " << (versioned ? "&svm" : "0") << "));" + << "}"; + } + } + + // Generate function table registration for dynamic multi-database + // support. + // + if (multi_dynamic) + { + string fn (flat_name (type)); + string dt ("access::object_traits_impl< " + type + ", id_common >"); + + os << "static const" << endl + << dt << "::" << endl + << "function_table_type function_table_" << fn << "_ =" + << "{"; + + // persist () + // + os << "&" << traits << "::persist"; + + if (id != 0) + { + // find (id) + // + if (c.default_ctor ()) + os << "," << endl + << "&" << traits << "::find"; + + // find (id, obj) + // + os << "," << endl + << "&" << traits << "::find"; + + // reload () + // + os << "," << endl + << "&" << traits << "::reload"; + + // update () + // + if (!readonly || poly) + os << "," << endl + << "&" << traits << "::update"; + + // erase () + // + os << "," << endl + << "&" << traits << "::erase"; + + os << "," << endl + << "&" << traits << "::erase"; + + // Sections. + // + if (uss.count (user_sections::count_total | + user_sections::count_load | + (poly ? user_sections::count_load_empty : 0)) != 0) + os << "," << endl + << "&" << traits << "::load"; + + if (uss.count (user_sections::count_total | + user_sections::count_update | + (poly ? user_sections::count_update_empty : 0)) != 0) + os << "," << endl + << "&" << traits << "::update"; + } + + if (options.generate_query ()) + { + if (!options.omit_unprepared ()) + os << "," << endl + << "&" << traits << "::query"; + + os << "," << endl + << "&" << traits << "::erase_query"; + + if (options.generate_prepared ()) + { + os << "," << endl + << "&" << traits << "::prepare_query"; + + os << "," << endl + << "&" << traits << "::execute_query"; + } + } + + os << "};"; + + os << "static const object_function_table_entry< " << type << ", " << + "id_" << db << " >" << endl + << "function_table_entry_" << fn << "_ (" << endl + << "&function_table_" << fn << "_);" + << endl; + } +} + +void relational::source::class_:: +traverse_view (type& c) +{ + view_query& vq (c.get<view_query> ("query")); + + // Only process the view query if it is versioned since the query text + // (e.g., in the native view) must be structured. We also shouldn't try + // to optimize JOINs (since the objects are JOINed explicitly by the + // user), unless we are adding poly-base/derived JOINs. + // + bool versioned (context::versioned (c)); + bool query_optimize (false); + + string const& type (class_fq_name (c)); + string traits ("access::view_traits_impl< " + type + ", id_" + + db.string () + " >"); + + size_t columns (column_count (c).total); + + // Schema name as a string literal or empty. + // + string schema_name (options.schema_name ()[db]); + if (!schema_name.empty ()) + schema_name = strlit (schema_name); + + // Generate the from-list. Also collect relationships via which + // the objects are joined. + // + strings from; + view_relationship_map rel_map; + + if (vq.kind == view_query::condition) + { + view_objects& objs (c.get<view_objects> ("objects")); + + for (view_objects::iterator i (objs.begin ()); i != objs.end (); ++i) + { + bool first (i == objs.begin ()); + string l; + + // + // Tables. + // + + if (i->kind == view_object::table) + { + if (first) + { + l = "FROM "; + l += quote_id (i->tbl_name); + + if (!i->alias.empty ()) + l += (need_alias_as ? " AS " : " ") + quote_id (i->alias); + + l += from_trailer (c); + + from.push_back (l); + continue; + } + + l = join_syntax (*i); + l += ' '; + l += quote_id (i->tbl_name); + + if (!i->alias.empty ()) + l += (need_alias_as ? " AS " : " ") + quote_id (i->alias); + + if (i->join == view_object::cross) // No ON condition for CROSS JOIN. + { + from.push_back (l); + continue; + } + + semantics::scope& scope ( + dynamic_cast<semantics::scope&> (*unit.find (i->scope))); + + expression e ( + translate_expression ( + c, i->cond, scope, i->loc, "table")); + + if (e.kind != expression::literal) + { + error (i->loc) << "invalid join condition in db pragma " << + "table" << endl; + throw operation_failed (); + } + + l += " ON"; + + // Add the pragma location for easier error tracking. + // + from.push_back (l); + from.push_back ("// From " + location_string (i->loc, true)); + from.push_back (e.value); + continue; + } + + // + // Objects. + // + semantics::class_& o (*i->obj); + + bool poly (polymorphic (o)); + size_t poly_depth (poly ? polymorphic_depth (o) : 1); + + string alias (i->alias); + + // For polymorphic objects, alias is just a prefix. + // + if (poly && !alias.empty ()) + alias += "_" + table_name (o).uname (); + + // First object. + // + if (first) + { + l = "FROM "; + l += table_qname (o); + + if (!alias.empty ()) + l += (need_alias_as ? " AS " : " ") + quote_id (alias); + + l += from_trailer (c); + + from.push_back (l); + + if (poly_depth != 1) + { + bool t (true); //@@ (im)perfect forwarding + size_t d (poly_depth - 1); //@@ (im)perfect forward. + instance<polymorphic_object_joins> j (o, t, d, i->alias); + j->traverse (polymorphic_base (o)); + + from.insert (from.end (), j->begin (), j->end ()); + query_optimize = query_optimize || !j->joins.empty (); + } + continue; + } + + semantics::scope& scope ( + dynamic_cast<semantics::scope&> (*unit.find (i->scope))); + + expression e (i->join == view_object::cross + ? expression ("") // Dummy literal expression. + : translate_expression ( + c, i->cond, scope, i->loc, "object")); + + // Literal expression. + // + if (e.kind == expression::literal) + { + l = join_syntax (*i); + l += ' '; + l += table_qname (o); + + if (!alias.empty ()) + l += (need_alias_as ? " AS " : " ") + quote_id (alias); + + if (i->join == view_object::cross) // No ON condition for CROSS JOIN. + { + from.push_back (l); + continue; + } + + l += " ON"; + + // Add the pragma location for easier error tracking. + // + from.push_back (l); + from.push_back ("// From " + location_string (i->loc, true)); + from.push_back (e.value); + + if (poly_depth != 1) + { + bool t (true); //@@ (im)perfect forwarding + size_t d (poly_depth - 1); //@@ (im)perfect forward. + instance<polymorphic_object_joins> j (o, t, d, i->alias); + j->traverse (polymorphic_base (o)); + + from.insert (from.end (), j->begin (), j->end ()); + query_optimize = query_optimize || !j->joins.empty (); + } + continue; + } + + // We have an object relationship (pointer) for which we need + // to come up with the corresponding JOIN condition. If this + // is a to-many relationship, then we first need to JOIN the + // container table. This code is similar to object_joins. + // + // Note that this cannot be CROSS JOIN; we've handled that case + // above. + // + using semantics::data_member; + + data_member& m (*e.member_path.back ()); + data_member_path* imp (inverse (m)); + + // Resolve the pointed-to object to view_object and do + // some sanity checks while at it. + // + semantics::class_* c (0); + + if (container (m)) + c = object_pointer (container_vt (m)); + else + c = object_pointer (utype (m)); + + view_object* vo (0); + + // Check if the pointed-to object has been previously associated + // with this view and is unambiguous. A pointer to ourselves is + // always assumed to point to this association. + // + if (&o == c) + vo = &*i; + else + { + bool ambig (false); + + for (view_objects::iterator j (objs.begin ()); j != i; ++j) + { + if (j->obj != c) + continue; + + if (vo == 0) + { + vo = &*j; + continue; + } + + // If it is the first ambiguous object, issue the + // error. + // + if (!ambig) + { + error (i->loc) << "pointed-to object '" << class_name (*c) << + "' is ambiguous" << endl; + info (i->loc) << "candidates are:" << endl; + info (vo->loc) << " '" << vo->name () << "'" << endl; + ambig = true; + } + + info (j->loc) << " '" << j->name () << "'" << endl; + } + + if (ambig) + { + info (i->loc) << "use the other side of the relationship " << + "or full join condition clause in db pragma object to " << + "resolve this ambiguity" << endl; + throw operation_failed (); + } + + if (vo == 0) + { + error (i->loc) << "pointed-to object '" << class_name (*c) << + "' specified in the join condition has not been " << + "previously associated with this view" << endl; + throw operation_failed (); + } + } + + // JOIN relationship points to us: + // vo - us + // e.vo - other side + // e.member_path - in other side + // + // JOIN relationship points to other side: + // vo - other side + // e.vo - us + // e.member_path - in us + // + if (imp == 0) + rel_map.insert (make_pair (e.member_path, make_pair (e.vo, vo))); + else + rel_map.insert (make_pair (*imp, make_pair (vo, e.vo))); + + // Left and right-hand side table names. + // + qname lt; + { + using semantics::class_; + + class_& o (*e.vo->obj); + string const& a (e.vo->alias); + + if (class_* root = polymorphic (o)) + { + // If the object is polymorphic, then figure out which of the + // bases this member comes from and use the corresponding + // table. + // + class_* c ( + &static_cast<class_&> ( + e.member_path.front ()->scope ())); + + // If this member's class is not polymorphic (root uses reuse + // inheritance), then use the root table. + // + if (!polymorphic (*c)) + c = root; + + qname const& t (table_name (*c)); + + if (a.empty ()) + lt = t; + else + lt = qname (a + "_" + t.uname ()); + } + else + lt = a.empty () ? table_name (o) : qname (a); + } + + qname rt; + { + qname t (table_name (*vo->obj)); + string const& a (vo->alias); + rt = a.empty () + ? t + : qname (polymorphic (*vo->obj) ? a + "_" + t.uname () : a); + } + + // First join the container table if necessary. + // + semantics::type* cont (container (imp != 0 ? *imp->back () : m)); + + string ct; // Container table. + if (cont != 0) + { + l = join_syntax (*i); + l += ' '; + + // The same relationship can be used by multiple associated + // objects. So if the object that contains this container has + // an alias, then also construct one for the table that we + // are joining. + // + { + using semantics::class_; + + qname t; + + // In a polymorphic hierarchy the member can be in a base (see + // above). + // + if (class_* root = polymorphic (imp != 0 ? *vo->obj : *e.vo->obj)) + { + class_* c; + if (imp == 0) + { + c = &static_cast<class_&> (e.member_path.front ()->scope ()); + + if (!polymorphic (*c)) + c = root; + + t = table_name (*c, e.member_path); + } + else + { + c = &static_cast<class_&> (imp->front ()->scope ()); + + if (!polymorphic (*c)) + c = root; + + t = table_name (*c, *imp); + } + } + else + t = imp != 0 + ? table_name (*vo->obj, *imp) + : table_name (*e.vo->obj, e.member_path); + + // The tricky part is to figure out which view_object, vo + // or e.vo we should use. We want to use the alias from the + // object that actually contains this container. The following + // might not make intuitive sense, but it has been verified + // with the truth table. + // + string const& a (imp != 0 ? vo->alias : e.vo->alias); + + if (a.empty ()) + { + ct = quote_id (t); + l += ct; + } + else + { + ct = quote_id (a + '_' + t.uname ()); + l += quote_id (t); + l += (need_alias_as ? " AS " : " ") + ct; + } + } + + l += " ON"; + from.push_back (l); + + // If we are the pointed-to object, then we have to turn + // things around. This is necessary to have the proper + // JOIN order. There seems to be a pattern there but it + // is not yet intuitively clear what it means. + // + instance<object_columns_list> c_cols; // Container columns. + instance<object_columns_list> o_cols; // Object columns. + + qname* ot; // Object table (either lt or rt). + + if (imp != 0) + { + semantics::data_member& imb (*imp->back ()); + + if (&o == c) + { + // container.value = pointer.id + // + data_member_path& id (*id_member (*e.vo->obj)); + + c_cols->traverse (imb, utype (id), "value", "value"); + o_cols->traverse (id); + ot = < + } + else + { + // container.id = pointed-to.id + // + data_member_path& id (*id_member (*vo->obj)); + + c_cols->traverse (imb, utype (id), "id", "object_id", vo->obj); + o_cols->traverse (id); + ot = &rt; + } + } + else + { + if (&o == c) + { + // container.id = pointer.id + // + data_member_path& id (*id_member (*e.vo->obj)); + + c_cols->traverse ( + m, utype (id), "id", "object_id", e.vo->obj); + o_cols->traverse (id); + ot = < + } + else + { + // container.value = pointed-to.id + // + data_member_path& id (*id_member (*vo->obj)); + + c_cols->traverse (m, utype (id), "value", "value"); + o_cols->traverse (id); + ot = &rt; + } + } + + for (object_columns_list::iterator b (c_cols->begin ()), i (b), + j (o_cols->begin ()); i != c_cols->end (); ++i, ++j) + { + l.clear (); + + if (i != b) + l += "AND "; + + l += ct; + l += '.'; + l += quote_id (i->name); + l += '='; + l += quote_id (*ot); + l += '.'; + l += quote_id (j->name); + + from.push_back (strlit (l)); + } + } + + // If we have already joined the container with the desired + // join type, then use LEFT JOIN to join the object to the + // container. This is the right thing to do since an entry + // in the container can only point (either via id or value) + // to a single object. + // + l = (cont == 0 ? join_syntax (*i) : "LEFT JOIN"); + l += ' '; + + l += table_qname (o); + + if (!alias.empty ()) + l += (need_alias_as ? " AS " : " ") + quote_id (alias); + + l += " ON"; + from.push_back (l); + + if (cont != 0) + { + instance<object_columns_list> c_cols; // Container columns. + instance<object_columns_list> o_cols; // Object columns. + + qname* ot; // Object table (either lt or rt). + + if (imp != 0) + { + semantics::data_member& imb (*imp->back ()); + + if (&o == c) + { + // container.id = pointed-to.id + // + data_member_path& id (*id_member (*vo->obj)); + + c_cols->traverse (imb, utype (id), "id", "object_id", vo->obj); + o_cols->traverse (id); + ot = &rt; + } + else + { + // container.value = pointer.id + // + data_member_path& id (*id_member (*e.vo->obj)); + + c_cols->traverse (imb, utype (id), "value", "value"); + o_cols->traverse (id); + ot = < + } + } + else + { + if (&o == c) + { + // container.value = pointed-to.id + // + data_member_path& id (*id_member (*vo->obj)); + + c_cols->traverse (m, utype (id), "value", "value"); + o_cols->traverse (id); + ot = &rt; + } + else + { + // container.id = pointer.id + // + data_member_path& id (*id_member (*e.vo->obj)); + + c_cols->traverse (m, utype (id), "id", "object_id", e.vo->obj); + o_cols->traverse (id); + ot = < + } + } + + for (object_columns_list::iterator b (c_cols->begin ()), i (b), + j (o_cols->begin ()); i != c_cols->end (); ++i, ++j) + { + l.clear (); + + if (i != b) + l += "AND "; + + l += ct; + l += '.'; + l += quote_id (i->name); + l += '='; + l += quote_id (*ot); + l += '.'; + l += quote_id (j->name); + + from.push_back (strlit (l)); + } + } + else + { + instance<object_columns_list> l_cols; + instance<object_columns_list> r_cols; + + if (imp != 0) + { + // our.id = pointed-to.pointer + // + l_cols->traverse (*id_member (*e.vo->obj)); + r_cols->traverse (*imp->back (), column_prefix (*imp)); + } + else + { + // our.pointer = pointed-to.id + // + l_cols->traverse (*e.member_path.back (), + column_prefix (e.member_path)); + r_cols->traverse (*id_member (*vo->obj)); + } + + for (object_columns_list::iterator b (l_cols->begin ()), i (b), + j (r_cols->begin ()); i != l_cols->end (); ++i, ++j) + { + l.clear (); + + if (i != b) + l += "AND "; + + l += quote_id (lt); + l += '.'; + l += quote_id (i->name); + l += '='; + l += quote_id (rt); + l += '.'; + l += quote_id (j->name); + + from.push_back (strlit (l)); + } + } + + if (poly_depth != 1) + { + bool t (true); //@@ (im)perfect forwarding + size_t d (poly_depth - 1); //@@ (im)perfect forward. + instance<polymorphic_object_joins> j (o, t, d, i->alias); + j->traverse (polymorphic_base (o)); + + from.insert (from.end (), j->begin (), j->end ()); + query_optimize = query_optimize || !j->joins.empty (); + } + } // End JOIN-generating for-loop. + } + + // Check that pointed-to objects inside objects that we are loading + // have session support enabled (required to have a shared copy). + // Also see if we need to throw if there is no session. + // + bool need_session (false); + if (vq.kind == view_query::condition) + { + view_objects& objs (c.get<view_objects> ("objects")); + for (view_objects::iterator i (objs.begin ()); i != objs.end (); ++i) + { + if (i->kind != view_object::object || i->ptr == 0) + continue; // Not an object or not loaded. + + instance<view_object_check> t (*i, rel_map); + t->traverse (*i->obj); + need_session = need_session || t->session_; + } + } + + os << "// " << class_name (c) << endl + << "//" << endl + << endl; + + view_extra (c); + + // query_columns + // + if (c.get<size_t> ("object-count") != 0) + view_query_columns_type_->traverse (c); + + // + // Functions. + // + + // grow () + // + if (generate_grow && columns != 0) + { + os << "bool " << traits << "::" << endl + << "grow (image_type& i," << endl + << truncated_vector << " t"; + + if (versioned) + os << "," << endl + << "const schema_version_migration& svm"; + + os << ")" + << "{" + << "ODB_POTENTIALLY_UNUSED (i);" + << "ODB_POTENTIALLY_UNUSED (t);"; + + if (versioned) + os << "ODB_POTENTIALLY_UNUSED (svm);"; + + os << endl + << "bool grew (false);" + << endl; + + index_ = 0; + names (c, grow_member_names_); + + os << "return grew;" + << "}"; + } + + // bind (image_type) + // + if (columns != 0) + { + os << "void " << traits << "::" << endl + << "bind (" << bind_vector << " b," << endl + << "image_type& i"; + + if (versioned) + os << "," << endl + << "const schema_version_migration& svm"; + + os << ")" + << "{"; + + if (versioned) + os << "ODB_POTENTIALLY_UNUSED (svm);" + << endl; + + os << "using namespace " << db << ";" + << endl + << db << "::statement_kind sk (statement_select);" + << "ODB_POTENTIALLY_UNUSED (sk);" + << endl + << "std::size_t n (0);" + << endl; + + names (c, bind_member_names_); + + os << "}"; + } + + // init (view, image) + // + if (columns != 0) + { + os << "void " << traits << "::" << endl + << "init (view_type& o," << endl + << "const image_type& i," << endl + << "database* db"; + + if (versioned) + os << "," << endl + << "const schema_version_migration& svm"; + + os << ")" + << "{" + << "ODB_POTENTIALLY_UNUSED (o);" + << "ODB_POTENTIALLY_UNUSED (i);" + << "ODB_POTENTIALLY_UNUSED (db);"; + + if (versioned) + os << "ODB_POTENTIALLY_UNUSED (svm);"; + + os << endl; + + if (need_session) + os << "if (!" << options.session_type () << "::_has_cache ())" << endl + << "throw session_required ();" + << endl; + + // Note: db must be not NULL in order to load pointers. + // + if (has_a (c, test_pointer)) + os << db << "::connection& conn (" << endl + << db << "::transaction::current ().connection (*db));" + << endl; + + names (c, init_view_pointer_member_pre_names_); + names (c, init_value_member_names_); + names (c, init_view_pointer_member_post_names_); + + os << "}"; + } + + // query_statement() + // + if (vq.kind != view_query::runtime) + { + os << traits << "::query_base_type" << endl + << traits << "::" << endl + << "query_statement (const query_base_type& q)" + << "{"; + + if (vq.kind == view_query::complete_select || + vq.kind == view_query::complete_execute) + { + os << "query_base_type r (" << endl; + + bool ph (false); + bool pred (vq.kind == view_query::complete_select); + + if (!vq.literal.empty ()) + { + // See if we have the '(?)' placeholder. + // + // @@ Ideally we would need to make sure we don't match + // this inside strings and quoted identifier. So the + // proper way to handle this would be to tokenize the + // statement using sql_lexer, once it is complete enough. + // + string::size_type p (vq.literal.find ("(?)")); + + if (p != string::npos) + { + ph = true; + // For the SELECT query we keep the parenthesis in (?) and + // also handle the case where the query expression is empty. + // + if (pred) + os << strlit (string (vq.literal, 0, p + 1)) << " +" << endl + << "(q.empty () ? query_base_type::true_expr : q) +" << endl + << strlit (string (vq.literal, p + 2)); + else + { + os << strlit (string (vq.literal, 0, p)) << " + q"; + + p += 3; + if (p != vq.literal.size ()) + os << " + " << strlit (string (vq.literal, p)); + } + } + else + os << strlit (vq.literal); + } + else + { + semantics::scope& scope ( + dynamic_cast<semantics::scope&> (*unit.find (vq.scope))); + + // Output the pragma location for easier error tracking. + // + os << "// From " << location_string (vq.loc, true) << endl + << translate_expression ( + c, vq.expr, scope, vq.loc, "query", &ph, pred).value; + } + + os << ");"; + + // If there was no placeholder, add the query condition + // at the end. + // + if (!ph) + os << endl + << "if (!q.empty ())" + << "{" + << "r += " << strlit (versioned ? "\n" : " ") << ";" + << "r += q.clause_prefix ();" + << "r += q;" + << "}"; + } + else // vq.kind == view_query::condition + { + // Use the from-list generated above. + // + statement_columns sc; + { + instance<view_columns> t (sc, from, rel_map); + t->traverse (c); + process_statement_columns ( + sc, statement_select, versioned || query_optimize); + } + + string sep (versioned || query_optimize ? "\n" : " "); + + os << "query_base_type r (" << endl + << strlit ((vq.distinct ? "SELECT DISTINCT" : "SELECT") + sep); + + for (statement_columns::const_iterator i (sc.begin ()), + e (sc.end ()); i != e;) + { + string const& c (i->column); + os << endl + << strlit (c + (++i != e ? "," : "") + sep); + } + + os << ");" + << endl; + + // It is much easier to add the separator at the beginning of the + // next line since the JOIN condition may not be a string literal. + // + for (strings::const_iterator i (from.begin ()); i != from.end (); ++i) + { + if (i->compare (0, 5, "FROM ") == 0) + os << "r += " << strlit (*i) << ";"; + else if (i->compare (0, 3, "// ") == 0) + os << *i << endl; + else + { + // See if this is a JOIN. The exact spelling is database-dependent, + // but we know there is the JOIN word in there somewhere and before + // it we should only have keywords and spaces. + // + size_t p (i->find ("JOIN ")); + if (p != string::npos) + { + // Make sure before it we only have A-Z and spaces. + // + for (char c; p != 0; --p) + { + c = (*i)[p - 1]; + if ((c < 'A' || c > 'Z') && c != ' ') + break; + } + + if (p == 0) + os << endl + << "r += " << strlit (sep + *i) << ";"; + } + + // Something else, assume already a string literal. + // + if (p != 0) + os << "r += " << *i << ";"; + } + } + + // Generate the query condition. + // + if (!vq.literal.empty () || !vq.expr.empty ()) + { + os << endl + << "query_base_type c (" << endl; + + bool ph (false); + + if (!vq.literal.empty ()) + { + // See if we have the '(?)' placeholder. + // + // @@ Ideally we would need to make sure we don't match + // this inside strings and quoted identifier. So the + // proper way to handle this would be to tokenize the + // statement using sql_lexer, once it is complete enough. + // + string::size_type p (vq.literal.find ("(?)")); + + if (p != string::npos) + { + ph = true; + os << strlit (string (vq.literal, 0, p + 1))<< " +" << endl + << "(q.empty () ? query_base_type::true_expr : q) +" << endl + << strlit (string (vq.literal, p + 2)); + } + else + os << strlit (vq.literal); + + os << ");"; + } + else + { + semantics::scope& scope ( + dynamic_cast<semantics::scope&> (*unit.find (vq.scope))); + + // Output the pragma location for easier error tracking. + // + os << "// From " << location_string (vq.loc, true) << endl + << translate_expression ( + c, vq.expr, scope, vq.loc, "query", &ph).value; + + os << ");"; + + // Optimize the query if it had a placeholder. This gets + // rid of useless clauses like WHERE TRUE. + // + if (ph) + os << endl + << "c.optimize ();"; + } + + if (!ph) + os << endl + << "c += q;"; + + os << endl + << "if (!c.empty ())" + << "{" + << "r += " << strlit (sep) << ";" + << "r += c.clause_prefix ();" + << "r += c;" + << "}"; + + string st (select_trailer (c)); + if (!st.empty ()) + { + os << "r += " << strlit (sep) << ";" + << "r += " << strlit (st) << ";"; + } + } + else + { + os << endl + << "if (!q.empty ())" + << "{" + << "r += " << strlit (sep) << ";" + << "r += q.clause_prefix ();" + << "r += q;" + << "}"; + } + } + + os << "return r;" + << "}"; + } + + // Unprepared. + // + if (!options.omit_unprepared ()) + { + os << "result< " << traits << "::view_type >" << endl + << traits << "::" << endl + << "query (database& db, const query_base_type& q)" + << "{" + << "using namespace " << db << ";" + << "using odb::details::shared;" + << "using odb::details::shared_ptr;" + << endl; + + os << db << "::connection& conn (" << endl + << db << "::transaction::current ().connection (db));" + << "statements_type& sts (" << endl + << "conn.statement_cache ().find_view<view_type> ());"; + + if (versioned) + os << "const schema_version_migration& svm (" << + "sts.version_migration (" << schema_name << "));"; + + os << endl + << "image_type& im (sts.image ());" + << "binding& imb (sts.image_binding ());" + << endl + << "if (im.version != sts.image_version () || imb.version == 0)" + << "{" + << "bind (imb.bind, im" << (versioned ? ", svm" : "") << ");" + << "sts.image_version (im.version);" + << "imb.version++;" + << "}"; + + if (vq.kind == view_query::runtime) + os << "const query_base_type& qs (q);"; + else + os << "const query_base_type& qs (query_statement (q));"; + + os << "qs.init_parameters ();" + << "shared_ptr<select_statement> st (" << endl + << "new (shared) select_statement (" << endl; + view_query_statement_ctor_args ( + c, "qs", versioned || query_optimize, false); + os << "));" << endl + << "st->execute ();"; + + post_query_ (c, true); + + os << endl + << "shared_ptr< odb::view_result_impl<view_type> > r (" << endl + << "new (shared) " << db << "::view_result_impl<view_type> (" << endl + << "qs, st, sts, " << (versioned ? "&svm" : "0") << "));" + << endl + << "return result<view_type> (r);" + << "}"; + + // query(odb::query_base) + // + if (multi_dynamic) + os << "result< " << traits << "::view_type >" << endl + << traits << "::" << endl + << "query (database& db, const odb::query_base& q)" + << "{" + << "return query (db, query_base_type (q));" + << "}"; + } + + // Prepared. Very similar to unprepared but has some annoying variations + // that make it difficult to factor out something common. + // + if (options.generate_prepared ()) + { + // prepare_query + // + os << "odb::details::shared_ptr<prepared_query_impl>" << endl + << traits << "::" << endl + << "prepare_query (connection& c, const char* n, " << + "const query_base_type& q)" + << "{" + << "using namespace " << db << ";" + << "using odb::details::shared;" + << "using odb::details::shared_ptr;" + << endl; + + os << db << "::connection& conn (" << endl + << "static_cast<" << db << "::connection&> (c));" + << "statements_type& sts (" << endl + << "conn.statement_cache ().find_view<view_type> ());"; + + if (versioned) + os << "const schema_version_migration& svm (" << + "sts.version_migration (" << schema_name << "));"; + + os << endl; + + // Rebind the image if necessary. + // + os << "image_type& im (sts.image ());" + << "binding& imb (sts.image_binding ());" + << endl + << "if (im.version != sts.image_version () || imb.version == 0)" + << "{" + << "bind (imb.bind, im" << (versioned ? ", svm" : "") << ");" + << "sts.image_version (im.version);" + << "imb.version++;" + << "}"; + + os << "shared_ptr<" << db << "::prepared_query_impl> r (" << endl + << "new (shared) " << db << "::prepared_query_impl (conn));" + << "r->name = n;" + << "r->execute = &execute_query;"; + + if (vq.kind == view_query::runtime) + os << "r->query = q;"; + else + os << "r->query = query_statement (q);"; + + os << "r->stmt.reset (" << endl + << "new (shared) select_statement (" << endl; + view_query_statement_ctor_args ( + c, "r->query", versioned || query_optimize, true); + os << "));" + << endl + << "return r;" + << "}"; + + // prepare_query(odb::query_base) + // + if (multi_dynamic) + os << "odb::details::shared_ptr<prepared_query_impl>" << endl + << traits << "::" << endl + << "prepare_query (connection& c, const char* n, " << + "const odb::query_base& q)" + << "{" + << "return prepare_query (c, n, query_base_type (q));" + << "}"; + + // execute_query + // + os << "odb::details::shared_ptr<result_impl>" << endl + << traits << "::" << endl + << "execute_query (prepared_query_impl& q)" + << "{" + << "using namespace " << db << ";" + << "using odb::details::shared;" + << "using odb::details::shared_ptr;" + << endl + << db << "::prepared_query_impl& pq (" << endl + << "static_cast<" << db << "::prepared_query_impl&> (q));" + << "shared_ptr<select_statement> st (" << endl + << "odb::details::inc_ref (" << endl + << "static_cast<select_statement*> (pq.stmt.get ())));" + << endl; + + os << db << "::transaction& tr (" << db << "::transaction::current ());" + << endl + << "// The connection used by the current transaction and the" << endl + << "// one used to prepare this statement must be the same." << endl + << "//" << endl + << "assert (q.verify_connection (tr));" + << endl + << "statements_type& sts (" << endl + << "st->connection ().statement_cache ().find_view<view_type> ());"; + + if (versioned) + os << "const schema_version_migration& svm (" << + "sts.version_migration (" << schema_name << "));"; + + os << endl; + + // Rebind the image if necessary. + // + os << "image_type& im (sts.image ());" + << "binding& imb (sts.image_binding ());" + << endl + << "if (im.version != sts.image_version () || imb.version == 0)" + << "{" + << "bind (imb.bind, im" << (versioned ? ", svm" : "") << ");" + << "sts.image_version (im.version);" + << "imb.version++;" + << "}"; + + os << "pq.query.init_parameters ();" + << "st->execute ();"; + + post_query_ (c, false); + + os << endl + << "return shared_ptr<result_impl> (" << endl + << "new (shared) " << db << "::view_result_impl<view_type> (" << endl + << "pq.query, st, sts, " << (versioned ? "&svm" : "0") << "));" + << "}"; + } + + // Generate function table registration for dynamic multi-database + // support. + // + if (multi_dynamic) + { + string fn (flat_name (type)); + string dt ("access::view_traits_impl< " + type + ", id_common >"); + + os << "static const" << endl + << dt << "::" << endl + << "function_table_type function_table_" << fn << "_ =" + << "{"; + + if (!options.omit_unprepared ()) + os << "&" << traits << "::query"; + + if (options.generate_prepared ()) + { + if (!options.omit_unprepared ()) + os << "," << endl; + + os << "&" << traits << "::prepare_query" << "," << endl + << "&" << traits << "::execute_query"; + } + + os << "};"; + + os << "static const view_function_table_entry< " << type << ", " << + "id_" << db << " >" << endl + << "function_table_entry_" << fn << "_ (" << endl + << "&function_table_" << fn << "_);" + << endl; + } +} + +namespace relational +{ + namespace source + { + static inline void + add_space (string& s) + { + string::size_type n (s.size ()); + if (n != 0 && s[n - 1] != ' ') + s += ' '; + } + + static string + translate_name_trailer (cxx_lexer& l, + cpp_ttype& tt, + string& tl, + tree& tn, + cpp_ttype& ptt) + { + string r; + + for (; tt != CPP_EOF; ptt = tt, tt = l.next (tl, &tn)) + { + bool done (false); + + switch (tt) + { + case CPP_SCOPE: + case CPP_DOT: + { + r += cxx_lexer::token_spelling[tt]; + break; + } + default: + { + // Handle CPP_KEYWORD here to avoid a warning (it is not + // part of the cpp_ttype enumeration). + // + if (tt == CPP_NAME || tt == CPP_KEYWORD) + { + // For names like 'foo::template bar'. + // + if (ptt == CPP_NAME || ptt == CPP_KEYWORD) + r += ' '; + + r += tl; + } + else + done = true; + + break; + } + } + + if (done) + break; + } + + return r; + } + + static class_::expression + translate_name (cxx_lexer& l, + cpp_ttype& tt, + string& tl, + tree& tn, + cpp_ttype& ptt, + semantics::scope& start_scope, + location_t loc, + string const& prag, + bool check_ptr, + view_alias_map const& amap, + view_object_map const& omap) + { + using semantics::scope; + using semantics::data_member; + typedef class_::expression expression; + + bool multi_obj ((amap.size () + omap.size ()) > 1); + + bool fail (false); + string name; + string r ("query_columns"); + context& ctx (context::current ()); + + // This code is quite similar to view_data_members in the type + // processor. + // + try + { + data_member* m (0); + view_object* vo (0); + + // Check if this is an alias. + // + if (tt == CPP_NAME) + { + view_alias_map::const_iterator i (amap.find (tl)); + + if (i != amap.end ()) + { + if (multi_obj) + { + r += "::"; + r += i->first; + } + + vo = i->second; + fail = true; // This must be a data member. + + // Skip '::'. + // + ptt = tt; + tt = l.next (tl, &tn); + + if (tt != CPP_SCOPE) + { + error (loc) << "member name expected after an alias in db " << + "pragma " << prag << endl; + throw operation_failed (); + } + + ptt = tt; + if (l.next (tl, &tn) != CPP_NAME) + throw lookup::invalid_name (); + + m = &vo->obj->lookup<data_member> (tl, scope::include_hidden); + + tt = l.next (tl, &tn); + } + } + + // If it is not an alias, do the normal lookup. + // + if (vo == 0) + { + // Also get the object type. We need to do it so that + // we can get the correct (derived) object name (the + // member itself can come from a base class). + // + scope* s; + cpp_ttype ptt; // Not used. + m = &lookup::resolve_scoped_name<data_member> ( + l, tt, tl, tn, ptt, + start_scope, + name, + false, + &s); + + view_object_map::const_iterator i ( + omap.find (dynamic_cast<semantics::class_*> (s))); + + if (i == omap.end ()) + { + // Not an object associated with this view. Assume it + // is some other valid name. + // + return expression ( + name + translate_name_trailer (l, tt, tl, tn, ptt)); + } + + vo = i->second; + + if (multi_obj) + { + r += "::"; + r += context::class_name (*vo->obj); + } + } + + expression e (vo); + r += "::"; + r += ctx.public_name (*m); + + // Assemble the member path if we may need to return a pointer + // expression. + // + if (check_ptr) + e.member_path.push_back (m); + + fail = true; // Now we definitely fail if anything goes wrong. + + // Finally, resolve nested members if any. + // + for (; tt == CPP_DOT; ptt = tt, tt = l.next (tl, &tn)) + { + // Check if this member is actually of a composite value type. + // This is to handle expressions like "object::member.is_null ()" + // correctly. The remaining issue here is that in the future + // is_null()/is_not_null() will be valid for composite values + // as well. + // + semantics::class_* comp ( + context::composite_wrapper (context::utype (*m))); + if (comp == 0) + break; + + ptt = tt; + tt = l.next (tl, &tn); + + if (tt != CPP_NAME) + { + error (loc) << "name expected after '.' in db pragma " << + prag << endl; + throw operation_failed (); + } + + m = &comp->lookup<data_member> (tl, scope::include_hidden); + + r += '.'; + r += ctx.public_name (*m); + + if (check_ptr) + e.member_path.push_back (m); + } + + // If requested, check if this member is a pointer. We only do this + // if there is nothing after this name. + // + if (check_ptr && tt == CPP_EOF) + { + using semantics::type; + + type* t; + + if (context::container (*m)) + t = &context::container_vt (*m); + else + t = &context::utype (*m); + + if (context::object_pointer (*t)) + return e; + } + + // Read the remainder of the expression (e.g., '.is_null ()') if + // the member is not composite and we bailed out from the above + // loop. + // + if (tt == CPP_DOT) + r += translate_name_trailer (l, tt, tl, tn, ptt); + + return expression (r); + } + catch (lookup::invalid_name const&) + { + if (!fail) + return expression ( + name + translate_name_trailer (l, tt, tl, tn, ptt)); + + error (loc) << "invalid name in db pragma " << prag << endl; + throw operation_failed (); + } + catch (semantics::unresolved const& e) + { + if (!fail) + return expression ( + name + translate_name_trailer (l, tt, tl, tn, ptt)); + + if (e.type_mismatch) + error (loc) << "name '" << e.name << "' in db pragma " << prag << + " does not refer to a data member" << endl; + else + error (loc) << "unable to resolve data member '" << e.name << + "' specified with db pragma " << prag << endl; + + throw operation_failed (); + } + catch (semantics::ambiguous const& e) + { + error (loc) << "data member name '" << e.first.name () << "' " << + "specified with db pragma " << prag << " is ambiguous" << endl; + + info (e.first.named ().location ()) << "could resolve to this " << + "data member" << endl; + + info (e.second.named ().location ()) << "or could resolve to this " << + "data member" << endl; + + throw operation_failed (); + } + } + + class_::expression class_:: + translate_expression (type& c, + cxx_tokens const& ts, + semantics::scope& scope, + location_t loc, + string const& prag, + bool* placeholder, + bool predicate) + { + // This code is similar to translate() from context.cxx. + // + + // The overall idea is as folows: read in tokens and add them + // to the string. If a token starts a name, try to resolve it + // to an object member (taking into account aliases). If this + // was successful, translate it to the query column reference. + // Otherwise, output it as is. + // + // If the placeholder argument is not NULL, then we need to + // detect the special '(?)' token sequence and replace it + // with the query variable ('q'). + // + expression e (""); + string& r (e.value); + + view_alias_map const& amap (c.get<view_alias_map> ("alias-map")); + view_object_map const& omap (c.get<view_object_map> ("object-map")); + + cxx_tokens_lexer l; + l.start (ts); + + tree tn; + string tl; + for (cpp_ttype tt (l.next (tl, &tn)), ptt (CPP_EOF); tt != CPP_EOF;) + { + // Try to format the expression to resemble the style of the + // generated code. + // + switch (tt) + { + case CPP_NOT: + { + add_space (r); + r += '!'; + break; + } + case CPP_COMMA: + { + r += ", "; + break; + } + case CPP_OPEN_PAREN: + { + if (ptt == CPP_NAME || + ptt == CPP_KEYWORD) + add_space (r); + + r += '('; + break; + } + case CPP_CLOSE_PAREN: + { + r += ')'; + break; + } + case CPP_OPEN_SQUARE: + { + r += '['; + break; + } + case CPP_CLOSE_SQUARE: + { + r += ']'; + break; + } + case CPP_OPEN_BRACE: + { + add_space (r); + r += "{ "; + break; + } + case CPP_CLOSE_BRACE: + { + add_space (r); + r += '}'; + break; + } + case CPP_SEMICOLON: + { + r += ';'; + break; + } + case CPP_ELLIPSIS: + { + add_space (r); + r += "..."; + break; + } + case CPP_PLUS: + case CPP_MINUS: + { + bool unary (ptt != CPP_NAME && + ptt != CPP_SCOPE && + ptt != CPP_NUMBER && + ptt != CPP_STRING && + ptt != CPP_CLOSE_PAREN && + ptt != CPP_PLUS_PLUS && + ptt != CPP_MINUS_MINUS); + + if (!unary) + add_space (r); + + r += cxx_lexer::token_spelling[tt]; + + if (!unary) + r += ' '; + break; + } + case CPP_PLUS_PLUS: + case CPP_MINUS_MINUS: + { + if (ptt != CPP_NAME && + ptt != CPP_CLOSE_PAREN && + ptt != CPP_CLOSE_SQUARE) + add_space (r); + + r += cxx_lexer::token_spelling[tt]; + break; + } + case CPP_DEREF: + case CPP_DEREF_STAR: + case CPP_DOT: + case CPP_DOT_STAR: + { + r += cxx_lexer::token_spelling[tt]; + break; + } + case CPP_STRING: + { + if (ptt == CPP_NAME || + ptt == CPP_KEYWORD || + ptt == CPP_STRING || + ptt == CPP_NUMBER) + add_space (r); + + r += strlit (tl); + break; + } + case CPP_NUMBER: + { + if (ptt == CPP_NAME || + ptt == CPP_KEYWORD || + ptt == CPP_STRING || + ptt == CPP_NUMBER) + add_space (r); + + r += tl; + break; + } + case CPP_SCOPE: + case CPP_NAME: + { + // Start of a name. + // + if (ptt == CPP_NAME || + ptt == CPP_KEYWORD || + ptt == CPP_STRING || + ptt == CPP_NUMBER) + add_space (r); + + // Check if this is a pointer expression. + // + // If r is not empty, then it means this is not just the + // name. If placeholder is not 0, then we are translating + // a query expression, not a join condition. + // + expression e ( + translate_name ( + l, tt, tl, tn, ptt, + scope, loc, prag, + r.empty () && placeholder == 0, amap, omap)); + + if (e.kind == expression::literal) + r += e.value; + else + return e; + + continue; // We have already extracted the next token. + } + case CPP_QUERY: + { + if (placeholder != 0 && !*placeholder) + { + if (ptt == CPP_OPEN_PAREN) + { + // Get the next token and see if it is ')'. + // + ptt = tt; + tt = l.next (tl, &tn); + + if (tt == CPP_CLOSE_PAREN) + { + *placeholder = true; + + // Predicate is true if this is a SELECT statement clause. + // Otherwise it is a stored procedure parameters. + // + if (predicate) + r += "q.empty () ? query_base_type::true_expr : q"; + else + { + r.resize (r.size () - 1); // Remove opening paren. + r += "q"; + break; // Skip the closing paren as well. + } + } + else + { + // The same as in the default case. + // + add_space (r); + r += "? "; + } + continue; // We have already gotten the next token. + } + } + } + // Fall through. + default: + { + // Handle CPP_KEYWORD here to avoid a warning (it is not + // part of the cpp_ttype enumeration). + // + if (tt == CPP_KEYWORD) + { + if (ptt == CPP_NAME || + ptt == CPP_KEYWORD || + ptt == CPP_STRING || + ptt == CPP_NUMBER) + add_space (r); + + r += tl; + } + else + { + // All the other operators. + // + add_space (r); + r += cxx_lexer::token_spelling[tt]; + r += ' '; + } + break; + } + } + + // + // Watch out for the continue statements above if you add any + // logic here. + // + + ptt = tt; + tt = l.next (tl, &tn); + } + + return e; + } + + void + generate () + { + context ctx; + ostream& os (ctx.os); + + traversal::unit unit; + traversal::defines unit_defines; + typedefs unit_typedefs (false); + traversal::namespace_ ns; + instance<class_> c; + + unit >> unit_defines >> ns; + unit_defines >> c; + unit >> unit_typedefs >> c; + + traversal::defines ns_defines; + typedefs ns_typedefs (false); + + ns >> ns_defines >> ns; + ns_defines >> c; + ns >> ns_typedefs >> c; + + instance<include> i; + i->generate (); + + os << "namespace odb" + << "{"; + + unit.dispatch (ctx.unit); + + os << "}"; + } + } +} diff --git a/odb/odb/relational/source.hxx b/odb/odb/relational/source.hxx new file mode 100644 index 0000000..3c6f5da --- /dev/null +++ b/odb/odb/relational/source.hxx @@ -0,0 +1,7154 @@ +// file : odb/relational/source.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_RELATIONAL_SOURCE_HXX +#define ODB_RELATIONAL_SOURCE_HXX + +#include <map> +#include <set> +#include <list> +#include <vector> +#include <sstream> + +#include <odb/diagnostics.hxx> + +#include <odb/relational/context.hxx> +#include <odb/relational/common.hxx> +#include <odb/relational/schema.hxx> + +namespace relational +{ + namespace source + { + // Column literal in a statement (e.g., in select-list, etc). + // + struct statement_column + { + statement_column (): member (0) {} + statement_column (std::string const& tbl, + std::string const& col, + std::string const& t, + semantics::data_member& m, + std::string const& kp = "") + : table (tbl), column (col), type (t), member (&m), key_prefix (kp) + { + } + + std::string table; // Schema-qualifed and quoted table name. + std::string column; // Table-qualifed and quoted column expr. + std::string type; // Column SQL type. + semantics::data_member* member; + std::string key_prefix; + }; + + typedef std::list<statement_column> statement_columns; + + // Query parameter generator. A new instance is created for each + // query, so the customized version can have a counter to implement, + // for example, numbered parameters (e.g., $1, $2, etc). The auto_id() + // function is called instead of next() for the automatically-assigned + // object id member when generating the persist statement. If empty + // string is returned, then parameter is ignored. + // + struct query_parameters: virtual context + { + typedef query_parameters base; + + query_parameters (statement_kind sk, qname const& table) + : sk_ (sk), table_ (table) {} + + virtual string + next (semantics::data_member&, + const std::string& /*column*/, // Table qualified and quoted. + const std::string& /*sqlt*/) + { + return "?"; + } + + virtual string + auto_id (semantics::data_member& m, + const std::string& column, + const std::string& sqlt) + { + return next (m, column, sqlt); + } + + string + next (const object_columns_list::column& c) + { + return next (*c.member, quote_id (c.name), c.type); + } + + string + next (const statement_column& c) + { + return next (*c.member, c.column, c.type); + } + + protected: + statement_kind sk_; + qname table_; + }; + + struct object_columns: object_columns_base, virtual context + { + typedef object_columns base; + + // If provided, used to resolve table/alias names for inverse + // pointed-to and base objects. Returns qualified name. + // + struct table_name_resolver + { + virtual string + resolve_pointer (semantics::data_member&) const = 0; + + virtual string + resolve_base (semantics::class_&) const = 0; + }; + + object_columns (statement_kind sk, + statement_columns& sc, + query_parameters* param = 0, + object_section* section = 0) + : object_columns_base (true, true, section), + sk_ (sk), + ro_ (true), + sc_ (sc), + param_ (param), + table_name_resolver_ (0), + depth_ (1) + { + } + + object_columns (statement_kind sk, + bool ignore_ro, + statement_columns& sc, + query_parameters* param) + : object_columns_base (true, true, 0), + sk_ (sk), + ro_ (ignore_ro), + sc_ (sc), + param_ (param), + table_name_resolver_ (0), + depth_ (1) + { + } + + object_columns (std::string const& table_qname, + statement_kind sk, + statement_columns& sc, + size_t depth = 1, + object_section* section = 0, + table_name_resolver* tnr = 0) + : object_columns_base (true, true, section), + sk_ (sk), + ro_ (true), + sc_ (sc), + param_ (0), + table_name_ (table_qname), + table_name_resolver_ (tnr), + depth_ (depth) + { + } + + virtual bool + section_test (data_member_path const& mp) + { + object_section& s (section (mp)); + + // Include eager loaded members into the main section for + // SELECT statements. Also include optimistic version into + // section's SELECT and UPDATE statements. + // + return section_ == 0 || + *section_ == s || + (sk_ == statement_select && + *section_ == main_section && + !s.separate_load ()) || + (version (mp) && + (sk_ == statement_update || sk_ == statement_select)); + } + + virtual void + traverse_object (semantics::class_& c) + { + // If we are generating a select statement and this is a derived + // type in a polymorphic hierarchy, then we need to include base + // columns, but do it in reverse order as well as switch the table + // name (base columns come from different tables). + // + semantics::class_* poly_root (polymorphic (c)); + if (poly_root != 0 && poly_root != &c) + { + names (c); + + if (sk_ == statement_select && --depth_ != 0) + { + semantics::class_& b (polymorphic_base (c)); + + table_name_ = table_name_resolver_ != 0 + ? table_name_resolver_->resolve_base (b) + : table_qname (b); + + inherits (c); + } + } + else + object_columns_base::traverse_object (c); + } + + virtual void + traverse_pointer (semantics::data_member& m, semantics::class_& c) + { + // Ignore polymorphic id references for select statements. + // + if (sk_ == statement_select && m.count ("polymorphic-ref")) + return; + + data_member_path* imp (inverse (m, key_prefix_)); + + // Ignore certain columns depending on what kind of statement we are + // generating. Columns corresponding to the inverse members are + // only present in the select statements. + // + if (imp != 0 && sk_ != statement_select) + return; + + // Inverse object pointers come from a joined table. + // + if (imp != 0) + { + bool poly (polymorphic (c) != 0); + semantics::data_member& imf (*imp->front ()); + semantics::data_member& imb (*imp->back ()); + + // In a polymorphic hierarchy the inverse member can be in + // the base class, in which case we should use that table. + // + semantics::class_& imc ( + poly ? dynamic_cast<semantics::class_&> (imf.scope ()) : c); + + data_member_path& id (*id_member (imc)); + semantics::type& idt (utype (id)); + + if (container (imb)) + { + // This container is a direct member of the class so the table + // prefix is just the class table name. We don't assign join + // aliases for container tables so use the actual table name. + // Note that the if(!table_name_.empty ()) test may look wrong + // at first but it is not; if table_name_ is empty then we are + // generating a container table where we don't qualify columns + // with tables. + // + string table; + + if (!table_name_.empty ()) + { + if (table_name_resolver_ != 0) + table = table_name_resolver_->resolve_pointer (m); + else + table = table_qname (imc, *imp); + } + + instance<object_columns> oc (table, sk_, sc_); + oc->traverse (imb, idt, "id", "object_id", &imc); + } + else + { + // Use the join alias instead of the actual table name unless we + // are handling a container. Generally, we want the join alias + // to be based on the column name. This is straightforward for + // single-column references. In case of a composite id, we will + // need to use the column prefix which is based on the data + // member name, unless overridden by the user. In the latter + // case the prefix can be empty, in which case we will just + // fall back on the member's public name. Note that the + // if(!table_name_.empty ()) test may look wrong at first but + // it is not; if table_name_ is empty then we are generating a + // container table where we don't qualify columns with tables. + // + string alias; + + if (!table_name_.empty ()) + { + if (table_name_resolver_ != 0) + alias = table_name_resolver_->resolve_pointer (m); + else + { + string n; + + if (composite_wrapper (idt)) + { + n = column_prefix (m, key_prefix_, default_name_).prefix; + + if (n.empty ()) + n = public_name_db (m); + else if (n[n.size () - 1] == '_') + n.resize (n.size () - 1); // Remove trailing underscore. + } + else + { + bool dummy; + n = column_name (m, key_prefix_, default_name_, dummy); + } + + alias = column_prefix_.prefix + n; + + if (poly) + { + qname const& table (table_name (imc)); + alias = quote_id (alias + "_" + table.uname ()); + } + else + alias = quote_id (alias); + } + } + + instance<object_columns> oc (alias, sk_, sc_); + oc->traverse (id); + } + } + else + object_columns_base::traverse_pointer (m, c); + } + + virtual bool + traverse_column (semantics::data_member& m, string const& name, bool) + { + // Ignore certain columns depending on what kind statement we are + // generating. Id and readonly columns are not present in the update + // statements. + // + if ((id () || readonly (member_path_, member_scope_)) && + sk_ == statement_update && ro_) + return false; + + return column (m, table_name_, quote_id (name)); + } + + virtual bool + column (semantics::data_member& m, + string const& table, + string const& column) + { + string r; + + if (!table.empty ()) + { + r += table; // Already quoted. + r += '.'; + } + + r += column; // Already quoted. + + string const& sqlt (column_type ()); + + // Version column (optimistic concurrency) requires special + // handling in the UPDATE statement. + // + // + if (sk_ == statement_update && version (m)) + { + r += "=" + r + "+1"; + } + else if (param_ != 0) + { + r += '='; + r += convert_to (param_->next (m, column, sqlt), sqlt, m); + } + else if (sk_ == statement_select) + r = convert_from (r, sqlt, m); + + sc_.push_back (statement_column (table, r, sqlt, m, key_prefix_)); + return true; + } + + protected: + statement_kind sk_; + bool ro_; + statement_columns& sc_; + query_parameters* param_; + string table_name_; + table_name_resolver* table_name_resolver_; + size_t depth_; + }; + + struct view_columns: object_columns_base, + object_columns::table_name_resolver, + virtual context + { + typedef view_columns base; + + view_columns (statement_columns& sc, + strings& from, + const view_relationship_map& rm) + : sc_ (sc), from_ (from), rel_map_ (rm), in_composite_ (false) {} + + // Implementation of table_name_resolver for object_columns. + // + virtual string + resolve_pointer (semantics::data_member& m) const + { + view_object& us (*ptr_->get<view_object*> ("view-object")); + + data_member_path& imp (*inverse (m)); + semantics::data_member& imf (*imp.front ()); + semantics::data_member& imb (*imp.back ()); + + using semantics::class_; + typedef view_relationship_map::const_iterator iterator; + + std::pair<iterator, iterator> r (rel_map_.equal_range (imp)); + + for (; r.first != r.second; ++r.first) + { + if (r.first->second.second != &us) // Not our associated. + continue; + + view_object& vo (*r.first->second.first); // First because inverse. + + // Derive the table name the same way as the JOIN code. + // + class_* c (vo.obj); + if (class_* root = polymorphic (*c)) + { + // Can be in base. + // + c = &static_cast<class_&> (imf.scope ()); + + if (!polymorphic (*c)) + c = root; + } + + string const& a (vo.alias); + + if (container (imb)) + { + // If this is a container, then object_columns will use the + // column from the container table, not from the object table + // (which, strictly speaking, might not have been JOIN'ed). + // + qname t (table_name (*c, imp)); + return a.empty () + ? quote_id (t) + : quote_id (a + '_' + t.uname ()); + } + else + { + qname t; + if (a.empty ()) + t = table_name (*c); + else + { + if (polymorphic (*c)) + t = qname (a + "_" + table_name (*c).uname ()); + else + t = qname (a); + } + return quote_id (t); + } + } + + // So there is no associated object for this column. The initial + // plan was to complain and ask the user to explicitly associate + // the object. This is not a bad plan except for one thing: if + // the direct side of the relationship is a container, then + // associating that object explicitly will result in both the + // container table and the object table being JOIN'ed. But we + // only need the container table (for the object id) So we will + // be joining a table for nothing, which is not very clean. So + // the alternative, and more difficult, plan is to go ahead and + // add the necessary JOIN's automatically. + // + // This code follows the normal JOIN generation code. + // + class_* o (object_pointer (utype (m))); + if (class_* root = polymorphic (*o)) + { + o = &static_cast<class_&> (imf.scope ()); + + if (!polymorphic (*o)) + o = root; + } + + string const& a (us.alias); + string lt (a.empty () ? table_qname (*us.obj) : quote_id (a)); + string rt; + qname ct (container (imb) ? table_name (*o, imp) : table_name (*o)); + + string l ("LEFT JOIN "); + + if (a.empty ()) + { + rt = quote_id (ct); + l += rt; + } + else + { + // The same relationship can be used by multiple associated + // objects. So if we have an alias, then also construct one + // for the table that we are joining. + // + rt = quote_id (a + '_' + ct.uname ()); + l += quote_id (ct); + l += (need_alias_as ? " AS " : " ") + rt; + } + + l += " ON"; + from_.push_back (l); + + instance<object_columns_list> l_cols; // Our id columns. + instance<object_columns_list> r_cols; // Other side id columns. + + data_member_path& id (*id_member (*us.obj)); + + l_cols->traverse (id); + + if (container (imb)) + r_cols->traverse (imb, utype (id), "value", "value"); + else + r_cols->traverse (imb, column_prefix (imp)); + + for (object_columns_list::iterator b (l_cols->begin ()), i (b), + j (r_cols->begin ()); i != l_cols->end (); ++i, ++j) + { + l.clear (); + + if (i != b) + l += "AND "; + + l += lt; + l += '.'; + l += quote_id (i->name); + l += '='; + l += rt; + l += '.'; + l += quote_id (j->name); + + from_.push_back (strlit (l)); + } + + return rt; + + /* + // The alternative implementation: + // + location const& l1 (m.location ()); + location const& l2 (ptr_->location ()); + + string n1 (class_name (*object_pointer (utype (m)))); + string n2 (class_name (*object_pointer (utype (*ptr_)))); + + error (l1) << "object '" << n1 << "' pointed-to by the inverse " + << "data member in object '" << n2 << "' must be " + << "explicitly associated with the view" << endl; + + info (l2) << "view data member that loads '" << n2 << "' is " + << "defined here" << endl; + + throw operation_failed (); + */ + } + + virtual string + resolve_base (semantics::class_& b) const + { + view_object& vo (*ptr_->get<view_object*> ("view-object")); + + qname t (vo.alias.empty () + ? table_name (b) + : qname (vo.alias + "_" + table_name (b).uname ())); + + return quote_id (t); + } + + virtual void + traverse_pointer (semantics::data_member& m, semantics::class_& c) + { + type* poly_root (polymorphic (c)); + bool poly (poly_root != 0); + bool poly_derived (poly && poly_root != &c); + size_t poly_depth (poly_derived ? polymorphic_depth (c) : 1); + + view_object& vo (*m.get<view_object*> ("view-object")); + string const& a (vo.alias); + + qname t; + if (a.empty ()) + t = table_name (c); + else + { + if (poly) + t = qname (a + "_" + table_name (c).uname ()); + else + t = qname (a); + } + string qt (quote_id (t)); + + ptr_ = &m; + + statement_kind sk (statement_select); // Imperfect forwarding. + object_section* s (&main_section); // Imperfect forwarding. + instance<object_columns> oc (qt, sk, sc_, poly_depth, s, this); + oc->traverse (c); + } + + virtual void + traverse_composite (semantics::data_member* pm, semantics::class_& c) + { + if (in_composite_) + { + object_columns_base::traverse_composite (pm, c); + return; + } + + // Override the column prerix. + // + semantics::data_member& m (*pm); + + // If we have literal column specified, use that. + // + if (m.count ("column")) + { + table_column const& tc (m.get<table_column> ("column")); + + if (!tc.table.empty ()) + table_prefix_ = tc.table; + + column_prefix_ = object_columns_base::column_prefix (m); + } + // Otherwise, see if there is a column expression. For composite + // members in a view, this should be a single reference. + // + else if (m.count ("column-expr")) + { + column_expr const& e (m.get<column_expr> ("column-expr")); + + if (e.size () > 1) + { + cerr << m.file () << ":" << m.line () << ":" << m.column () + << ": error: column expression specified for a data member " + << "of a composite value type" << endl; + + throw operation_failed (); + } + + data_member_path const& mp (e.back ().member_path); + + if (mp.size () > 1) + { + cerr << m.file () << ":" << m.line () << ":" << m.column () + << ": error: invalid data member in db pragma column" + << endl; + + throw operation_failed (); + } + + table_prefix_ = e.back ().table; + column_prefix_ = object_columns_base::column_prefix (*mp.back ()); + } + else + { + cerr << m.file () << ":" << m.line () << ":" << m.column () + << ": error: no column prefix provided for a view data member" + << endl; + + cerr << m.file () << ":" << m.line () << ":" << m.column () + << ": info: use db pragma column to specify the column prefix" + << endl; + + throw operation_failed (); + } + + in_composite_ = true; + object_columns_base::traverse_composite (pm, c); + in_composite_ = false; + } + + virtual bool + traverse_column (semantics::data_member& m, string const& name, bool) + { + string tbl; + string col; + + // If we are inside a composite value, use the standard + // column name machinery. + // + if (in_composite_) + { + if (!table_prefix_.empty ()) + { + tbl = quote_id (table_prefix_); + col += tbl; + col += '.'; + } + + col += quote_id (name); + } + // If we have literal column specified, use that. + // + else if (m.count ("column")) + { + table_column const& tc (m.get<table_column> ("column")); + + if (!tc.expr) + { + if (!tc.table.empty ()) + { + tbl = quote_id (tc.table); + col += tbl; + col += '.'; + } + + col += quote_id (tc.column); + } + else + col += tc.column; + } + // Otherwise, see if there is a column expression. + // + else if (m.count ("column-expr")) + { + column_expr const& e (m.get<column_expr> ("column-expr")); + + for (column_expr::const_iterator i (e.begin ()); i != e.end (); ++i) + { + switch (i->kind) + { + case column_expr_part::literal: + { + col += i->value; + break; + } + case column_expr_part::reference: + { + tbl = quote_id (i->table); + col += tbl; + col += '.'; + col += column_qname (i->member_path); + break; + } + } + } + } + else + { + cerr << m.file () << ":" << m.line () << ":" << m.column () + << ": error: no column name provided for a view data member" + << endl; + + cerr << m.file () << ":" << m.line () << ":" << m.column () + << ": info: use db pragma column to specify the column name" + << endl; + + throw operation_failed (); + } + + return column (m, tbl, col); + } + + // The column argument is a qualified and quoted column or + // expression. + // + virtual bool + column (semantics::data_member& m, + string const& table, + string const& column) + { + string const& sqlt (column_type ()); + sc_.push_back ( + statement_column ( + table, convert_from (column, sqlt, m), sqlt, m)); + return true; + } + + protected: + statement_columns& sc_; + strings& from_; + const view_relationship_map& rel_map_; + + bool in_composite_; + qname table_prefix_; // Table corresponding to column_prefix_; + + // Set to the current pointer data member that we are traversing. + // + semantics::data_member* ptr_; + }; + + struct polymorphic_object_joins: object_columns_base, virtual context + { + typedef polymorphic_object_joins base; + + polymorphic_object_joins (semantics::class_& obj, + bool query, + size_t depth, + string const& alias = "", + user_section* section = 0) + : object_columns_base (true, true), + obj_ (obj), + query_ (query), + depth_ (depth), + section_ (section), + alias_ (alias) + { + // Get the table and id columns. + // + table_ = alias_.empty () + ? table_qname (obj_) + : quote_id (alias_ + "_" + table_name (obj_).uname ()); + + cols_->traverse (*id_member (obj_)); + } + + virtual void + traverse_object (semantics::class_& c) + { + // If section is specified, skip bases that don't add anything + // to load. + // + bool skip (false), stop (false); + if (section_ != 0) + { + skip = true; + + if (section_->object == &c) + { + user_section& s (*section_); + + if (s.total != 0 || s.optimistic ()) + skip = false; + + section_ = s.base; // Move to the next base. + + if (section_ == 0) + stop = true; // Stop at this base if there are no more overrides. + } + } + // Skip intermediates that don't add any data members. + // + else if (!query_) + { + column_count_type const& cc (column_count (c)); + if (cc.total == cc.id + cc.separate_load) + skip = true; + } + + if (!skip) + { + std::ostringstream cond; + + qname table (table_name (c)); + string alias (alias_.empty () + ? quote_id (table) + : quote_id (alias_ + "_" + table.uname ())); + + for (object_columns_list::iterator b (cols_->begin ()), i (b); + i != cols_->end (); + ++i) + { + if (i != b) + cond << " AND "; + + string qn (quote_id (i->name)); + cond << alias << '.' << qn << '=' << table_ << '.' << qn; + } + + string line ("LEFT JOIN " + quote_id (table)); + + if (!alias_.empty ()) + line += (need_alias_as ? " AS " : " ") + alias; + + line += " ON " + cond.str (); + + joins.push_back (line); + } + + if (!stop && --depth_ != 0) + inherits (c); + } + + public: + strings joins; + + strings::const_iterator + begin () const {return joins.begin ();} + + strings::const_iterator + end () const {return joins.end ();} + + private: + semantics::class_& obj_; + bool query_; + size_t depth_; + user_section* section_; + string alias_; + string table_; + instance<object_columns_list> cols_; + }; + + struct object_joins: object_columns_base, virtual context + { + typedef object_joins base; + + //@@ context::{cur,top}_object; might have to be created every time. + // + object_joins (semantics::class_& scope, + bool query, + size_t depth, + object_section* section = 0) + : object_columns_base (true, true, section), + query_ (query), + depth_ (depth), + table_ (table_qname (scope)), + id_ (*id_member (scope)) + { + id_cols_->traverse (id_); + } + + virtual bool + section_test (data_member_path const& mp) + { + object_section& s (section (mp)); + + // Include eager loaded members into the main section. + // + return section_ == 0 || + *section_ == s || + (*section_ == main_section && !s.separate_load ()); + } + + virtual void + traverse_object (semantics::class_& c) + { + // If this is a derived type in a polymorphic hierarchy, then we + // need to include base joins, but do it in reverse order as well + // as switch the table name (base columns come from different + // tables). + // + semantics::class_* poly_root (polymorphic (c)); + if (poly_root != 0 && poly_root != &c) + { + names (c); + + if (query_ || --depth_ != 0) + { + table_ = table_qname (polymorphic_base (c)); + inherits (c); + } + } + else + object_columns_base::traverse_object (c); + } + + virtual void + traverse_pointer (semantics::data_member& m, semantics::class_& c) + { + // Ignore polymorphic id references; they are joined by + // polymorphic_object_joins in a special way. + // + if (m.count ("polymorphic-ref")) + return; + + string t, a, dt, da; + std::ostringstream cond, dcond; // @@ diversion? + + // Derive table alias for this member. Generally, we want the + // alias to be based on the column name. This is straightforward + // for single-column references. In case of a composite id, we + // will need to use the column prefix which is based on the data + // member name, unless overridden by the user. In the latter + // case the prefix can be empty, in which case we will just + // fall back on the member's public name. + // + string alias; + { + string n; + + if (composite_wrapper (utype (*id_member (c)))) + { + n = column_prefix (m, key_prefix_, default_name_).prefix; + + if (n.empty ()) + n = public_name_db (m); + else if (n[n.size () - 1] == '_') + n.resize (n.size () - 1); // Remove trailing underscore. + } + else + { + bool dummy; + n = column_name (m, key_prefix_, default_name_, dummy); + } + + alias = column_prefix_.prefix + n; + } + + semantics::class_* poly_root (polymorphic (c)); + bool poly (poly_root != 0); + + semantics::class_* joined_obj (0); + + if (data_member_path* imp = inverse (m, key_prefix_)) + { + semantics::data_member& imf (*imp->front ()); + semantics::data_member& imb (*imp->back ()); + + // In a polymorphic hierarchy the inverse member can be in + // the base class, in which case we should use that table. + // + semantics::class_& imc ( + poly ? dynamic_cast<semantics::class_&> (imf.scope ()) : c); + + if (container (imb)) + { + // This container is a direct member of the class so the table + // prefix is just the class table name. + // + t = table_qname (imc, *imp); + + // Container's value is our id. + // + instance<object_columns_list> id_cols; + id_cols->traverse (imb, utype (id_), "value", "value"); + + for (object_columns_list::iterator b (id_cols->begin ()), i (b), + j (id_cols_->begin ()); i != id_cols->end (); ++i, ++j) + { + + if (i != b) + cond << " AND "; + + cond << t << '.' << quote_id (i->name) << '=' << + table_ << '.' << quote_id (j->name); + } + + // Add the join for the object itself so that we are able to + // use it in the WHERE clause. + // + if (query_) + { + // Here we can use the most derived class instead of the + // one containing the inverse member. + // + qname const& table (table_name (c)); + + dt = quote_id (table); + da = quote_id (poly ? alias + "_" + table.uname () : alias); + + data_member_path& id (*id_member (c)); + + instance<object_columns_list> oid_cols, cid_cols; + oid_cols->traverse (id); + cid_cols->traverse (imb, utype (id), "id", "object_id", &c); + + for (object_columns_list::iterator b (cid_cols->begin ()), i (b), + j (oid_cols->begin ()); i != cid_cols->end (); ++i, ++j) + { + + if (i != b) + dcond << " AND "; + + dcond << da << '.' << quote_id (j->name) << '=' << + t << '.' << quote_id (i->name); + } + + joined_obj = &c; + } + } + else + { + qname const& table (table_name (imc)); + + t = quote_id (table); + a = quote_id (poly ? alias + "_" + table.uname () : alias); + + instance<object_columns_list> id_cols; + id_cols->traverse (imb, column_prefix (*imp)); + + for (object_columns_list::iterator b (id_cols->begin ()), i (b), + j (id_cols_->begin ()); i != id_cols->end (); ++i, ++j) + { + if (i != b) + cond << " AND "; + + cond << a << '.' << quote_id (i->name) << '=' << + table_ << '.' << quote_id (j->name); + } + + // If we are generating query, JOIN base/derived classes so + // that we can use their data in the WHERE clause. + // + if (query_) + joined_obj = &imc; + } + } + else if (query_) + { + // We need the join to be able to use the referenced object + // in the WHERE clause. + // + qname const& table (table_name (c)); + + t = quote_id (table); + a = quote_id (poly ? alias + "_" + table.uname () : alias); + + instance<object_columns_list> oid_cols (column_prefix_); + oid_cols->traverse (m); + + instance<object_columns_list> pid_cols; + pid_cols->traverse (*id_member (c)); + + for (object_columns_list::iterator b (pid_cols->begin ()), i (b), + j (oid_cols->begin ()); i != pid_cols->end (); ++i, ++j) + { + + if (i != b) + cond << " AND "; + + cond << a << '.' << quote_id (i->name) << '=' << + table_ << '.' << quote_id (j->name); + } + + joined_obj = &c; + } + + if (!t.empty ()) + { + string line ("LEFT JOIN "); + line += t; + + if (!a.empty ()) + line += (need_alias_as ? " AS " : " ") + a; + + line += " ON "; + line += cond.str (); + + joins.push_back (line); + } + + // Add dependent join (i.e., an object table join via the + // container table). + // + if (!dt.empty ()) + { + string line ("LEFT JOIN "); + line += dt; + + if (!da.empty ()) + line += (need_alias_as ? " AS " : " ") + da; + + line += " ON "; + line += dcond.str (); + + joins.push_back (line); + } + + // If we joined the object that is part of a polymorphic type + // hierarchy, then we may need join its bases as well as its + // derived types. This is only done for queries. + // + if (joined_obj != 0 && poly) + { + size_t depth (polymorphic_depth (*joined_obj)); + + // Join "up" (derived). + // + if (joined_obj != &c) + { + bool t (true); //@@ (im)perfect forward. + size_t d (polymorphic_depth (c) - depth); //@@ (im)perfect forward. + instance<polymorphic_object_joins> j (*joined_obj, t, d, alias); + j->traverse (c); + joins.insert (joins.end (), j->joins.begin (), j->joins.end ()); + } + + // Join "down" (base). + // + if (joined_obj != poly_root) + { + bool t (true); //@@ (im)perfect forward. + size_t d (depth - 1); //@@ (im)perfect forward. + instance<polymorphic_object_joins> j (*joined_obj, t, d, alias); + j->traverse (polymorphic_base (*joined_obj)); + joins.insert (joins.end (), j->joins.begin (), j->joins.end ()); + } + } + } + + public: + strings joins; + + strings::const_iterator + begin () const {return joins.begin ();} + + strings::const_iterator + end () const {return joins.end ();} + + private: + bool query_; + size_t depth_; + string table_; + data_member_path& id_; + instance<object_columns_list> id_cols_; + }; + + // Check that eager object pointers in the objects that a view loads + // can be loaded from the cache (i.e., they have session support + // enabled). + // + struct view_object_check: object_members_base + { + typedef view_object_check base; + + view_object_check (view_object& vo, view_relationship_map& rm) + : object_members_base (false, &main_section), + session_ (false), vo_ (vo), rel_map_ (rm) {} + + virtual bool + section_test (data_member_path const& mp) + { + // Include eager loaded members into the main section. + // + object_section& s (section (mp)); + return *section_ == s || !s.separate_load (); + } + + virtual void + traverse_pointer (semantics::data_member& m, semantics::class_& c) + { + // Ignore polymorphic id references. + // + if (m.count ("polymorphic-ref")) + return; + + check (m, inverse (m), utype (m), c); + } + + virtual void + traverse_container (semantics::data_member& m, semantics::type&) + { + semantics::type& vt (container_vt (m)); + + if (semantics::class_* cvt = composite_wrapper (vt)) + { + // Check this composite value for any pointers. + // + instance<view_object_check> t (vo_, rel_map_); + t->traverse (*cvt); + + session_ = session_ || t->session_; + } + else if (semantics::class_* c = object_pointer (vt)) + check (m, inverse (m, "value"), vt, *c); + } + + void + check (semantics::data_member& m, + data_member_path* imp, + semantics::type& pt, // Pointer type. + semantics::class_& c) + { + // We don't care about lazy pointers. + // + if (lazy_pointer (pt)) + return; + + // First check the pointed-to object recursively. + // + if (!c.count ("view-object-check-seen")) + { + c.set ("view-object-check-seen", true); + instance<view_object_check> t (vo_, rel_map_); + t->traverse (c); + + // We may come again for another view. + // + c.remove ("view-object-check-seen"); + + session_ = session_ || t->session_; + } + + // See if the pointed-to object in this relationship is loaded + // by this view. + // + typedef view_relationship_map::const_iterator iterator; + + std::pair<iterator, iterator> r ( + rel_map_.equal_range (imp != 0 ? *imp : member_path_)); + + if (r.first == r.second) + return; // This relationship does not figure in the view. + + view_object& vo (*(imp != 0 + ? r.first->second.first + : r.first->second.second)); + + assert (vo.obj == &c); // Got the above right? + + if (vo.ptr == 0) + return; // This object is not loaded by the view. + + // The pointed-to object in this relationship is loaded + // by the view. The hard question, of course, is whether + // it has anything to do with us. We assume it does. + // + if (!session (c)) + { + // Always direct data member. + // + semantics::class_& v ( + dynamic_cast<semantics::class_&> (vo.ptr->scope ())); + + location const& l1 (c.location ()); + location const& l2 (m.location ()); + location const& l3 (vo_.ptr->location ()); + location const& l4 (vo.ptr->location ()); + + string on (class_name (c)); + string vn (class_name (v)); + + error (l1) << "object '" << on << "' has session support disabled " + << "but may be loaded by view '" << vn << "' via " + << "several data members" << endl; + + info (l2) << "indirectly via this data member..." << endl; + info (l3) << "...as a result of this object load" << endl; + info (l4) << "and directly as a result of this load" << endl; + info (l1) << "session support is required to only load one copy " + << "of the object" << endl; + info (l1) << "and don't forget to create a session instance when " + << "using this view" << endl; + + throw operation_failed (); + } + + session_ = true; + } + + bool session_; + + private: + view_object& vo_; + view_relationship_map& rel_map_; + }; + + // + // bind + // + + struct bind_member: virtual member_base + { + typedef bind_member base; + + // NULL section means we are generating object bind(). + // + bind_member (string const& var = string (), + string const& arg = string (), + object_section* section = 0) + : member_base (var, 0, 0, string (), string (), section), + arg_override_ (arg) {} + + bind_member (string const& var, + string const& arg, + semantics::type& t, + const custom_cxx_type* ct, + string const& fq_type, + string const& key_prefix) + : member_base (var, &t, ct, fq_type, key_prefix), + arg_override_ (arg) {} + + protected: + string arg_override_; + }; + + template <typename T> + struct bind_member_impl: bind_member, virtual member_base_impl<T> + { + typedef bind_member_impl base_impl; + + bind_member_impl (base const& x): base (x) {} + + typedef typename member_base_impl<T>::member_info member_info; + + using member_base_impl<T>::container; + + virtual bool + pre (member_info& mi) + { + if (container (mi)) + return false; + + // Treat version as present in every section. + // + if (section_ != 0 && !version (mi.m) && *section_ != section (mi.m)) + return false; + + // Ignore polymorphic id references; they are bound in a special + // way. + // + if (mi.ptr != 0 && mi.m.count ("polymorphic-ref")) + return false; + + std::ostringstream ostr; + ostr << "b[n]"; + b = ostr.str (); + + arg = arg_override_.empty () ? string ("i") : arg_override_; + + if (var_override_.empty ()) + { + // Ignore inverse, separately-loaded members in the main + // section (nothing to persist). + // + if (section_ == 0 && separate_load (mi.m) && inverse (mi.m)) + return false; + + semantics::class_* comp (composite (mi.t)); + + os << "// " << mi.m.name () << endl + << "//" << endl; + + // Order of these tests is important. + // + if (!insert_send_auto_id && auto_ (mi.m)) + os << "if (sk != statement_insert && sk != statement_update)" + << "{"; + else if (section_ == 0 && separate_load (mi.m)) + os << "if (sk == statement_insert)" + << "{"; + else if (inverse (mi.m, key_prefix_) || version (mi.m)) + os << "if (sk == statement_select)" + << "{"; + // If the whole class is readonly, then we will never be + // called with sk == statement_update. + // + else if (!readonly (*context::top_object)) + { + if (id (mi.m) || + readonly (mi.m) || + (comp != 0 && readonly (*comp)) || + (section_ == 0 && separate_update (mi.m))) + os << "if (sk != statement_update)" + << "{"; + } + + // If the member is soft- added or deleted, check the version. + // + unsigned long long av (added (mi.m)); + unsigned long long dv (deleted (mi.m)); + + // If this is a composite member, see if it is summarily + // added/deleted. + // + if (comp != 0) + { + unsigned long long cav (added (*comp)); + unsigned long long cdv (deleted (*comp)); + + if (cav != 0 && (av == 0 || av < cav)) + av = cav; + + if (cdv != 0 && (dv == 0 || dv > cdv)) + dv = cdv; + } + + // If the addition/deletion version is the same as the section's, + // then we don't need the test. + // + if (user_section* s = dynamic_cast<user_section*> (section_)) + { + if (av == added (*s->member)) + av = 0; + + if (dv == deleted (*s->member)) + dv = 0; + } + + if (av != 0 || dv != 0) + { + os << "if ("; + + if (av != 0) + os << "svm >= schema_version_migration (" << av << "ULL, true)"; + + if (av != 0 && dv != 0) + os << " &&" << endl; + + if (dv != 0) + os << "svm <= schema_version_migration (" << dv << "ULL, true)"; + + os << ")" + << "{"; + } + } + + return true; + } + + virtual void + post (member_info& mi) + { + if (var_override_.empty ()) + { + semantics::class_* comp (composite (mi.t)); + + // We need to increment the index even if we skipped this + // member due to the schema version. + // + unsigned long long av (added (mi.m)); + unsigned long long dv (deleted (mi.m)); + + if (comp != 0) + { + unsigned long long cav (added (*comp)); + unsigned long long cdv (deleted (*comp)); + + if (cav != 0 && (av == 0 || av < cav)) + av = cav; + + if (cdv != 0 && (dv == 0 || dv > cdv)) + dv = cdv; + } + + if (user_section* s = dynamic_cast<user_section*> (section_)) + { + if (av == added (*s->member)) + av = 0; + + if (dv == deleted (*s->member)) + dv = 0; + } + + if (av != 0 || dv != 0) + os << "}"; + + if (mi.ptr != 0 && view_member (mi.m)) + { + // See column_count_impl for details on what's going on here. + // + column_count_type cc; + if (semantics::class_* root = polymorphic (*mi.ptr)) + { + for (semantics::class_* b (mi.ptr);; b = &polymorphic_base (*b)) + { + column_count_type const& ccb (column_count (*b)); + + cc.total += ccb.total - (b != root ? ccb.id : 0); + cc.separate_load += ccb.separate_load; + + if (b == root) + break; + } + } + else + cc = column_count (*mi.ptr); + + os << "n += " << cc.total - cc.separate_load << "UL;"; + } + else if (comp != 0) + { + bool ro (readonly (*comp)); + column_count_type const& cc (column_count (*comp)); + + os << "n += " << cc.total << "UL"; + + // select = total + // insert = total - inverse + // update = total - inverse - readonly + // + if (cc.inverse != 0 || (!ro && cc.readonly != 0)) + { + os << " - (" << endl + << "sk == statement_select ? 0 : "; + + if (cc.inverse != 0) + os << cc.inverse << "UL"; + + if (!ro && cc.readonly != 0) + { + if (cc.inverse != 0) + os << " + "; + + os << "(" << endl + << "sk == statement_insert ? 0 : " << + cc.readonly << "UL)"; + } + + os << ")"; + } + + os << ";"; + } + else + os << "n++;"; + + bool block (false); + + // The same logic as in pre(). + // + if (!insert_send_auto_id && auto_ (mi.m)) + block = true; + else if (section_ == 0 && separate_load (mi.m)) + block = true; + else if (inverse (mi.m, key_prefix_) || version (mi.m)) + block = true; + else if (!readonly (*context::top_object)) + { + semantics::class_* c; + + if (id (mi.m) || + readonly (mi.m) || + ((c = composite (mi.t)) && readonly (*c)) || + (section_ == 0 && separate_update (mi.m))) + block = true; + } + + if (block) + os << "}"; + else + os << endl; + } + } + + virtual void + traverse_pointer (member_info& mi) + { + // Object pointers in views require special treatment. + // + if (view_member (mi.m)) + { + semantics::class_& c (*mi.ptr); + semantics::class_* poly_root (polymorphic (c)); + bool poly_derived (poly_root != 0 && poly_root != &c); + + os << "object_traits_impl< " << class_fq_name (c) << ", id_" << + db << " >::bind (" << endl + << "b + n, " << (poly_derived ? "0, 0, " : "") << arg << "." << + mi.var << "value, sk" << (versioned (c) ? ", svm" : "") << ");"; + } + else + member_base_impl<T>::traverse_pointer (mi); + } + + virtual void + traverse_composite (member_info& mi) + { + os << "composite_value_traits< " << mi.fq_type () << ", id_" << + db << " >::bind (" << endl + << "b + n, " << arg << "." << mi.var << "value, sk" << + (versioned (*composite (mi.t)) ? ", svm" : "") << ");"; + } + + protected: + string b; + string arg; + }; + + struct bind_base: traversal::class_, virtual context + { + typedef bind_base base; + + virtual void + traverse (type& c) + { + bool obj (object (c)); + + // Ignore transient bases. Not used for views. + // + if (!(obj || composite (c))) + return; + + os << "// " << class_name (c) << " base" << endl + << "//" << endl; + + // If the derived class is readonly, then we will never be + // called with sk == statement_update. + // + bool ro (readonly (c)); + bool check (ro && !readonly (*context::top_object)); + + if (check) + os << "if (sk != statement_update)" + << "{"; + + if (obj) + os << "object_traits_impl< "; + else + os << "composite_value_traits< "; + + os << class_fq_name (c) << ", id_" << db << " >::bind (b + n, i, sk" << + (versioned (c) ? ", svm" : "") << ");"; + + column_count_type const& cc (column_count (c)); + + os << "n += "; + + // select = total - separate_load + // insert = total - inverse - optimistic_managed - id(auto & !sending) + // update = total - inverse - optimistic_managed - id - readonly - + // separate_update + // + size_t select (cc.total - cc.separate_load); + size_t insert (cc.total - cc.inverse - cc.optimistic_managed); + size_t update (insert - cc.id - cc.readonly - cc.separate_update); + + data_member_path* id; + if (!insert_send_auto_id && (id = id_member (c)) != 0 && auto_ (*id)) + insert -= cc.id; + + if (select == insert && insert == update) + os << select << "UL;"; + else if (select != insert && insert == update) + os << "sk == statement_select ? " << select << "UL : " << + insert << "UL;"; + else if (select == insert && insert != update) + os << "sk == statement_update ? " << update << "UL : " << + select << "UL;"; + else + os << "sk == statement_select ? " << select << "UL : " << + "sk == statement_insert ? " << insert << "UL : " << + update << "UL;"; + + if (check) + os << "}"; + else + os << endl; + } + }; + + // + // grow + // + + struct grow_member: virtual member_base + { + typedef grow_member base; + + grow_member (size_t& index, + string const& var = string (), + user_section* section = 0) + : member_base (var, 0, 0, string (), string (), section), + index_ (index) {} + + grow_member (size_t& index, + string const& var, + semantics::type& t, + const custom_cxx_type* ct, + string const& fq_type, + string const& key_prefix) + : member_base (var, &t, ct, fq_type, key_prefix), index_ (index) {} + + protected: + size_t& index_; + }; + + template <typename T> + struct grow_member_impl: grow_member, virtual member_base_impl<T> + { + typedef grow_member_impl base_impl; + + grow_member_impl (base const& x) + : member_base::base (x), // virtual base + base (x) {} + + typedef typename member_base_impl<T>::member_info member_info; + + using member_base_impl<T>::container; + + virtual bool + pre (member_info& mi) + { + if (container (mi)) + return false; + + // If we have a key prefix (container), then it can't be in a section + // (while mi.m can). The same for top-level -- if we got called, then + // we shouldn't ignore it. (Same logic as in has_grow_member). + // + if (!(!key_prefix_.empty () || top_level_ || + (section_ == 0 && !separate_load (mi.m)) || + (section_ != 0 && *section_ == section (mi.m)))) + return false; + + // Ignore polymorphic id references; they are not returned by + // the select statement. + // + if (mi.ptr != 0 && mi.m.count ("polymorphic-ref")) + return false; + + std::ostringstream ostr; + ostr << "t[" << index_ << "UL]"; + e = ostr.str (); + + if (var_override_.empty ()) + { + os << "// " << mi.m.name () << endl + << "//" << endl; + + semantics::class_* comp (composite (mi.t)); + + // If the member is soft- added or deleted, check the version. + // + unsigned long long av (added (mi.m)); + unsigned long long dv (deleted (mi.m)); + + // If this is a composite member, see if it is summarily + // added/deleted. + // + if (comp != 0) + { + unsigned long long cav (added (*comp)); + unsigned long long cdv (deleted (*comp)); + + if (cav != 0 && (av == 0 || av < cav)) + av = cav; + + if (cdv != 0 && (dv == 0 || dv > cdv)) + dv = cdv; + } + + // If the addition/deletion version is the same as the section's, + // then we don't need the test. + // + if (user_section* s = dynamic_cast<user_section*> (section_)) + { + if (av == added (*s->member)) + av = 0; + + if (dv == deleted (*s->member)) + dv = 0; + } + + if (av != 0 || dv != 0) + { + os << "if ("; + + if (av != 0) + os << "svm >= schema_version_migration (" << av << "ULL, true)"; + + if (av != 0 && dv != 0) + os << " &&" << endl; + + if (dv != 0) + os << "svm <= schema_version_migration (" << dv << "ULL, true)"; + + os << ")" + << "{"; + } + } + + return true; + } + + virtual void + post (member_info& mi) + { + semantics::class_* comp (composite (mi.t)); + + if (var_override_.empty ()) + { + unsigned long long av (added (mi.m)); + unsigned long long dv (deleted (mi.m)); + + if (comp != 0) + { + unsigned long long cav (added (*comp)); + unsigned long long cdv (deleted (*comp)); + + if (cav != 0 && (av == 0 || av < cav)) + av = cav; + + if (cdv != 0 && (dv == 0 || dv > cdv)) + dv = cdv; + } + + if (user_section* s = dynamic_cast<user_section*> (section_)) + { + if (av == added (*s->member)) + av = 0; + + if (dv == deleted (*s->member)) + dv = 0; + } + + if (av != 0 || dv != 0) + os << "}"; + } + + if (mi.ptr != 0 && view_member (mi.m)) + { + // See column_count_impl for details on what's going on here. + // + column_count_type cc; + if (semantics::class_* root = polymorphic (*mi.ptr)) + { + for (semantics::class_* b (mi.ptr);; b = &polymorphic_base (*b)) + { + column_count_type const& ccb (column_count (*b)); + + cc.total += ccb.total - (b != root ? ccb.id : 0); + cc.separate_load += ccb.separate_load; + + if (b == root) + break; + } + } + else + cc = column_count (*mi.ptr); + + index_ += cc.total - cc.separate_load; + } + else if (comp != 0) + index_ += column_count (*comp).total; + else + index_++; + } + + virtual void + traverse_pointer (member_info& mi) + { + // Object pointers in views require special treatment. They + // can only be immediate members of the view class. + // + if (view_member (mi.m)) + { + semantics::class_& c (*mi.ptr); + + os << "if (object_traits_impl< " << class_fq_name (c) << + ", id_" << db << " >::grow (" << endl + << "i." << mi.var << "value, t + " << index_ << "UL" << + (versioned (c) ? ", svm" : "") << "))" << endl + << "grew = true;" + << endl; + } + else + member_base_impl<T>::traverse_pointer (mi); + } + + virtual void + traverse_composite (member_info& mi) + { + semantics::class_& c (*composite (mi.t)); + + os << "if (composite_value_traits< " << mi.fq_type () << + ", id_" << db << " >::grow (" << endl + << "i." << mi.var << "value, t + " << index_ << "UL" << + (versioned (c) ? ", svm" : "") << "))" << endl + << "grew = true;" + << endl; + } + + protected: + string e; + }; + + struct grow_base: traversal::class_, virtual context + { + typedef grow_base base; + + grow_base (size_t& index): index_ (index) {} + + virtual void + traverse (type& c) + { + bool obj (object (c)); + + // Ignore transient bases. Not used for views. + // + if (!(obj || composite (c))) + return; + + os << "// " << class_name (c) << " base" << endl + << "//" << endl; + + os << "if ("; + + if (obj) + os << "object_traits_impl< "; + else + os << "composite_value_traits< "; + + os << class_fq_name (c) << ", id_" << db << " >::grow (" << endl + << "i, t + " << index_ << "UL" << + (versioned (c) ? ", svm" : "") << "))" << endl + << "grew = true;" + << endl; + + index_ += column_count (c).total; + } + + protected: + size_t& index_; + }; + + // + // init image + // + + struct init_image_member: virtual member_base + { + typedef init_image_member base; + + init_image_member (string const& var = string (), + string const& member = string (), + user_section* section = 0) + : member_base (var, 0, 0, string (), string (), section), + member_override_ (member) + { + } + + init_image_member (string const& var, + string const& member, + semantics::type& t, + const custom_cxx_type* ct, + string const& fq_type, + string const& key_prefix) + : member_base (var, &t, ct, fq_type, key_prefix), + member_override_ (member) + { + } + + protected: + string member_override_; + }; + + template <typename T> + struct init_image_member_impl: init_image_member, + virtual member_base_impl<T> + { + typedef init_image_member_impl base_impl; + + init_image_member_impl (base const& x) + : base (x), + member_database_type_id_ (base::type_override_, + base::custom_override_, + base::fq_type_override_, + base::key_prefix_) + { + } + + typedef typename member_base_impl<T>::member_info member_info; + + using member_base_impl<T>::container; + + virtual void + set_null (member_info&) = 0; + + virtual void + check_accessor (member_info&, member_access&) {} + + virtual bool + pre (member_info& mi) + { + // Ignore containers (they get their own table) and inverse + // object pointers (they are not present in this binding). + // + if (container (mi) || inverse (mi.m, key_prefix_)) + return false; + + if (section_ != 0 && *section_ != section (mi.m)) + return false; + + // Ignore polymorphic id references; they are initialized in a + // special way. + // + if (mi.ptr != 0 && mi.m.count ("polymorphic-ref")) + return false; + + semantics::class_* comp (composite (mi.t)); + + if (!member_override_.empty ()) + { + member = member_override_; + os << "{"; + } + else + { + // If we are generating standard init() and this member + // contains version, ignore it. + // + if (version (mi.m)) + return false; + + // If we don't send auto id in INSERT statement, ignore this + // member altogether (we never send auto id in UPDATE). + // + if (!insert_send_auto_id && auto_ (mi.m)) + return false; + + os << "// " << mi.m.name () << endl + << "//" << endl; + + // If the member is soft- added or deleted, check the version. + // + unsigned long long av (added (mi.m)); + unsigned long long dv (deleted (mi.m)); + + // If this is a composite member, see if it is summarily + // added/deleted. + // + if (comp != 0) + { + unsigned long long cav (added (*comp)); + unsigned long long cdv (deleted (*comp)); + + if (cav != 0 && (av == 0 || av < cav)) + av = cav; + + if (cdv != 0 && (dv == 0 || dv > cdv)) + dv = cdv; + } + + // If the addition/deletion version is the same as the section's, + // then we don't need the test. + // + if (user_section* s = dynamic_cast<user_section*> (section_)) + { + if (av == added (*s->member)) + av = 0; + + if (dv == deleted (*s->member)) + dv = 0; + } + + if (av != 0 || dv != 0) + { + os << "if ("; + + if (av != 0) + os << "svm >= schema_version_migration (" << av << "ULL, true)"; + + if (av != 0 && dv != 0) + os << " &&" << endl; + + if (dv != 0) + os << "svm <= schema_version_migration (" << dv << "ULL, true)"; + + os << ")" + << "{"; + } + + // If the whole class is readonly, then we will never be + // called with sk == statement_update. + // + if (!readonly (*context::top_object)) + { + if (id (mi.m) || + readonly (mi.m) || + (section_ == 0 && separate_update (mi.m)) || + (comp != 0 && readonly (*comp))) // Can't be id. + { + // If we are generating section init(), then sk can only be + // statement_update. + // + if (section_ == 0) + os << "if (sk == statement_insert)"; + } + } + + os << "{"; + + if (discriminator (mi.m)) + member = "di.discriminator"; + else + { + // Get the member using the accessor expression. + // + member_access& ma (mi.m.template get<member_access> ("get")); + + // Make sure this kind of member can be accessed with this + // kind of accessor (database-specific, e.g., streaming). + // + if (comp == 0) + check_accessor (mi, ma); + + // If this is not a synthesized expression, then output + // its location for easier error tracking. + // + if (!ma.synthesized) + os << "// From " << location_string (ma.loc, true) << endl; + + // Use the original type to form the const reference. VC++ + // cannot grok the constructor syntax. + // + os << member_ref_type (mi.m, true, "v") << " =" << endl + << " " << ma.translate ("o") << ";" + << endl; + + member = "v"; + } + } + + // Translate. + // + if (mi.ct != 0) + { + os << "// From " << location_string (mi.ct->loc, true) << endl + << type_ref_type (*mi.ct->as, mi.ct->as_hint, true, "vt") << + " =" << endl + << " " << mi.ct->translate_to (member) << ";" + << endl; + + member = "vt"; + } + + // If this is a wrapped composite value, then we need to "unwrap" + // it. If this is a NULL wrapper, then we also need to handle that. + // For simple values this is taken care of by the value_traits + // specializations. + // + if (mi.wrapper != 0 && comp != 0) + { + // The wrapper type, not the wrapped type. + // + string const& wt (mi.fq_type (false)); + + // If this is a NULL wrapper and the member can be NULL, then + // we need to handle the NULL value. + // + if (null (mi.m, key_prefix_) && + mi.wrapper->template get<bool> ("wrapper-null-handler")) + { + os << "if (wrapper_traits< " << wt << " >::get_null (" << + member << "))" << endl + << "composite_value_traits< " << mi.fq_type () << ", id_" << + db << " >::set_null (" << endl + << "i." << mi.var << "value, sk" << + (versioned (*comp) ? ", svm" : "") << ");" + << "else" + << "{"; + } + + os << "const" << mi.fq_type () << "& vw = " << endl + << " wrapper_traits< " + wt + " >::get_ref (" + member + ");" + << endl; + + member = "vw"; + } + + if (discriminator (mi.m)) + os << "const info_type& di (map->find (typeid (o)));" + << endl; + + if (mi.ptr != 0) + { + // When handling a pointer, mi.t is the id type of the referenced + // object. + // + semantics::type& pt (utype (mi.m, key_prefix_)); + + type = "obj_traits::id_type"; + + // Handle NULL pointers and extract the id. + // + os << "typedef object_traits< " << class_fq_name (*mi.ptr) << + " > obj_traits;"; + + if (weak_pointer (pt)) + { + os << "typedef odb::pointer_traits< " << mi.ptr_fq_type () << + " > wptr_traits;" + << "typedef odb::pointer_traits< wptr_traits::" << + "strong_pointer_type > ptr_traits;" + << endl + << "wptr_traits::strong_pointer_type sp (" << + "wptr_traits::lock (" << member << "));"; + + member = "sp"; + } + else + os << "typedef odb::pointer_traits< " << mi.ptr_fq_type () << + " > ptr_traits;" + << endl; + + os << "bool is_null (ptr_traits::null_ptr (" << member << "));" + << "if (!is_null)" + << "{" + << "const " << type << "& ptr_id (" << endl; + + if (lazy_pointer (pt)) + os << "ptr_traits::object_id< ptr_traits::element_type > (" << + member << ")"; + else + os << "obj_traits::id (ptr_traits::get_ref (" << member << "))"; + + os << ");" + << endl; + + member = "ptr_id"; + } + else if (comp != 0) + type = mi.fq_type (); + else + { + type = mi.fq_type (); + + // Indicate to the value_traits whether this column can be NULL. + // + os << "bool is_null (" << null (mi.m, key_prefix_) << ");"; + } + + if (comp != 0) + traits = "composite_value_traits< " + type + ", id_" + + db.string () + " >"; + else + { + db_type_id = member_database_type_id_->database_type_id (mi.m); + traits = db.string () + "::value_traits<\n " + + type + ",\n " + + db_type_id + " >"; + } + + return true; + } + + virtual void + post (member_info& mi) + { + semantics::class_* comp (composite (mi.t)); + + if (mi.ptr != 0) + { + os << "}" + << "else" << endl; + + if (!null (mi.m, key_prefix_)) + os << "throw null_pointer ();"; + else if (comp != 0) + os << traits << "::set_null (i." << mi.var << "value, sk" << + (versioned (*comp) ? ", svm" : "") << ");"; + else + set_null (mi); + } + + if (mi.wrapper != 0 && comp != 0) + { + if (null (mi.m, key_prefix_) && + mi.wrapper->template get<bool> ("wrapper-null-handler")) + os << "}"; + } + + os << "}"; + + if (member_override_.empty ()) + { + unsigned long long av (added (mi.m)); + unsigned long long dv (deleted (mi.m)); + + if (comp != 0) + { + unsigned long long cav (added (*comp)); + unsigned long long cdv (deleted (*comp)); + + if (cav != 0 && (av == 0 || av < cav)) + av = cav; + + if (cdv != 0 && (dv == 0 || dv > cdv)) + dv = cdv; + } + + if (user_section* s = dynamic_cast<user_section*> (section_)) + { + if (av == added (*s->member)) + av = 0; + + if (dv == deleted (*s->member)) + dv = 0; + } + + if (av != 0 || dv != 0) + os << "}"; + } + } + + virtual void + traverse_composite (member_info& mi) + { + bool grow (generate_grow && + context::grow (mi.m, mi.t, mi.ct, key_prefix_)); + + if (grow) + os << "if ("; + + os << traits << "::init (" << endl + << "i." << mi.var << "value," << endl + << member << "," << endl + << "sk"; + + if (versioned (*composite (mi.t))) + os << "," << endl + << "svm"; + + os << ")"; + + if (grow) + os << ")" << endl + << "grew = true"; + + os << ";"; + } + + protected: + string type; + string db_type_id; + string member; + string traits; + + instance<member_database_type_id> member_database_type_id_; + }; + + struct init_image_base: traversal::class_, virtual context + { + typedef init_image_base base; + + virtual void + traverse (type& c) + { + bool obj (object (c)); + + // Ignore transient bases. Not used for views. + // + if (!(obj || composite (c))) + return; + + os << "// " << class_name (c) << " base" << endl + << "//" << endl; + + // If the derived class is readonly, then we will never be + // called with sk == statement_update. + // + bool check (readonly (c) && !readonly (*context::top_object)); + + if (check) + os << "if (sk != statement_update)" + << "{"; + + if (generate_grow) + os << "if ("; + + if (obj) + os << "object_traits_impl< "; + else + os << "composite_value_traits< "; + + os << class_fq_name (c) << ", id_" << db << " >::init (i, o, sk" << + (versioned (c) ? ", svm" : "") << ")"; + + if (generate_grow) + os << ")" << endl + << "grew = true"; + + os << ";"; + + if (check) + os << "}"; + else + os << endl; + } + }; + + // + // init value + // + + struct init_value_member: virtual member_base + { + typedef init_value_member base; + + init_value_member (string const& member = string (), + string const& var = string (), + bool ignore_implicit_discriminator = true, + user_section* section = 0) + : member_base (var, 0, 0, string (), string (), section), + member_override_ (member), + ignore_implicit_discriminator_ (ignore_implicit_discriminator) + { + } + + init_value_member (string const& var, + string const& member, + semantics::type& t, + const custom_cxx_type* ct, + string const& fq_type, + string const& key_prefix) + : member_base (var, &t, ct, fq_type, key_prefix), + member_override_ (member), + ignore_implicit_discriminator_ (true) + { + } + + virtual void + get_null (string const& /*var*/) const {}; + + protected: + string member_override_; + bool ignore_implicit_discriminator_; + }; + + template <typename T> + struct init_value_member_impl: init_value_member, + virtual member_base_impl<T> + { + typedef init_value_member_impl base_impl; + + init_value_member_impl (base const& x) + : base (x), + member_database_type_id_ (base::type_override_, + base::custom_override_, + base::fq_type_override_, + base::key_prefix_) + { + } + + typedef typename member_base_impl<T>::member_info member_info; + + using member_base_impl<T>::container; + + virtual void + get_null (string const& var) const = 0; + + virtual void + check_modifier (member_info&, member_access&) {} + + virtual bool + pre (member_info& mi) + { + if (container (mi)) + return false; + + if (section_ != 0 && *section_ != section (mi.m)) + return false; + + // Ignore polymorphic id references; they are initialized in a + // special way. + // + if (mi.ptr != 0 && mi.m.count ("polymorphic-ref")) + return false; + + // Ignore implicit discriminators. + // + if (ignore_implicit_discriminator_ && discriminator (mi.m)) + return false; + + semantics::class_* comp (composite (mi.t)); + + if (!member_override_.empty ()) + { + os << "{"; + member = member_override_; + } + else + { + // Ignore separately loaded members. + // + if (section_ == 0 && separate_load (mi.m)) + return false; + + os << "// " << mi.m.name () << endl + << "//" << endl; + + // If the member is soft- added or deleted, check the version. + // + unsigned long long av (added (mi.m)); + unsigned long long dv (deleted (mi.m)); + + // If this is a composite member, see if it is summarily + // added/deleted. + // + if (comp != 0) + { + unsigned long long cav (added (*comp)); + unsigned long long cdv (deleted (*comp)); + + if (cav != 0 && (av == 0 || av < cav)) + av = cav; + + if (cdv != 0 && (dv == 0 || dv > cdv)) + dv = cdv; + } + + // If the addition/deletion version is the same as the section's, + // then we don't need the test. + // + if (user_section* s = dynamic_cast<user_section*> (section_)) + { + if (av == added (*s->member)) + av = 0; + + if (dv == deleted (*s->member)) + dv = 0; + } + + if (av != 0 || dv != 0) + { + os << "if ("; + + if (av != 0) + os << "svm >= schema_version_migration (" << av << "ULL, true)"; + + if (av != 0 && dv != 0) + os << " &&" << endl; + + if (dv != 0) + os << "svm <= schema_version_migration (" << dv << "ULL, true)"; + + os << ")"; + } + + os << "{"; + + if (mi.ptr != 0 && view_member (mi.m)) + return true; // That's enough for the object pointer in view. + + // Set the member using the modifier expression. + // + member_access& ma (mi.m.template get<member_access> ("set")); + + // Make sure this kind of member can be modified with this + // kind of accessor (database-specific, e.g., streaming). + // + if (comp == 0) + check_modifier (mi, ma); + + // If this is not a synthesized expression, then output + // its location for easier error tracking. + // + if (!ma.synthesized) + os << "// From " << location_string (ma.loc, true) << endl; + + // See if we are modifying via a reference or proper modifier. + // + if (ma.placeholder ()) + os << member_val_type (mi.m, false, "v") << ";" + << endl; + else + { + // Use the original type to form the reference. VC++ cannot + // grok the constructor syntax. + // + os << member_ref_type (mi.m, false, "v") << " =" << endl + << " "; + + // If this member is const and we have a synthesized direct + // access, then cast away constness. Otherwise, we assume + // that the user-provided expression handles this. + // + bool cast (mi.cq && ma.direct ()); + if (cast) + os << "const_cast< " << member_ref_type (mi.m, false) << + " > (" << endl; + + os << ma.translate ("o"); + + if (cast) + os << ")"; + + os << ";" + << endl; + } + + member = "v"; + } + + // Translate. + // + if (mi.ct != 0) + { + os << type_val_type (*mi.ct->as, mi.ct->as_hint, false, "vt") << ";" + << endl; + + translate_member = member; + member = "vt"; + } + + // If this is a wrapped composite value, then we need to "unwrap" it. + // If this is a NULL wrapper, then we also need to handle that. For + // simple values this is taken care of by the value_traits + // specializations. + // + if (mi.wrapper != 0 && comp != 0) + { + // The wrapper type, not the wrapped type. + // + string const& wt (mi.fq_type (false)); + + // If this is a NULL wrapper and the member can be NULL, then + // we need to handle the NULL value. + // + if (null (mi.m, key_prefix_) && + mi.wrapper->template get<bool> ("wrapper-null-handler")) + { + os << "if (composite_value_traits< " << mi.fq_type () << + ", id_" << db << " >::get_null (" << endl + << "i." << mi.var << "value" << + (versioned (*comp) ? ", svm" : "") << "))" << endl + << "wrapper_traits< " << wt << " >::set_null (" << member + ");" + << "else" + << "{"; + } + + os << mi.fq_type () << "& vw =" << endl + << " wrapper_traits< " + wt + " >::set_ref (" + member + ");" + << endl; + + wrap_member = member; + member = "vw"; + } + + if (mi.ptr != 0) + { + type = "obj_traits::id_type"; + + // Handle NULL pointers and extract the id. + // + os << "typedef object_traits< " << class_fq_name (*mi.ptr) << + " > obj_traits;" + << "typedef odb::pointer_traits< " << mi.ptr_fq_type () << + " > ptr_traits;" + << endl; + + os << "if ("; + + if (comp != 0) + os << "composite_value_traits< " << type << ", id_" << db << + " >::get_null (" << endl + << "i." << mi.var << "value" << + (versioned (*comp) ? ", svm" : "") << ")"; + else + get_null (mi.var); + + os << ")" << endl; + + // Don't throw null_pointer if we can't have NULLs and the pointer + // is NULL since this can be useful during migration. Instead, we + // rely on the database enforcing this. + // + os << member << " = ptr_traits::pointer_type ();"; + + os << "else" + << "{"; + + os << type << " ptr_id;"; + + member = "ptr_id"; + } + else + type = mi.fq_type (); + + if (comp != 0) + traits = "composite_value_traits< " + type + ", id_" + + db.string () + " >"; + else + { + db_type_id = member_database_type_id_->database_type_id (mi.m); + traits = db.string () + "::value_traits<\n " + + type + ",\n " + + db_type_id + " >"; + } + + return true; + } + + virtual void + post (member_info& mi) + { + if (mi.ptr != 0) + { + if (view_member (mi.m)) + { + // The object pointer in view doesn't need any of this. + os << "}"; + return; + } + + // Restore the member variable name. + // + member = member_override_.empty () ? "v" : member_override_; + + // When handling a pointer, mi.t is the id type of the referenced + // object. + // + semantics::type& pt (utype (mi.m, key_prefix_)); + + if (lazy_pointer (pt)) + os << member << " = ptr_traits::pointer_type (" << endl + << "*static_cast<" << db << "::database*> (db), ptr_id);"; + else + { + os << "// If a compiler error points to the line below, then" << endl + << "// it most likely means that a pointer used in a member" << endl + << "// cannot be initialized from an object pointer." << endl + << "//" << endl + << member << " = ptr_traits::pointer_type (" << endl + << "static_cast<" << db << "::database*> (db)->load<" << endl + << " obj_traits::object_type > (ptr_id));"; + + // If we are loading into an eager weak pointer, make sure there + // is someone else holding a strong pointer to it (normally a + // session). Otherwise, the object will be loaded and immediately + // deleted. Besides not making much sense, this also breaks the + // delayed loading machinery which expects the object to be around + // at least until the top-level load() returns. + // + if (weak_pointer (pt)) + { + os << endl + << "if (odb::pointer_traits<" << + "ptr_traits::strong_pointer_type>::null_ptr (" << endl + << "ptr_traits::lock (" << member << ")))" << endl + << "throw session_required ();"; + } + } + + os << "}"; + } + + // Wrap back (so to speak). + // + if (mi.wrapper != 0 && composite (mi.t) != 0) + { + if (null (mi.m, key_prefix_) && + mi.wrapper->template get<bool> ("wrapper-null-handler")) + os << "}"; + + member = wrap_member; + } + + // Untranslate. + // + if (mi.ct != 0) + { + //@@ Use move() in C++11? Or not. + // + os << "// From " << location_string (mi.ct->loc, true) << endl + << translate_member << " = " << + mi.ct->translate_from (member) << ";"; + + member = translate_member; + } + + // Call the modifier if we are using a proper one. + // + if (member_override_.empty ()) + { + member_access& ma (mi.m.template get<member_access> ("set")); + + if (ma.placeholder ()) + { + // If this is not a synthesized expression, then output its + // location for easier error tracking. + // + if (!ma.synthesized) + os << "// From " << location_string (ma.loc, true) << endl; + + os << ma.translate ( + "o", "v", "*static_cast<" + db.string () + "::database*> (db)") + << ";"; + } + } + + os << "}"; + } + + virtual void + traverse_pointer (member_info& mi) + { + // Object pointers in views require special treatment. + // + if (view_member (mi.m)) + { + // This is the middle part. The pre and post parts are generated + // by init_view_pointer_member below. + // + using semantics::class_; + + class_& c (*mi.ptr); + class_* poly_root (polymorphic (c)); + bool poly (poly_root != 0); + bool poly_derived (poly && poly_root != &c); + + string o_tp (mi.var + "object_type"); + string o_tr (mi.var + "object_traits"); + string r_tr (poly_derived ? mi.var + "root_traits" : o_tr); + string i_tp (mi.var + "info_type"); + + string id (mi.var + "id"); + string o (mi.var + "o"); + string pi (mi.var + "pi"); // Polymorphic type info. + + // If load_() will be loading containers or the rest of the + // polymorphic object, then we need to perform several extra + // things. We need to initialize the id image in the object + // statements. We also have to lock the statements so that + // nobody messes up this id image. + // + bool init_id ( + poly || + has_a (c, test_container | include_eager_load, &main_section)); + + bool versioned (context::versioned (c)); + + os << "if (" << o << " != 0)" + << "{"; + + if (poly) + os << "callback_event ce (callback_event::pre_load);" + << pi << "->dispatch (" << i_tp << "::call_callback, " << + "*db, " << o << ", &ce);"; + else + os << o_tr << "::callback (*db, *" << o << + ", callback_event::pre_load);"; + + os << o_tr << "::init (*" << o << ", i." << mi.var << "value, db" << + (versioned ? ", svm" : "") << ");"; + + // Call load_() to load the rest of the object (containers, etc). + // + if (id_member (poly ? *poly_root : c) != 0) + { + const char* s (poly_derived ? "osts" : "sts"); + + os << o_tr << "::statements_type& " << s << " (" << endl + << "conn.statement_cache ().find_object<" << o_tp << "> ());"; + + if (poly_derived) + os << r_tr << "::statements_type& sts (osts.root_statements ());"; + + if (init_id) + { + // This can only be top-level call so lock must succeed. + // + os << r_tr << "::statements_type::auto_lock l (sts);" + << "assert (l.locked ()) /* Must be a top-level call. */;" + << endl + << r_tr << "::id_image_type& i (sts.id_image ());" + << r_tr << "::init (i, " << id << ");" + << db << "::binding& idb (sts.id_image_binding ());" + << "if (i.version != sts.id_image_version () || " << + "idb.version == 0)" + << "{" + << r_tr << "::bind (idb.bind, i);" + << "sts.id_image_version (i.version);" + << "idb.version++;"; + if (optimistic (poly ? *poly_root : c) != 0) + os << "sts.optimistic_id_image_binding ().version++;"; + os << "}"; + } + + os << o_tr << "::load_ (" << s << ", *" << o << ", false" << + (versioned ? ", svm" : "") << ");"; + + // Load the dynamic part of the object unless static and dynamic + // types are the same. + // + if (poly) + os << endl + << "if (" << pi << " != &" << o_tr << "::info)" + << "{" + << "std::size_t d (" << o_tr << "::depth);" + << pi << "->dispatch (" << i_tp << "::call_load, *db, " << + o << ", &d);" + << "}"; + + if (init_id) + os << "sts.load_delayed (" << (versioned ? "&svm" : "0") << ");" + << "l.unlock ();"; + } + + os << "}"; + } + else + member_base_impl<T>::traverse_pointer (mi); + } + + virtual void + traverse_composite (member_info& mi) + { + os << traits << "::init (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "db"; + + if (versioned (*composite (mi.t))) + os << "," << endl + << "svm"; + + os << ");" + << endl; + } + + protected: + string type; + string db_type_id; + string traits; + string member; + string translate_member; // Untranslated member. + string wrap_member; // Wrapped member. + + instance<member_database_type_id> member_database_type_id_; + }; + + // This class generates the pre and post parts. The middle part is + // generated by init_value_member above. + // + struct init_view_pointer_member: virtual member_base, + member_base_impl<bool> // Dummy SQL type. + { + typedef init_view_pointer_member base; + + init_view_pointer_member (bool pre, init_value_member const& ivm) + : member_base (0, 0, string (), string (), 0), + pre_ (pre), init_value_member_ (ivm) {} + + virtual bool + pre (member_info& mi) + { + // Only interested in object pointers inside views. + // + return mi.ptr != 0 && view_member (mi.m); + } + + virtual void + traverse_pointer (member_info& mi) + { + using semantics::class_; + + class_& c (*mi.ptr); + bool abst (abstract (c)); + class_* poly_root (polymorphic (c)); + bool poly (poly_root != 0); + bool poly_derived (poly && poly_root != &c); + size_t poly_depth (poly_derived ? polymorphic_depth (c) : 1); + + data_member_path* idm (id_member (poly ? *poly_root : c)); + + os << "// " << mi.m.name () << (pre_ ? " pre" : " post") << endl + << "//" << endl; + + string o_tp (mi.var + "object_type"); + string o_tr (mi.var + "object_traits"); + string r_tr (poly_derived ? mi.var + "root_traits" : o_tr); + string i_tp (mi.var + "info_type"); + string p_tp (mi.var + "pointer_type"); + string p_tr (mi.var + "pointer_traits"); + string c_tr (mi.var + "cache_traits"); + + string id (mi.var + "id"); // Object id. + string p (mi.var + "p"); // Object pointer. + string pg (mi.var + "pg"); // Pointer guard. + string ig (mi.var + "ig"); // Cache insert guard. + string o (mi.var + "o"); // Object. + string pi (mi.var + "pi"); // Polymorphic type info. + + bool op_raw (c.get<bool> ("object-pointer-raw")); + bool mp_raw (utype (mi.m).is_a<semantics::pointer> ()); + + // Output aliases and variables before any schema version if- + // blocks since we need to be able to access them across all + // three parts. + // + if (pre_) + { + os << "typedef " << class_fq_name (c) << " " << o_tp << ";" + << "typedef object_traits_impl<" << o_tp << ", id_" << db << + "> " << o_tr << ";"; + + if (poly_derived) + os << "typedef " << o_tr << "::root_traits " << r_tr << ";"; + + if (poly) + os << "typedef " << r_tr << "::info_type " << i_tp << ";"; + + os << "typedef " << r_tr << "::pointer_type " << p_tp << ";" + << "typedef " << r_tr << "::pointer_traits " << p_tr << ";"; + if (idm != 0) + os << "typedef " << r_tr << "::pointer_cache_traits " << + c_tr << ";"; + os << endl; + + if (idm != 0) + os << r_tr << "::id_type " << id << ";"; + os << p_tp << " " << p << (op_raw ? " = 0" : "") << ";" // VC++ + << p_tr << "::guard " << pg << ";"; + if (idm != 0) + os << c_tr << "::insert_guard " << ig << ";"; + os << o_tp << "* " << o << " (0);"; + + if (poly) + os << "const " << i_tp << "* " << pi << " = 0;"; // VC++ + + os << endl; + } + + // If the member is soft- added or deleted, check the version. + // + unsigned long long av (added (mi.m)); + unsigned long long dv (deleted (mi.m)); + + if (av != 0 || dv != 0) + { + os << "if ("; + + if (av != 0) + os << "svm >= schema_version_migration (" << av << "ULL, true)"; + + if (av != 0 && dv != 0) + os << " &&" << endl; + + if (dv != 0) + os << "svm <= schema_version_migration (" << dv << "ULL, true)"; + + os << ")"; + } + + os << "{"; + + if (pre_) + { + string id_im; + if (idm != 0) + { + // Check for NULL. + // + string id_var; + { + id_im = mi.var + "value"; + + // In a polymorphic class, the id is in the root image. + // + for (size_t i (0); i < poly_depth - 1; ++i) + id_im += (i == 0 ? ".base" : "->base"); + + string n; + for (data_member_path::const_iterator i (idm->begin ()); + i != idm->end (); + ++i) + { + // The same logic as in member_base. + // + if (!n.empty ()) + n += "value."; // Composite. + + string const& name ((*i)->name ()); + n += name; + + if (n[n.size () - 1] != '_') + n += '_'; + } + + id_var = id_im + (poly_derived ? "->" : ".") + n; + id_im = (poly_derived ? "*i." : "i.") + id_im; + } + + os << "if ("; + + if (semantics::class_* comp = composite (mi.t)) + os << "!composite_value_traits< " << o_tr << "::id_type, id_" << + db << " >::get_null (" << endl + << "i." << id_var << "value" << + (versioned (*comp) ? ", svm" : "") << ")"; + else + { + os << "!("; + init_value_member_.get_null (id_var); + os << ")"; + } + + os << ")" + << "{"; + + // Check cache. + // + os << id << " = " << r_tr << "::id (" << id_im << ");" + << p << " = " << c_tr << "::find (*db, " << id << ");" + << endl; + + os << "if (" << p_tr << "::null_ptr (" << p << "))" + << "{"; + } + + // To support by-value object loading, we are going to load + // into an existing instance if the pointer is already not + // NULL. To limit the potential misuse (especially when it + // comes to sessions), we are going to limit this support + // only to raw pointers. Furthermore, we will only insert + // such an object into the cache if its object pointer is + // also raw. + // + if (mp_raw && !poly) + { + // Get the member using the accessor expression. + // + member_access& ma (mi.m.get<member_access> ("get")); + + // If this is not a synthesized expression, then output + // its location for easier error tracking. + // + if (!ma.synthesized) + os << "// From " << location_string (ma.loc, true) << endl; + + // Use the original type to form the const reference. VC++ + // cannot grok the constructor syntax. + // + os << member_ref_type (mi.m, true, "m") << " =" << endl + << " " << ma.translate ("o") << ";" + << endl; + + os << "if (m != 0)" + << "{"; + + if (op_raw) + os << ig << ".reset (" << c_tr << "::insert (*db, " << id << + ", m));"; + + os << o << " = m;" + << "}" + << "else" + << "{"; + } + + if (poly) + { + os << r_tr << "::discriminator_type d (" << endl + << r_tr << "::discriminator (" << id_im << "));"; + + if (abst) + os << pi << " = &" << r_tr << "::map->find (d);"; + else + os << pi << " = (d == " << o_tr << "::info.discriminator" << endl + << "? &" << o_tr << "::info" << endl + << ": &" << r_tr << "::map->find (d));"; + + os << p << " = " << pi << "->create ();"; + } + else + os << p << " = object_factory<" << o_tp << ", " << p_tp << + ">::create ();"; + + os << pg << ".reset (" << p << ");"; + if (idm != 0) + os << ig << ".reset (" << c_tr << "::insert (*db, " << id << + ", " << p << "));"; + + if (poly_derived) + os << o << " = static_cast<" << o_tp << "*> (" << p_tr << + "::get_ptr (" << p << "));"; + else + os << o << " = " << p_tr << "::get_ptr (" << p << ");"; + + if (mp_raw && !poly) + os << "}"; + + if (idm != 0) + os << "}" // Cache. + << "}"; // NULL. + } + else + { + os << "if (" << o << " != 0)" + << "{"; + + if (poly) + os << "callback_event ce (callback_event::post_load);" + << pi << "->dispatch (" << i_tp << "::call_callback, " << + "*db, " << o << ", &ce);"; + else + os << o_tr << "::callback (*db, *" << o << + ", callback_event::post_load);"; + + if (idm != 0) + { + if (mp_raw && !op_raw && !poly) + os << "if (!" << p_tr << "::null_ptr (" << p << "))" + << "{"; + + os << c_tr << "::load (" << ig << ".position ());" + << ig << ".release ();"; + + if (mp_raw && !op_raw && !poly) + os << "}"; + } + + os << pg << ".release ();"; + + os << "}"; + + // If member pointer is not raw, then result is in p. + // If both member and object are raw, then result is in o. + // If member is raw but object is not, then result is in + // p if it is not NULL, and in o (either NULL or the same + // as the member value) otherwise. + // + member_access& ma (mi.m.get<member_access> ("set")); + + if (ma.empty () && !poly) + { + // It is ok to have empty modifier expression as long as + // the member pointer is raw. This is the by-value load + // and the user is not interested in learning whether the + // object is NULL. + // + if (!mp_raw) + { + error (ma.loc) << "non-empty modifier expression required " << + "for loading an object via a smart pointer" << endl; + throw operation_failed (); + } + + os << "// Empty modifier expression was specified for this\n" + << "// object so make sure we have actually loaded the\n" + << "// data into the existing instance rather than, say,\n" + << "// finding the object in the cache or creating a new one.\n" + << "//\n" + << "assert (" << p_tr << "::null_ptr (" << p << "));"; + } + else + { + if (!(mp_raw && op_raw) || poly) + { + string r (options.std () >= cxx_version::cxx11 + ? "std::move (" + p + ")" + : p); + + if (poly_derived) + // This pointer could have come from cache, so use dynamic + // cast. + // + r = p_tr + "::dynamic_pointer_cast<" + o_tp + "> (\n" + + r + ")"; + + // Unless the pointer is raw, explicitly construct the + // smart pointer from the object pointer so that we get + // the behavior similar to calling database::load() (in + // both cases we are the "ownership end-points"; unless + // the object was already in the session before loading + // this view (in which case using raw pointers as object + // pointers is a really stupid idea), this logic will do + // the right thing and what the user most likely expects. + // + if (!mp_raw) + r = member_val_type (mi.m, false) + " (\n" + r + ")"; + + if (mp_raw && !op_raw) + os << "if (!" << p_tr << "::null_ptr (" << p << "))" << endl; + + os << "// If a compiler error points to the line below, then\n" + << "// it most likely means that a pointer used in view\n" + << "// member cannot be initialized from an object pointer.\n" + << "//" << endl; + + set_member (mi.m, "o", r, "db"); + } + + if (mp_raw && !poly) + { + if (!op_raw) + os << "else" << endl; // NULL p + + set_member (mi.m, "o", o, "db"); + } + } + } + + os << "}"; + } + + virtual bool const& + member_sql_type (semantics::data_member&) {return pre_;}; + + protected: + bool pre_; + init_value_member const& init_value_member_; + }; + + struct init_value_base: traversal::class_, virtual context + { + typedef init_value_base base; + + virtual void + traverse (type& c) + { + bool obj (object (c)); + + // Ignore transient bases. Not used for views. + // + if (!(obj || composite (c))) + return; + + os << "// " << class_name (c) << " base" << endl + << "//" << endl; + + if (obj) + os << "object_traits_impl< "; + else + os << "composite_value_traits< "; + + os << class_fq_name (c) << ", id_" << db << " >::init (o, i, db" << + (versioned (c) ? ", svm" : "") << ");" + << endl; + } + }; + + // Member-specific traits types for container members. + // + struct container_traits: object_members_base, virtual context + { + typedef container_traits base; + + container_traits (semantics::class_& c) + : object_members_base ( + true, + object (c), // Only build table prefix for objects. + false), + c_ (c) + { + scope_ = object (c) + ? "access::object_traits_impl< " + : "access::composite_value_traits< "; + + scope_ += class_fq_name (c) + ", id_" + db.string () + " >"; + } + + // Unless the database system can execute several interleaving + // statements, cache the result set. + // + virtual void + cache_result (string const& statement) + { + os << statement << ".cache ();"; + } + + // Additional code that need to be executed following the call to + // init_value. + // + virtual void + init_value_extra () + { + } + + virtual void + process_statement_columns (statement_columns&, + statement_kind, + bool /*dynamic*/) + { + } + + virtual void + traverse_pointer (semantics::data_member&, semantics::class_&) + { + // We don't want to traverse composite id. + } + + virtual void + traverse_composite (semantics::data_member* m, semantics::class_& c) + { + if (object (c_)) + object_members_base::traverse_composite (m, c); + else + { + // If we are generating traits for a composite value type, then + // we don't want to go into its bases or it composite members. + // + if (m == 0 && &c == &c_) + names (c); + } + } + + virtual void + container_extra (semantics::data_member&, semantics::type&) + { + } + + virtual void + traverse_container (semantics::data_member& m, semantics::type& t) + { + using semantics::type; + + // Figure out if this member is from a base object or composite + // value and if it's from an object, whether it is reuse-abstract. + // + bool base, reuse_abst; + + if (object (c_)) + { + base = cur_object != &c_ || + !object (dynamic_cast<type&> (m.scope ())); + reuse_abst = abstract (c_) && !polymorphic (c_); + } + else + { + base = false; // We don't go into bases. + reuse_abst = true; // Always abstract. + } + + container_kind_type ck (container_kind (t)); + + const custom_cxx_type* vct (0); + const custom_cxx_type* ict (0); + const custom_cxx_type* kct (0); + + type& vt (container_vt (m, &vct)); + type* it (0); + type* kt (0); + + data_member_path* imp (context::inverse (m, "value")); + + bool ordered (false); + bool inverse (imp != 0); + bool grow (false); + + switch (ck) + { + case ck_ordered: + { + if (!unordered (m)) + { + it = &container_it (m, &ict); + ordered = true; + + if (generate_grow) + grow = grow || context::grow (m, *it, ict, "index"); + } + + break; + } + case ck_map: + case ck_multimap: + { + kt = &container_kt (m, &kct); + + if (generate_grow) + grow = grow || context::grow (m, *kt, kct, "key"); + + break; + } + case ck_set: + case ck_multiset: + { + break; + } + } + + bool smart (!inverse && + (ck != ck_ordered || ordered) && + container_smart (t)); + + if (generate_grow) + grow = grow || context::grow (m, vt, vct, "value"); + + bool eager_ptr (is_a (member_path_, + member_scope_, + test_eager_pointer, + vt, + "value")); + if (!eager_ptr) + { + if (semantics::class_* cvt = composite_wrapper (vt)) + eager_ptr = has_a (*cvt, test_eager_pointer); + } + + bool versioned (context::versioned (m)); + + string name (flat_prefix_ + public_name (m) + "_traits"); + string scope (scope_ + "::" + name); + + os << "// " << m.name () << endl + << "//" << endl + << endl; + + container_extra (m, t); + + // + // Statements. + // + if (!reuse_abst) + { + string sep (versioned ? "\n" : " "); + + semantics::type& idt (container_idt (m)); + + qname table (table_name (m, table_prefix_)); + string qtable (quote_id (table)); + instance<object_columns_list> id_cols; + instance<object_columns_list> ik_cols; // index/key columns + + if (smart) + { + switch (ck) + { + case ck_ordered: + { + ik_cols->traverse (m, *it, "index", "index"); + break; + } + case ck_map: + case ck_multimap: + { + break; + } + case ck_set: + case ck_multiset: + { + break; + } + } + } + + // select_statement + // + os << "const char " << scope << "::" << endl + << "select_statement[] =" << endl; + + if (inverse) + { + semantics::class_* c (object_pointer (vt)); + semantics::data_member& imf (*imp->front ()); + semantics::data_member& imb (*imp->back ()); + + // In a polymorphic hierarchy the inverse member can be in + // the base class, in which case we should use that class + // for the table name, etc. + // + if (polymorphic (*c)) + c = &dynamic_cast<semantics::class_&> (imf.scope ()); + + data_member_path& inv_id (*id_member (*c)); + + qname inv_table; // Other table name. + string inv_qtable; + instance<object_columns_list> inv_id_cols; // Other id column. + instance<object_columns_list> inv_fid_cols; // Other foreign id + // column (ref to us). + statement_columns sc; + + if (container (imb)) + { + // many(i)-to-many + // + + // This other container is a direct member of the class so the + // table prefix is just the class table name. + // + inv_table = table_name (*c, *imp); + inv_qtable = quote_id (inv_table); + + inv_id_cols->traverse (imb, utype (inv_id), "id", "object_id", c); + inv_fid_cols->traverse (imb, idt, "value", "value"); + + for (object_columns_list::iterator i (inv_id_cols->begin ()); + i != inv_id_cols->end (); ++i) + { + // If this is a simple id, then pass the "id" key prefix. If + // it is a composite id, then the members have no prefix. + // + sc.push_back ( + statement_column ( + inv_qtable, + inv_qtable + "." + quote_id (i->name), + i->type, + *i->member, + inv_id_cols->size () == 1 ? "id" : "")); + } + } + else + { + // many(i)-to-one + // + inv_table = table_name (*c); + inv_qtable = quote_id (inv_table); + + inv_id_cols->traverse (inv_id); + inv_fid_cols->traverse (imb, column_prefix (*imp)); + + for (object_columns_list::iterator i (inv_id_cols->begin ()); + i != inv_id_cols->end (); ++i) + { + sc.push_back ( + statement_column ( + inv_qtable, + inv_qtable + "." + quote_id (i->name), + i->type, + *i->member)); + } + } + + process_statement_columns (sc, statement_select, versioned); + + os << strlit ("SELECT" + sep) << endl; + + for (statement_columns::const_iterator i (sc.begin ()), + e (sc.end ()); i != e;) + { + string const& c (i->column); + os << strlit (c + (++i != e ? "," : "") + sep) << endl; + } + + instance<query_parameters> qp (statement_select, inv_table); + os << strlit ("FROM " + inv_qtable + sep) << endl; + + string where ("WHERE "); + for (object_columns_list::iterator b (inv_fid_cols->begin ()), + i (b); i != inv_fid_cols->end (); ++i) + { + if (i != b) + where += " AND "; + + where += inv_qtable + "." + quote_id (i->name) + "=" + + convert_to (qp->next (*i), i->type, *i->member); + } + os << strlit (where); + } + else + { + id_cols->traverse (m, idt, "id", "object_id"); + + statement_columns sc; + statement_kind sk (statement_select); // Imperfect forwarding. + instance<object_columns> t (qtable, sk, sc); + + switch (ck) + { + case ck_ordered: + { + if (ordered) + t->traverse (m, *it, "index", "index"); + break; + } + case ck_map: + case ck_multimap: + { + t->traverse (m, *kt, "key", "key"); + break; + } + case ck_set: + case ck_multiset: + { + break; + } + } + + t->traverse (m, vt, "value", "value"); + + process_statement_columns (sc, statement_select, versioned); + + os << strlit ("SELECT" + sep) << endl; + + for (statement_columns::const_iterator i (sc.begin ()), + e (sc.end ()); i != e;) + { + string const& c (i->column); + os << strlit (c + (++i != e ? "," : "") + sep) << endl; + } + + instance<query_parameters> qp (statement_select, table); + os << strlit ("FROM " + qtable + sep) << endl; + + string where ("WHERE "); + for (object_columns_list::iterator b (id_cols->begin ()), i (b); + i != id_cols->end (); ++i) + { + if (i != b) + where += " AND "; + + where += qtable + "." + quote_id (i->name) + "=" + + convert_to (qp->next (*i), i->type, *i->member); + } + + if (ordered) + { + // Top-level column. + // + string const& col ( + column_qname (m, "index", "index", column_prefix ())); + + where += " ORDER BY " + qtable + "." + col; + } + + os << strlit (where); + } + + os << ";" + << endl; + + // insert_statement + // + os << "const char " << scope << "::" << endl + << "insert_statement[] =" << endl; + + if (inverse) + os << strlit ("") << ";" + << endl; + else + { + statement_columns sc; + statement_kind sk (statement_insert); // Imperfect forwarding. + instance<object_columns> t (sk, sc); + + t->traverse (m, idt, "id", "object_id"); + + switch (ck) + { + case ck_ordered: + { + if (ordered) + t->traverse (m, *it, "index", "index"); + break; + } + case ck_map: + case ck_multimap: + { + t->traverse (m, *kt, "key", "key"); + break; + } + case ck_set: + case ck_multiset: + { + break; + } + } + + t->traverse (m, vt, "value", "value"); + + process_statement_columns (sc, statement_insert, versioned); + + os << strlit ("INSERT INTO " + qtable + sep) << endl; + + for (statement_columns::const_iterator b (sc.begin ()), i (b), + e (sc.end ()); i != e;) + { + string s; + + if (i == b) + s += '('; + s += i->column; + s += (++i != e ? ',' : ')'); + s += sep; + + os << strlit (s) << endl; + } + + os << strlit ("VALUES" + sep) << endl; + + string values ("("); + instance<query_parameters> qp (statement_insert, table); + for (statement_columns::const_iterator b (sc.begin ()), i (b), + e (sc.end ()); i != e; ++i) + { + if (i != b) + { + values += ','; + values += sep; + } + + values += convert_to (qp->next (*i), i->type, *i->member); + } + values += ')'; + + os << strlit (values) << ";" + << endl; + } + + // update_statement + // + if (smart) + { + os << "const char " << scope << "::" << endl + << "update_statement[] =" << endl + << strlit ("UPDATE " + qtable + sep) << endl + << strlit ("SET" + sep) << endl; + + instance<query_parameters> qp (statement_update, table); + statement_columns sc; + { + bool f (false); // Imperfect forwarding. + query_parameters* p (qp.get ()); // Imperfect forwarding. + statement_kind sk (statement_update); // Imperfect forwarding. + instance<object_columns> t (sk, f, sc, p); + t->traverse (m, vt, "value", "value"); + process_statement_columns (sc, statement_update, versioned); + } + + for (statement_columns::const_iterator i (sc.begin ()), + e (sc.end ()); i != e;) + { + string const& c (i->column); + os << strlit (c + (++i != e ? "," : "") + sep) << endl; + } + + string where ("WHERE "); + for (object_columns_list::iterator b (id_cols->begin ()), i (b); + i != id_cols->end (); ++i) + { + if (i != b) + where += " AND "; + + where += quote_id (i->name) + "=" + + convert_to (qp->next (*i), i->type, *i->member); + } + + for (object_columns_list::iterator b (ik_cols->begin ()), i (b); + i != ik_cols->end (); ++i) + { + where += " AND " + quote_id (i->name) + "=" + + convert_to (qp->next (*i), i->type, *i->member); + } + + os << strlit (where) << ";" + << endl; + } + + // delete_statement + // + os << "const char " << scope << "::" << endl + << "delete_statement[] =" << endl; + + if (inverse) + os << strlit ("") << ";" + << endl; + else + { + instance<query_parameters> qp (statement_delete, table); + + os << strlit ("DELETE FROM " + qtable + " ") << endl; + + string where ("WHERE "); + for (object_columns_list::iterator b (id_cols->begin ()), i (b); + i != id_cols->end (); ++i) + { + if (i != b) + where += " AND "; + + where += quote_id (i->name) + "=" + + convert_to (qp->next (*i), i->type, *i->member); + } + + if (smart) + { + for (object_columns_list::iterator b (ik_cols->begin ()), i (b); + i != ik_cols->end (); ++i) + { + where += " AND " + quote_id (i->name) + + (ck == ck_ordered ? ">=" : "=") + + convert_to (qp->next (*i), i->type, *i->member); + } + } + + os << strlit (where) << ";" + << endl; + } + } + + if (base) + return; + + // + // Functions. + // + + // bind (cond_image_type) + // + if (smart) + { + os << "void " << scope << "::" << endl + << "bind (" << bind_vector << " b," << endl + << "const " << bind_vector << " id," << endl + << "std::size_t id_size," << endl + << "cond_image_type& c)" + << "{" + << "using namespace " << db << ";" + << endl + << "statement_kind sk (statement_select);" + << "ODB_POTENTIALLY_UNUSED (sk);" + << endl + << "std::size_t n (0);" + << endl; + + os << "// object_id" << endl + << "//" << endl + << "if (id != 0)" << endl + << "std::memcpy (&b[n], id, id_size * sizeof (id[0]));" + << "n += id_size;" // Not in if for "id unchanged" optimization. + << endl; + + // We don't need to update the bind index since this is the + // last element. + // + switch (ck) + { + case ck_ordered: + { + if (ordered) + { + os << "// index" << endl + << "//" << endl; + instance<bind_member> bm ( + "index_", "c", *it, ict, "index_type", "index"); + bm->traverse (m); + } + break; + } + case ck_map: + case ck_multimap: + { + os << "// key" << endl + << "//" << endl; + instance<bind_member> bm ( + "key_", "c", *kt, kct, "key_type", "key"); + bm->traverse (m); + break; + } + case ck_set: + case ck_multiset: + { + os << "// value" << endl + << "//" << endl; + instance<bind_member> bm ( + "value_", "c", vt, vct, "value_type", "value"); + bm->traverse (m); + break; + } + } + os << "}"; + } + + // bind (data_image_type) + // + { + os << "void " << scope << "::" << endl + << "bind (" << bind_vector << " b," << endl + << "const " << bind_vector << " id," << endl + << "std::size_t id_size," << endl + << "data_image_type& d"; + + if (versioned) + os << "," << endl + << "const schema_version_migration& svm"; + + os << ")" + << "{" + << "using namespace " << db << ";" + << endl + // In the case of containers, insert and select column sets are + // the same since we can't have inverse members as container + // elements. + // + << "statement_kind sk (statement_select);" + << "ODB_POTENTIALLY_UNUSED (sk);" + << endl + << "size_t n (0);" + << endl; + + os << "// object_id" << endl + << "//" << endl + << "if (id != 0)" << endl + << "std::memcpy (&b[n], id, id_size * sizeof (id[0]));" + << "n += id_size;" // Not in if for "id unchanged" optimization. + << endl; + + switch (ck) + { + case ck_ordered: + { + if (ordered) + { + os << "// index" << endl + << "//" << endl; + instance<bind_member> bm ( + "index_", "d", *it, ict, "index_type", "index"); + bm->traverse (m); + os << "n++;" // Simple value. + << endl; + } + break; + } + case ck_map: + case ck_multimap: + { + os << "// key" << endl + << "//" << endl; + instance<bind_member> bm ( + "key_", "d", *kt, kct, "key_type", "key"); + bm->traverse (m); + + if (semantics::class_* c = composite_wrapper (*kt)) + os << "n += " << column_count (*c).total << "UL;" + << endl; + else + os << "n++;" + << endl; + break; + } + case ck_set: + case ck_multiset: + { + break; + } + } + + // We don't need to update the bind index since this is the + // last element. + // + os << "// value" << endl + << "//" << endl; + instance<bind_member> bm ( + "value_", "d", vt, vct, "value_type", "value"); + bm->traverse (m); + + os << "}"; + } + + // bind (cond_image, data_image) (update) + // + if (smart) + { + os << "void " << scope << "::" << endl + << "bind (" << bind_vector << " b," << endl + << "const " << bind_vector << " id," << endl + << "std::size_t id_size," << endl + << "cond_image_type& c," << endl + << "data_image_type& d"; + + if (versioned) + os << "," << endl + << "const schema_version_migration& svm"; + + os << ")" + << "{" + << "using namespace " << db << ";" + << endl + // Use insert instead of update to include read-only members. + // + << "statement_kind sk (statement_insert);" + << "ODB_POTENTIALLY_UNUSED (sk);" + << endl + << "std::size_t n (0);" + << endl; + + os << "// value" << endl + << "//" << endl; + instance<bind_member> bm ( + "value_", "d", vt, vct, "value_type", "value"); + bm->traverse (m); + + if (semantics::class_* c = composite_wrapper (vt)) + os << "n += " << column_count (*c).total << "UL;" + << endl; + else + os << "n++;" + << endl; + + os << "// object_id" << endl + << "//" << endl + << "if (id != 0)" << endl + << "std::memcpy (&b[n], id, id_size * sizeof (id[0]));" + << "n += id_size;" // Not in if for "id unchanged" optimization. + << endl; + + // We don't need to update the bind index since this is the + // last element. + // + switch (ck) + { + case ck_ordered: + { + if (ordered) + { + os << "// index" << endl + << "//" << endl; + instance<bind_member> bm ( + "index_", "c", *it, ict, "index_type", "index"); + bm->traverse (m); + } + break; + } + case ck_map: + case ck_multimap: + { + os << "// key" << endl + << "//" << endl; + instance<bind_member> bm ( + "key_", "c", *kt, kct, "key_type", "key"); + bm->traverse (m); + break; + } + case ck_set: + case ck_multiset: + { + os << "// value" << endl + << "//" << endl; + instance<bind_member> bm ( + "value_", "c", vt, vct, "value_type", "value"); + bm->traverse (m); + break; + } + } + os << "}"; + } + + // grow () + // + if (generate_grow) + { + size_t index (0); + + os << "void " << scope << "::" << endl + << "grow (data_image_type& i," << endl + << truncated_vector << " t"; + + if (versioned) + os << "," << endl + << "const schema_version_migration& svm"; + + os << ")" + << "{" + << "bool grew (false);" + << endl; + + switch (ck) + { + case ck_ordered: + { + if (ordered) + { + os << "// index" << endl + << "//" << endl; + instance<grow_member> gm ( + index, "index_", *it, ict, "index_type", "index"); + gm->traverse (m); + } + break; + } + case ck_map: + case ck_multimap: + { + os << "// key" << endl + << "//" << endl; + instance<grow_member> gm ( + index, "key_", *kt, kct, "key_type", "key"); + gm->traverse (m); + break; + } + case ck_set: + case ck_multiset: + { + break; + } + } + + os << "// value" << endl + << "//" << endl; + instance<grow_member> gm ( + index, "value_", vt, vct, "value_type", "value"); + gm->traverse (m); + + os << "if (grew)" << endl + << "i.version++;" + << "}"; + } + + // init (data_image) + // + if (!inverse) + { + os << "void " << scope << "::" << endl + << "init (data_image_type& i," << endl; + + switch (ck) + { + case ck_ordered: + { + if (ordered) + os << "index_type* j," << endl; + break; + } + case ck_map: + case ck_multimap: + { + os << "const key_type* k," << endl; + break; + } + case ck_set: + case ck_multiset: + break; + } + + os << "const value_type& v"; + + if (versioned) + os << "," << endl + << "const schema_version_migration& svm"; + + os << ")" + << "{" + << "using namespace " << db << ";" + << endl + << "statement_kind sk (statement_insert);" + << "ODB_POTENTIALLY_UNUSED (sk);" + << endl; + + if (generate_grow) + os << "bool grew (false);" + << endl; + + switch (ck) + { + case ck_ordered: + { + if (ordered) + { + os << "// index" << endl + << "//" << endl + << "if (j != 0)"; + + instance<init_image_member> im ( + "index_", "*j", *it, ict, "index_type", "index"); + im->traverse (m); + } + break; + } + case ck_map: + case ck_multimap: + { + os << "// key" << endl + << "//" << endl + << "if (k != 0)"; + + instance<init_image_member> im ( + "key_", "*k", *kt, kct, "key_type", "key"); + im->traverse (m); + + break; + } + case ck_set: + case ck_multiset: + { + break; + } + } + + os << "// value" << endl + << "//" << endl; + { + instance<init_image_member> im ( + "value_", "v", vt, vct, "value_type", "value"); + im->traverse (m); + } + + if (generate_grow) + os << "if (grew)" << endl + << "i.version++;"; + + os << "}"; + } + + // init (cond_image) + // + if (smart) + { + os << "void " << scope << "::" << endl; + + switch (ck) + { + case ck_ordered: + { + os << "init (cond_image_type& i, index_type j)" + << "{" + << "using namespace " << db << ";" + << endl + << "statement_kind sk (statement_select);" + << "ODB_POTENTIALLY_UNUSED (sk);" + << endl; + + instance<init_image_member> im ( + "index_", "j", *it, ict, "index_type", "index"); + im->traverse (m); + + os << "}"; + break; + } + case ck_map: + case ck_multimap: + { + // Need to handle growth. + // + // os << "init (data_image_type&, const key_type&);"; + break; + } + case ck_set: + case ck_multiset: + { + // Need to handle growth. + // + // os << "init (data_image_type&, const value_type&);"; + break; + } + } + + os << endl; + } + + // init (data) + // + os << "void " << scope << "::" << endl + << "init ("; + + switch (ck) + { + case ck_ordered: + { + if (ordered) + os << "index_type& j," << endl; + break; + } + case ck_map: + case ck_multimap: + { + os << "key_type& k," << endl; + break; + } + case ck_set: + case ck_multiset: + break; + } + + os << "value_type& v," << endl; + os << "const data_image_type& i," << endl + << "database* db"; + + if (versioned) + os << "," << endl + << "const schema_version_migration& svm"; + + os << ")" + << "{" + << "ODB_POTENTIALLY_UNUSED (db);" + << endl; + + switch (ck) + { + case ck_ordered: + { + if (ordered) + { + os << "// index" << endl + << "//" << endl; + + instance<init_value_member> im ( + "index_", "j", *it, ict, "index_type", "index"); + im->traverse (m); + } + + break; + } + case ck_map: + case ck_multimap: + { + os << "// key" << endl + << "//" << endl; + + instance<init_value_member> im ( + "key_", "k", *kt, kct, "key_type", "key"); + im->traverse (m); + + break; + } + case ck_set: + case ck_multiset: + break; + } + + os << "// value" << endl + << "//" << endl; + { + // If the value is an object pointer, pass the id type as a + // type override. + // + instance<init_value_member> im ( + "value_", "v", vt, vct, "value_type", "value"); + im->traverse (m); + } + os << "}"; + + // insert + // + { + string ia, ka, va, da; + + if (!inverse) + { + ia = ordered ? " i" : ""; + ka = " k"; + va = " v"; + da = " d"; + } + + os << "void " << scope << "::" << endl; + + switch (ck) + { + case ck_ordered: + { + os << "insert (index_type" << ia << ", " << + "const value_type&" << va << ", " << + "void*" << da << ")"; + break; + } + case ck_map: + case ck_multimap: + { + os << "insert (const key_type&" << ka << ", " << + "const value_type&" << va << ", " << + "void*" << da << ")"; + break; + } + case ck_set: + case ck_multiset: + { + os << "insert (const value_type&" << va << ", " << + "void*" << da << ")"; + break; + } + } + + os << "{"; + + if (!inverse) + { + os << "using namespace " << db << ";" + << endl + << "statements_type& sts (*static_cast< statements_type* > (d));" + << "data_image_type& di (sts.data_image ());"; + + if (versioned) + os << "const schema_version_migration& svm (" << + "sts.version_migration ());"; + + os << endl + << "init (di, "; + + switch (ck) + { + case ck_ordered: + { + if (ordered) + os << "&i, "; + break; + } + case ck_map: + case ck_multimap: + { + os << "&k, "; + break; + } + case ck_set: + case ck_multiset: + break; + } + + os << "v" << (versioned ? ", svm" : "") << ");"; + + os << endl + << "if (sts.data_binding_test_version ())" + << "{" + << "const binding& id (sts.id_binding ());" + << "bind (sts.data_bind (), id.bind, id.count, di" << + (versioned ? ", svm" : "") << ");" + << "sts.data_binding_update_version ();" + << "}" + << "if (!sts.insert_statement ().execute ())" << endl + << "throw object_already_persistent ();"; + } + + os << "}"; + } + + // update + // + if (smart) + { + os << "void " << scope << "::" << endl; + + switch (ck) + { + case ck_ordered: + { + os << "update (index_type i, const value_type& v, void* d)"; + break; + } + case ck_map: + case ck_multimap: + { + break; + } + case ck_set: + case ck_multiset: + { + break; + } + } + + os << "{"; + + os << "using namespace " << db << ";" + << endl + << "statements_type& sts (*static_cast< statements_type* > (d));" + << "cond_image_type& ci (sts.cond_image ());" + << "data_image_type& di (sts.data_image ());"; + + if (versioned) + os << "const schema_version_migration& svm (" << + "sts.version_migration ());"; + + os << endl; + + switch (ck) + { + case ck_ordered: + { + os << "init (ci, i);"; + os << "init (di, 0, v" << (versioned ? ", svm" : "") << ");"; + break; + } + case ck_map: + case ck_multimap: + { + //os << "init (di, 0, v);"; + break; + } + case ck_set: + case ck_multiset: + { + //os << "init (di, v);"; + break; + } + } + + os << endl + << "if (sts.update_binding_test_version ())" + << "{" + << "const binding& id (sts.id_binding ());" + << "bind (sts.update_bind (), id.bind, id.count, ci, di" << + (versioned ? ", svm" : "") << ");" + << "sts.update_binding_update_version ();" + << "}"; + + os << "if (sts.update_statement ().execute () == 0)" << endl + << "throw object_not_persistent ();" + << "}"; + } + + // select + // + os << "bool " << scope << "::" << endl; + + switch (ck) + { + case ck_ordered: + { + os << "select (index_type&" << (ordered ? " i" : "") << + ", value_type& v, void* d)"; + break; + } + case ck_map: + case ck_multimap: + { + os << "select (key_type& k, value_type& v, void* d)"; + break; + } + case ck_set: + case ck_multiset: + { + os << "select (value_type& v, void* d)"; + break; + } + } + + os << "{" + << "using namespace " << db << ";" + << "using " << db << "::select_statement;" // Conflicts. + << endl + << "statements_type& sts (*static_cast< statements_type* > (d));" + << "data_image_type& di (sts.data_image ());"; + + if (versioned) + os << "const schema_version_migration& svm (" << + "sts.version_migration ());"; + + os << endl + << "init ("; + + // Extract current element. + // + switch (ck) + { + case ck_ordered: + { + if (ordered) + os << "i, "; + break; + } + case ck_map: + case ck_multimap: + { + os << "k, "; + break; + } + case ck_set: + case ck_multiset: + break; + } + + os << "v, di, &sts.connection ().database ()" << + (versioned ? ", svm" : "") << ");" + << endl; + + init_value_extra (); + + // If we are loading an eager pointer, then the call to init + // above executes other statements which potentially could + // change the image, including the id. + // + if (eager_ptr) + { + os << "if (sts.data_binding_test_version ())" + << "{" + << "const binding& id (sts.id_binding ());" + << "bind (sts.data_bind (), id.bind, id.count, di" << + (versioned ? ", svm" : "") << ");" + << "sts.data_binding_update_version ();" + << "}"; + } + + // Fetch next. + // + os << "select_statement& st (sts.select_statement ());" + << "select_statement::result r (st.fetch ());"; + + if (grow) + os << endl + << "if (r == select_statement::truncated)" + << "{" + << "grow (di, sts.select_image_truncated ()" << + (versioned ? ", svm" : "") << ");" + << endl + << "if (sts.data_binding_test_version ())" + << "{" + // Id cannot change. + // + << "bind (sts.data_bind (), 0, sts.id_binding ().count, di" << + (versioned ? ", svm" : "") << ");" + << "sts.data_binding_update_version ();" + << "st.refetch ();" + << "}" + << "}"; + + os << "return r != select_statement::no_data;" + << "}"; + + // delete_ + // + os << "void " << scope << "::" << endl + << "delete_ ("; + + if (smart) + { + switch (ck) + { + case ck_ordered: + { + os << "index_type i, "; + break; + } + case ck_map: + case ck_multimap: + { + break; + } + case ck_set: + case ck_multiset: + { + break; + } + } + } + + os << "void*" << (inverse ? "" : " d") << ")" + << "{"; + + if (!inverse) + { + os << "using namespace " << db << ";" + << endl + << "statements_type& sts (*static_cast< statements_type* > (d));"; + + if (smart) + { + os << "cond_image_type& ci (sts.cond_image ());" + << endl; + + switch (ck) + { + case ck_ordered: + { + os << "init (ci, i);"; + break; + } + case ck_map: + case ck_multimap: + { + break; + } + case ck_set: + case ck_multiset: + { + break; + } + } + + os << endl + << "if (sts.cond_binding_test_version ())" + << "{" + << "const binding& id (sts.id_binding ());" + << "bind (sts.cond_bind (), id.bind, id.count, ci);" + << "sts.cond_binding_update_version ();" + << "}"; + } + + os << "sts.delete_statement ().execute ();"; + } + + os << "}"; + + // persist + // + if (!inverse) + { + os << "void " << scope << "::" << endl + << "persist (const container_type& c," << endl + << "statements_type& sts"; + + if (versioned) + os << "," << endl + << "const schema_version_migration& svm"; + + os << ")" + << "{" + << "using namespace " << db << ";" + << endl + << "functions_type& fs (sts.functions ());"; + + if (versioned) + os << "sts.version_migration (svm);"; + + if (!smart && ck == ck_ordered) + os << "fs.ordered_ = " << ordered << ";"; + + os << "container_traits_type::persist (c, fs);" + << "}"; + } + + // load + // + os << "void " << scope << "::" << endl + << "load (container_type& c," << endl + << "statements_type& sts"; + + if (versioned) + os << "," << endl + << "const schema_version_migration& svm"; + + os << ")" + << "{" + << "using namespace " << db << ";" + << "using " << db << "::select_statement;" // Conflicts. + << endl + << "const binding& id (sts.id_binding ());" + << endl + << "if (sts.data_binding_test_version ())" + << "{" + << "bind (sts.data_bind (), id.bind, id.count, sts.data_image ()" << + (versioned ? ", svm" : "") << ");" + << "sts.data_binding_update_version ();" + << "}" + // We use the id binding directly so no need to check cond binding. + // + << "select_statement& st (sts.select_statement ());" + << "st.execute ();" + << "auto_result ar (st);"; + + // If we are loading eager object pointers, we may need to cache + // the result since we will be loading other objects. + // + if (eager_ptr) + cache_result ("st"); + + os << "select_statement::result r (st.fetch ());"; + + if (grow) + os << endl + << "if (r == select_statement::truncated)" + << "{" + << "data_image_type& di (sts.data_image ());" + << "grow (di, sts.select_image_truncated ()" << + (versioned ? ", svm" : "") << ");" + << endl + << "if (sts.data_binding_test_version ())" + << "{" + // Id cannot change. + // + << "bind (sts.data_bind (), 0, id.count, di" << + (versioned ? ", svm" : "") << ");" + << "sts.data_binding_update_version ();" + << "st.refetch ();" + << "}" + << "}"; + + os << "bool more (r != select_statement::no_data);" + << endl + << "functions_type& fs (sts.functions ());"; + + if (versioned) + os << "sts.version_migration (svm);"; + + if (!smart && ck == ck_ordered) + os << "fs.ordered_ = " << ordered << ";"; + + os << "container_traits_type::load (c, more, fs);" + << "}"; + + // update + // + if (!(inverse || readonly (member_path_, member_scope_))) + { + os << "void " << scope << "::" << endl + << "update (const container_type& c," << endl + << "statements_type& sts"; + + if (versioned) + os << "," << endl + << "const schema_version_migration& svm"; + + os << ")" + << "{" + << "using namespace " << db << ";" + << endl + << "functions_type& fs (sts.functions ());"; + + if (versioned) + os << "sts.version_migration (svm);"; + + if (!smart && ck == ck_ordered) + os << "fs.ordered_ = " << ordered << ";"; + + os << "container_traits_type::update (c, fs);" + << "}"; + } + + // erase + // + if (!inverse) + { + os << "void " << scope << "::" << endl + << "erase ("; + + if (smart) + os << "const container_type* c, "; + + os << "statements_type& sts)" + << "{" + << "using namespace " << db << ";" + << endl + << "functions_type& fs (sts.functions ());"; + + if (!smart && ck == ck_ordered) + os << "fs.ordered_ = " << ordered << ";"; + + os << "container_traits_type::erase (" << (smart ? "c, " : "") << "fs);" + << "}"; + } + } + + protected: + string scope_; + semantics::class_& c_; + }; + + // Extra statement cache members for containers. + // + struct container_cache_members: object_members_base, virtual context + { + typedef container_cache_members base; + + container_cache_members () + : object_members_base (true, false, false) + { + } + + virtual void + traverse_container (semantics::data_member& m, semantics::type& c) + { + bool smart (!context::inverse (m, "value") && + !unordered (m) && + container_smart (c)); + + string traits (flat_prefix_ + public_name (m) + "_traits"); + + os << db << "::" << (smart ? "smart_" : "") << + "container_statements_impl< " << traits << " > " << + flat_prefix_ << m.name () << ";"; + } + }; + + struct container_cache_init_members: object_members_base, virtual context + { + typedef container_cache_init_members base; + + container_cache_init_members () + : object_members_base (true, false, false), first_ (true) + { + } + + virtual void + traverse_container (semantics::data_member& m, semantics::type&) + { + if (first_) + { + os << endl + << ": "; + first_ = false; + } + else + os << "," << endl + << " "; + + os << flat_prefix_ << m.name () << " (c, id"; + extra_members (); + os << ")"; + } + + virtual void + extra_members () {} + + protected: + bool first_; + }; + + // Extra statement cache members for sections. + // + struct section_cache_members: virtual context + { + typedef section_cache_members base; + + virtual void + traverse (user_section& s) + { + string traits (public_name (*s.member) + "_traits"); + + os << db << "::" << "section_statements< " << + class_fq_name (*s.object) << ", " << traits << " > " << + s.member->name () << ";"; + } + }; + + struct section_cache_init_members: virtual context + { + typedef section_cache_init_members base; + + section_cache_init_members (bool first): first_ (first) {} + + virtual void + traverse (user_section& s) + { + if (first_) + { + os << endl + << ": "; + first_ = false; + } + else + os << "," << endl + << " "; + + os << s.member->name () << " (c, im, idim, id, idv"; + extra_members (); + os << ")"; + } + + virtual void + extra_members () {} + + protected: + bool first_; + }; + + // Calls for container members. + // + struct container_calls: object_members_base, virtual context + { + typedef container_calls base; + + enum call_type + { + persist_call, + load_call, + update_call, + erase_obj_call, + erase_id_call, + section_call + }; + + container_calls (call_type call, object_section* section = 0) + : object_members_base (true, false, true, false, section), + call_ (call), + obj_prefix_ ("obj"), + by_value_ (0) + { + } + + virtual bool + section_test (data_member_path const& mp) + { + object_section& s (section (mp)); + + // Include eager loaded members into the main section for + // load calls. + // + return section_ == 0 || + *section_ == s || + (call_ == load_call && + *section_ == main_section && + !s.separate_load ()); + } + + virtual void + traverse_pointer (semantics::data_member&, semantics::class_&) + { + // We don't want to traverse composite id. + } + + virtual void + traverse_composite_wrapper (semantics::data_member* m, + semantics::class_& c, + semantics::type* w) + { + if (m == 0 || + call_ == erase_id_call || + (call_ == load_call && by_value_ != 0)) + { + object_members_base::traverse_composite (m, c); + return; + } + + // Get this member using the accessor expression. + // + member_access& ma ( + m->get<member_access> (call_ == load_call ? "set" : "get")); + + // We don't support by-value modifiers for composite values + // with containers. However, at this point we don't know + // whether this composite value has any containers. So we + // are just going to set a flag that can be checked in + // traverse_container() below. + // + if (call_ == load_call && ma.placeholder ()) + { + by_value_ = &ma; + object_members_base::traverse_composite (m, c); + by_value_ = 0; + return; + } + + // We also don't support by-value accessors is there is a + // smart container inside (which, again, we don't know at + // this point). So keep track of such first instance. + // + member_access* old_by_value (by_value_); + if (call_ != load_call && ma.by_value && by_value_ == 0) + by_value_ = &ma; + + string old_op (obj_prefix_); + string old_f (from_); + obj_prefix_.clear (); + + // If this member is const and we have a synthesized direct + // access, then cast away constness. Otherwise, we assume + // that the user-provided expression handles this. + // + bool cast (call_ == load_call && ma.direct () && const_member (*m)); + if (cast) + obj_prefix_ = "const_cast< " + member_ref_type (*m, false) + + " > (\n"; + + obj_prefix_ += ma.translate (old_op); + + if (cast) + obj_prefix_ += ")"; + + // If this is not a synthesized expression, then store its + // location which we will output later for easier error + // tracking. + // + if (!ma.synthesized) + from_ += "// From " + location_string (ma.loc, true) + "\n"; + + // If this is a wrapped composite value, then we need to "unwrap" it. + // + if (w != 0) + { + semantics::names* hint; + semantics::type& t (utype (*m, hint)); + + // Because we cannot have nested containers, member type should + // be the same as w. + // + assert (&t == w); + + obj_prefix_ = "wrapper_traits< " + t.fq_name (hint) + " >::" + + (call_ == load_call ? "set_ref" : "get_ref") + + " (\n" + obj_prefix_ + ")"; + } + + object_members_base::traverse_composite (m, c); + from_ = old_f; + obj_prefix_ = old_op; + by_value_ = old_by_value; + } + + virtual void + traverse_container (semantics::data_member& m, semantics::type& c) + { + using semantics::type; + + bool inverse (context::inverse (m, "value")); + bool smart (!inverse && !unordered (m) && container_smart (c)); + bool versioned (context::versioned (m)); + + // In certain cases we don't need to do anything. + // + if ((call_ != load_call && inverse) || + (call_ == section_call && !smart) || + (call_ == update_call && readonly (member_path_, member_scope_))) + return; + + string const& name (m.name ()); + string sts_name (flat_prefix_ + name); + string traits (flat_prefix_ + public_name (m) + "_traits"); + + os << "// " << member_prefix_ << m.name () << endl + << "//" << endl; + + // Get this member using the accessor expression. + // + string var; + member_access& ma ( + m.get<member_access> (call_ == load_call ? "set" : "get")); + + // We don't support by-value modifiers for composite values + // with containers. + // + if (call_ == load_call && by_value_ != 0) + { + error (by_value_->loc) << "by-value modification of a composite " + << "value with container is not supported" + << endl; + info (m.location ()) << "container member is defined here" << endl; + throw operation_failed (); + } + + // We don't support by-value accessors for smart containers. + // + if (call_ != load_call && smart) + { + if (by_value_ != 0) + { + error (by_value_->loc) << "by-value access to a composite value " + << "with smart container is not supported" + << endl; + info (m.location ()) << "container member is defined here" << endl; + throw operation_failed (); + } + + if (ma.by_value) + { + error (ma.loc) << "by-value access to a smart container is not " + << "supported" << endl; + info (m.location ()) << "container member is defined here" << endl; + throw operation_failed (); + } + } + + // If the member is soft- added or deleted, check the version. + // + unsigned long long av (added (member_path_)); + unsigned long long dv (deleted (member_path_)); + + // If the addition/deletion version is the same as the section's, + // then we don't need the test. + // + if (user_section* s = dynamic_cast<user_section*> (section_)) + { + if (av == added (*s->member)) + av = 0; + + if (dv == deleted (*s->member)) + dv = 0; + } + + if (av != 0 || dv != 0) + { + os << "if ("; + + if (av != 0) + os << "svm >= schema_version_migration (" << av << "ULL, true)"; + + if (av != 0 && dv != 0) + os << " &&" << endl; + + if (dv != 0) + os << "svm <= schema_version_migration (" << dv << "ULL, true)"; + + os << ")" << endl; + } + + os << "{"; + + if (call_ != erase_id_call && (call_ != erase_obj_call || smart)) + { + // See if we are modifying via a reference or proper modifier. + // + if (call_ == load_call && ma.placeholder ()) + os << member_val_type (m, false, "v") << ";" + << endl; + else + { + // Note: this case is for both access and modification. + // + + // Output stored locations, if any. + // + os << from_; + + // If this is not a synthesized expression, then output its + // location for easier error tracking. + // + if (!ma.synthesized) + os << "// From " << location_string (ma.loc, true) << endl; + + // Note that here we don't decay arrays. + // + const string& ref_type ( + member_ref_type (m, call_ != load_call, "v", false /* decay */)); + + // VC++ cannot grok the constructor syntax. + // + os << ref_type << " =" << endl + << " "; + + // If this member is const and we have a synthesized direct + // access, then cast away constness. Otherwise, we assume + // that the user-provided expression handles this. + // + bool cast (call_ == load_call && ma.direct () && const_member (m)); + if (cast) + os << "const_cast< " << member_ref_type (m, false, "", false) << + " > (" << endl; + + os << ma.translate (obj_prefix_); + + if (cast) + os << ")"; + + os << ";" + << endl; + } + + var = "v"; + + semantics::names* hint; + semantics::type& t (utype (m, hint)); + + // If this is a wrapped container, then we need to "unwrap" it. + // + if (wrapper (t)) + { + var = "wrapper_traits< " + t.fq_name (hint) + " >::" + + (call_ == load_call ? "set_ref" : "get_ref") + " (" + var + ")"; + } + } + + switch (call_) + { + case persist_call: + { + os << traits << "::persist (" << endl + << var << "," << endl + << "esc." << sts_name; + + if (versioned) + os << "," << endl + << "svm"; + + os << ");"; + break; + } + case load_call: + { + os << traits << "::load (" << endl + << var << "," << endl + << "esc." << sts_name; + + if (versioned) + os << "," << endl + << "svm"; + + os << ");"; + break; + } + case update_call: + { + os << traits << "::update (" << endl + << var << "," << endl + << "esc." << sts_name; + + if (versioned) + os << "," << endl + << "svm"; + + os << ");"; + break; + } + case erase_obj_call: + { + os << traits << "::erase (" << endl; + + if (smart) + os << "&" << var << "," << endl; + + os << "esc." << sts_name << ");" + << endl; + break; + } + case erase_id_call: + { + os << traits << "::erase (" << endl; + + if (smart) + os << "0," << endl; + + os << "esc." << sts_name << ");" + << endl; + break; + } + case section_call: + { + os << "if (" << traits << "::container_traits_type::changed (" << + var << "))" << endl + << "s.reset (true, true);"; // loaded, changed + break; + } + } + + if (call_ == load_call) + { + // Call the modifier if we are using a proper one. + // + if (ma.placeholder ()) + { + os << endl + << from_; + + // If this is not a synthesized expression, then output its + // location for easier error tracking. + // + if (!ma.synthesized) + os << "// From " << location_string (ma.loc, true) << endl; + + os << ma.translate ( + obj_prefix_, "v", "static_cast<" + db.string () + + "::database&> (db)") << ";"; + } + } + + os << "}"; + } + + protected: + call_type call_; + string obj_prefix_; + string from_; + member_access* by_value_; + }; + + // + // + struct section_traits: traversal::class_, virtual context + { + typedef section_traits base; + + section_traits (semantics::class_& c) + : c_ (c), + scope_ ("access::object_traits_impl< " + class_fq_name (c) + + ", id_" + db.string () + " >") + { + } + + // Additional code that need to be executed following the call to + // init_value(). + // + virtual void + init_value_extra () + { + } + + virtual void + process_statement_columns (statement_columns&, + statement_kind, + bool /*dynamic*/) + { + } + + virtual void + section_extra (user_section&) + { + } + + // Returning "1" means increment by one. + // + virtual string + optimistic_version_increment (semantics::data_member&) + { + return "1"; + } + + virtual string + update_statement_extra (user_section&) + { + return ""; + } + + virtual void + traverse (user_section& s) + { + using semantics::class_; + using semantics::data_member; + + data_member& m (*s.member); + + class_* poly_root (polymorphic (c_)); + bool poly (poly_root != 0); + bool poly_derived (poly && poly_root != &c_); + class_* poly_base (poly_derived ? &polymorphic_base (c_) : 0); + + data_member* opt (optimistic (c_)); + + // Treat the special version update sections as abstract in reuse + // inheritance. + // + bool reuse_abst (!poly && + (abstract (c_) || + s.special == user_section::special_version)); + + bool load (s.total != 0 && s.separate_load ()); + bool load_con (s.containers && s.separate_load ()); + bool load_opt (s.optimistic () && s.separate_load ()); + + bool update (s.total != s.inverse + s.readonly); // Always separate. + bool update_con (s.readwrite_containers); + bool update_opt (s.optimistic () && (s.readwrite_containers || poly)); + + // Don't generate anything for empty sections. + // + if (!(load || load_con || load_opt || + update || update_con || update_opt)) + return; + + // If we are adding a new section to a derived class in an optimistic + // polymorphic hierarchy, then pretend it inherits from the special + // version update section. + // + user_section* rs (0); + if (opt != 0) + { + // Skip overrides and get to the new section if polymorphic. + // + for (rs = &s; poly && rs->base != 0; rs = rs->base) ; + + if (rs != 0) + { + if (rs->object != &opt->scope ()) + rs->base = &(poly ? poly_root : &opt->scope ())-> + get<user_sections> ("user-sections").back (); + else + rs = 0; + } + } + + string name (public_name (m) + "_traits"); + string scope (scope_ + "::" + name); + + os << "// " << m.name () << endl + << "//" << endl + << endl; + + // bind (id, image_type) + // + if (load || load_opt || update || update_opt) + { + os << "std::size_t " << scope << "::" << endl + << "bind (" << bind_vector << " b," << endl + << "const " << bind_vector << (reuse_abst ? "," : " id,") << endl + << "std::size_t" << (reuse_abst ? "," : " id_size,") << endl + << "image_type& i," << endl + << db << "::statement_kind sk"; + + if (s.versioned) + os << "," << endl + << "const schema_version_migration& svm"; + + os << ")" + << "{" + << "ODB_POTENTIALLY_UNUSED (sk);"; + + if (s.versioned) + os << "ODB_POTENTIALLY_UNUSED (svm);"; + + os << endl + << "using namespace " << db << ";" + << endl + << "std::size_t n (0);" + << endl; + + // Bind reuse base. It is always first and we never ask it + // to bind id(+ver). + // + if (s.base != 0 && !poly_derived) + { + user_section& b (*s.base); + + bool load (b.total != 0 && b.separate_load ()); + bool load_opt (b.optimistic () && b.separate_load ()); + + bool update (b.total != b.inverse + b.readonly); + + if (load || load_opt || update) + os << "// " << class_name (*b.object) << endl + << "//" << endl + << "n += object_traits_impl< " << class_fq_name (*b.object) << + ", id_" << db << " >::" << public_name (*b.member) << + "_traits::bind (" << endl + << "b, 0, 0, i, sk" << (b.versioned ? ", svm" : "") << ");" + << endl; + } + + // Bind members. + // + { + instance<bind_member> bm ("", "", &s); + traversal::names n (*bm); + names (c_, n); + } + + // Bind polymorphic image chain for the select statement. + // + if (s.base != 0 && poly_derived && s.separate_load ()) + { + // Find the next base that has something to load, if any. + // + user_section* b (s.base); + string acc (".base"); + for (class_* bo (poly_base);; bo = &polymorphic_base (*bo)) + { + if (b->object == bo) + { + if (b->total != 0 || b->optimistic ()) + break; + + b = b->base; + if (b == 0 || !polymorphic (*b->object)) + { + b = 0; + break; + } + } + acc += "->base"; + } + + if (b != 0) + os << "// " << class_name (*b->object) << endl + << "//" << endl + << "if (sk == statement_select)" << endl + << "n += object_traits_impl< " << class_fq_name (*b->object) << + ", id_" << db << " >::" << public_name (*b->member) << + "_traits::bind (" << endl + << "b + n, 0, 0, *i" << acc << ", sk" << + (b->versioned ? ", svm" : "") << ");" + << endl; + } + + if (!reuse_abst) + os << "// object_id" << endl + << "//" << endl + << "if (id != 0)" << endl + << "std::memcpy (&b[n], id, id_size * sizeof (id[0]));" + << "n += id_size;" // Not in if for "id unchanged" optimization. + << endl; + + os << "return n;" + << "}"; + } + + // grow () + // + if (generate_grow && (load || load_opt)) + { + os << "bool " << scope << "::" << endl + << "grow (image_type& i," << endl + << truncated_vector << " t"; + + if (s.versioned) + os << "," << endl + << "const schema_version_migration& svm"; + + os << ")" + << "{" + << "ODB_POTENTIALLY_UNUSED (i);" + << "ODB_POTENTIALLY_UNUSED (t);"; + + if (s.versioned) + os << "ODB_POTENTIALLY_UNUSED (svm);"; + + os << endl + << "bool grew (false);" + << endl; + + size_t index (0); + + if (s.base != 0 && !poly_derived) + { + user_section& b (*s.base); + + bool load (b.total != 0); + bool load_opt (b.optimistic ()); + + if (load || load_opt) + { + os << "// " << class_name (*b.object) << endl + << "//" << endl + << "grew = object_traits_impl< " << class_fq_name (*b.object) << + ", id_" << db << " >::" << public_name (*b.member) << + "_traits::grow (i, t" << (b.versioned ? ", svm" : "") << ");" + << endl; + + index += b.total + (load_opt ? 1 : 0); + } + } + + { + user_section* ps (&s); + instance<grow_member> gm (index, "", ps); + traversal::names n (*gm); + names (c_, n); + } + + // Grow polymorphic image chain. + // + if (s.base != 0 && poly_derived) + { + // Find the next base that has something to load, if any. + // + user_section* b (s.base); + string acc (".base"); + size_t cols; + for (class_* bo (poly_base);; bo = &polymorphic_base (*bo)) + { + if (b->object == bo) + { + cols = b->total + (b->optimistic () ? 1 : 0); + if (cols != 0) + break; + + b = b->base; + if (b == 0 || !polymorphic (*b->object)) + { + b = 0; + break; + } + } + acc += "->base"; + } + + if (b != 0) + os << "// " << class_name (*b->object) << endl + << "//" << endl + << "if (object_traits_impl< " << class_fq_name (*b->object) << + ", id_" << db << " >::" << public_name (*b->member) << + "_traits::grow (" << endl + << "*i" << acc << ", t + " << cols << "UL" << + (b->versioned ? ", svm" : "") << "))" << endl + << "i" << acc << "->version++;" + << endl; + } + + os << "return grew;" << endl + << "}"; + } + + // init (object, image) + // + if (load) + { + os << "void " << scope << "::" << endl + << "init (object_type& o," << endl + << "const image_type& i," << endl + << "database* db"; + + if (s.versioned) + os << "," << endl + << "const schema_version_migration& svm"; + + os << ")" + << "{" + << "ODB_POTENTIALLY_UNUSED (db);"; + + if (s.versioned) + os << "ODB_POTENTIALLY_UNUSED (svm);"; + + os << endl; + + if (s.base != 0) + { + if (!poly_derived) + { + user_section& b (*s.base); + + bool load (b.total != 0); + + if (load) + os << "// " << class_name (*b.object) << endl + << "//" << endl + << "object_traits_impl< " << class_fq_name (*b.object) << + ", id_" << db << " >::" << public_name (*b.member) << + "_traits::init (o, i, db" << + (b.versioned ? ", svm" : "") << ");" + << endl; + } + else + { + // Find the next base that has something to load, if any. + // + user_section* b (s.base); + string acc (".base"); + for (class_* bo (poly_base);; bo = &polymorphic_base (*bo)) + { + if (b->object == bo) + { + if (b->total != 0) + break; + + b = b->base; + if (b == 0 || !polymorphic (*b->object)) + { + b = 0; + break; + } + } + acc += "->base"; + } + + if (b != 0) + os << "// " << class_name (*b->object) << endl + << "//" << endl + << "object_traits_impl< " << class_fq_name (*b->object) << + ", id_" << db << " >::" << public_name (*b->member) << + "_traits::init (" << endl + << "o, *i" << acc << ", db" << + (b->versioned ? ", svm" : "") << ");" + << endl; + } + } + + { + instance<init_value_member> iv ("", "", true, &s); + traversal::names n (*iv); + names (c_, n); + } + + os << "}"; + } + + // init (image, object) + // + if (update) + { + os << (generate_grow ? "bool " : "void ") << scope << "::" << endl + << "init (image_type& i," << endl + << "const object_type& o"; + + if (s.versioned) + os << "," << endl + << "const schema_version_migration& svm"; + + os << ")" + << "{"; + + if (s.versioned) + os << "ODB_POTENTIALLY_UNUSED (svm);" + << endl; + + os << "using namespace " << db << ";" + << endl + << "statement_kind sk (statement_insert);" + << "ODB_POTENTIALLY_UNUSED (sk);" + << endl; + + // There is no call to init_image_pre() here (which calls the + // copy callback for some databases) since we are not going to + // touch any of the members that were loaded by query. + + if (generate_grow) + os << "bool grew (false);" + << endl; + + if (s.base != 0 && !poly_derived) + { + user_section& b (*s.base); + + bool update (b.total != b.inverse + b.readonly); + + if (update) + os << "// " << class_name (*b.object) << endl + << "//" << endl + << (generate_grow ? "grew = " : "") << + "object_traits_impl< " << class_fq_name (*b.object) << + ", id_" << db << " >::" << public_name (*b.member) << + "_traits::init (i, o" << (b.versioned ? ", svm" : "") << ");" + << endl; + } + + { + instance<init_image_member> ii ("", "", &s); + traversal::names n (*ii); + names (c_, n); + } + + if (generate_grow) + os << "return grew;"; + + os << "}"; + } + + // The rest does not apply to reuse-abstract sections. + // + if (reuse_abst) + { + section_extra (s); + return; + } + + string sep (s.versioned ? "\n" : " "); + + // Schema name as a string literal or empty. + // + string schema_name (options.schema_name ()[db]); + if (!schema_name.empty ()) + schema_name = strlit (schema_name); + + // Statements. + // + qname table (table_name (c_)); + string qtable (quote_id (table)); + + instance<object_columns_list> id_cols; + id_cols->traverse (*id_member (c_)); + + // select_statement + // + if (load || load_opt) + { + size_t depth (poly_derived ? polymorphic_depth (c_) : 1); + + statement_columns sc; + { + statement_kind sk (statement_select); // Imperfect forwarding. + object_section* ps (&s); // Imperfect forwarding. + instance<object_columns> t (qtable, sk, sc, depth, ps); + t->traverse (c_); + process_statement_columns (sc, statement_select, s.versioned); + } + + os << "const char " << scope << "::" << endl + << "select_statement[] =" << endl + << strlit ("SELECT" + sep) << endl; + + for (statement_columns::const_iterator i (sc.begin ()), + e (sc.end ()); i != e;) + { + string const& c (i->column); + os << strlit (c + (++i != e ? "," : "") + sep) << endl; + } + + os << strlit ("FROM " + qtable + sep) << endl; + + // Join polymorphic bases. + // + if (depth != 1 && s.base != 0) + { + bool f (false); //@@ (im)perfect forwarding + size_t d (depth - 1); //@@ (im)perfect forward. + instance<polymorphic_object_joins> j (c_, f, d, "", s.base); + j->traverse (*poly_base); + + for (strings::const_iterator i (j->begin ()); i != j->end (); ++i) + os << strlit (*i + sep) << endl; + } + + // Join tables of inverse members belonging to this section. + // + { + bool f (false); // @@ (im)perfect forwarding + object_section* ps (&s); // @@ (im)perfect forwarding + instance<object_joins> j (c_, f, depth, ps); + j->traverse (c_); + + for (strings::const_iterator i (j->begin ()); i != j->end (); ++i) + os << strlit (*i + sep) << endl; + } + + string where ("WHERE "); + instance<query_parameters> qp (statement_select, table); + for (object_columns_list::iterator b (id_cols->begin ()), i (b); + i != id_cols->end (); ++i) + { + if (i != b) + where += " AND "; + + where += qtable + "." + quote_id (i->name) + "=" + + convert_to (qp->next (*i), i->type, *i->member); + } + + os << strlit (where) << ";" + << endl; + } + + // update_statement + // + if (update || update_opt) + { + instance<query_parameters> qp (statement_update, table); + + statement_columns sc; + { + query_parameters* p (qp.get ()); // Imperfect forwarding. + statement_kind sk (statement_update); // Imperfect forwarding. + object_section* ps (&s); // Imperfect forwarding. + instance<object_columns> t (sk, sc, p, ps); + t->traverse (c_); + process_statement_columns (sc, statement_update, s.versioned); + } + + os << "const char " << scope << "::" << endl + << "update_statement[] =" << endl + << strlit ("UPDATE " + qtable + sep) << endl + << strlit ("SET" + sep) << endl; + + for (statement_columns::const_iterator i (sc.begin ()), + e (sc.end ()); i != e;) + { + string const& c (i->column); + os << strlit (c + (++i != e ? "," : "") + sep) << endl; + } + + // This didn't work out: cannot change the identity column. + // + //if (sc.empty ()) + //{ + // // We can end up with nothing to set if we need to "touch" a row + // // in order to increment its optimistic concurrency version. In + // // this case just do a dummy assignment based on the id column. + // // + // string const& c (quote_id (id_cols->begin ()->name)); + // os << strlit (c + "=" + c) << endl; + //} + + string extra (update_statement_extra (s)); + + if (!extra.empty ()) + os << strlit (extra + sep) << endl; + + string where ("WHERE "); + for (object_columns_list::iterator b (id_cols->begin ()), i (b); + i != id_cols->end (); ++i) + { + if (i != b) + where += " AND "; + + where += quote_id (i->name) + "=" + + convert_to (qp->next (*i), i->type, *i->member); + } + + if (s.optimistic ()) // Note: not update_opt. + { + string name (column_qname (*opt, column_prefix ())); + string type (column_type (*opt)); + + where += " AND " + name + "=" + + convert_to (qp->next (*opt, name, type), type, *opt); + } + + os << strlit (where) << ";" + << endl; + } + + // load () + // + if (load || load_opt || load_con) + { + os << "void " << scope << "::" << endl + << "load (extra_statement_cache_type& esc, object_type& obj" << + (poly ? ", bool top" : "") << ")" + << "{"; + + if (poly) + os << "ODB_POTENTIALLY_UNUSED (top);" + << endl; + + if (s.versioned || s.versioned_containers) + os << "const schema_version_migration& svm (" << endl + << "esc." << m.name () << ".version_migration (" << + schema_name << "));" + << endl; + + // Load values, if any. + // + if (load || load_opt) + { + // The SELECT statement for the top override loads all the + // values. + // + if (poly) + os << "if (top)" + << "{"; + + // Note that we don't use delayed load machinery here. While + // a section can definitely contain self-referencing pointers, + // loading such a pointer won't mess up the data members in the + // image that we care about. It also holds true for streaming + // result, since the bindings are different. + + os << "using namespace " << db << ";" + << "using " << db << "::select_statement;" // Conflicts. + << endl + << "statements_type& sts (esc." << m.name () << ");" + << endl + << "image_type& im (sts.image ());" + << "binding& imb (sts.select_image_binding ());" + << endl; + + // For the polymorphic case, instead of storing an array of + // versions as we do for objects, we will add all the versions + // up and use that as a cumulative image chain version. If you + // meditate a bit on that, you will realize that it will work + // (hint: versions can only increase). + // + string ver; + string ver_decl; + + if (s.base != 0 && poly_derived) + { + ver = "imv"; + ver_decl = "std::size_t imv (im.version"; + + user_section* b (s.base); + string acc ("im.base"); + for (class_* bo (poly_base);; bo = &polymorphic_base (*bo)) + { + if (b->object == bo) + { + if (b->total != 0 || b->optimistic ()) + ver_decl += " +\n" + acc + "->version"; + + b = b->base; + if (b == 0 || !polymorphic (*b->object)) + { + b = 0; + break; + } + } + acc += "->base"; + } + + ver_decl += ")"; + + os << ver_decl << ";" + << endl; + } + else + ver = "im.version"; + + os << "if (" << ver << " != sts.select_image_version () ||" << endl + << "imb.version == 0)" + << "{" + << "bind (imb.bind, 0, 0, im, statement_select" << + (s.versioned ? ", svm" : "") << ");" + << "sts.select_image_version (" << ver << ");" + << "imb.version++;" + << "}"; + + // Id binding is assumed initialized and bound. + // + os << "select_statement& st (sts.select_statement ());"; + + // The statement can be dynamically empty. + // + if (s.versioned) + os << "if (!st.empty ())" + << "{"; + + os << "st.execute ();" + << "auto_result ar (st);" + << "select_statement::result r (st.fetch ());" + << endl; + + os << "if (r == select_statement::no_data)" << endl + << "throw object_not_persistent ();" + << endl; + + if (grow (c_, &s)) + { + os << "if (r == select_statement::truncated)" + << "{" + << "if (grow (im, sts.select_image_truncated ()" << + (s.versioned ? ", svm" : "") << "))" << endl + << "im.version++;" + << endl; + + // The same logic as above. + // + if (s.base != 0 && poly_derived) + os << ver_decl << ";" + << endl; + + os << "if (" << ver << " != sts.select_image_version ())" + << "{" + << "bind (imb.bind, 0, 0, im, statement_select" << + (s.versioned ? ", svm" : "") << ");" + << "sts.select_image_version (" << ver << ");" + << "imb.version++;" + << "st.refetch ();" + << "}" + << "}"; + } + + if (opt != 0) // Not load_opt, we do it in poly-derived as well. + { + os << "if ("; + + if (poly_derived) + { + os << "root_traits::version (*im.base"; + for (class_* b (poly_base); + b != poly_root; + b = &polymorphic_base (*b)) + os << "->base"; + os << ")"; + } + else + os << "version (im)"; + + os << " != " << (poly_derived ? "root_traits::" : "") << + "version (obj))" << endl + << "throw object_changed ();" + << endl; + } + + if (load) + { + os << "init (obj, im, &sts.connection ().database ()" << + (s.versioned ? ", svm" : "") << ");"; + init_value_extra (); // Stream results, etc. + os << endl; + } + + if (s.versioned) + os << "}"; // if (!st.empty ()) + + if (poly) + os << "}"; // if (top) + } + + // Call base to load its containers, if this is an override. + // + if (poly_derived && s.base != 0) + { + user_section* b (s.base); + for (class_* bo (poly_base);; bo = &polymorphic_base (*bo)) + { + if (b->object == bo) + { + // If we don't have any values of our own but out base + // does, then allow it to load them. + // + if (b->containers || + (!load && (b->total != 0 || b->optimistic ()))) + break; + + b = b->base; + if (b == 0 || !polymorphic (*b->object)) + { + b = 0; + break; + } + } + } + + // This one is tricky: ideally we would do a direct call to + // the base's load() (which may not be our immediate base, + // BTW) but there is no easy way to resolve base's extra + // statements from ours. So, instead, we are going to go + // via the dispatch machinery which requires a connection + // rather than statements. Not the most efficient way but + // simple. + + // Find the "previous" override by starting the search from + // our base. + // + if (b != 0) + { + // Note that here we are using the base section index to + // handle the special version update base. + // + os << "info.base->find_section_load (" << b->index << "UL) (" << + "esc." << m.name () << ".connection (), obj, " << + // If we don't have any values of our own, then allow the + // base load its. + // + (load ? "false" : "top") << ");" + << endl; + } + } + + // Load our containers, if any. + // + if (s.containers) + { + instance<container_calls> t (container_calls::load_call, &s); + t->traverse (c_); + } + + os << "}"; + } + + // update () + // + if (update || update_opt || update_con) + { + os << "void " << scope << "::" << endl + << "update (extra_statement_cache_type& esc, " << + "const object_type& obj" << + (poly_derived && s.base != 0 ? ", bool base" : "") << ")" + << "{"; + + // Call base if this is an override. + // + if (poly_derived && s.base != 0) + { + user_section* b (s.base); + for (class_* bo (poly_base);; bo = &polymorphic_base (*bo)) + { + if (b->object == bo) + { + if (b->total != b->inverse + b->readonly || + b->readwrite_containers || + (poly && b->optimistic ())) + break; + + b = b->base; + if (b == 0 || !polymorphic (*b->object)) + { + b = 0; + break; + } + } + } + + // The same (tricky) logic as in load(). Note that here we are + // using the base section index to handle the special version + // update base. + // + if (b != 0) + os << "if (base)" << endl + << "info.base->find_section_update (" << b->index << + "UL) (esc." << m.name () << ".connection (), obj);" + << endl; + else + os << "ODB_POTENTIALLY_UNUSED (base);" + << endl; + } + + if (s.versioned || s.readwrite_versioned_containers) + os << "const schema_version_migration& svm (" << endl + << "esc." << m.name () << ".version_migration (" << + schema_name << "));" + << endl; + + // Update values, if any. + // + if (update || update_opt) + { + os << "using namespace " << db << ";" + << "using " << db << "::update_statement;" // Conflicts. + << endl + << "statements_type& sts (esc." << m.name () << ");" + << endl + << "image_type& im (sts.image ());" + << "const binding& id (sts.idv_binding ());" // id+version + << "binding& imb (sts.update_image_binding ());" + << endl; + + if (update) + { + if (generate_grow) + os << "if ("; + + os << "init (im, obj" << (s.versioned ? ", svm" : "") << ")"; + + if (generate_grow) + os << ")" << endl + << "im.version++"; + + os << ";" + << endl; + } + + os << "if (im.version != sts.update_image_version () ||" << endl + << "id.version != sts.update_id_binding_version () ||" << endl + << "imb.version == 0)" + << "{" + << "bind (imb.bind, id.bind, id.count, im, statement_update" << + (s.versioned ? ", svm" : "") << ");" + << "sts.update_image_version (im.version);" + << "sts.update_id_binding_version (id.version);" + << "imb.version++;" + << "}"; + + os << "update_statement& st (sts.update_statement ());" + << "if ("; + + if (s.versioned) + os << "!st.empty () && "; + + os << "st.execute () == 0)" << endl; + + if (opt == 0) + os << "throw object_not_persistent ();"; + else + os << "throw object_changed ();"; + + os << endl; + } + + // Update readwrite containers if any. + // + if (s.readwrite_containers) + { + instance<container_calls> t (container_calls::update_call, &s); + t->traverse (c_); + } + + // Update the optimistic concurrency version in the object member. + // Very similar code to object. + // + if (s.optimistic ()) // Note: not update_opt. + { + // Object is passed as const reference so we need to cast away + // constness. + // + const char* obj ("const_cast<object_type&> (obj)"); + string inc (optimistic_version_increment (*opt)); + + if (inc == "1") + inc_member (*opt, obj, "obj", "version_type"); + else + set_member (*opt, obj, inc, "", "version_type"); + } + + os << "}"; + } + + section_extra (s); + + if (rs != 0) + rs->base = 0; + } + + using class_::traverse; // Unhide. + + protected: + semantics::class_& c_; + string scope_; + }; + + // Output a list of parameters for the persist statement. + // + struct persist_statement_params: object_columns_base, virtual context + { + typedef persist_statement_params base; + + persist_statement_params (string& params, + query_parameters& qp, + const string& sep) + : params_ (params), qp_ (qp), sep_ (sep) + { + } + + virtual void + traverse_pointer (semantics::data_member& m, semantics::class_& c) + { + if (!inverse (m, key_prefix_)) + object_columns_base::traverse_pointer (m, c); + } + + virtual bool + traverse_column (semantics::data_member& m, + string const& name, + bool first) + { + string p; + + if (version (m)) + p = version_value (m); + else + { + const string& qname (quote_id (name)); + const string& type (column_type ()); + + p = auto_ (m) // Only simple, direct id can be auto. + ? qp_.auto_id (m, qname, type) + : qp_.next (m, qname, type); + } + + if (!p.empty ()) + { + if (!first) + { + params_ += ','; + params_ += sep_; + } + + params_ += (p != "DEFAULT" ? convert_to (p, column_type (), m) : p); + } + + return !p.empty (); + } + + virtual string + version_value (semantics::data_member&) + { + return "1"; + } + + private: + string& params_; + query_parameters& qp_; + const string& sep_; + }; + + // + // + struct class_: traversal::class_, virtual context + { + typedef class_ base; + + class_ () + : typedefs_ (false), + query_columns_type_ (false, false, false), + view_query_columns_type_ (false), + index_ (0), + grow_base_ (index_), + grow_member_ (index_), + grow_version_member_ (index_, "version_"), + grow_discriminator_member_ (index_, "discriminator_"), + bind_id_member_ ("id_"), + bind_version_member_ ("version_"), + bind_discriminator_member_ ("discriminator_"), + init_id_image_member_ ("id_", "id"), + init_version_image_member_ ("version_", "(*v)"), + init_view_pointer_member_pre_ (true, *init_value_member_), + init_view_pointer_member_post_ (false, *init_value_member_), + init_id_value_member_ ("id"), + init_id_value_member_id_image_ ("id", "id_"), + init_version_value_member_ ("v"), + init_named_version_value_member_ ("v", "version_"), + init_discriminator_value_member_ ("d", "", false), + init_named_discriminator_value_member_ ( + "d", "discriminator_", false) + { + init (); + } + + class_ (class_ const&) + : root_context (), //@@ -Wextra + context (), + typedefs_ (false), + query_columns_type_ (false, false, false), + view_query_columns_type_ (false), + index_ (0), + grow_base_ (index_), + grow_member_ (index_), + grow_version_member_ (index_, "version_"), + grow_discriminator_member_ (index_, "discriminator_"), + bind_id_member_ ("id_"), + bind_version_member_ ("version_"), + bind_discriminator_member_ ("discriminator_"), + init_id_image_member_ ("id_", "id"), + init_version_image_member_ ("version_", "(*v)"), + init_view_pointer_member_pre_ (true, *init_value_member_), + init_view_pointer_member_post_ (false, *init_value_member_), + init_id_value_member_ ("id"), + init_id_value_member_id_image_ ("id", "id_"), + init_version_value_member_ ("v"), + init_named_version_value_member_ ("v", "version_"), + init_discriminator_value_member_ ("d", "", false), + init_named_discriminator_value_member_ ( + "d", "discriminator_", false) + { + init (); + } + + void + init () + { + *this >> defines_ >> *this; + *this >> typedefs_ >> *this; + + if (generate_grow) + { + grow_base_inherits_ >> grow_base_; + grow_member_names_ >> grow_member_; + } + + bind_base_inherits_ >> bind_base_; + bind_member_names_ >> bind_member_; + + init_image_base_inherits_ >> init_image_base_; + init_image_member_names_ >> init_image_member_; + + init_value_base_inherits_ >> init_value_base_; + init_value_member_names_ >> init_value_member_; + + init_view_pointer_member_pre_names_ >> init_view_pointer_member_pre_; + init_view_pointer_member_post_names_ >> init_view_pointer_member_post_; + } + + virtual void + init_auto_id (semantics::data_member&, // id member + string const&) // image variable prefix + { + if (insert_send_auto_id) + assert (false); + } + + virtual void + init_image_pre (type&) + { + } + + virtual void + init_value_extra () + { + } + + virtual void + traverse (type& c) + { + class_kind_type ck (class_kind (c)); + + if (ck == class_other || + (!options.at_once () && class_file (c) != unit.file ())) + return; + + names (c); + + context::top_object = context::cur_object = &c; + + switch (ck) + { + case class_object: traverse_object (c); break; + case class_view: traverse_view (c); break; + case class_composite: traverse_composite (c); break; + default: break; + } + + context::top_object = context::cur_object = 0; + } + + // + // statements + // + + enum persist_position + { + persist_after_columns, + persist_after_values + }; + + virtual string + persist_statement_extra (type&, query_parameters&, persist_position) + { + return ""; + } + + virtual string + update_statement_extra (type&) + { + return ""; + } + + // + // common + // + + virtual void + post_query_ (type&, bool /*once_off*/) + { + } + + virtual void + process_statement_columns (statement_columns&, + statement_kind, + bool /*dynamic*/) + { + } + + // + // object + // + + virtual void + object_extra (type&) {} + + virtual void + extra_statement_cache_extra_args (bool /*containers*/, + bool /*sections*/) {} + + virtual void + object_query_statement_ctor_args (type&, + std::string const& q, + bool process, + bool /*prepared*/) + { + os << "conn," << endl + << "text," << endl + << process << "," << endl // Process. + << "true," << endl // Optimize. + << q << ".parameters_binding ()," << endl + << "imb"; + } + + virtual void + object_erase_query_statement_ctor_args (type&) + { + os << "conn," << endl + << "text," << endl + << "q.parameters_binding ()"; + } + + virtual string + optimistic_version_init (semantics::data_member&, bool /*index*/ = false) + { + return "1"; + } + + // Returning "1" means increment by one. + // + virtual string + optimistic_version_increment (semantics::data_member&, + bool /*index*/ = false) + { + return "1"; + } + + virtual bool + optimistic_insert_bind_version (semantics::data_member&) + { + return false; + } + + virtual void + traverse_object (type& c); + + // + // view + // + + virtual void + view_extra (type&) + { + } + + virtual void + view_query_statement_ctor_args (type&, + string const& q, + bool process, + bool /*prepared*/) + { + os << "conn," << endl + << q << ".clause ()," << endl + << process << "," << endl // Process. + << "true," << endl // Optimize. + << q << ".parameters_binding ()," << endl + << "imb"; + } + + virtual string + from_trailer (type&) { return "";} + + virtual string + select_trailer (type& c) + { + return c.get<view_query> ("query").for_update ? "FOR UPDATE" : ""; + } + + virtual string + join_syntax (view_object const& vo) + { + const char* r (0); + + switch (vo.join) + { + case view_object::left: r = "LEFT JOIN"; break; + case view_object::right: r = "RIGHT JOIN"; break; + case view_object::full: r = "FULL JOIN"; break; + case view_object::inner: r = "INNER JOIN"; break; + case view_object::cross: r = "CROSS JOIN"; break; + } + + return r; + } + + virtual void + traverse_view (type& c); + + struct expression + { + explicit + expression (std::string const& v): kind (literal), value (v) {} + expression (view_object* vo): kind (pointer), vo (vo) {} + + enum kind_type {literal, pointer}; + + kind_type kind; + std::string value; + data_member_path member_path; + view_object* vo; + }; + + expression + translate_expression (type& c, + cxx_tokens const&, + semantics::scope& start_scope, + location_t loc, + string const& prag, + bool* placeholder = 0, + bool predicate = true); + // + // composite + // + + virtual void + traverse_composite (type& c) + { + bool versioned (context::versioned (c)); + + string const& type (class_fq_name (c)); + string traits ("access::composite_value_traits< " + type + ", id_" + + db.string () + " >"); + + os << "// " << class_name (c) << endl + << "//" << endl + << endl; + + // Containers. + // + { + instance<container_traits> t (c); + t->traverse (c); + } + + // grow () + // + if (generate_grow) + { + os << "bool " << traits << "::" << endl + << "grow (image_type& i," << endl + << truncated_vector << " t"; + + if (versioned) + os << "," << endl + << "const schema_version_migration& svm"; + + os << ")" + << "{" + << "ODB_POTENTIALLY_UNUSED (i);" + << "ODB_POTENTIALLY_UNUSED (t);"; + + if (versioned) + os << "ODB_POTENTIALLY_UNUSED (svm);"; + + os << endl + << "bool grew (false);" + << endl; + + index_ = 0; + inherits (c, grow_base_inherits_); + names (c, grow_member_names_); + + os << "return grew;" + << "}"; + } + + // bind (image_type) + // + os << "void " << traits << "::" << endl + << "bind (" << bind_vector << " b," << endl + << "image_type& i," << endl + << db << "::statement_kind sk"; + + if (versioned) + os << "," << endl + << "const schema_version_migration& svm"; + + os << ")" + << "{" + << "ODB_POTENTIALLY_UNUSED (b);" + << "ODB_POTENTIALLY_UNUSED (i);" + << "ODB_POTENTIALLY_UNUSED (sk);"; + + if (versioned) + os << "ODB_POTENTIALLY_UNUSED (svm);"; + + os << endl + << "using namespace " << db << ";" + << endl; + + if (readonly (c)) + os << "assert (sk != statement_update);" + << endl; + + os << "std::size_t n (0);" + << "ODB_POTENTIALLY_UNUSED (n);" + << endl; + + inherits (c, bind_base_inherits_); + names (c, bind_member_names_); + + os << "}"; + + // init (image, value) + // + os << (generate_grow ? "bool " : "void ") << traits << "::" << endl + << "init (image_type& i," << endl + << "const value_type& o," << endl + << db << "::statement_kind sk"; + + if (versioned) + os << "," << endl + << "const schema_version_migration& svm"; + + os << ")" + << "{" + << "ODB_POTENTIALLY_UNUSED (i);" + << "ODB_POTENTIALLY_UNUSED (o);" + << "ODB_POTENTIALLY_UNUSED (sk);"; + + if (versioned) + os << "ODB_POTENTIALLY_UNUSED (svm);"; + + os << endl + << "using namespace " << db << ";" + << endl; + + if (readonly (c)) + os << "assert (sk != statement_update);" + << endl; + + if (generate_grow) + os << "bool grew (false);" + << endl; + + inherits (c, init_image_base_inherits_); + names (c, init_image_member_names_); + + if (generate_grow) + os << "return grew;"; + + os << "}"; + + // init (value, image) + // + os << "void " << traits << "::" << endl + << "init (value_type& o," << endl + << "const image_type& i," << endl + << "database* db"; + + if (versioned) + os << "," << endl + << "const schema_version_migration& svm"; + + os << ")" + << "{" + << "ODB_POTENTIALLY_UNUSED (o);" + << "ODB_POTENTIALLY_UNUSED (i);" + << "ODB_POTENTIALLY_UNUSED (db);"; + + if (versioned) + os << "ODB_POTENTIALLY_UNUSED (svm);"; + + os << endl; + + inherits (c, init_value_base_inherits_); + names (c, init_value_member_names_); + + os << "}"; + } + + private: + traversal::defines defines_; + typedefs typedefs_; + + instance<query_columns_type> query_columns_type_; + instance<view_query_columns_type> view_query_columns_type_; + + size_t index_; + instance<grow_base> grow_base_; + traversal::inherits grow_base_inherits_; + instance<grow_member> grow_member_; + traversal::names grow_member_names_; + instance<grow_member> grow_version_member_; + instance<grow_member> grow_discriminator_member_; + + + instance<bind_base> bind_base_; + traversal::inherits bind_base_inherits_; + instance<bind_member> bind_member_; + traversal::names bind_member_names_; + instance<bind_member> bind_id_member_; + instance<bind_member> bind_version_member_; + instance<bind_member> bind_discriminator_member_; + + instance<init_image_base> init_image_base_; + traversal::inherits init_image_base_inherits_; + instance<init_image_member> init_image_member_; + traversal::names init_image_member_names_; + + instance<init_image_member> init_id_image_member_; + instance<init_image_member> init_version_image_member_; + + instance<init_value_base> init_value_base_; + traversal::inherits init_value_base_inherits_; + instance<init_value_member> init_value_member_; + traversal::names init_value_member_names_; + + instance<init_view_pointer_member> init_view_pointer_member_pre_; + instance<init_view_pointer_member> init_view_pointer_member_post_; + traversal::names init_view_pointer_member_pre_names_; + traversal::names init_view_pointer_member_post_names_; + + instance<init_value_member> init_id_value_member_; + instance<init_value_member> init_id_value_member_id_image_; + instance<init_value_member> init_version_value_member_; + instance<init_value_member> init_named_version_value_member_; + instance<init_value_member> init_discriminator_value_member_; + instance<init_value_member> init_named_discriminator_value_member_; + }; + + struct include: virtual context + { + typedef include base; + + virtual void + generate () + { + extra_pre (); + + os << "#include <cassert>" << endl + << "#include <cstring> // std::memcpy" << endl; + + if (features.polymorphic_object) + os << "#include <typeinfo>" << endl; + + os << endl; + + if (features.polymorphic_object) + os << "#include <odb/polymorphic-map.hxx>" << endl; + + if (embedded_schema) + os << "#include <odb/schema-catalog-impl.hxx>" << endl; + + if (multi_dynamic) + os << "#include <odb/function-table.hxx>" << endl; + + os << endl; + + os << "#include <odb/" << db << "/traits.hxx>" << endl + << "#include <odb/" << db << "/database.hxx>" << endl + << "#include <odb/" << db << "/transaction.hxx>" << endl + << "#include <odb/" << db << "/connection.hxx>" << endl + << "#include <odb/" << db << "/statement.hxx>" << endl + << "#include <odb/" << db << "/statement-cache.hxx>" << endl; + + if (features.simple_object) + os << "#include <odb/" << db << "/simple-object-statements.hxx>" << endl; + + if (features.polymorphic_object) + os << "#include <odb/" << db << "/polymorphic-object-statements.hxx>" << endl; + + if (features.no_id_object) + os << "#include <odb/" << db << "/no-id-object-statements.hxx>" << endl; + + if (features.view) + os << "#include <odb/" << db << "/view-statements.hxx>" << endl; + + if (features.section) + os << "#include <odb/" << db << "/section-statements.hxx>" << endl; + + os << "#include <odb/" << db << "/container-statements.hxx>" << endl + << "#include <odb/" << db << "/exceptions.hxx>" << endl; + + if (options.generate_query ()) + { + if (options.generate_prepared ()) + os << "#include <odb/" << db << "/prepared-query.hxx>" << endl; + + if (features.simple_object) + os << "#include <odb/" << db << "/simple-object-result.hxx>" << endl; + + if (features.polymorphic_object) + os << "#include <odb/" << db << "/polymorphic-object-result.hxx>" << endl; + + if (features.no_id_object) + os << "#include <odb/" << db << "/no-id-object-result.hxx>" << endl; + + if (features.view) + os << "#include <odb/" << db << "/view-result.hxx>" << endl; + } + + extra_post (); + + os << endl; + } + + virtual void + extra_pre () + { + } + + virtual void + extra_post () + { + } + }; + } +} + +#endif // ODB_RELATIONAL_SOURCE_HXX diff --git a/odb/odb/relational/sqlite/common.cxx b/odb/odb/relational/sqlite/common.cxx new file mode 100644 index 0000000..03a3599 --- /dev/null +++ b/odb/odb/relational/sqlite/common.cxx @@ -0,0 +1,217 @@ +// file : odb/relational/sqlite/common.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <cassert> + +#include <odb/relational/sqlite/common.hxx> + +using namespace std; + +namespace relational +{ + namespace sqlite + { + // + // member_base + // + + sql_type const& member_base:: + member_sql_type (semantics::data_member& m) + { + return parse_sql_type (column_type (m, key_prefix_), m); + } + + void member_base:: + traverse_simple (member_info& mi) + { + switch (mi.st->type) + { + case sql_type::INTEGER: + { + traverse_integer (mi); + break; + } + case sql_type::REAL: + { + traverse_real (mi); + break; + } + case sql_type::TEXT: + { + if (mi.st->stream) + traverse_text_stream (mi); + else + traverse_text (mi); + break; + } + case sql_type::BLOB: + { + if (mi.st->stream) + traverse_blob_stream (mi); + else + traverse_blob (mi); + break; + } + case sql_type::invalid: + { + assert (false); + break; + } + } + } + + // + // member_image_type + // + + member_image_type:: + member_image_type (base const& x) + : member_base::base (x), // virtual base + base (x) {} + + member_image_type:: + member_image_type () + : relational::member_base (0, 0, string (), string ()) {} + + member_image_type:: + member_image_type (semantics::type* type, + const custom_cxx_type* ct, + string const& fq_type, + string const& key_prefix) + : relational::member_base (type, ct, fq_type, key_prefix) {} + + string member_image_type:: + image_type (semantics::data_member& m) + { + type_.clear (); + member_base::traverse (m, true); + return type_; + } + + void member_image_type:: + traverse_composite (member_info& mi) + { + type_ = "composite_value_traits< " + mi.fq_type () + + ", id_sqlite >::image_type"; + } + + void member_image_type:: + traverse_integer (member_info&) + { + type_ = "long long"; + } + + void member_image_type:: + traverse_real (member_info&) + { + type_ = "double"; + } + + void member_image_type:: + traverse_string (member_info&) + { + type_ = "details::buffer"; + } + + void member_image_type:: + traverse_stream (member_info&) + { + type_ = "sqlite::stream_buffers"; + } + + entry<member_image_type> member_image_type_; + + // + // member_database_type + // + + member_database_type_id:: + member_database_type_id (base const& x) + : member_base::base (x), // virtual base + base (x) {} + + member_database_type_id:: + member_database_type_id () + : member_base::base (0, 0, string (), string ()), // virtual base + base (0, 0, string (), string ()) {} + + member_database_type_id:: + member_database_type_id (semantics::type* type, + const custom_cxx_type* ct, + string const& fq_type, + string const& key_prefix) + : member_base::base (type, ct, fq_type, key_prefix), // virtual base + base (type, ct, fq_type, key_prefix) {} + + string member_database_type_id:: + database_type_id (type& m) + { + type_id_.clear (); + member_base::traverse (m, true); + return type_id_; + } + + void member_database_type_id:: + traverse_composite (member_info&) + { + assert (false); + } + + void member_database_type_id:: + traverse_integer (member_info&) + { + type_id_ = "sqlite::id_integer"; + } + + void member_database_type_id:: + traverse_real (member_info&) + { + type_id_ = "sqlite::id_real"; + } + + void member_database_type_id:: + traverse_text (member_info&) + { + type_id_ = "sqlite::id_text"; + } + + void member_database_type_id:: + traverse_blob (member_info&) + { + type_id_ = "sqlite::id_blob"; + } + + void member_database_type_id:: + traverse_text_stream (member_info&) + { + type_id_ = "sqlite::id_text_stream"; + } + + void member_database_type_id:: + traverse_blob_stream (member_info&) + { + type_id_ = "sqlite::id_blob_stream"; + } + + entry<member_database_type_id> member_database_type_id_; + + // + // query_columns + // + + struct query_columns: relational::query_columns, context + { + query_columns (base const& x): base_impl (x) {} + + virtual string + database_type_id (semantics::data_member& m) + { + return member_database_type_id_.database_type_id (m); + } + + private: + member_database_type_id member_database_type_id_; + }; + entry<query_columns> query_columns_; + } +} diff --git a/odb/odb/relational/sqlite/common.hxx b/odb/odb/relational/sqlite/common.hxx new file mode 100644 index 0000000..4d6089e --- /dev/null +++ b/odb/odb/relational/sqlite/common.hxx @@ -0,0 +1,147 @@ +// file : odb/relational/sqlite/common.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_RELATIONAL_SQLITE_COMMON_HXX +#define ODB_RELATIONAL_SQLITE_COMMON_HXX + +#include <odb/relational/common.hxx> +#include <odb/relational/sqlite/context.hxx> + +namespace relational +{ + namespace sqlite + { + struct member_base: virtual relational::member_base_impl<sql_type>, context + { + member_base (base const& x): base (x), base_impl (x) {} + + // This c-tor is for the direct use inside the sqlite namespace. + // If you do use this c-tor, you should also explicitly call + // relational::member_base (aka base). + // + member_base () {} + + virtual sql_type const& + member_sql_type (semantics::data_member&); + + virtual void + traverse_simple (member_info&); + + virtual void + traverse_integer (member_info&) + { + } + + virtual void + traverse_real (member_info&) + { + } + + virtual void + traverse_text (member_info& m) + { + traverse_string (m); + } + + virtual void + traverse_blob (member_info& m) + { + traverse_string (m); + } + + // String covers both text and blob. + // + virtual void + traverse_string (member_info&) + { + } + + virtual void + traverse_text_stream (member_info& m) + { + traverse_stream (m); + } + + virtual void + traverse_blob_stream (member_info& m) + { + traverse_stream (m); + } + + virtual void + traverse_stream (member_info&) + { + } + }; + + struct member_image_type: relational::member_image_type, + member_base + { + member_image_type (base const&); + member_image_type (); + member_image_type (semantics::type* type, + const custom_cxx_type*, + string const& fq_type = string (), + string const& key_prefix = string ()); + virtual string + image_type (semantics::data_member&); + + virtual void + traverse_composite (member_info&); + + virtual void + traverse_integer (member_info&); + + virtual void + traverse_real (member_info&); + + virtual void + traverse_string (member_info&); + + virtual void + traverse_stream (member_info&); + + private: + string type_; + }; + + struct member_database_type_id: relational::member_database_type_id, + member_base + { + member_database_type_id (base const&); + member_database_type_id (); + member_database_type_id (semantics::type* type, + const custom_cxx_type*, + string const& fq_type = string (), + string const& key_prefix = string ()); + + virtual string + database_type_id (type&); + + virtual void + traverse_composite (member_info&); + + virtual void + traverse_integer (member_info&); + + virtual void + traverse_real (member_info&); + + virtual void + traverse_text (member_info&); + + virtual void + traverse_blob (member_info&); + + virtual void + traverse_text_stream (member_info&); + + virtual void + traverse_blob_stream (member_info&); + + private: + string type_id_; + }; + } +} +#endif // ODB_RELATIONAL_SQLITE_COMMON_HXX diff --git a/odb/odb/relational/sqlite/context.cxx b/odb/odb/relational/sqlite/context.cxx new file mode 100644 index 0000000..9a4369f --- /dev/null +++ b/odb/odb/relational/sqlite/context.cxx @@ -0,0 +1,490 @@ +// file : odb/relational/sqlite/context.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <vector> +#include <cassert> +#include <sstream> + +#include <odb/sql-token.hxx> +#include <odb/sql-lexer.hxx> + +#include <odb/relational/sqlite/context.hxx> +#include <odb/relational/sqlite/common.hxx> + +using namespace std; + +namespace relational +{ + namespace sqlite + { + 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", "INTEGER", 0, false}, + + {"char", "TEXT", 0, false}, + {"wchar_t", "TEXT", 0, false}, + {"signed char", "INTEGER", 0, false}, + {"unsigned char", "INTEGER", 0, false}, + + {"short int", "INTEGER", 0, false}, + {"short unsigned int", "INTEGER", 0, false}, + + {"int", "INTEGER", 0, false}, + {"unsigned int", "INTEGER", 0, false}, + + {"long int", "INTEGER", 0, false}, + {"long unsigned int", "INTEGER", 0, false}, + + {"long long int", "INTEGER", 0, false}, + {"long long unsigned int", "INTEGER", 0, false}, + + // SQLite stores NaN as NULL. + // + {"float", "REAL", 0, true}, + {"double", "REAL", 0, true}, + + {"::std::string", "TEXT", 0, false}, + {"::std::wstring", "TEXT", 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 = true; + need_alias_as = true; + insert_send_auto_id = true; + delay_freeing_statement_result = false; + need_image_clone = false; + generate_bulk = false; + global_index = true; + global_fkey = false; + data_->bind_vector_ = "sqlite::bind*"; + data_->truncated_vector_ = "bool*"; + + // 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; + } + + namespace + { + struct has_grow: traversal::class_ + { + has_grow (bool& r, user_section* s) + : r_ (r), section_ (s) + { + *this >> inherits_ >> *this; + } + + virtual void + traverse (type& c) + { + // Ignore transient bases. + // + if (!(context::object (c) || context::composite (c))) + return; + + if (section_ == 0 && c.count ("sqlite-grow")) + r_ = c.get<bool> ("sqlite-grow"); + else + { + // r_ should be false. + // + inherits (c); + + if (!r_) + names (c); + + if (section_ == 0) + c.set ("sqlite-grow", r_); + } + } + + private: + bool& r_; + user_section* section_; + traversal::inherits inherits_; + }; + + struct has_grow_member: member_base + { + has_grow_member (bool& r, user_section* section = 0) + : relational::member_base (0, 0, string (), string (), section), + r_ (r) {} + + has_grow_member (bool& r, + user_section* section, + semantics::type* t, + const custom_cxx_type* ct, + string const& key_prefix = string ()) + : relational::member_base (t, ct, string (), key_prefix, section), + r_ (r) {} + + virtual bool + pre (member_info& mi) + { + // If we have a key prefix (container), then it can't be in a + // section (while mi.m can). The same for top-level -- if we got + // called, then we shouldn't ignore it. + // + return !key_prefix_.empty () || top_level_ || + (section_ == 0 && !separate_load (mi.m)) || + (section_ != 0 && *section_ == section (mi.m)); + } + + virtual void + traverse_composite (member_info& mi) + { + // By calling grow() instead of recursing, we reset any overrides. + // We also don't pass section since they don't apply inside + // composites. + // + r_ = r_ || context::grow (dynamic_cast<semantics::class_&> (mi.t)); + } + + virtual void + traverse_string (member_info&) + { + r_ = true; + } + + private: + bool& r_; + }; + } + + bool context:: + grow_impl (semantics::class_& c, user_section* section) + { + if (section == 0 && c.count ("sqlite-grow")) + return c.get<bool> ("sqlite-grow"); + + bool r (false); + has_grow ct (r, section); + has_grow_member mt (r, section); + traversal::names names; + ct >> names >> mt; + ct.traverse (c); + return r; + } + + bool context:: + grow_impl (semantics::data_member& m) + { + bool r (false); + has_grow_member mt (r); + mt.traverse (m, true); + return r; + } + + bool context:: + grow_impl (semantics::data_member& m, + semantics::type& t, + const custom_cxx_type* ct, + string const& kp) + { + bool r (false); + has_grow_member mt (r, 0, &t, ct, kp); + mt.traverse (m, true); + 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> () || + bt.is_a<semantics::fund_wchar> ()) + { + if (a->size () != 0) + r = "TEXT"; + } + } + + return r; + } + + // + // SQL type parsing. + // + + namespace + { + struct sql_parser + { + typedef context::invalid_sql_type invalid_sql_type; + + sql_parser (custom_db_types const* ct): ct_ (ct) {} + + sql_type + parse (string sql) + { + 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 (sql)) + { + r.to = t.type.replace (sql, t.to); + r.from = t.type.replace (sql, t.from); + sql = t.type.replace (sql, t.as); + break; + } + } + } + + // Parse the type into a sequence of identifiers. + // + try + { + l_.lex (sql); + + for (sql_token t (l_.next ()); t.type () != sql_token::t_eos;) + { + sql_token::token_type tt (t.type ()); + + if (tt == sql_token::t_identifier) + { + ids_.push_back (context::upcase (t.identifier ())); + t = l_.next (); + + if (t.punctuation () == sql_token::p_lparen) + { + if (!parse_range ()) + return error (m_); + + t = l_.next (); + } + } + else + return error ("expected SQLite type name instead of '" + + t.string () + "'"); + } + } + catch (sql_lexer::invalid_input const& e) + { + return error ("invalid SQLite type declaration: " + e.message); + } + + if (ids_.empty ()) + return error ("expected SQLite type name"); + + // First check our own types. + // + if (ids_.size () == 2 && ids_[0] == "TEXT" && ids_[1] == "STREAM") + { + r.type = sql_type::TEXT; + r.stream = true; + } + if (ids_.size () == 2 && ids_[0] == "BLOB" && ids_[1] == "STREAM") + { + r.type = sql_type::BLOB; + r.stream = true; + } + // + // Apply the first four rules of the SQLite type to affinity + // conversion algorithm. + // + else if (find ("INT")) + r.type = sql_type::INTEGER; + else if (find ("TEXT") || find ("CHAR") || find ("CLOB")) + r.type = sql_type::TEXT; + else if (find ("BLOB")) + r.type = sql_type::BLOB; + else if (find ("REAL") || find ("FLOA") || find ("DOUB")) + r.type = sql_type::REAL; + else + { + // Instead of the fifth rule which maps everything else + // to NUMERICAL (which we don't have), map some commonly + // used type names to one of the above types. + // + string const& id (ids_[0]); + + if (id == "NUMERIC") + r.type = sql_type::REAL; + else if (id == "DECIMAL") + r.type = sql_type::TEXT; + else if (id == "BOOLEAN" || id == "BOOL") + r.type = sql_type::INTEGER; + else if (id == "DATE" || id == "TIME" || id == "DATETIME") + r.type = sql_type::TEXT; + else + return error ("unknown SQLite type '" + id + "'"); + } + + return r; + } + + bool + parse_range () + { + // Skip tokens until we get the closing paren. + // + for (sql_token t (l_.next ());; t = l_.next ()) + { + if (t.punctuation () == sql_token::p_rparen) + break; + + if (t.type () == sql_token::t_eos) + { + m_ = "missing ')' in SQLite type declaration"; + return false; + } + } + + return true; + } + + private: + sql_type + error (string const& m) + { + if (ct_ == 0) + return sql_type (); + else + throw invalid_sql_type (m); + } + + bool + find (string const& str) const + { + for (identifiers::const_iterator i (ids_.begin ()); + i != ids_.end (); ++i) + { + if (i->find (str) != string::npos) + return true; + } + + return false; + } + + private: + custom_db_types const* ct_; + sql_lexer l_; + string m_; // Error message. + + typedef vector<string> identifiers; + identifiers ids_; + }; + } + + 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 (); + } + } + } + + sql_type context:: + parse_sql_type (string const& sqlt, custom_db_types const* ct) + { + sql_parser p (ct); + return p.parse (sqlt); + } + } +} diff --git a/odb/odb/relational/sqlite/context.hxx b/odb/odb/relational/sqlite/context.hxx new file mode 100644 index 0000000..777998b --- /dev/null +++ b/odb/odb/relational/sqlite/context.hxx @@ -0,0 +1,146 @@ +// file : odb/relational/sqlite/context.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_RELATIONAL_SQLITE_CONTEXT_HXX +#define ODB_RELATIONAL_SQLITE_CONTEXT_HXX + +#include <map> + +#include <odb/relational/context.hxx> + +namespace relational +{ + namespace sqlite + { + struct sql_type + { + // Keep the order in each block of types. + // + enum core_type + { + INTEGER, + REAL, + TEXT, + BLOB, + invalid + }; + + sql_type (): type (invalid), stream (false) {} + + core_type type; + bool stream; // TEXT or BLOB via sqlite3_blob_open(). + + // Conversion expressions for custom database types. + // + std::string to; + std::string from; + }; + + class context: public virtual relational::context + { + public: + sql_type const& + parse_sql_type (string const&, + semantics::data_member&, + bool custom = true); + public: + struct invalid_sql_type + { + invalid_sql_type (string const& message): message_ (message) {} + + string const& + message () const {return message_;} + + private: + string message_; + }; + + // If custom_db_types is NULL, then this function returns + // invalid type instead of throwing in case an unknown type + // is encountered. + // + static sql_type + parse_sql_type (string const&, custom_db_types const* = 0); + + protected: + virtual string const& + convert_expr (string const&, semantics::data_member&, bool); + + virtual bool + grow_impl (semantics::class_&, user_section*); + + virtual bool + grow_impl (semantics::data_member&); + + virtual bool + grow_impl (semantics::data_member&, + semantics::type&, + const custom_cxx_type*, + string const&); + + protected: + virtual string + database_type_impl (semantics::type&, semantics::names*, bool, bool*); + + public: + virtual + ~context (); + context (); + context (std::ostream&, + semantics::unit&, + options_type const&, + features_type& f, + sema_rel::model*); + + static context& + current () + { + return *current_; + } + + private: + static context* current_; + + private: + struct data: base_context::data + { + data (std::ostream& os): base_context::data (os) {} + + struct sql_type_cache_entry + { + sql_type_cache_entry () + : custom_cached (false), straight_cached (false) {} + + sql_type const& + cache_custom (sql_type const& t) + { + custom = t; + custom_cached = true; + return custom; + } + + sql_type const& + cache_straight (sql_type const& t) + { + straight = t; + straight_cached = true; + return straight; + } + + sql_type custom; // With custom mapping. + sql_type straight; // Without custom mapping. + + bool custom_cached; + bool straight_cached; + }; + + typedef std::map<string, sql_type_cache_entry> sql_type_cache; + sql_type_cache sql_type_cache_; + }; + + data* data_; + }; + } +} + +#endif // ODB_RELATIONAL_SQLITE_CONTEXT_HXX diff --git a/odb/odb/relational/sqlite/header.cxx b/odb/odb/relational/sqlite/header.cxx new file mode 100644 index 0000000..1aafe7a --- /dev/null +++ b/odb/odb/relational/sqlite/header.cxx @@ -0,0 +1,63 @@ +// file : odb/relational/sqlite/header.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <odb/relational/header.hxx> + +#include <odb/relational/sqlite/common.hxx> +#include <odb/relational/sqlite/context.hxx> + +namespace relational +{ + namespace sqlite + { + namespace header + { + namespace relational = relational::header; + + struct image_member: relational::image_member_impl<sql_type>, + member_base + { + image_member (base const& x) + : member_base::base (x), // virtual base + member_base::base_impl (x), // virtual base + base_impl (x), + member_base (x) {} + + virtual void + traverse_integer (member_info& mi) + { + os << image_type << " " << mi.var << "value;" + << "bool " << mi.var << "null;" + << endl; + } + + virtual void + traverse_real (member_info& mi) + { + os << image_type << " " << mi.var << "value;" + << "bool " << mi.var << "null;" + << endl; + } + + virtual void + traverse_string (member_info& mi) + { + os << image_type << " " << mi.var << "value;" + << "std::size_t " << mi.var << "size;" + << "bool " << mi.var << "null;" + << endl; + } + + virtual void + traverse_stream (member_info& mi) + { + os << image_type << " " << mi.var << "value;" + << "std::size_t " << mi.var << "size;" + << "bool " << mi.var << "null;" + << endl; + } + }; + entry<image_member> image_member_; + } + } +} diff --git a/odb/odb/relational/sqlite/inline.cxx b/odb/odb/relational/sqlite/inline.cxx new file mode 100644 index 0000000..dd3274f --- /dev/null +++ b/odb/odb/relational/sqlite/inline.cxx @@ -0,0 +1,42 @@ +// file : odb/relational/sqlite/inline.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <odb/relational/inline.hxx> + +#include <odb/relational/sqlite/common.hxx> +#include <odb/relational/sqlite/context.hxx> + +using namespace std; + +namespace relational +{ + namespace sqlite + { + namespace inline_ + { + namespace relational = relational::inline_; + + struct null_member: relational::null_member_impl<sql_type>, + member_base + { + null_member (base const& x) + : member_base::base (x), // virtual base + member_base::base_impl (x), // virtual base + base_impl (x), + member_base (x) + { + } + + virtual void + traverse_simple (member_info& mi) + { + if (get_) + os << "r = r && i." << mi.var << "null;"; + else + os << "i." << mi.var << "null = true;"; + } + }; + entry<null_member> null_member_; + } + } +} diff --git a/odb/odb/relational/sqlite/model.cxx b/odb/odb/relational/sqlite/model.cxx new file mode 100644 index 0000000..da16ded --- /dev/null +++ b/odb/odb/relational/sqlite/model.cxx @@ -0,0 +1,91 @@ +// file : odb/relational/sqlite/model.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <sstream> + +#include <odb/relational/model.hxx> + +#include <odb/relational/sqlite/common.hxx> +#include <odb/relational/sqlite/context.hxx> + +using namespace std; + +namespace relational +{ + namespace sqlite + { + namespace model + { + namespace relational = relational::model; + + struct object_columns: relational::object_columns, context + { + object_columns (base const& x): base (x) {} + + virtual string + type (semantics::data_member& m) + { + // Translate BLOB|TEXT STREAM to just BLOB|TEXT. + // + string r (relational::object_columns::type (m)); + + sql_type const& t (parse_sql_type (r, m, false)); + if (t.stream) + { + switch (t.type) + { + case sql_type::BLOB: r = "BLOB"; break; + case sql_type::TEXT: r = "TEXT"; break; + default: break; + } + } + + return r; + } + + virtual bool + null (semantics::data_member& m) + { + return options.sqlite_override_null () || base::null (m); + } + + virtual string + default_enum (semantics::data_member& m, tree en, string const&) + { + // Make sure the column is mapped to INTEGER. + // + sql_type const& t (parse_sql_type (column_type (), m, false)); + if (t.type != sql_type::INTEGER) + { + cerr << m.file () << ":" << m.line () << ":" << m.column () + << ": error: column with default value specified as C++ " + << "enumerator must map to SQLite INTEGER" << endl; + + throw operation_failed (); + } + + using semantics::enumerator; + + enumerator& e (dynamic_cast<enumerator&> (*unit.find (en))); + + ostringstream ostr; + + if (e.enum_ ().unsigned_ ()) + ostr << e.value (); + else + ostr << static_cast<long long> (e.value ()); + + return ostr.str (); + } + + virtual void + primary_key (sema_rel::primary_key& pk) + { + if (pk.auto_ () && options.sqlite_lax_auto_id ()) + pk.extra ()["lax"] = "true"; + } + }; + entry<object_columns> object_columns_; + } + } +} diff --git a/odb/odb/relational/sqlite/schema.cxx b/odb/odb/relational/sqlite/schema.cxx new file mode 100644 index 0000000..f5549b4 --- /dev/null +++ b/odb/odb/relational/sqlite/schema.cxx @@ -0,0 +1,455 @@ +// file : odb/relational/sqlite/schema.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <odb/relational/schema.hxx> + +#include <odb/relational/sqlite/common.hxx> +#include <odb/relational/sqlite/context.hxx> + +namespace relational +{ + namespace sqlite + { + namespace schema + { + namespace relational = relational::schema; + + // + // Drop. + // + + struct drop_column: trav_rel::drop_column, relational::common + { + drop_column (relational::common const& c) + : relational::common (c), first_ (true) {} + + virtual void + traverse (sema_rel::drop_column& dc) + { + // SQLite does not support dropping columns. If this column is + // not NULLable, then there is nothing we can do. Otherwise, do + // a logical DROP by setting all the values to NULL. + // + sema_rel::column& c (find<sema_rel::column> (dc)); + + if (!c.null ()) + { + cerr << "error: SQLite does not support dropping of columns" << + endl; + cerr << "info: first dropped column is '" << dc.name () << + "' in table '" << dc.table ().name () << "'" << endl; + cerr << "info: could have performed logical drop if the column " << + "allowed NULL values" << endl; + throw operation_failed (); + } + + if (first_) + first_ = false; + else + os << "," << endl + << " "; + + os << quote_id (dc.name ()) << " = NULL"; + } + + private: + bool first_; + }; + // Not registered as an override. + + struct drop_index: relational::drop_index, context + { + drop_index (base const& x): base (x) {} + + virtual string + name (sema_rel::index& in) + { + // In SQLite, index names can be qualified with the database. + // + sema_rel::table& t (static_cast<sema_rel::table&> (in.scope ())); + sema_rel::qname n (t.name ().qualifier ()); + n.append (in.name ()); + return quote_id (n); + } + }; + entry<drop_index> drop_index_; + + struct drop_table: relational::drop_table, context + { + drop_table (base const& x): base (x) {} + + virtual void + traverse (sema_rel::table& t, bool migration) + { + // In SQLite there is no way to drop foreign keys except as part + // of the table. + // + if (pass_ != 2) + return; + + // Polymorphic base cleanup code. Because we cannot drop foreign + // keys, we will trigger cascade deletion. The only way to work + // around this problem is to delete from the root table and rely + // on the cascade to clean up the rest. + // + if (migration && t.extra ()["kind"] == "polymorphic derived object") + { + using sema_rel::model; + using sema_rel::table; + using sema_rel::primary_key; + using sema_rel::foreign_key; + + model& m (dynamic_cast<model&> (t.scope ())); + + table* p (&t); + do + { + // The polymorphic link is the first primary key. + // + for (table::names_iterator i (p->names_begin ()); + i != p->names_end (); ++i) + { + if (foreign_key* fk = dynamic_cast<foreign_key*> ( + &i->nameable ())) + { + p = m.find<table> (fk->referenced_table ()); + assert (p != 0); // Base table should be there. + break; + } + } + } + while (p->extra ()["kind"] != "polymorphic root object"); + + primary_key& rkey (*p->find<primary_key> ("")); + primary_key& dkey (*t.find<primary_key> ("")); + assert (rkey.contains_size () == dkey.contains_size ()); + delete_ (p->name (), t.name (), rkey, dkey); + } + + drop (t, migration); + } + }; + entry<drop_table> drop_table_; + + // + // Create. + // + + struct create_column: relational::create_column, context + { + create_column (base const& x): base (x) {} + + virtual void + traverse (sema_rel::add_column& ac) + { + using sema_rel::alter_table; + using sema_rel::add_column; + using sema_rel::add_foreign_key; + + alter_table& at (static_cast<alter_table&> (ac.scope ())); + + pre_statement (); + + os << "ALTER TABLE " << quote_id (at.name ()) << endl + << " ADD COLUMN "; + + // In SQLite it is impossible to alter a column later, so unless + // it has a default value, we add it as NULL. Without this, it + // will be impossible to add a column to a table that contains + // some rows. + // + create (ac); + + // SQLite doesn't support adding foreign keys other than inline + // via a column definition. See if we can handle any. + // + add_foreign_key* afk (0); + + for (add_column::contained_iterator i (ac.contained_begin ()); + i != ac.contained_end (); + ++i) + { + if ((afk = dynamic_cast<add_foreign_key*> (&i->key ()))) + { + // Check that it is a single-column foreign key. Also make + // sure the column and foreign key are from the same changeset. + // + if (afk->contains_size () != 1 || &ac.scope () != &afk->scope ()) + afk = 0; + else + break; + } + } + + if (afk != 0) + { + os << " CONSTRAINT " << quote_id (afk->name ()) << " REFERENCES " << + quote_id (afk->referenced_table ().uname ()) << " (" << + quote_id (afk->referenced_columns ()[0]) << ")"; + + bool del (afk->on_delete () != sema_rel::foreign_key::no_action); + bool def (!afk->not_deferrable ()); + + if (del || def) + { + instance<relational::create_foreign_key> cfk (*this); + + if (del) + cfk->on_delete (afk->on_delete ()); + + if (def) + cfk->deferrable (afk->deferrable ()); + } + + afk->set ("sqlite-fk-defined", true); // Mark it as defined. + } + + os << endl; + post_statement (); + } + + virtual void + auto_ (sema_rel::primary_key& pk) + { + if (pk.extra ().count ("lax")) + os << " /*AUTOINCREMENT*/"; + else + os << " AUTOINCREMENT"; + } + }; + entry<create_column> create_column_; + + struct create_foreign_key: relational::create_foreign_key, context + { + create_foreign_key (base const& x): base (x) {} + + virtual void + traverse (sema_rel::foreign_key& fk) + { + // In SQLite, all constraints are defined as part of a table. + // + os << "," << endl + << " CONSTRAINT "; + + create (fk); + } + + virtual string + table_name (sema_rel::foreign_key& fk) + { + // In SQLite, the referenced table cannot be qualified with the + // database name (it has to be in the same database anyway). + // + return quote_id (fk.referenced_table ().uname ()); + } + }; + entry<create_foreign_key> create_foreign_key_; + + struct create_index: relational::create_index, context + { + create_index (base const& x): base (x) {} + + virtual string + name (sema_rel::index& in) + { + // In SQLite, index names can be qualified with the database. + // + sema_rel::table& t (static_cast<sema_rel::table&> (in.scope ())); + sema_rel::qname n (t.name ().qualifier ()); + n.append (in.name ()); + return quote_id (n); + } + + virtual string + table_name (sema_rel::index& in) + { + // In SQLite, the index table cannot be qualified with the + // database name (it has to be in the same database). + // + return quote_id ( + static_cast<sema_rel::table&> (in.scope ()).name ().uname ()); + } + }; + entry<create_index> create_index_; + + struct create_table: relational::create_table, context + { + create_table (base const& x): base (x) {} + + void + traverse (sema_rel::table& t) + { + // For SQLite we do everything in a single pass since there + // is no way to add constraints later. + // + if (pass_ == 1) + create (t); + } + }; + entry<create_table> create_table_; + + // + // Alter. + // + + struct alter_table_pre: relational::alter_table_pre, context + { + alter_table_pre (base const& x): base (x) {} + + virtual void + alter (sema_rel::alter_table& at) + { + // SQLite can only add a single column per ALTER TABLE statement. + // + instance<create_column> cc (*this); + trav_rel::unames n (*cc); + names (at, n); + + // SQLite does not support altering columns. + // + if (sema_rel::alter_column* ac = check<sema_rel::alter_column> (at)) + { + cerr << "error: SQLite does not support altering of columns" + << endl; + cerr << "info: first altered column is '" << ac->name () << + "' in table '" << at.name () << "'" << endl; + throw operation_failed (); + } + + // SQLite does not support dropping constraints. We are going to + // ignore this if the column is NULL'able since in most cases + // the constraint is going to be dropped as a result of the + // column drop (e.g., an object pointer member got deleted). + // If we were not to allow this, then it would be impossible + // to do logical drop for pointer columns. + // + for (sema_rel::alter_table::names_iterator i (at.names_begin ()); + i != at.names_end (); ++i) + { + using sema_rel::foreign_key; + using sema_rel::drop_foreign_key; + + drop_foreign_key* dfk ( + dynamic_cast<drop_foreign_key*> (&i->nameable ())); + + if (dfk == 0) + continue; + + foreign_key& fk (find<foreign_key> (*dfk)); + + for (foreign_key::contains_iterator j (fk.contains_begin ()); + j != fk.contains_end (); ++j) + { + if (j->column ().null ()) + continue; + + cerr << "error: SQLite does not support dropping of foreign " << + "keys" << endl; + cerr << "info: first dropped foreign key is '" << dfk->name () << + "' in table '" << at.name () << "'" << endl; + cerr << "info: could have ignored it if the contained " << + "column(s) allowed NULL values" << endl; + throw operation_failed (); + } + } + } + }; + entry<alter_table_pre> alter_table_pre_; + + struct alter_table_post: relational::alter_table_post, context + { + alter_table_post (base const& x): base (x) {} + + virtual void + alter (sema_rel::alter_table& at) + { + // SQLite does not support altering columns (we have to do this + // in both alter_table_pre/post because of the + // check_alter_column_null() test in the common code). + // + if (sema_rel::alter_column* ac = check<sema_rel::alter_column> (at)) + { + cerr << "error: SQLite does not support altering of columns" + << endl; + cerr << "info: first altered column is '" << ac->name () << + "' in table '" << at.name () << "'" << endl; + throw operation_failed (); + } + + // Try to do logical column drop. + // + if (check<sema_rel::drop_column> (at)) + { + pre_statement (); + + os << "UPDATE " << quote_id (at.name ()) << endl + << " SET "; + + drop_column dc (*this); + trav_rel::unames n (dc); + names (at, n); + os << endl; + + post_statement (); + } + + // SQLite doesn't support adding foreign keys other than inline + // via a column definition. See if there are any that we couldn't + // handle that way. + // + for (sema_rel::alter_table::names_iterator i (at.names_begin ()); + i != at.names_end (); ++i) + { + sema_rel::add_foreign_key* afk ( + dynamic_cast<sema_rel::add_foreign_key*> (&i->nameable ())); + + if (afk == 0 || afk->count ("sqlite-fk-defined")) + continue; + + cerr << "error: SQLite does not support adding foreign keys" + << endl; + cerr << "info: first added foreign key is '" << afk->name () << + "' in table '" << at.name () << "'" << endl; + throw operation_failed (); + } + } + }; + entry<alter_table_post> alter_table_post_; + + // + // Schema version table. + // + + struct version_table: relational::version_table, context + { + version_table (base const& x): base (x) {} + + virtual void + create_table () + { + pre_statement (); + + os << "CREATE TABLE IF NOT EXISTS " << qt_ << " (" << endl + << " " << qn_ << " TEXT NOT NULL PRIMARY KEY," << endl + << " " << qv_ << " INTEGER NOT NULL," << endl + << " " << qm_ << " INTEGER NOT NULL)" << endl; + + post_statement (); + } + + virtual void + create (sema_rel::version v) + { + pre_statement (); + + os << "INSERT OR IGNORE INTO " << qt_ << " (" << endl + << " " << qn_ << ", " << qv_ << ", " << qm_ << ")" << endl + << " VALUES (" << qs_ << ", " << v << ", 0)" << endl; + + post_statement (); + } + }; + entry<version_table> version_table_; + } + } +} diff --git a/odb/odb/relational/sqlite/source.cxx b/odb/odb/relational/sqlite/source.cxx new file mode 100644 index 0000000..5a4b9d3 --- /dev/null +++ b/odb/odb/relational/sqlite/source.cxx @@ -0,0 +1,471 @@ +// file : odb/relational/sqlite/source.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <odb/relational/source.hxx> + +#include <odb/relational/sqlite/common.hxx> +#include <odb/relational/sqlite/context.hxx> + +using namespace std; + +namespace relational +{ + namespace sqlite + { + namespace source + { + namespace relational = relational::source; + + struct query_parameters: relational::query_parameters, context + { + query_parameters (base const& x): base (x) {} + + virtual string + next (semantics::data_member& m, + const string& column, + const string& sqlt) + { + // Handle stream columns. Specifically, we somehow need to + // pass the column name to the code that runs in the + // statement. So what we are going to do is encode it + // in the parameter name. + // + if (sk_ == statement_insert || sk_ == statement_update) + { + const sql_type& t (parse_sql_type (sqlt, m, false)); + if (t.stream) + { + // The column name is quoted. + // + string r (column); + r[0] = '$'; // Replace leading '"'. + r.resize (r.size () - 1); // Remove trailing '"'. + + // Verify it only contains allowed characters. + // + for (size_t i (1); i != r.size (); ++i) + { + char c (r[i]); + if (c != '_' && + (c < '0' || c > '9') && + (c < 'a' || c > 'z') && + (c < 'A' || c > 'Z')) + { + cerr << m.file () << ":" << m.line () << ":" << m.column () + << ": error: unsupported character '" << c << "' in " + << sqlt << " column name " << column << endl; + + cerr << m.file () << ":" << m.line () << ":" << m.column () + << ": info: STREAM column can contain alpha-numeric " + << "characters plus '_'" << endl; + + throw operation_failed (); + } + } + + // For TEXT columns, since we use the *_bind_zeroblob() + // function (there is no *_bind_zerotext()), the value + // that will be stored is BLOB, not TEXT, unless we + // explicitly CAST it. The user better make sure the + // encoding of raw TEXT data they are going to write + // matches the database encoding. + // + if (t.type == sql_type::TEXT) + r = "CAST(" + r + " AS TEXT)"; + + return r; + } + } + + return "?"; + } + }; + entry<query_parameters> query_parameters_; + + // + // bind + // + + struct bind_member: relational::bind_member_impl<sql_type>, + member_base + { + bind_member (base const& x) + : member_base::base (x), // virtual base + member_base::base_impl (x), // virtual base + base_impl (x), + member_base (x) + { + } + + virtual void + traverse_integer (member_info& mi) + { + os << b << ".type = sqlite::bind::integer;" + << b << ".buffer = &" << arg << "." << mi.var << "value;" + << b << ".is_null = &" << arg << "." << mi.var << "null;"; + } + + virtual void + traverse_real (member_info& mi) + { + os << b << ".type = sqlite::bind::real;" + << b << ".buffer = &" << arg << "." << mi.var << "value;" + << b << ".is_null = &" << arg << "." << mi.var << "null;"; + } + + virtual void + traverse_text (member_info& mi) + { + os << b << ".type = sqlite::image_traits<" << endl + << " " << mi.fq_type () << "," << endl + << " sqlite::id_text>::bind_value;" + << b << ".buffer = " << arg << "." << mi.var << "value.data ();" + << b << ".size = &" << arg << "." << mi.var << "size;" + << b << ".capacity = " << arg << "." << mi.var << + "value.capacity ();" + << b << ".is_null = &" << arg << "." << mi.var << "null;"; + } + + virtual void + traverse_blob (member_info& mi) + { + os << b << ".type = sqlite::bind::blob;" + << b << ".buffer = " << arg << "." << mi.var << "value.data ();" + << b << ".size = &" << arg << "." << mi.var << "size;" + << b << ".capacity = " << arg << "." << mi.var << + "value.capacity ();" + << b << ".is_null = &" << arg << "." << mi.var << "null;"; + } + + virtual void + traverse_stream (member_info& mi) + { + os << b << ".type = sqlite::bind::stream;" + << b << ".buffer = &" << arg << "." << mi.var << "value;" + << b << ".size = &" << arg << "." << mi.var << "size;" + << b << ".is_null = &" << arg << "." << mi.var << "null;"; + } + }; + entry<bind_member> bind_member_; + + // + // grow + // + + struct grow_member: relational::grow_member_impl<sql_type>, + member_base + { + grow_member (base const& x) + : member_base::base (x), // virtual base + member_base::base_impl (x), // virtual base + base_impl (x), + member_base (x) {} + + virtual void + traverse_integer (member_info&) + { + os << e << " = false;" + << endl; + } + + virtual void + traverse_real (member_info&) + { + os << e << " = false;" + << endl; + } + + virtual void + traverse_string (member_info& mi) + { + os << "if (" << e << ")" << endl + << "{" + << "i." << mi.var << "value.capacity (i." << mi.var << "size);" + << "grew = true;" + << "}"; + } + + virtual void + traverse_stream (member_info&) + { + os << e << " = false;" + << endl; + } + }; + entry<grow_member> grow_member_; + + // + // init image + // + + struct init_image_member: relational::init_image_member_impl<sql_type>, + member_base + { + init_image_member (base const& x) + : member_base::base (x), // virtual base + member_base::base_impl (x), // virtual base + base_impl (x), + member_base (x) + { + } + + virtual void + set_null (member_info& mi) + { + os << "i." << mi.var << "null = true;"; + } + + virtual void + traverse_integer (member_info& mi) + { + os << traits << "::set_image (" << endl + << "i." << mi.var << "value," << endl + << "is_null," << endl + << member << ");" + << "i." << mi.var << "null = is_null;"; + } + + virtual void + traverse_real (member_info& mi) + { + os << traits << "::set_image (" << endl + << "i." << mi.var << "value," << endl + << "is_null," << endl + << member << ");" + << "i." << mi.var << "null = is_null;"; + } + + virtual void + traverse_string (member_info& mi) + { + os << "std::size_t cap (i." << mi.var << "value.capacity ());" + << traits << "::set_image (" << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "size," << endl + << "is_null," << endl + << member << ");" + << "i." << mi.var << "null = is_null;" + << "grew = grew || (cap != i." << mi.var << "value.capacity ());"; + } + + virtual void + traverse_stream (member_info& mi) + { + os << traits << "::set_image (" << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "size," << endl + << "is_null," << endl + << member << ");" + << "i." << mi.var << "null = is_null;"; + } + }; + entry<init_image_member> init_image_member_; + + // + // init value + // + + struct init_value_member: relational::init_value_member_impl<sql_type>, + member_base + { + init_value_member (base const& x) + : member_base::base (x), // virtual base + member_base::base_impl (x), // virtual base + base_impl (x), + member_base (x) + { + } + + virtual void + get_null (string const& var) const + { + os << "i." << var << "null"; + } + + virtual void + traverse_integer (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "null);" + << endl; + } + + virtual void + traverse_real (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "null);" + << endl; + } + + virtual void + traverse_string (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "size," << endl + << "i." << mi.var << "null);" + << endl; + } + + virtual void + traverse_stream (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "size," << endl + << "i." << mi.var << "null);" + << endl; + } + }; + entry<init_value_member> init_value_member_; + + struct statement_columns_common: context + { + void + process (relational::statement_columns& cs, statement_kind sk) + { + using relational::statement_columns; + + // For SELECT statements, add _ROWID_ "follow-up" column to + // each stream column. The reason we need both, and not just + // ROWID is the NULL value. Let's hope that SELECT'ing a BLOB + // but not actually reading it with sqlite3_result_blob() is + // as fast as not SELECT'ing it. + // + if (sk != statement_select) + return; + + for (statement_columns::iterator i (cs.begin ()); + i != cs.end (); ++i) + { + if (parse_sql_type (i->type, *i->member).stream) + { + // Column is already table-qualified and quoted. Do some + // surgery to replace it with _ROWID_. That is, we want to + // transform "table"."column" to "table"."_ROWID_". + // + string c (i->column); + string::size_type n (c.size ()), p (c.rfind ('"', n - 2)); + assert (p != string::npos); + string as (c, p + 1, n - p - 2); + c.resize (p); + c += "\"_ROWID_\""; + + // We are going to pack this "tightly", without any newlines, + // so that the statement processing code treats them as a + // single column. + // + i->column += ','; + i->column += c; + } + } + } + }; + + struct container_traits: relational::container_traits, + statement_columns_common + { + container_traits (base const& x): base (x) {} + + virtual void + cache_result (string const&) + { + // Caching is not necessary since SQLite can execute several + // interleaving statements. + // + } + + virtual void + process_statement_columns (relational::statement_columns& cols, + statement_kind sk, + bool) + { + statement_columns_common::process (cols, sk); + } + }; + entry<container_traits> container_traits_; + + struct section_traits: relational::section_traits, + statement_columns_common + { + section_traits (base const& x): base (x) {} + + virtual void + process_statement_columns (relational::statement_columns& cols, + statement_kind sk, + bool) + { + statement_columns_common::process (cols, sk); + } + }; + entry<section_traits> section_traits_; + + struct class_: relational::class_, statement_columns_common + { + class_ (base const& x): base (x) {} + + virtual void + init_auto_id (semantics::data_member& m, string const& im) + { + // Don't set the id value to NULL if this is a nullable wrapper. + // This will allow the user to control whether the value is auto or + // manually assigned by using something like this: + // + // #pragma db auto + // odb::nullable<int64_t> id; + // + semantics::type& t (utype (m)); + if (wrapper (t) && t.template get<bool> ("wrapper-null-handler")) + return; + + os << im << "null = true;" + << endl; + } + + virtual string + select_trailer (type&) + { + // SQLite has not support for FOR UPDATE and since this is an + // optimization, we simply ignore it. + // + return ""; + } + + virtual string + join_syntax (view_object const& vo) + { + const char* n (0); + + if (vo.join == view_object::full) + n = "FULL OUTER JOIN"; + else if (vo.join == view_object::right) + n = "RIGHT OUTER JOIN"; + + if (n != 0) + { + error (vo.loc) << n << " is not supported by SQLite" << endl; + throw operation_failed (); + } + + return base::join_syntax (vo); + } + + virtual void + process_statement_columns (relational::statement_columns& cols, + statement_kind sk, + bool) + { + statement_columns_common::process (cols, sk); + } + }; + entry<class_> class_entry_; + } + } +} diff --git a/odb/odb/relational/validator.cxx b/odb/odb/relational/validator.cxx new file mode 100644 index 0000000..50c887e --- /dev/null +++ b/odb/odb/relational/validator.cxx @@ -0,0 +1,638 @@ +// file : odb/relational/validator.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <map> +#include <iostream> + +#include <odb/diagnostics.hxx> +#include <odb/traversal.hxx> +#include <odb/relational/common.hxx> +#include <odb/relational/context.hxx> +#include <odb/relational/validator.hxx> + +using namespace std; + +namespace relational +{ + namespace + { + // + // Pass 2. + // + + struct data_member2: traversal::data_member, context + { + data_member2 (bool& valid) + : valid_ (valid) + { + } + + virtual void + traverse (type& m) + { + if (transient (m)) + return; + + if (null (m)) + { + if (semantics::class_* c = composite_wrapper (utype (m))) + { + if (has_a (*c, test_container)) + { + os << m.file () << ":" << m.line () << ":" << m.column () << ":" + << " error: composite member containing containers cannot " + << "be null" << endl; + + os << c->file () << ":" << c->line () << ":" << c->column () + << ": info: composite value type is defined here" << endl; + + valid_ = false; + } + } + } + + // Check on-delete. + // + if (m.count ("on-delete")) + { + const char* kp (container (m) ? "value" : ""); + location l (m.location ()); + + // Make sure it is a pointer or a member with points_to pragma. + // + if (!object_pointer (utype (m, kp)) && !points_to (m)) + { + error (l) << "on_delete specified for non-object pointer" << endl; + valid_ = false; + } + + // Make sure it is not inverse. + // + if (inverse (m, kp)) + { + error (l) << "on_delete specified for inverse object " << + "pointer" << endl; + valid_ = false; + } + + // Make sure the pointer is nullable if asked to set it to NULL. + // + using sema_rel::foreign_key; + + if (m.get<foreign_key::action_type> ("on-delete") == + foreign_key::set_null && + !null (m, kp)) + { + error (l) << "set_null specified for non-nullable object " + "pointer" << endl; + valid_ = false; + } + } + } + + bool& valid_; + }; + + struct object_no_id_members: object_members_base + { + object_no_id_members (bool& valid) + : object_members_base (false, false, true), valid_ (valid), dm_ (0) + { + } + + virtual void + traverse_pointer (semantics::data_member& m, semantics::class_&) + { + if (inverse (m)) + { + semantics::data_member& dm (dm_ != 0 ? *dm_ : m); + + os << dm.file () << ":" << dm.line () << ":" << dm.column () << ":" + << " error: inverse object pointer member '" << member_prefix_ + << m.name () << "' in an object without an object id" << endl; + + valid_ = false; + } + } + + virtual void + traverse_container (semantics::data_member& m, semantics::type&) + { + semantics::data_member& dm (dm_ != 0 ? *dm_ : m); + + os << dm.file () << ":" << dm.line () << ":" << dm.column () << ":" + << " error: container member '" << member_prefix_ << m.name () + << "' in an object without an object id" << endl; + + valid_ = false; + } + + virtual void + traverse_composite (semantics::data_member* m, semantics::class_& c) + { + semantics::data_member* old_dm (dm_); + + if (dm_ == 0) + dm_ = m; + + object_members_base::traverse_composite (m, c); + + dm_ = old_dm; + } + + private: + bool& valid_; + semantics::data_member* dm_; // Direct object data member. + }; + + struct composite_id_members: object_members_base + { + composite_id_members (bool& valid) + : object_members_base (false, false, true), valid_ (valid), dm_ (0) + { + } + + virtual void + traverse_pointer (semantics::data_member& m, semantics::class_&) + { + semantics::data_member& dm (dm_ != 0 ? *dm_ : m); + + os << dm.file () << ":" << dm.line () << ":" << dm.column () << ":" + << " error: object pointer member '" << member_prefix_ << m.name () + << "' in a composite value type that is used as an object id" + << endl; + + valid_ = false; + } + + virtual void + traverse_simple (semantics::data_member& m) + { + if (readonly (member_path_, member_scope_)) + { + semantics::data_member& dm (dm_ != 0 ? *dm_ : m); + + os << dm.file () << ":" << dm.line () << ":" << dm.column () << ":" + << " error: readonly member '" << member_prefix_ << m.name () + << "' in a composite value type that is used as an object id" + << endl; + + valid_ = false; + } + } + + virtual void + traverse_container (semantics::data_member& m, semantics::type&) + { + semantics::data_member& dm (dm_ != 0 ? *dm_ : m); + + os << dm.file () << ":" << dm.line () << ":" << dm.column () << ":" + << " error: container member '" << member_prefix_ << m.name () + << "' in a composite value type that is used as an object id" + << endl; + + valid_ = false; + } + + virtual void + traverse_composite (semantics::data_member* m, semantics::class_& c) + { + semantics::data_member* old_dm (dm_); + + if (dm_ == 0) + dm_ = m; + + object_members_base::traverse_composite (m, c); + + dm_ = old_dm; + } + + private: + bool& valid_; + semantics::data_member* dm_; // Direct composite member. + }; + + struct view_members: object_members_base + { + view_members (bool& valid) + : object_members_base (false, false, true), valid_ (valid), dm_ (0) + { + } + + virtual void + traverse_pointer (semantics::data_member& m, semantics::class_&) + { + if (dm_ != 0 && object_pointer (utype (m))) + { + location const& l (dm_->location ()); + + error (l) << "nested view data member '" << member_prefix_ + << m.name () << "' is an object pointer" << endl; + info (l) << "views can only contain direct object pointer members" + << endl; + + valid_ = false; + } + } + + virtual void + traverse_container (semantics::data_member& m, semantics::type&) + { + semantics::data_member& dm (dm_ != 0 ? *dm_ : m); + + os << dm.file () << ":" << dm.line () << ":" << dm.column () << ":" + << " error: view data member '" << member_prefix_ << m.name () + << "' is a container" << endl; + + os << dm.file () << ":" << dm.line () << ":" << dm.column () << ":" + << ": info: views cannot contain containers" << endl; + + valid_ = false; + } + + virtual void + traverse_composite (semantics::data_member* m, semantics::class_& c) + { + semantics::data_member* old_dm (dm_); + + if (dm_ == 0) + dm_ = m; + + object_members_base::traverse_composite (m, c); + + dm_ = old_dm; + } + + private: + bool& valid_; + semantics::data_member* dm_; // Direct view data member. + }; + + struct class2: traversal::class_, context + { + class2 (bool& valid) + : valid_ (valid), + typedefs_ (true), + data_member_ (valid), + object_no_id_members_ (valid), + composite_id_members_ (valid), + view_members_ (valid) + { + *this >> defines_ >> *this; + *this >> typedefs_ >> *this; + + data_member_names_ >> data_member_; + } + + virtual void + traverse (type& c) + { + class_kind_type ck (class_kind (c)); + switch (ck) + { + case class_object: + names (c); + traverse_object (c); + break; + case class_view: + names (c); + traverse_view (c); + break; + case class_composite: + names (c); + traverse_composite (c); + break; + case class_other: + break; + } + + // Make sure indexes are not defined for anything other than objects. + // + if (c.count ("index") && ck != class_object) + { + indexes& ins (c.get<indexes> ("index")); + + for (indexes::iterator i (ins.begin ()); i != ins.end (); ++i) + { + error (i->loc) << "index definition on a non-persistent class" + << endl; + valid_ = false; + } + } + } + + virtual void + traverse_object (type& c) + { + data_member_path* id (id_member (c)); + + if (id != 0) + { + if (semantics::class_* cm = composite_wrapper (utype (*id))) + { + location idl (id->front ()->location ()); + + // Composite id cannot be auto. + // + if (auto_ (*id)) + { + error (idl) << "composite id cannot be automatically assigned" + << endl; + valid_ = false; + } + + // Make sure we don't have any containers or pointers in this + // composite value type. + // + if (valid_) + { + composite_id_members_.traverse (*cm); + + if (!valid_) + info (idl) << "composite id is defined here" << endl; + } + + // Check that the composite value type is default-constructible. + // + if (!cm->default_ctor ()) + { + os << cm->file () << ":" << cm->line () << ":" << cm->column () + << ": error: composite value type that is used as object id " + << "is not default-constructible" << endl; + + os << cm->file () << ":" << cm->line () << ":" << cm->column () + << ": info: provide default constructor for this value type" + << endl; + + info (idl) << "composite id is defined here" << endl; + + valid_ = false; + } + } + } + else + { + if (!abstract (c)) + { + // Make sure we don't have any containers or inverse pointers. + // + object_no_id_members_.traverse (c); + } + } + + names (c, data_member_names_); + + // Validate bulk operation support. + // + for (bool i (true); i && c.count ("bulk"); i = false) + { + location_t l (c.get<location_t> ("bulk-location")); + + if (polymorphic (c)) + { + error (l) << "bulk operations on polymorphic objects are " + "not supported" << endl; + valid_ = false; + break; + } + + if (has_a (c, test_straight_container)) + { + error (l) << "bulk operations on objects with containers are " + "not supported" << endl; + valid_ = false; + break; + } + + bool update (true); + + // Unless we only have manually-updated sections, we cannot generate + // the bulk update operation. + // + user_sections& uss (c.get<user_sections> ("user-sections")); + + for (user_sections::iterator i (uss.begin ()); + update && i != uss.end (); + ++i) + { + const user_section& s (*i); + + // Skip special sections. + // + if (s.special != user_section::special_ordinary) + continue; + + // Always-updated section still needs a separate statement + // (since it may not be loaded). + // + if (!s.update_empty () && s.update != user_section::update_manual) + update = false; + } + + c.set ("bulk-persist", true); + if (update) c.set ("bulk-update", true); + c.set ("bulk-erase", true); + } + + // Validate indexes. + // + { + indexes& ins (c.get<indexes> ("index")); + + // Make sure index members are not transient, inverse, or + // containers. + // + for (indexes::iterator i (ins.begin ()); i != ins.end (); ++i) + { + index& in (*i); + + for (index::members_type::iterator i (in.members.begin ()); + i != in.members.end (); ++i) + { + index::member& im (*i); + semantics::data_member& m (*im.path.back ()); + + if (transient (m)) + { + error (im.loc) << "index member is transient" << endl; + valid_ = false; + } + + if (inverse (m)) + { + error (im.loc) << "index member is an inverse object " << + "pointer" << endl; + valid_ = false; + } + + if (container (m)) + { + error (im.loc) << "index member is a container" << endl; + valid_ = false; + } + } + } + } + } + + virtual void + traverse_view (type& c) + { + const view_query& vq (c.get<view_query> ("query")); + + // Make sure we don't have any containers or object pointers. + // + view_members_.traverse (c); + + names (c, data_member_names_); + + // Allow certain kinds of empty views. + // + if (vq.kind != view_query::runtime && + vq.kind != view_query::complete_execute) + { + // Allow all the members to be deleted. + // + column_count_type const& cc (column_count (c)); + + if (cc.total == 0) + { + os << c.file () << ":" << c.line () << ":" << c.column () << ":" + << " error: no persistent data members in the class" << endl; + valid_ = false; + } + } + } + + virtual void + traverse_composite (type& c) + { + names (c, data_member_names_); + } + + public: + bool& valid_; + + traversal::defines defines_; + typedefs typedefs_; + + data_member2 data_member_; + traversal::names data_member_names_; + + object_no_id_members object_no_id_members_; + composite_id_members composite_id_members_; + view_members view_members_; + }; + } + + void + validate (options const&, + features&, + semantics::unit& u, + semantics::path const&, + unsigned short pass) + { + bool valid (true); + + // Validate custom type mapping. + // + if (pass == 1) + { + // Create an empty list if we don't have one. This makes the + // rest of the code simpler. + // + if (!u.count ("custom-db-types")) + u.set ("custom-db-types", custom_db_types ()); + + custom_db_types & cts (u.get<custom_db_types> ("custom-db-types")); + + for (custom_db_types::iterator i (cts.begin ()); i != cts.end (); ++i) + { + custom_db_type& ct (*i); + + if (ct.type.empty ()) + { + error (ct.loc) << "'type' clause expected in db pragma map" << endl; + valid = false; + } + + if (ct.as.empty ()) + { + error (ct.loc) << "'as' clause expected in db pragma map" << endl; + valid = false; + } + + if (ct.to.empty ()) + ct.to = "(?)"; + else + { + size_t p (ct.to.find ("(?)")); + + if (p == string::npos) + { + error (ct.loc) << "no '(?)' expression in the 'to' clause " + << "of db pragma map" << endl; + valid = false; + } + else if (ct.to.find ("(?)", p + 3) != string::npos) + { + error (ct.loc) << "multiple '(?)' expressions in the 'to' " + << "clause of db pragma map" << endl; + valid = false; + } + } + + if (ct.from.empty ()) + ct.from = "(?)"; + else + { + size_t p (ct.from.find ("(?)")); + + if (p == string::npos) + { + error (ct.loc) << "no '(?)' expression in the 'from' clause " + << "of db pragma map" << endl; + valid = false; + } + else if (ct.from.find ("(?)", p + 3) != string::npos) + { + error (ct.loc) << "multiple '(?)' expressions in the 'from' " + << "clause of db pragma map" << endl; + valid = false; + } + } + } + } + + if (!valid) + throw operation_failed (); + + if (pass == 1) + { + } + else + { + traversal::unit unit; + traversal::defines unit_defines; + typedefs unit_typedefs (true); + traversal::namespace_ ns; + class2 c (valid); + + unit >> unit_defines >> ns; + unit_defines >> c; + unit >> unit_typedefs >> c; + + traversal::defines ns_defines; + typedefs ns_typedefs (true); + + ns >> ns_defines >> ns; + ns_defines >> c; + ns >> ns_typedefs >> c; + + unit.dispatch (u); + } + + if (!valid) + throw operation_failed (); + } +} diff --git a/odb/odb/relational/validator.hxx b/odb/odb/relational/validator.hxx new file mode 100644 index 0000000..d6602f7 --- /dev/null +++ b/odb/odb/relational/validator.hxx @@ -0,0 +1,24 @@ +// file : odb/relational/validator.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_RELATIONAL_VALIDATOR_HXX +#define ODB_RELATIONAL_VALIDATOR_HXX + +#include <odb/options.hxx> +#include <odb/features.hxx> +#include <odb/semantics/unit.hxx> + +namespace relational +{ + // The first pass is performed before processing. The second -- after. + // Throws operation_failed to signal a failure. + // + void + validate (options const&, + features&, + semantics::unit&, + semantics::path const&, + unsigned short pass); +} + +#endif // ODB_RELATIONAL_VALIDATOR_HXX diff --git a/odb/odb/semantics.hxx b/odb/odb/semantics.hxx new file mode 100644 index 0000000..83416a6 --- /dev/null +++ b/odb/odb/semantics.hxx @@ -0,0 +1,19 @@ +// file : odb/semantics.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_SEMANTICS_HXX +#define ODB_SEMANTICS_HXX + +#include <odb/semantics/class.hxx> +#include <odb/semantics/class-template.hxx> +#include <odb/semantics/derived.hxx> +#include <odb/semantics/elements.hxx> +#include <odb/semantics/enum.hxx> +#include <odb/semantics/fundamental.hxx> +#include <odb/semantics/namespace.hxx> +#include <odb/semantics/template.hxx> +#include <odb/semantics/union.hxx> +#include <odb/semantics/union-template.hxx> +#include <odb/semantics/unit.hxx> + +#endif // ODB_SEMANTICS_HXX diff --git a/odb/odb/semantics/class-template.cxx b/odb/odb/semantics/class-template.cxx new file mode 100644 index 0000000..f8bbca4 --- /dev/null +++ b/odb/odb/semantics/class-template.cxx @@ -0,0 +1,54 @@ +// file : odb/semantics/class-template.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <libcutl/compiler/type-info.hxx> +#include <odb/semantics/class-template.hxx> + +namespace semantics +{ + class_template:: + class_template (path const& file, size_t line, size_t column, tree tn) + : node (file, line, column, tn) + { + } + + class_instantiation:: + class_instantiation (path const& file, + size_t line, + size_t column, + tree tn) + : node (file, line, column, tn) + { + } + + // type info + // + namespace + { + struct init + { + init () + { + using compiler::type_info; + + // class_template + // + { + type_info ti (typeid (class_template)); + ti.add_base (typeid (type_template)); + ti.add_base (typeid (scope)); + insert (ti); + } + + // class_instantiation + // + { + type_info ti (typeid (class_instantiation)); + ti.add_base (typeid (class_)); + ti.add_base (typeid (type_instantiation)); + insert (ti); + } + } + } init_; + } +} diff --git a/odb/odb/semantics/class-template.hxx b/odb/odb/semantics/class-template.hxx new file mode 100644 index 0000000..bffb3f2 --- /dev/null +++ b/odb/odb/semantics/class-template.hxx @@ -0,0 +1,68 @@ +// file : odb/semantics/class-template.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_SEMANTICS_CLASS_TEMPLATE_HXX +#define ODB_SEMANTICS_CLASS_TEMPLATE_HXX + +#include <odb/semantics/elements.hxx> +#include <odb/semantics/class.hxx> +#include <odb/semantics/template.hxx> + +namespace semantics +{ + class class_template: public type_template, public scope + { + private: + typedef std::vector<inherits*> inherits_list; + + public: + typedef pointer_iterator<inherits_list::const_iterator> inherits_iterator; + + inherits_iterator + inherits_begin () const + { + return inherits_.begin (); + } + + inherits_iterator + inherits_end () const + { + return inherits_.end (); + } + + public: + class_template (path const&, size_t line, size_t column, tree); + + void + add_edge_left (inherits& e) + { + inherits_.push_back (&e); + } + + void + add_edge_right (inherits&) + { + } + + using scope::add_edge_left; + using scope::add_edge_right; + + // Resolve conflict between scope::scope and nameable::scope. + // + using nameable::scope; + + private: + inherits_list inherits_; + }; + + class class_instantiation: public class_, public type_instantiation + { + public: + class_instantiation (path const&, size_t line, size_t column, tree); + + using class_::add_edge_left; + using type_instantiation::add_edge_left; + }; +} + +#endif // ODB_SEMANTICS_CLASS_TEMPLATE_HXX diff --git a/odb/odb/semantics/class.cxx b/odb/odb/semantics/class.cxx new file mode 100644 index 0000000..97cf088 --- /dev/null +++ b/odb/odb/semantics/class.cxx @@ -0,0 +1,175 @@ +// file : odb/semantics/class.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <odb/gcc.hxx> // TYPE_HAS_DEFAULT_CONSTRUCTOR + +#include <libcutl/compiler/type-info.hxx> +#include <odb/semantics/class.hxx> + +namespace semantics +{ + inherits:: + inherits (access_type access, bool virt) + : virt_ (virt), access_ (access) + { + } + + class_:: + class_ (path const& file, size_t line, size_t column, tree tn) + : node (file, line, column, tn) + { + } + + bool class_:: + default_ctor () const + { + tree t (tree_node ()); + + // TYPE_HAS_DEFAULT_CONSTRUCTOR() returns true if we have a deleted + // default ctor. locate_ctor(), on the other hand, returns NULL_TREE in + // this case. + // + if (TYPE_HAS_DEFAULT_CONSTRUCTOR (t)) + { +#if BUILDING_GCC_MAJOR >= 8 + + // Work around GCC bug 86441. Essentially, we should not trigger an + // instantiation or completion of the default ctor. As a result, we will + // assume that if we have a lazy default ctor, it is not implicitly + // deleted. + // + if (CLASSTYPE_LAZY_DEFAULT_CTOR (t)) + return true; + + for (ovl_iterator i (CLASSTYPE_CONSTRUCTORS (t)); i; ++i) + { + tree f (*i); + + if (TREE_CODE (f) == FUNCTION_DECL && DECL_DELETED_FN (f)) + continue; + + if (default_ctor_p (f)) + return true; + } +#else + return locate_ctor (t) != NULL_TREE; +#endif + + } + + return false; + } + + bool class_:: + complete () const + { + return COMPLETE_TYPE_P (tree_node ()); + } + + bool class_:: + abstract () const + { + return CLASSTYPE_PURE_VIRTUALS (tree_node ()); + } + + names* class_:: + lookup (string const& name, + type_id const& ti, + unsigned int flags, + bool* ph) const + { + bool h (false); + bool& rh (ph != 0 ? *ph : h); + + names* r (scope::lookup (name, ti, flags | exclude_outer, &rh)); + + if (r != 0) + return r; + + // If we found a name but the types didn't match, then bail out + // unless we want hidden names. + // + if (rh && (flags & include_hidden) == 0) + return 0; + + // Look in the base classes unless requested not to. For the name + // lookup purposes, bases can be viewed as a parallel set of outer + // scopes that are searched after the class scope and before any + // real outer scope. Interestingly, outer scopes of bases are not + // considered during this lookup, only their bases. + // + if ((flags & exclude_base) == 0) + { + // Being hidden in one base doesn't mean it is also hidden in the + // other. Normally that would be an ambiguous lookup, but we use + // relaxed rules. + // + bool any_h (false); // Indicates whether any base hides the name. + + for (inherits_iterator i (inherits_begin ()); i != inherits_end (); ++i) + { + bool h (false); // Indicates whether this base hides the name. + names* br (i->base ().lookup (name, ti, flags | exclude_outer, &h)); + any_h = any_h || h; + + if (br != 0) + { + if (r != 0) + throw ambiguous (*r, *br); + + r = br; + + if (h) + rh = true; + } + } + + if (r != 0) + return r; + + if (any_h) + { + rh = true; + if ((flags & include_hidden) == 0) + return 0; + } + } + + // Look in the outer scope unless requested not to. + // + if ((flags & exclude_outer) == 0) + return scope ().lookup (name, ti, flags, &rh); + + return 0; + } + + // type info + // + namespace + { + struct init + { + init () + { + using compiler::type_info; + + // inherits + // + { + type_info ti (typeid (inherits)); + ti.add_base (typeid (edge)); + insert (ti); + } + + // class_ + // + { + type_info ti (typeid (class_)); + ti.add_base (typeid (type)); + ti.add_base (typeid (scope)); + insert (ti); + } + } + } init_; + } +} diff --git a/odb/odb/semantics/class.hxx b/odb/odb/semantics/class.hxx new file mode 100644 index 0000000..e02337a --- /dev/null +++ b/odb/odb/semantics/class.hxx @@ -0,0 +1,142 @@ +// file : odb/semantics/class.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_SEMANTICS_CLASS_HXX +#define ODB_SEMANTICS_CLASS_HXX + +#include <vector> +#include <odb/semantics/elements.hxx> + +namespace semantics +{ + class class_; + + class inherits: public edge + { + public: + typedef semantics::access access_type; + + class_& + base () const + { + return *base_; + } + + class_& + derived () const + { + return *derived_; + } + + bool + virtual_ () const + { + return virt_; + } + + access_type + access () const + { + return access_; + } + + public: + inherits (access_type, bool virt); + + void + set_left_node (class_& n) + { + derived_ = &n; + } + + void + set_right_node (class_& n) + { + base_ = &n; + } + + protected: + bool virt_; + access_type access_; + + class_* base_; + class_* derived_; + }; + + // + // + class class_: public virtual type, public scope + { + private: + typedef std::vector<inherits*> inherits_list; + + public: + typedef pointer_iterator<inherits_list::const_iterator> inherits_iterator; + + inherits_iterator + inherits_begin () const + { + return inherits_.begin (); + } + + inherits_iterator + inherits_end () const + { + return inherits_.end (); + } + + public: + bool + default_ctor () const; + + bool + complete () const; + + bool + abstract () const; + + public: + // When doing lookup in class scope, take into account bases. + // + static unsigned int const exclude_base = 0x04; // Exclude base classes. + + virtual names* + lookup (string const& name, + type_id const&, + unsigned int flags = 0, + bool* hidden = 0) const; + + using scope::lookup; + + public: + class_ (path const&, size_t line, size_t column, tree); + + void + add_edge_left (inherits& e) + { + inherits_.push_back (&e); + } + + void + add_edge_right (inherits&) + { + } + + using scope::add_edge_left; + using type::add_edge_right; + + // Resolve conflict between scope::scope and nameable::scope. + // + using nameable::scope; + + protected: + class_ () + { + } + + private: + inherits_list inherits_; + }; +} + +#endif // ODB_SEMANTICS_CLASS_HXX diff --git a/odb/odb/semantics/derived.cxx b/odb/odb/semantics/derived.cxx new file mode 100644 index 0000000..771ad21 --- /dev/null +++ b/odb/odb/semantics/derived.cxx @@ -0,0 +1,254 @@ +// file : odb/semantics/derived.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <sstream> + +#include <libcutl/compiler/type-info.hxx> +#include <odb/semantics/derived.hxx> + +using namespace std; + +namespace semantics +{ + qualifies:: + qualifies () + : hint_ (0) + { + } + + qualifier:: + qualifier (path const& file, + size_t line, + size_t column, + tree tn, + bool c, + bool v, + bool r) + : node (file, line, column, tn), c_ (c), v_ (v), r_ (r) + { + } + + string qualifier:: + fq_name (names* hint) const + { + if (hint != 0 || defined_ != 0) + return nameable::fq_name (hint); + + // GCC type_as_string() for some reason cannot correctly print names + // like 'const std::string'. Instead it prints 'const string'. + // + type& bt (base_type ()); + + // Use the trailing qualifier syntax so that we don't get bogged down + // in stuff like 'const const foo*'. We also have to handle arrays in + // a special way since char[16] const is not a legal syntax. + // + string q; + if (c_) + q += " const"; + + if (v_) + q += " volatile"; + + if (r_) + q += " __restrict"; + + hint = qualifies ().hint (); + + if (array* a = dynamic_cast<array*> (&bt)) + return a->fq_name (hint, q); + else + return bt.fq_name (hint) + q; + } + + points:: + points () + : hint_ (0) + { + } + + pointer:: + pointer (path const& file, size_t line, size_t column, tree tn) + : node (file, line, column, tn) + { + } + + string pointer:: + fq_name (names* hint) const + { + if (hint != 0 || defined_ != 0) + return nameable::fq_name (hint); + + // GCC type_as_string() for some reason cannot correctly print names + // like 'const std::string*'. Instead it prints 'const string*'. + // + string r (base_type ().fq_name (points ().hint ())); + r += '*'; + return r; + } + + references:: + references () + : hint_ (0) + { + } + + reference:: + reference (path const& file, size_t line, size_t column, tree tn) + : node (file, line, column, tn) + { + } + + string reference:: + fq_name (names* hint) const + { + if (hint != 0 || defined_ != 0) + return nameable::fq_name (hint); + + // GCC type_as_string() for some reason cannot correctly print names + // like 'const std::string&'. Instead it prints 'const string&'. + // + string r (base_type ().fq_name (points ().hint ())); + r += '&'; + return r; + } + + contains:: + contains () + : hint_ (0) + { + } + + array:: + array (path const& file, + size_t line, + size_t column, + tree tn, + unsigned long long size) + : node (file, line, column, tn), size_ (size) + { + } + + string array:: + fq_name (names* hint) const + { + // GCC type_as_string() for some reason cannot correctly print names + // like 'const std::string[123]'. Instead it prints 'const string[123]'. + // + string t; + return fq_name (hint, t); + } + + string array:: + fq_name (names* hint, string& t) const + { + if (hint != 0 || defined_ != 0) + return nameable::fq_name (hint) + t; + + t += '['; + ostringstream ostr; + ostr << size (); + t += ostr.str (); + + if (size () > 0xFFFFFFFF) + t += "ULL"; + else if (size () > 2147483647) + t += "U"; + + t += ']'; + + type& bt (base_type ()); + hint = contains ().hint (); + array* a; + + if (hint != 0 || (a = dynamic_cast<array*> (&bt)) == 0) + return bt.fq_name (hint) + t; + else + return a->fq_name (0, t); + } + + // type info + // + namespace + { + struct init + { + init () + { + using compiler::type_info; + + // derived_type + // + { + type_info ti (typeid (derived_type)); + ti.add_base (typeid (type)); + insert (ti); + } + + // qualifies + // + { + type_info ti (typeid (qualifies)); + ti.add_base (typeid (edge)); + insert (ti); + } + + // qualifier + // + { + type_info ti (typeid (qualifier)); + ti.add_base (typeid (derived_type)); + insert (ti); + } + + // points + // + { + type_info ti (typeid (points)); + ti.add_base (typeid (edge)); + insert (ti); + } + + // pointer + // + { + type_info ti (typeid (pointer)); + ti.add_base (typeid (derived_type)); + insert (ti); + } + + // references + // + { + type_info ti (typeid (references)); + ti.add_base (typeid (edge)); + insert (ti); + } + + // reference + // + { + type_info ti (typeid (reference)); + ti.add_base (typeid (derived_type)); + insert (ti); + } + + // contains + // + { + type_info ti (typeid (contains)); + ti.add_base (typeid (edge)); + insert (ti); + } + + // array + // + { + type_info ti (typeid (array)); + ti.add_base (typeid (derived_type)); + insert (ti); + } + } + } init_; + } +} diff --git a/odb/odb/semantics/derived.hxx b/odb/odb/semantics/derived.hxx new file mode 100644 index 0000000..e58ec9f --- /dev/null +++ b/odb/odb/semantics/derived.hxx @@ -0,0 +1,440 @@ +// file : odb/semantics/derived.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_SEMANTICS_DERIVED_HXX +#define ODB_SEMANTICS_DERIVED_HXX + +#include <odb/semantics/elements.hxx> + +namespace semantics +{ + // + // Derived types (cvr-qualifiers, pointer, reference, and array). + // + + class derived_type: public type + { + public: + virtual type& + base_type () const = 0; + }; + + // + // + class qualifier; + + class qualifies: public edge + { + public: + typedef semantics::type type_type; + typedef semantics::qualifier qualifier_type; + + type_type& + type () const + { + return *type_; + } + + qualifier_type& + qualifier () const + { + return *qualifier_; + } + + // Name hint of the base type. + // + public: + void + hint (names& hint) + { + hint_ = &hint; + } + + names* + hint () const + { + return hint_; + } + + public: + qualifies (); + + void + set_left_node (qualifier_type& n) + { + qualifier_ = &n; + } + + void + set_right_node (type_type& n) + { + type_ = &n; + } + + protected: + type_type* type_; + qualifier_type* qualifier_; + names* hint_; + }; + + class qualifier: public derived_type + { + public: + typedef semantics::qualifies qualifies_type; + + bool + const_ () const + { + return c_; + } + + bool + volatile_ () const + { + return v_; + } + + bool + restrict_ () const + { + return r_; + } + + virtual type& + base_type () const + { + return qualifies_->type (); + } + + qualifies_type& + qualifies () const + { + return *qualifies_; + } + + public: + virtual string + fq_name (names*) const; + + public: + qualifier (path const&, + size_t line, + size_t column, + tree, + bool c, + bool v, + bool r); + + void + add_edge_left (qualifies_type& e) + { + qualifies_ = &e; + } + + private: + bool c_, v_, r_; + qualifies_type* qualifies_; + }; + + // + // + class pointer; + + class points: public edge + { + public: + typedef semantics::type type_type; + typedef semantics::pointer pointer_type; + + type_type& + type () const + { + return *type_; + } + + pointer_type& + pointer () const + { + return *pointer_; + } + + // Name hint of the base type. + // + public: + void + hint (names& hint) + { + hint_ = &hint; + } + + names* + hint () const + { + return hint_; + } + + public: + points (); + + void + set_left_node (pointer_type& n) + { + pointer_ = &n; + } + + void + set_right_node (type_type& n) + { + type_ = &n; + } + + protected: + type_type* type_; + pointer_type* pointer_; + names* hint_; + }; + + class pointer: public derived_type + { + public: + typedef semantics::points points_type; + + virtual type& + base_type () const + { + return points_->type (); + } + + points_type& + points () const + { + return *points_; + } + + public: + virtual string + fq_name (names*) const; + + public: + pointer (path const&, size_t line, size_t column, tree); + + void + add_edge_left (points_type& e) + { + points_ = &e; + } + + private: + points_type* points_; + }; + + + // + // + class reference; + + class references: public edge + { + public: + typedef semantics::type type_type; + typedef semantics::reference reference_type; + + type_type& + type () const + { + return *type_; + } + + reference_type& + reference () const + { + return *reference_; + } + + // Name hint of the base type. + // + public: + void + hint (names& hint) + { + hint_ = &hint; + } + + names* + hint () const + { + return hint_; + } + + public: + references (); + + void + set_left_node (reference_type& n) + { + reference_ = &n; + } + + void + set_right_node (type_type& n) + { + type_ = &n; + } + + protected: + type_type* type_; + reference_type* reference_; + names* hint_; + }; + + class reference: public derived_type + { + public: + typedef semantics::references references_type; + + virtual type& + base_type () const + { + return references_->type (); + } + + references_type& + references () const + { + return *references_; + } + + public: + virtual string + fq_name (names*) const; + + public: + reference (path const&, size_t line, size_t column, tree); + + void + add_edge_left (references_type& e) + { + references_ = &e; + } + + private: + references_type* references_; + }; + + + // + // + class array; + + class contains: public edge + { + public: + typedef semantics::type type_type; + typedef semantics::array array_type; + + type_type& + type () const + { + return *type_; + } + + array_type& + array () const + { + return *array_; + } + + // Name hint of the base type. + // + public: + void + hint (names& hint) + { + hint_ = &hint; + } + + names* + hint () const + { + return hint_; + } + + public: + contains (); + + void + set_left_node (array_type& n) + { + array_ = &n; + } + + void + set_right_node (type_type& n) + { + type_ = &n; + } + + protected: + type_type* type_; + array_type* array_; + names* hint_; + }; + + class array: public derived_type + { + public: + typedef semantics::contains contains_type; + + // Return the number of elements in the array or 0 if it is not + // specified (e.g., int[]). + // + unsigned long long + size () const + { + return size_; + } + + virtual type& + base_type () const + { + return contains_->type (); + } + + contains_type& + contains () const + { + return *contains_; + } + + public: + virtual string + fq_name (names*) const; + + private: + friend class qualifier; + + string + fq_name (names*, string& trailer) const; + + using derived_type::fq_name; // Unhide. + + public: + array (path const&, + size_t line, + size_t column, + tree, + unsigned long long size); + + void + add_edge_left (contains_type& e) + { + contains_ = &e; + } + + private: + contains_type* contains_; + unsigned long long size_; + }; +} + +#endif // ODB_SEMANTICS_DERIVED_HXX diff --git a/odb/odb/semantics/elements.cxx b/odb/odb/semantics/elements.cxx new file mode 100644 index 0000000..b5793d0 --- /dev/null +++ b/odb/odb/semantics/elements.cxx @@ -0,0 +1,619 @@ +// file : odb/semantics/elements.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <odb/gcc.hxx> + +#include <libcutl/compiler/type-info.hxx> + +#include <odb/cxx-lexer.hxx> + +#include <odb/semantics/elements.hxx> +#include <odb/semantics/namespace.hxx> +#include <odb/semantics/unit.hxx> + +using namespace std; + +namespace semantics +{ + // access + // + static char const* access_str[] = {"public", "protected", "private"}; + + char const* access:: + string () const + { + return access_str[value_]; + } + + // + // + node:: + node (path const& file, size_t line, size_t column, tree tn) + : tree_node_ (tn), loc_ (file, line, column) + { + } + + node:: + node () + : loc_ (0) + { + // GCC plugin machinery #define's abort as a macro. + // + // std::abort (); + abort (); + } + + // nameable + // + + bool nameable:: + in_scope (scope_type& s) + { + for (scope_type* p (&scope ());; p = &p->scope_ ()) + { + //@@ Need to handle namespace extensions. + // + if (p == &s) + return true; + + if (!p->named_p () || p->global_scope ()) + break; + } + + return false; + } + + bool nameable:: + anonymous_ () const + { + tree n (tree_node ()); + + if (TYPE_P (n)) + { + tree name (0); + + if (tree decl = TYPE_NAME (n)) + name = DECL_NAME (decl); + + return name != 0 && IDENTIFIER_ANON_P (name); + } + + return true; + } + + bool nameable:: + fq_anonymous_ (scope_entry const* prev) const + { + scope_entry scope (this, prev); + + // Nameable is fq-anonymous if all the paths to the global scope + // have at least one anonymous link. + // + if (defined_ != 0 || !named_.empty ()) + { + if (named ().global_scope ()) + return false; + + if (defined_ != 0) + { + nameable const& s (defined_->scope ()); + + if (!scope.find (&s) && !s.fq_anonymous_ (&scope)) + return false; + } + + for (names_list::const_iterator i (named_.begin ()), e (named_.end ()); + i != e; ++i) + { + nameable const& s ((*i)->scope ()); + + if (!scope.find (&s) && !s.fq_anonymous_ (&scope)) + return false; + } + } + + // If we can get a literal name for this type node, then it is not + // anonymous as long as its scope is not anonymous. + // + tree type (tree_node ()); + + if (TYPE_P (type)) + { + tree name (0); + + if (tree decl = TYPE_NAME (type)) + { + name = DECL_NAME (decl); + if (name != 0 && IDENTIFIER_ANON_P (name)) + return true; + + tree s (CP_DECL_CONTEXT (decl)); + + gcc_tree_code_type tc (TREE_CODE (s)); + + if (tc == TYPE_DECL) + s = TREE_TYPE (s); + else if (tc == NAMESPACE_DECL) + { + // "Unwind" any inline namespaces since they are not in + // semantic grapth. + // + while (s != global_namespace) + { + tree prev (CP_DECL_CONTEXT (s)); + +#if BUILDING_GCC_MAJOR >= 8 + if (!is_nested_namespace (prev, s, true)) +#else + if (!is_associated_namespace (prev, s)) +#endif + break; + + s = prev; + } + } + + if (nameable* n = dynamic_cast<nameable*> (unit ().find (s))) + return scope.find (n) || n->fq_anonymous_ (&scope); + } + else + return false; // Assume this is a derived type (e.g., pointer). + } + + return true; + } + + bool nameable:: + fq_anonymous (names* hint) const + { + if (hint != 0 || defined_ != 0) + { + names& n (hint ? *hint : *defined_); + + if (n.global_scope ()) + return false; + + return n.scope ().fq_anonymous (); + } + else + return fq_anonymous (); + } + + static string + qualify_names (string const& n, bool qualify_first) + { + // @@ Creating a lexer for each call is a bad idea. Need + // to cache it somewhere. + // + cxx_string_lexer l; + l.start (n); + + string r, t; + bool punc (false); + bool scoped (false); + + // Names returned by GCC's type_as_string() (on which this function + // is called) include inline namespaces (e.g., std::__cxx11::string). + // So, besides fully-qualifying names, this function also needs to get + // rid of those. The idea is to resolve names as we lex them, skipping + // inline namespaces and stopping once we reach something other than a + // namespace. + // + tree ns (global_namespace); + tree id; + + for (cpp_ttype tt = l.next (t, &id); tt != CPP_EOF; tt = l.next (t, &id)) + { + if (punc && tt > CPP_LAST_PUNCTUATOR) + r += ' '; + + punc = false; + tree new_ns (global_namespace); // By default, revert to global. + + switch (static_cast<unsigned> (tt)) + { + case CPP_LESS: + { + r += "< "; + break; + } + case CPP_GREATER: + { + r += " >"; + break; + } + case CPP_COMMA: + { + r += ", "; + break; + } + case CPP_NAME: + { + // Check if this is a namespace and, if so, whether it is + // inline. + // + if (ns != 0) + { + new_ns = lookup_qualified_name (ns, id, false, false); + + if (new_ns == error_mark_node || + TREE_CODE (new_ns) != NAMESPACE_DECL) + new_ns = 0; // Not a namespace, stop resolving. + else + { + // Check if this is an inline namespace and skip it if so. + // +#if BUILDING_GCC_MAJOR >= 8 + if (is_nested_namespace (ns, new_ns, true)) +#else + if (is_associated_namespace (ns, new_ns)) +#endif + { + // Skip also the following scope operator. Strictly speaking + // there could be none (i.e., this is a name of an inline + // namespace) but we only use this function to print names + // of anonymous types. + // + assert (l.next (t) == CPP_SCOPE); + continue; + } + } + } + else + new_ns = 0; // Keep it disabled until we hit a new name. + + // If the name was not preceeded with '::', qualify it. + // + if (!scoped) + { + if (!qualify_first) + qualify_first = true; + else + r += "::"; + } + + r += t; + punc = true; + break; + } + case CPP_KEYWORD: + case CPP_NUMBER: + { + r += t; + punc = true; + break; + } + case CPP_SCOPE: + { + new_ns = ns; // Don't change the namespace. + } + // Fall through. + default: + { + r += t; + break; + } + } + + scoped = (tt == CPP_SCOPE); + ns = new_ns; + } + + return r; + } + + string nameable:: + name_ () const + { + tree n (tree_node ()); + + if (!TYPE_P (n)) + return "<anonymous>"; + + // @@ Doing this once and caching the result is probably a + // good idea. + // + return qualify_names ( + type_as_string (n, TFF_PLAIN_IDENTIFIER | TFF_UNQUALIFIED_NAME), false); + } + + string nameable:: + fq_name () const + { + return fq_name_ (0); + } + + string nameable:: + fq_name_ (scope_entry const* prev) const + { + // @@ Doing this once and caching the result is probably a + // good idea. + // + scope_entry scope (this, prev); + + if (named_p () && named ().global_scope ()) + return ""; + + if (defined_ != 0) + { + nameable const& s (defined_->scope ()); + + if (!scope.find (&s) && !s.fq_anonymous_ (&scope)) + return s.fq_name_ (&scope) + "::" + name (); + } + + for (names_list::const_iterator i (named_.begin ()), e (named_.end ()); + i != e; ++i) + { + nameable const& s ((*i)->scope ()); + + if (!scope.find (&s) && !s.fq_anonymous_ (&scope)) + return s.fq_name_ (&scope) + "::" + name (); + } + + tree n (tree_node ()); + + if (!TYPE_P (n)) + return "<anonymous>"; + + return qualify_names (type_as_string (n, TFF_PLAIN_IDENTIFIER), true); + } + + string nameable:: + fq_name (names* hint) const + { + if (hint != 0 || defined_ != 0) + { + names& n (hint ? *hint : *defined_); + + if (n.global_scope ()) + return ""; + + return n.scope ().fq_name () + "::" + n.name (); + } + else + { + // Since there was no hint, prefer the literal name over the names + // edges. + // + tree n (tree_node ()); + + if (TYPE_P (n)) + return qualify_names (type_as_string (n, TFF_PLAIN_IDENTIFIER), true); + + // Last resort is to call the other version of fq_name which will + // check the names edges. + // + return fq_name (); + } + } + + // scope + // + + scope::names_iterator_pair scope:: + find (string const& name) const + { + names_map::const_iterator i (names_map_.find (name)); + + if (i == names_map_.end ()) + return names_iterator_pair (names_.end (), names_.end ()); + else + return names_iterator_pair (i->second.begin (), i->second.end ()); + } + + scope::names_iterator scope:: + find (names& e) + { + list_iterator_map::iterator i (iterator_map_.find (&e)); + return i != iterator_map_.end () ? i->second : names_.end (); + } + + static bool + is_base (type_id const& b, compiler::type_info const& d) + { + using compiler::type_info; + + for (type_info::base_iterator i (d.begin_base ()); + i != d.end_base (); ++i) + { + type_info const& ti (i->type_info ()); + + if (b == ti.type_id () || is_base (b, ti)) + return true; + } + + return false; + } + + names* scope:: + lookup (string const& name, + type_id const& ti, + unsigned int flags, + bool* hidden) const + { + names_iterator_pair p (find (name)); + names* r (0); + + for (names_const_iterator i (p.first); i != p.second; ++i) + { + type_id const& xti (typeid (i->named ())); + + // If types are equal, then we found a match. Also check if ti is + // a base type of xti. + // + if (xti == ti || is_base (ti, compiler::lookup (xti))) + { + if (r != 0) + { + // If both are namespaces, then the one is just an extension + // of the other. + // + if (!(r->named ().is_a<namespace_> () && + i->named ().is_a<namespace_> ())) + throw ambiguous (*r, *i); + } + else + r = &*i; + } + } + + if (r != 0) + return r; + + // If we found a name but the types didn't match, then bail out + // unless we want hidden names. + // + if (p.first != p.second) + { + if (hidden != 0) + *hidden = true; + + if ((flags & include_hidden) == 0) + return 0; + } + + // Look in the outer scope unless requested not to or if this is + // the global scope. + // + if ((flags & exclude_outer) == 0 && named_p () && !global_scope ()) + return scope ().lookup (name, ti, flags, hidden); + + return 0; + } + + void scope:: + add_edge_left (names& e) + { + names_list::iterator i (names_.insert (names_.end (), &e)); + iterator_map_[&e] = i; + names_map_[e.name ()].push_back (&e); + } + + void scope:: + add_edge_left (names& e, names_iterator after) + { + names_list::iterator i; + + if (after.base () == names_.end ()) + i = names_.insert (names_.begin (), &e); + else + { + names_list::iterator j (after.base ()); + i = names_.insert (++j, &e); + } + + iterator_map_[&e] = i; + names_map_[e.name ()].push_back (&e); + } + + // type info + // + namespace + { + struct init + { + init () + { + using compiler::type_info; + + // node + // + insert (type_info (typeid (node))); + + // edge + // + insert (type_info (typeid (edge))); + + // names + // + { + type_info ti (typeid (names)); + ti.add_base (typeid (edge)); + insert (ti); + } + + // declares + // + { + type_info ti (typeid (declares)); + ti.add_base (typeid (names)); + insert (ti); + } + + // defines + // + { + type_info ti (typeid (defines)); + ti.add_base (typeid (declares)); + insert (ti); + } + + // typedefs + // + { + type_info ti (typeid (typedefs)); + ti.add_base (typeid (declares)); + insert (ti); + } + + // nameable + // + { + type_info ti (typeid (nameable)); + ti.add_base (typeid (node)); + insert (ti); + } + + // scope + // + { + type_info ti (typeid (scope)); + ti.add_base (typeid (nameable)); + insert (ti); + } + + // type + // + { + type_info ti (typeid (type)); + ti.add_base (typeid (nameable)); + insert (ti); + } + + // belongs + // + { + type_info ti (typeid (belongs)); + ti.add_base (typeid (edge)); + insert (ti); + } + + // instance + // + { + type_info ti (typeid (instance)); + ti.add_base (typeid (node)); + insert (ti); + } + + // data_member + // + { + type_info ti (typeid (data_member)); + ti.add_base (typeid (nameable)); + ti.add_base (typeid (instance)); + insert (ti); + } + + // unsupported_type + // + { + type_info ti (typeid (unsupported_type)); + ti.add_base (typeid (type)); + insert (ti); + } + } + } init_; + } +} diff --git a/odb/odb/semantics/elements.hxx b/odb/odb/semantics/elements.hxx new file mode 100644 index 0000000..699a1be --- /dev/null +++ b/odb/odb/semantics/elements.hxx @@ -0,0 +1,841 @@ +// file : odb/semantics/elements.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_SEMANTICS_ELEMENTS_HXX +#define ODB_SEMANTICS_ELEMENTS_HXX + +#include <map> +#include <list> +#include <vector> +#include <string> +#include <cstdlib> // std::abort +#include <cstddef> // std::size_t +#include <utility> // std::pair +#include <cassert> + +#include <libcutl/fs/path.hxx> +#include <libcutl/container/graph.hxx> +#include <libcutl/container/pointer-iterator.hxx> +#include <libcutl/compiler/type-id.hxx> +#include <libcutl/compiler/context.hxx> + +#include <odb/gcc-fwd.hxx> +#include <odb/location.hxx> + +namespace semantics +{ + using namespace cutl; + + using std::size_t; + using std::string; + + using container::graph; + using container::pointer_iterator; + + using compiler::type_id; + using compiler::context; + + // + // + using fs::path; + using fs::invalid_path; + + // + // + class access + { + public: + enum value { public_, protected_, private_ }; + + access (value v) + : value_ (v) + { + } + + operator value () const + { + return value_; + } + + char const* string () const; + + private: + value value_; + }; + + // + // + class node; + class edge; + class unit; + + // Support for inserting edges at specified positions. + // + template <typename N, typename I> + struct node_position + { + node_position (N& node, I pos) + : node_ (node), pos_ (pos) + { + } + + operator N& () const + { + return node_; + } + + template <typename E> + void + add_edge_left (E& e) + { + node_.add_edge_left (e, pos_); + } + + template <typename E> + void + add_edge_right (E& e) + { + node_.add_edge_right (e, pos_); + } + + private: + N& node_; + I pos_; + }; + + // + // + class edge: public context + { + public: + virtual + ~edge () {} + + public: + template <typename X> + X* + is_a () {return dynamic_cast<X*> (this);} + + template <typename X> + const X* + is_a () const {return dynamic_cast<const X*> (this);} + }; + + // + // + class node: public context + { + public: + virtual + ~node () {} + + public: + tree + tree_node () const + { + return tree_node_; + } + + public: + typedef ::location location_type; + + path const& + file () const + { + return loc_.file; + } + + size_t + line () const + { + return loc_.line; + } + + size_t + column () const + { + return loc_.column; + } + + location_type const& + location () const + { + return loc_; + } + + public: + template <typename X> + X* + is_a () {return dynamic_cast<X*> (this);} + + template <typename X> + const X* + is_a () const {return dynamic_cast<const X*> (this);} + + public: + node (path const& file, size_t line, size_t column, tree); + + // Sink functions that allow extensions in the form of one-way + // edges. + // + void + add_edge_left (edge&) {} + + void + add_edge_right (edge&) {} + + protected: + // For virtual inheritance. Should never be actually called. + // + node (); + + protected: + typedef semantics::unit unit_type; + + unit_type const& + unit () const + { + return *unit_; + } + + unit_type& + unit () + { + return *unit_; + } + + private: + friend class semantics::unit; + + void + unit (unit_type& u) + { + unit_ = &u; + } + + private: + tree tree_node_; + unit_type* unit_; + + location_type loc_; + }; + + // + // + class scope; + class nameable; + + + // + // + class names: public edge + { + public: + typedef semantics::scope scope_type; + typedef semantics::access access_type; + + string const& + name () const + { + return name_; + } + + scope_type& + scope () const + { + return *scope_; + } + + // Return true if the entity that this edge names is a global scope. + // In this case calling scope() is undefined behavior. + // + bool + global_scope () const + { + return scope_ == 0; + } + + nameable& + named () const + { + return *named_; + } + + access_type + access () const + { + return access_; + } + + // Names edge in terms of which this edge was defined. Can be NULL. + // + public: + void + hint (names& hint) + { + hint_ = &hint; + } + + names* + hint () const + { + return hint_; + } + + public: + names (string const& name, access_type access = access_type::public_) + : name_ (name), access_ (access), hint_ (0) + { + } + + void + set_left_node (scope_type& n) + { + scope_ = &n; + } + + void + set_right_node (nameable& n) + { + named_ = &n; + } + + protected: + scope_type* scope_; + nameable* named_; + string name_; + access_type access_; + names* hint_; + }; + + // + // Declarations and definitions. + // + + class declares: public names + { + public: + declares (string const& name, access_type access = access_type::public_) + : names (name, access) + { + } + }; + + class defines: public declares + { + public: + defines (string const& name, access_type access = access_type::public_) + : declares (name, access) + { + } + }; + + class type; + class typedefs: public declares + { + public: + typedef semantics::type type_type; + + type_type& + type () const; + + public: + typedefs (string const& name, access_type access = access_type::public_) + : declares (name, access) + { + } + }; + + // + // + class nameable: public virtual node + { + typedef std::vector<names*> names_list; + + public: + typedef semantics::scope scope_type; + + // Return true if this type is unnamed and no literal name, such as + // template-id or derived type declarator, can be used instead. + // + bool + anonymous () const + { + if (defined_ != 0 || !named_.empty ()) + return false; + + return anonymous_ (); + } + + // Return true if the node itself or any of the scopes up to the + // global scope is anonymous. For a named class nested in an unnamed + // class, anonymous() will return false and fq_anonymous() will + // return true. + // + bool + fq_anonymous () const + { + return fq_anonymous_ (0); + } + + // As above but use the hint to select the first outer scope. If + // hint is 0, use the defines edge. + // + bool + fq_anonymous (names* hint) const; + + // Return the node's unqualifed name. If the node has a name, then + // return it, preferring the defines edge. Otherwise, return a + // literal name, e.g., template-id or a derived type declarator. + // Finally, if the type is anonymous, return <anonymous> string. + // + string + name () const + { + if (defined_ != 0) + return defined_->name (); + + if (!named_.empty ()) + return named_[0]->name (); + + return name_ (); + } + + // Return the node's fully-qualifed name. + // + virtual string + fq_name () const; + + // As above but use the hint to select the first outer scope. If hint + // is 0, use the defines edge. + // + virtual string + fq_name (names* hint) const; + + // Return true if the type is named. + // + bool + named_p () const + { + return defined_ != 0 || !named_.empty (); + } + + scope_type& + scope () const + { + return named ().scope (); + } + + names& + named () const + { + return defined_ != 0 ? *defined_ : *named_[0]; + } + + bool + in_scope (scope_type&); + + public: + nameable () + : defined_ (0) + { + } + + void + add_edge_right (defines& e) + { + assert (defined_ == 0); + defined_ = &e; + } + + void + add_edge_right (names& e) + { + named_.push_back (&e); + } + + using node::add_edge_right; + + protected: + // We need to keep the scope we have seen in the fq_* function + // family in order to detect names that are inside the node + // and which would otherwise lead to infinite recursion. Here + // is the canonical example: + // + // template <typename X> + // class c + // { + // typedef c this_type; + // }; + // + struct scope_entry + { + scope_entry (nameable const* e, scope_entry const* p) + : entry_ (e), prev_ (p) + { + } + + bool + find (nameable const* n) const + { + for (scope_entry const* i (this); i != 0; i = i->prev_) + if (i->entry_ == n) + return true; + + return false; + } + + private: + nameable const* entry_; + scope_entry const* prev_; + }; + + bool + anonymous_ () const; + + bool + fq_anonymous_ (scope_entry const*) const; + + string + name_ () const; + + string + fq_name_ (scope_entry const*) const; + + protected: + defines* defined_; + names_list named_; + }; + + + // Ambiguous name lookup exception. + // + struct ambiguous + { + ambiguous (names& f, names& s): first (f), second (s) {} + names& first; + names& second; + }; + + // Unresolved name lookup exception. + // + struct unresolved + { + unresolved (string const& n, bool tm): name (n), type_mismatch (tm) {} + string name; + bool type_mismatch; // True if the name resolved but types didn't match. + }; + + // + // + class scope: public virtual nameable + { + protected: + typedef std::list<names*> names_list; + typedef std::map<names*, names_list::iterator> list_iterator_map; + typedef std::map<string, names_list> names_map; + + public: + typedef pointer_iterator<names_list::iterator> names_iterator; + typedef pointer_iterator<names_list::const_iterator> names_const_iterator; + + typedef + std::pair<names_const_iterator, names_const_iterator> + names_iterator_pair; + + public: + bool + global_scope () const + { + return named ().global_scope (); + } + + scope& + scope_ () const + { + return nameable::scope (); + } + + names_iterator + names_begin () + { + return names_.begin (); + } + + names_iterator + names_end () + { + return names_.end (); + } + + names_const_iterator + names_begin () const + { + return names_.begin (); + } + + names_const_iterator + names_end () const + { + return names_.end (); + } + + // Find a name in this scope. + // + public: + virtual names_iterator_pair + find (string const& name) const; + + names_iterator + find (names&); + + // Lookup a name of the specified type in this scope and, if not + // found, in outer scopes. + // + public: + static unsigned int const exclude_outer = 0x01; // Exclude outer scopes. + static unsigned int const include_hidden = 0x02; // Include hidden names. + + virtual names* + lookup (string const& name, + type_id const&, + unsigned int flags = 0, + bool* hidden = 0) const; + + template <typename T> + T& + lookup (string const& name, unsigned int flags = 0) const + { + bool hidden (false); + + if (names* n = lookup (name, typeid (T), flags, &hidden)) + return dynamic_cast<T&> (n->named ()); + + throw unresolved (name, hidden); + } + + public: + scope (path const& file, size_t line, size_t column, tree tn) + : node (file, line, column, tn) + { + } + + void + add_edge_left (names&); + + void + add_edge_left (names&, names_iterator after); + + using nameable::add_edge_right; + + protected: + scope () + { + } + + private: + names_list names_; + list_iterator_map iterator_map_; + names_map names_map_; + }; + + // + // + class points; + + class belongs; + class qualifies; + + class type: public virtual nameable + { + typedef std::vector<qualifies*> qualified; + + public: + typedef pointer_iterator<qualified::const_iterator> qualified_iterator; + + qualified_iterator + qualified_begin () const + { + return qualified_.begin (); + } + + qualified_iterator + qualified_end () const + { + return qualified_.end (); + } + + public: + bool + pointed_p () const {return pointed_ != 0;} + + points& + pointed () const {return *pointed_;} + + public: + type (): pointed_ (0) {} + + void + add_edge_right (belongs&) + { + } + + void + add_edge_right (qualifies& e) + { + qualified_.push_back (&e); + } + + void + add_edge_right (points& e) + { + pointed_ = &e; + } + + using nameable::add_edge_right; + + private: + qualified qualified_; + points* pointed_; + }; + + // + // + class instance; + + class belongs: public edge + { + public: + typedef semantics::type type_type; + typedef semantics::instance instance_type; + + type_type& + type () const + { + return *type_; + } + + instance_type& + instance () const + { + return *instance_; + } + + public: + void + hint (names& hint) + { + hint_ = &hint; + } + + names* + hint () const + { + return hint_; + } + + public: + belongs () + : hint_ (0) + { + } + + void + set_left_node (instance_type& n) + { + instance_ = &n; + } + + void + set_right_node (type_type& n) + { + type_ = &n; + } + + private: + type_type* type_; + instance_type* instance_; + names* hint_; + }; + + // + // + class instance: public virtual node + { + public: + typedef semantics::type type_type; + typedef semantics::belongs belongs_type; + + type_type& + type () const + { + return belongs_->type (); + } + + belongs_type& + belongs () const + { + return *belongs_; + } + + public: + void + add_edge_left (belongs_type& e) + { + belongs_ = &e; + } + + protected: + instance () + { + } + + private: + belongs_type* belongs_; + }; + + // Data member for class and union types. + // + class data_member: public nameable, public instance + { + public: + data_member (path const& file, size_t line, size_t column, tree tn) + : node (file, line, column, tn) + { + } + + protected: + data_member () + { + } + }; + + // Unsupported type. + // + class unsupported_type: public type + { + public: + string const& + type_name () const + { + return type_name_; + } + + public: + unsupported_type (path const& file, + size_t line, + size_t column, + tree tn, + string const& type_name) + : node (file, line, column, tn), type_name_ (type_name) + { + } + + private: + string const type_name_; + }; +} + +#include <odb/semantics/elements.ixx> + +#endif // ODB_SEMANTICS_ELEMENTS_HXX diff --git a/odb/odb/semantics/elements.ixx b/odb/odb/semantics/elements.ixx new file mode 100644 index 0000000..32f9ced --- /dev/null +++ b/odb/odb/semantics/elements.ixx @@ -0,0 +1,13 @@ +// file : odb/semantics/elements.ixx +// license : GNU GPL v3; see accompanying LICENSE file + +namespace semantics +{ + // typedefs + // + inline typedefs::type_type& typedefs:: + type () const + { + return dynamic_cast<type_type&> (named ()); + } +} diff --git a/odb/odb/semantics/enum.cxx b/odb/odb/semantics/enum.cxx new file mode 100644 index 0000000..6432986 --- /dev/null +++ b/odb/odb/semantics/enum.cxx @@ -0,0 +1,85 @@ +// file : odb/semantics/enum.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <libcutl/compiler/type-info.hxx> +#include <odb/semantics/enum.hxx> + +namespace semantics +{ + enumerates:: + enumerates () + { + } + + enumerator:: + enumerator (path const& file, + size_t line, + size_t column, + tree tn, + unsigned long long value) + : node (file, line, column, tn), value_ (value) + { + } + + underlies:: + underlies () + : type_ (0), enum__ (0), hint_ (0) + { + } + + enum_:: + enum_ (path const& file, + size_t line, + size_t column, + tree tn) + : node (file, line, column, tn) + { + } + + // type info + // + namespace + { + struct init + { + init () + { + using compiler::type_info; + + // enumerates + // + { + type_info ti (typeid (enumerates)); + ti.add_base (typeid (edge)); + insert (ti); + } + + // enumerator + // + { + type_info ti (typeid (enumerator)); + ti.add_base (typeid (nameable)); + ti.add_base (typeid (instance)); + insert (ti); + } + + // underlies + // + { + type_info ti (typeid (underlies)); + ti.add_base (typeid (edge)); + insert (ti); + } + + // enum_ + // + { + type_info ti (typeid (enum_)); + ti.add_base (typeid (type)); + ti.add_base (typeid (scope)); + insert (ti); + } + } + } init_; + } +} diff --git a/odb/odb/semantics/enum.hxx b/odb/odb/semantics/enum.hxx new file mode 100644 index 0000000..bfcce53 --- /dev/null +++ b/odb/odb/semantics/enum.hxx @@ -0,0 +1,228 @@ +// file : odb/semantics/enum.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_SEMANTICS_ENUM_HXX +#define ODB_SEMANTICS_ENUM_HXX + +#include <vector> +#include <odb/semantics/elements.hxx> +#include <odb/semantics/fundamental.hxx> + +namespace semantics +{ + class enum_; + class enumerator; + + class enumerates: public edge + { + public: + typedef semantics::enum_ enum_type; + typedef semantics::enumerator enumerator_type; + + enum_type& + enum_ () const + { + return *enum__; + } + + enumerator_type& + enumerator () const + { + return *enumerator_; + } + + public: + enumerates (); + + void + set_left_node (enum_type& n) + { + enum__ = &n; + } + + void + set_right_node (enumerator_type& n) + { + enumerator_ = &n; + } + + protected: + enum_type* enum__; + enumerator_type* enumerator_; + }; + + // + // + class enumerator: public nameable, public instance + { + public: + typedef semantics::enum_ enum_type; + + enum_type& + enum_ () const + { + return enumerated_->enum_ (); + } + + enumerates& + enumerated () const + { + return *enumerated_; + } + + // If the enumeration is signed, then this value should be re- + // interpreted as signed. + // + unsigned long long + value () const + { + return value_; + } + + public: + enumerator (path const&, + size_t line, + size_t column, + tree, + unsigned long long value); + + void + add_edge_right (enumerates& e) + { + enumerated_ = &e; + } + + using nameable::add_edge_right; + + private: + unsigned long long value_; + enumerates* enumerated_; + }; + + // + // + class underlies: public edge + { + public: + typedef semantics::enum_ enum_type; + + integral_type& + type () const + { + return *type_; + } + + enum_type& + enum_ () const + { + return *enum__; + } + + // Names edge in terms of which this edge was defined. Can be NULL. + // + public: + void + hint (names& hint) + { + hint_ = &hint; + } + + names* + hint () const + { + return hint_; + } + + public: + underlies (); + + void + set_left_node (integral_type& n) + { + type_ = &n; + } + + void + set_right_node (enum_type& n) + { + enum__ = &n; + } + + protected: + integral_type* type_; + enum_type* enum__; + names* hint_; + }; + + // + // + class enum_: public type, public scope + { + private: + typedef std::vector<enumerates*> enumerates_list; + + public: + typedef + pointer_iterator<enumerates_list::const_iterator> + enumerates_iterator; + + enumerates_iterator + enumerates_begin () const + { + return enumerates_.begin (); + } + + enumerates_iterator + enumerates_end () const + { + return enumerates_.end (); + } + + underlies& + underlied () const + { + return *underlied_; + } + + integral_type& + underlying_type () const + { + return underlied_->type (); + } + + names* + underlying_type_hint () const + { + return underlied_->hint (); + } + + bool + unsigned_ () const + { + return underlying_type ().unsigned_ (); + } + + public: + enum_ (path const&, size_t line, size_t column, tree); + + void + add_edge_right (underlies& e) + { + underlied_ = &e; + } + + void + add_edge_left (enumerates& e) + { + enumerates_.push_back (&e); + } + + using scope::add_edge_left; + + private: + enumerates_list enumerates_; + underlies* underlied_; + }; +} + +#endif // ODB_SEMANTICS_ENUM_HXX diff --git a/odb/odb/semantics/fundamental.cxx b/odb/odb/semantics/fundamental.cxx new file mode 100644 index 0000000..ed4a67f --- /dev/null +++ b/odb/odb/semantics/fundamental.cxx @@ -0,0 +1,222 @@ +// file : odb/semantics/fundamental.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <odb/gcc.hxx> + +#include <libcutl/compiler/type-info.hxx> +#include <odb/semantics/fundamental.hxx> + +namespace semantics +{ + string fund_type:: + fq_name () const + { + return name (); + } + + string fund_type:: + fq_name (names* hint) const + { + if (hint == 0) + return name (); + + return type::fq_name (hint); + } + + // char + // + bool fund_char:: + unsigned_ () const + { + return TYPE_UNSIGNED (tree_node ()) != 0; + } + + // wchar_t + // + bool fund_wchar:: + unsigned_ () const + { + return TYPE_UNSIGNED (tree_node ()) != 0; + } + + // type info + // + namespace + { + struct init + { + init () + { + using compiler::type_info; + + // fund_type + // + { + type_info ti (typeid (fund_type)); + ti.add_base (typeid (type)); + insert (ti); + } + + // fund_void + // + { + type_info ti (typeid (fund_void)); + ti.add_base (typeid (fund_type)); + insert (ti); + } + + // integral_type + // + { + type_info ti (typeid (integral_type)); + ti.add_base (typeid (fund_type)); + insert (ti); + } + + // fund_bool + // + { + type_info ti (typeid (fund_bool)); + ti.add_base (typeid (integral_type)); + insert (ti); + } + + // fund_char + // + { + type_info ti (typeid (fund_char)); + ti.add_base (typeid (integral_type)); + insert (ti); + } + + // fund_wchar + // + { + type_info ti (typeid (fund_wchar)); + ti.add_base (typeid (integral_type)); + insert (ti); + } + + // fund_char16 + // + { + type_info ti (typeid (fund_char16)); + ti.add_base (typeid (integral_type)); + insert (ti); + } + + // fund_char32 + // + { + type_info ti (typeid (fund_char32)); + ti.add_base (typeid (integral_type)); + insert (ti); + } + + // fund_signed_char + // + { + type_info ti (typeid (fund_signed_char)); + ti.add_base (typeid (integral_type)); + insert (ti); + } + + // fund_unsigned_char + // + { + type_info ti (typeid (fund_unsigned_char)); + ti.add_base (typeid (integral_type)); + insert (ti); + } + + // fund_short + // + { + type_info ti (typeid (fund_short)); + ti.add_base (typeid (integral_type)); + insert (ti); + } + + // fund_unsigned_short + // + { + type_info ti (typeid (fund_unsigned_short)); + ti.add_base (typeid (integral_type)); + insert (ti); + } + + // fund_int + // + { + type_info ti (typeid (fund_int)); + ti.add_base (typeid (integral_type)); + insert (ti); + } + + // fund_unsigned_int + // + { + type_info ti (typeid (fund_unsigned_int)); + ti.add_base (typeid (integral_type)); + insert (ti); + } + + // fund_long + // + { + type_info ti (typeid (fund_long)); + ti.add_base (typeid (integral_type)); + insert (ti); + } + + // fund_unsigned_long + // + { + type_info ti (typeid (fund_unsigned_long)); + ti.add_base (typeid (integral_type)); + insert (ti); + } + + // fund_long_long + // + { + type_info ti (typeid (fund_long_long)); + ti.add_base (typeid (integral_type)); + insert (ti); + } + + // fund_unsigned_long_long + // + { + type_info ti (typeid (fund_unsigned_long_long)); + ti.add_base (typeid (integral_type)); + insert (ti); + } + + // fund_float + // + { + type_info ti (typeid (fund_float)); + ti.add_base (typeid (fund_type)); + insert (ti); + } + + // fund_double + // + { + type_info ti (typeid (fund_double)); + ti.add_base (typeid (fund_type)); + insert (ti); + } + + // fund_long_double + // + { + type_info ti (typeid (fund_long_double)); + ti.add_base (typeid (fund_type)); + insert (ti); + } + } + } init_; + } +} diff --git a/odb/odb/semantics/fundamental.hxx b/odb/odb/semantics/fundamental.hxx new file mode 100644 index 0000000..15b5cbb --- /dev/null +++ b/odb/odb/semantics/fundamental.hxx @@ -0,0 +1,150 @@ +// file : odb/semantics/fundamental.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_SEMANTICS_FUNDAMENTAL_HXX +#define ODB_SEMANTICS_FUNDAMENTAL_HXX + +#include <odb/semantics/elements.hxx> + +namespace semantics +{ + // + // Fundamental C++ types. + // + + struct fund_type: type + { + virtual string + fq_name () const; + + virtual string + fq_name (names*) const; + }; + + struct fund_void: fund_type + { + fund_void (tree tn): node (path ("<fundamental>"), 0, 0, tn) {} + }; + + // + // Integral. + // + + struct integral_type: fund_type + { + virtual bool + unsigned_ () const = 0; + }; + + struct fund_bool: integral_type + { + fund_bool (tree tn): node (path ("<fundamental>"), 0, 0, tn) {} + virtual bool unsigned_ () const {return true;} + }; + + struct fund_char: integral_type + { + fund_char (tree tn): node (path ("<fundamental>"), 0, 0, tn) {} + virtual bool unsigned_ () const; + }; + + struct fund_wchar: integral_type + { + fund_wchar (tree tn): node (path ("<fundamental>"), 0, 0, tn) {} + virtual bool unsigned_ () const; + }; + + struct fund_char16: integral_type + { + fund_char16 (tree tn): node (path ("<fundamental>"), 0, 0, tn) {} + virtual bool unsigned_ () const {return true;} + }; + + struct fund_char32: integral_type + { + fund_char32 (tree tn): node (path ("<fundamental>"), 0, 0, tn) {} + virtual bool unsigned_ () const {return true;} + }; + + struct fund_signed_char: integral_type + { + fund_signed_char (tree tn): node (path ("<fundamental>"), 0, 0, tn) {} + virtual bool unsigned_ () const {return false;} + }; + + struct fund_unsigned_char: integral_type + { + fund_unsigned_char (tree tn): node (path ("<fundamental>"), 0, 0, tn) {} + virtual bool unsigned_ () const {return true;} + }; + + struct fund_short: integral_type + { + fund_short (tree tn): node (path ("<fundamental>"), 0, 0, tn) {} + virtual bool unsigned_ () const {return false;} + }; + + struct fund_unsigned_short: integral_type + { + fund_unsigned_short (tree tn): node (path ("<fundamental>"), 0, 0, tn) {} + virtual bool unsigned_ () const {return true;} + }; + + struct fund_int: integral_type + { + fund_int (tree tn): node (path ("<fundamental>"), 0, 0, tn) {} + virtual bool unsigned_ () const {return false;} + }; + + struct fund_unsigned_int: integral_type + { + fund_unsigned_int (tree tn): node (path ("<fundamental>"), 0, 0, tn) {} + virtual bool unsigned_ () const {return true;} + }; + + struct fund_long: integral_type + { + fund_long (tree tn): node (path ("<fundamental>"), 0, 0, tn) {} + virtual bool unsigned_ () const {return false;} + }; + + struct fund_unsigned_long: integral_type + { + fund_unsigned_long (tree tn): node (path ("<fundamental>"), 0, 0, tn) {} + virtual bool unsigned_ () const {return true;} + }; + + struct fund_long_long: integral_type + { + fund_long_long (tree tn): node (path ("<fundamental>"), 0, 0, tn) {} + virtual bool unsigned_ () const {return false;} + }; + + struct fund_unsigned_long_long: integral_type + { + fund_unsigned_long_long (tree tn) + : node (path ("<fundamental>"), 0, 0, tn) {} + virtual bool unsigned_ () const {return true;} + }; + + // + // Real. + // + + struct fund_float: fund_type + { + fund_float (tree tn): node (path ("<fundamental>"), 0, 0, tn) {} + }; + + struct fund_double: fund_type + { + fund_double (tree tn): node (path ("<fundamental>"), 0, 0, tn) {} + }; + + struct fund_long_double: fund_type + { + fund_long_double (tree tn): node (path ("<fundamental>"), 0, 0, tn) {} + }; +} + +#endif // ODB_SEMANTICS_FUNDAMENTAL_HXX diff --git a/odb/odb/semantics/namespace.cxx b/odb/odb/semantics/namespace.cxx new file mode 100644 index 0000000..d9be903 --- /dev/null +++ b/odb/odb/semantics/namespace.cxx @@ -0,0 +1,107 @@ +// file : odb/semantics/namespace.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <libcutl/compiler/type-info.hxx> +#include <odb/semantics/namespace.hxx> + +namespace semantics +{ + namespace_:: + namespace_ (path const& file, size_t line, size_t column, tree tn) + : node (file, line, column, tn), original_ (0) + { + } + + namespace_:: + namespace_ () + : original_ (0) + { + } + + names* namespace_:: + lookup (string const& name, + type_id const& ti, + unsigned int flags, + bool* hidden) const + { + if (original_ != 0) + return original_->lookup (name, ti, flags, hidden); + + // Being hidden in one namespace doesn't mean it is also hidden in + // the other. Normally that would be an ambiguous lookup, but we use + // relaxed rules. + // + bool h (false); // Indicates whether this namespace hides the name. + bool any_h (false); // Indicates whether any namespace hides the name. + + names* r (scope::lookup (name, ti, flags | exclude_outer, &h)); + any_h = any_h || h; + + if (r != 0 && h && hidden != 0) + *hidden = true; + + for (extensions_iterator i (extensions_begin ()); + i != extensions_end (); + ++i) + { + h = false; + names* er ((*i)->scope::lookup (name, ti, flags | exclude_outer, &h)); + any_h = any_h || h; + + if (er != 0) + { + if (r != 0) + { + // If both are namespaces, then the one is just an extension + // of the other. + // + if (!(r->named ().is_a<namespace_> () && + er->named ().is_a<namespace_> ())) + throw ambiguous (*r, *er); + } + else + r = er; + + if (h && hidden != 0) + *hidden = true; + } + } + + if (r != 0) + return r; + + if (any_h) + { + if (hidden != 0) + *hidden = true; + + if ((flags & include_hidden) == 0) + return 0; + } + + // Look in the outer scope unless requested not to or if this is + // the global scope. + // + if ((flags & exclude_outer) == 0 && !global_scope ()) + return scope ().lookup (name, ti, flags, hidden); + + return 0; + } + + // type info + // + namespace + { + struct init + { + init () + { + using compiler::type_info; + + type_info ti (typeid (namespace_)); + ti.add_base (typeid (scope)); + insert (ti); + } + } init_; + } +} diff --git a/odb/odb/semantics/namespace.hxx b/odb/odb/semantics/namespace.hxx new file mode 100644 index 0000000..b025c2e --- /dev/null +++ b/odb/odb/semantics/namespace.hxx @@ -0,0 +1,71 @@ +// file : odb/semantics/namespace.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_SEMANTICS_NAMESPACE_HXX +#define ODB_SEMANTICS_NAMESPACE_HXX + +#include <vector> + +#include <odb/semantics/elements.hxx> + +namespace semantics +{ + class namespace_: public scope + { + typedef std::vector<namespace_*> extensions_type; + + public: + bool + extension () const + { + return original_ != 0; + } + + namespace_& + original () + { + return *original_; + } + + void + original (namespace_& ns) + { + original_ = &ns; + ns.extensions_.push_back (this); + } + + public: + typedef extensions_type::const_iterator extensions_iterator; + + extensions_iterator + extensions_begin () const {return extensions_.begin ();} + + extensions_iterator + extensions_end () const {return extensions_.end ();} + + public: + virtual names* + lookup (string const& name, + type_id const&, + unsigned int flags = 0, + bool* hidden = 0) const; + + using scope::lookup; + + public: + namespace_ (path const&, size_t line, size_t column, tree); + + // Resolve conflict between scope::scope and nameable::scope. + // + using nameable::scope; + + protected: + namespace_ (); + + private: + namespace_* original_; + extensions_type extensions_; + }; +} + +#endif // ODB_SEMANTICS_NAMESPACE_HXX diff --git a/odb/odb/semantics/relational.hxx b/odb/odb/semantics/relational.hxx new file mode 100644 index 0000000..db08a61 --- /dev/null +++ b/odb/odb/semantics/relational.hxx @@ -0,0 +1,18 @@ +// file : odb/semantics/relational.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_SEMANTICS_RELATIONAL_HXX +#define ODB_SEMANTICS_RELATIONAL_HXX + +#include <odb/semantics/relational/changelog.hxx> +#include <odb/semantics/relational/changeset.hxx> +#include <odb/semantics/relational/column.hxx> +#include <odb/semantics/relational/elements.hxx> +#include <odb/semantics/relational/foreign-key.hxx> +#include <odb/semantics/relational/index.hxx> +#include <odb/semantics/relational/key.hxx> +#include <odb/semantics/relational/model.hxx> +#include <odb/semantics/relational/primary-key.hxx> +#include <odb/semantics/relational/table.hxx> + +#endif // ODB_SEMANTICS_RELATIONAL_HXX diff --git a/odb/odb/semantics/relational/changelog.cxx b/odb/odb/semantics/relational/changelog.cxx new file mode 100644 index 0000000..353497f --- /dev/null +++ b/odb/odb/semantics/relational/changelog.cxx @@ -0,0 +1,187 @@ +// file : odb/semantics/relational/changelog.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <vector> +#include <sstream> + +#include <libcutl/compiler/type-info.hxx> + +#include <odb/semantics/relational/changelog.hxx> +#include <odb/semantics/relational/model.hxx> +#include <odb/semantics/relational/changeset.hxx> + +using namespace std; + +namespace semantics +{ + namespace relational + { + changelog:: + changelog (xml::parser& p) + : contains_model_ (0) + { + using namespace xml; + + p.next_expect (parser::start_element, xmlns, "changelog"); + p.content (content::complex); + + if (p.attribute<unsigned int> ("version") != 1) + throw parsing (p, "unsupported changelog format version"); + + database_ = p.attribute ("database"); + schema_name_ = p.attribute ("schema-name", ""); + + // Because things are stored in the reverse order, first save the + // changesets as XML chunks and then re-parse them in the reverse + // order. We have to do it this way so that we can do lookups along + // the alters edges. + // + typedef vector<string> changesets; + changesets cs; + + for (parser::event_type e (p.peek ()); + e == parser::start_element; + e = p.peek ()) + { + if (p.qname () != xml::qname (xmlns, "changeset")) + break; // Not our elements. + + ostringstream os; + os.exceptions (ios_base::badbit | ios_base::failbit); + serializer s (os, "changeset", 0); // No pretty-printing. + size_t depth (0); + + do + { + switch (p.next ()) + { + case parser::start_element: + { + s.start_element (p.qname ()); + + if (depth == 0) + s.namespace_decl (xmlns, ""); + + typedef parser::attribute_map_type attr_map; + attr_map const& am (p.attribute_map ()); + + for (attr_map::const_iterator i (am.begin ()); + i != am.end (); ++i) + s.attribute (i->first, i->second.value); + + depth++; + break; + } + case parser::end_element: + { + depth--; + s.end_element (); + break; + } + case parser::characters: + { + s.characters (p.value ()); + break; + } + default: + { + depth = 0; + break; + } + } + } while (depth != 0); + + cs.push_back (os.str ()); + } + + // Get the model. + // + p.next_expect (parser::start_element, xmlns, "model"); + model_type& m (new_node<model_type> (p, *this)); + new_edge<contains_model_type> (*this, m); + p.next_expect (parser::end_element); + + // Re-parse the changesets in reverse order. + // + qscope* base (&m); + for (changesets::reverse_iterator i (cs.rbegin ()); i != cs.rend (); ++i) + { + istringstream is (*i); + is.exceptions (ios_base::badbit | ios_base::failbit); + parser ip (is, p.input_name ()); + + ip.next_expect (parser::start_element, xmlns, "changeset"); + + changeset& c (new_node<changeset> (ip, *base, *this)); + new_edge<contains_changeset> (*this, c); + base = &c; + + ip.next_expect (parser::end_element); + } + + p.next_expect (parser::end_element); + } + + void changelog:: + serialize (xml::serializer& s) const + { + s.start_element (xmlns, "changelog"); + s.namespace_decl (xmlns, ""); + s.attribute ("database", database_); + if (!schema_name_.empty ()) + s.attribute ("schema-name", schema_name_); + s.attribute ("version", 1); // Format version. + + // For better readability serialize things in reverse order so that + // the most recent changeset appears first. + // + for (contains_changeset_list::const_reverse_iterator i ( + contains_changeset_.rbegin ()); + i != contains_changeset_.rend (); ++i) + { + (*i)->changeset ().serialize (s); + s.characters ("\n"); + } + + model ().serialize (s); + s.end_element (); + } + + // type info + // + namespace + { + struct init + { + init () + { + using compiler::type_info; + + // contains_model + // + { + type_info ti (typeid (contains_model)); + ti.add_base (typeid (edge)); + insert (ti); + } + + // contains_changeset + // + { + type_info ti (typeid (contains_changeset)); + ti.add_base (typeid (edge)); + insert (ti); + } + + // changelog + // + { + type_info ti (typeid (changelog)); + ti.add_base (typeid (node)); + insert (ti); + } + } + } init_; + } + } +} diff --git a/odb/odb/semantics/relational/changelog.hxx b/odb/odb/semantics/relational/changelog.hxx new file mode 100644 index 0000000..2398cf6 --- /dev/null +++ b/odb/odb/semantics/relational/changelog.hxx @@ -0,0 +1,164 @@ +// file : odb/semantics/relational/changelog.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_SEMANTICS_RELATIONAL_CHANGELOG_HXX +#define ODB_SEMANTICS_RELATIONAL_CHANGELOG_HXX + +#include <odb/semantics/relational/elements.hxx> + +namespace semantics +{ + namespace relational + { + class model; + class changeset; + class changelog; + + class contains_model: public edge + { + public: + typedef relational::changelog changelog_type; + typedef relational::model model_type; + + changelog_type& + changelog () const {return *changelog_;} + + model_type& + model () const {return *model_;} + + public: + void + set_left_node (changelog_type& n) {changelog_ = &n;} + + void + set_right_node (model_type& n) {model_ = &n;} + + protected: + changelog_type* changelog_; + model_type* model_; + }; + + class contains_changeset: public edge + { + public: + typedef relational::changelog changelog_type; + typedef relational::changeset changeset_type; + + changelog_type& + changelog () const {return *changelog_;} + + changeset_type& + changeset () const {return *changeset_;} + + public: + void + set_left_node (changelog_type& n) {changelog_ = &n;} + + void + set_right_node (changeset_type& n) {changeset_ = &n;} + + protected: + changelog_type* changelog_; + changeset_type* changeset_; + }; + + class changelog: public graph, public node + { + typedef std::vector<contains_changeset*> contains_changeset_list; + + public: + typedef relational::model model_type; + typedef relational::contains_model contains_model_type; + + model_type& + model () const {return contains_model_->model ();} + + contains_model_type& + contains_model () const {return *contains_model_;} + + public: + typedef + pointer_iterator<contains_changeset_list::const_iterator> + contains_changeset_iterator; + + contains_changeset const& + contains_changeset_back () const + { + return *contains_changeset_.back (); + } + + contains_changeset const& + contains_changeset_at (contains_changeset_list::size_type i) const + { + return *contains_changeset_[i]; + } + + contains_changeset_iterator + contains_changeset_begin () const + { + return contains_changeset_.begin (); + } + + contains_changeset_iterator + contains_changeset_end () const + { + return contains_changeset_.end (); + } + + contains_changeset_list::size_type + contains_changeset_size () const + { + return contains_changeset_.size (); + } + + bool + contains_changeset_empty () const + { + return contains_changeset_.empty (); + } + + public: + string const& + database () const {return database_;} + + string const& + schema_name () const {return schema_name_;} + + public: + changelog (string const& db, string const& sn) + : database_ (db), schema_name_ (sn), contains_model_ (0) {} + changelog (xml::parser&); + + void + add_edge_left (contains_model_type& e) + { + assert (contains_model_ == 0); + contains_model_ = &e; + } + + void + add_edge_left (contains_changeset& e) + { + contains_changeset_.push_back (&e); + } + + virtual string + kind () const {return "changelog";} + + virtual void + serialize (xml::serializer&) const; + + private: + changelog (changelog const&); + changelog& operator= (changelog const&); + + protected: + string database_; + string schema_name_; + contains_model_type* contains_model_; + contains_changeset_list contains_changeset_; + }; + } +} + +#endif // ODB_SEMANTICS_RELATIONAL_CHANGELOG_HXX diff --git a/odb/odb/semantics/relational/changeset.cxx b/odb/odb/semantics/relational/changeset.cxx new file mode 100644 index 0000000..b044a0c --- /dev/null +++ b/odb/odb/semantics/relational/changeset.cxx @@ -0,0 +1,56 @@ +// file : odb/semantics/relational/changeset.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <libcutl/compiler/type-info.hxx> + +#include <odb/semantics/relational/changeset.hxx> + +namespace semantics +{ + namespace relational + { + changeset:: + changeset (changeset const& c, qscope& b, graph& g) + : qscope (c, &b, g), + version_ (c.version_), + alters_model_ (0) + { + } + + changeset:: + changeset (xml::parser& p, qscope& b, graph& g) + : qscope (p, &b, g), + version_ (p.attribute<version_type> ("version")), + alters_model_ (0) + { + } + + void changeset:: + serialize (xml::serializer& s) const + { + s.start_element (xmlns, "changeset"); + s.attribute ("version", version_); + qscope::serialize_content (s); + s.end_element (); + } + + // type info + // + namespace + { + struct init + { + init () + { + using compiler::type_info; + + { + type_info ti (typeid (changeset)); + ti.add_base (typeid (qscope)); + insert (ti); + } + } + } init_; + } + } +} diff --git a/odb/odb/semantics/relational/changeset.hxx b/odb/odb/semantics/relational/changeset.hxx new file mode 100644 index 0000000..efe2c61 --- /dev/null +++ b/odb/odb/semantics/relational/changeset.hxx @@ -0,0 +1,93 @@ +// file : odb/semantics/relational/changeset.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_SEMANTICS_RELATIONAL_CHANGESET_HXX +#define ODB_SEMANTICS_RELATIONAL_CHANGESET_HXX + +#include <odb/semantics/relational/elements.hxx> + +namespace semantics +{ + namespace relational + { + class model; + class changeset; + + class alters_model: public edge + { + public: + typedef relational::model model_type; + typedef relational::changeset changeset_type; + + model_type& + model () const {return *model_;} + + changeset_type& + changeset () const {return *changeset_;} + + public: + alters_model () : model_ (0), changeset_ (0) {} + + void + set_left_node (changeset_type& c) + { + assert (changeset_ == 0); + changeset_ = &c; + } + + void + set_right_node (model_type& m) + { + assert (model_ == 0); + model_ = &m; + } + + protected: + model_type* model_; + changeset_type* changeset_; + }; + + class changeset: public qscope + { + public: + typedef relational::version version_type; + + version_type + version () const {return version_;} + + // Returns model to which this changeset applies (as opposed to the + // ultimate base model). Note that it may not be set. + // + model& + base_model () const {return alters_model_->model ();} + + public: + changeset (version_type v): version_ (v), alters_model_ (0) {} + changeset (changeset const&, qscope& base, graph&); + changeset (xml::parser&, qscope& base, graph&); + + virtual string + kind () const {return "changeset";} + + virtual void + serialize (xml::serializer&) const; + + public: + virtual void + add_edge_left (alters_model& am) + { + assert (alters_model_ == 0); + alters_model_ = &am; + } + + using qscope::add_edge_left; + using qscope::add_edge_right; + + private: + version_type version_; + alters_model* alters_model_; + }; + } +} + +#endif // ODB_SEMANTICS_RELATIONAL_CHANGESET_HXX diff --git a/odb/odb/semantics/relational/column.cxx b/odb/odb/semantics/relational/column.cxx new file mode 100644 index 0000000..9d4d6e5 --- /dev/null +++ b/odb/odb/semantics/relational/column.cxx @@ -0,0 +1,201 @@ +// file : odb/semantics/relational/column.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <libcutl/compiler/type-info.hxx> + +#include <odb/semantics/relational/column.hxx> + +namespace semantics +{ + namespace relational + { + // column + // + column:: + column (column const& c, uscope&, graph& g) + : unameable (c, g), + type_ (c.type_), + null_ (c.null_), + default__ (c.default__), + options_ (c.options_) + { + } + + column:: + column (xml::parser& p, uscope&, graph& g) + : unameable (p, g), + type_ (p.attribute ("type", string ())), + null_ (p.attribute<bool> ("null")), + default__ (p.attribute ("default", string ())), + options_ (p.attribute ("options", string ())) + { + p.content (xml::content::empty); + } + + column& column:: + clone (uscope& s, graph& g) const + { + return g.new_node<column> (*this, s, g); + } + + void column:: + serialize (xml::serializer& s) const + { + s.start_element (xmlns, "column"); + serialize_attributes (s); + s.end_element (); + } + + void column:: + serialize_attributes (xml::serializer& s) const + { + unameable::serialize_attributes (s); + + s.attribute ("type", type ()); + s.attribute ("null", null ()); // Output even if false. + + if (!default_ ().empty ()) + s.attribute ("default", default_ ()); + + if (!options ().empty ()) + s.attribute ("options", options ()); + } + + // add_column + // + add_column& add_column:: + clone (uscope& s, graph& g) const + { + return g.new_node<add_column> (*this, s, g); + } + + void add_column:: + serialize (xml::serializer& s) const + { + s.start_element (xmlns, "add-column"); + column::serialize_attributes (s); + s.end_element (); + } + + // drop_column + // + drop_column:: + drop_column (xml::parser& p, uscope&, graph& g) + : unameable (p, g) + { + p.content (xml::content::empty); + } + + drop_column& drop_column:: + clone (uscope& s, graph& g) const + { + return g.new_node<drop_column> (*this, s, g); + } + + void drop_column:: + serialize (xml::serializer& s) const + { + s.start_element (xmlns, "drop-column"); + unameable::serialize_attributes (s); + s.end_element (); + } + + // alter_column + // + alter_column:: + alter_column (alter_column const& ac, uscope& s, graph& g) + : column (ac, s, g), + alters_ (0), + null_altered_ (ac.null_altered_) + { + column* b (s.lookup<column, drop_column> (ac.name ())); + assert (b != 0); + g.new_edge<alters> (*this, *b); + } + + alter_column:: + alter_column (xml::parser& p, uscope& s, graph& g) + : column (p, s, g), + alters_ (0), + null_altered_ (p.attribute_present ("null")) + { + name_type n (p.attribute<name_type> ("name")); + column* b (s.lookup<column, drop_column> (n)); + assert (b != 0); + g.new_edge<alters> (*this, *b); + } + + alter_column& alter_column:: + clone (uscope& s, graph& g) const + { + return g.new_node<alter_column> (*this, s, g); + } + + void alter_column:: + serialize (xml::serializer& s) const + { + s.start_element (xmlns, "alter-column"); + + // Here we override the standard column logic. + // + unameable::serialize_attributes (s); + + if (null_altered_) + s.attribute ("null", null_); + + s.end_element (); + } + + // type info + // + namespace + { + struct init + { + init () + { + unameable::parser_map& m (unameable::parser_map_); + + m["column"] = &unameable::parser_impl<column>; + m["add-column"] = &unameable::parser_impl<add_column>; + m["drop-column"] = &unameable::parser_impl<drop_column>; + m["alter-column"] = &unameable::parser_impl<alter_column>; + + using compiler::type_info; + + // column + // + { + type_info ti (typeid (column)); + ti.add_base (typeid (unameable)); + insert (ti); + } + + // add_column + // + { + type_info ti (typeid (add_column)); + ti.add_base (typeid (column)); + insert (ti); + } + + // drop_column + // + { + type_info ti (typeid (drop_column)); + ti.add_base (typeid (unameable)); + insert (ti); + } + + // alter_column + // + { + type_info ti (typeid (alter_column)); + ti.add_base (typeid (column)); + insert (ti); + } + } + } init_; + } + } +} diff --git a/odb/odb/semantics/relational/column.hxx b/odb/odb/semantics/relational/column.hxx new file mode 100644 index 0000000..b7a2c31 --- /dev/null +++ b/odb/odb/semantics/relational/column.hxx @@ -0,0 +1,205 @@ +// file : odb/semantics/relational/column.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_SEMANTICS_RELATIONAL_COLUMN_HXX +#define ODB_SEMANTICS_RELATIONAL_COLUMN_HXX + +#include <odb/semantics/relational/elements.hxx> +#include <odb/semantics/relational/table.hxx> + +namespace semantics +{ + namespace relational + { + class contains; + + class column: public unameable + { + typedef std::vector<contains*> contained_list; + + public: + virtual string const& + type () const {return type_;} + + virtual bool + null () const {return null_;} + + virtual void + null (bool n) {null_ = n;} + + virtual string const& + default_ () const {return default__;} + + virtual void + default_ (string const& d) {default__ = d;} + + virtual string const& + options () const {return options_;} + + virtual void + options (string const& o) {options_ = o;} + + public: + typedef relational::table table_type; + + table_type& + table () const {return dynamic_cast<table_type&> (scope ());} + + // Key containment. + // + public: + typedef + pointer_iterator<contained_list::const_iterator> + contained_iterator; + + contained_iterator + contained_begin () const {return contained_.begin ();} + + contained_iterator + contained_end () const {return contained_.end ();} + + public: + column (string const& id, string const& type, bool null) + : unameable (id), type_ (type), null_ (null) + { + } + + column (column const&, uscope&, graph&); + column (xml::parser&, uscope&, graph&); + + virtual column& + clone (uscope&, graph&) const; + + void + add_edge_right (contains& e) + { + contained_.push_back (&e); + } + + using unameable::add_edge_right; + + virtual string + kind () const + { + return "column"; + } + + virtual void + serialize (xml::serializer&) const; + + protected: + void + serialize_attributes (xml::serializer&) const; + + protected: + string type_; + bool null_; + string default__; + string options_; + + private: + contained_list contained_; + }; + + class add_column: public column + { + public: + add_column (string const& id, string const& type, bool null) + : column (id, type, null) {} + add_column (column const& c, uscope& s, graph& g): column (c, s, g) {} + add_column (xml::parser& p, uscope& s, graph& g): column (p, s, g) {} + + virtual add_column& + clone (uscope&, graph&) const; + + virtual string + kind () const {return "add column";} + + virtual void + serialize (xml::serializer&) const; + }; + + class drop_column: public unameable + { + public: + drop_column (string const& id): unameable (id) {} + drop_column (drop_column const& c, uscope&, graph& g) + : unameable (c, g) {} + drop_column (xml::parser&, uscope&, graph&); + + public: + typedef relational::table table_type; + + table_type& + table () const {return dynamic_cast<table_type&> (scope ());} + + public: + virtual drop_column& + clone (uscope&, graph&) const; + + virtual string + kind () const {return "drop column";} + + virtual void + serialize (xml::serializer&) const; + }; + + class alter_column: public column + { + public: + virtual string const& + type () const {return base ().type ();} + + bool + null_altered () const {return null_altered_;} + + virtual bool + null () const {return null_altered_ ? null_ : base ().null ();} + + virtual void + null (bool n) {null_ = n; null_altered_ = true;} + + virtual string const& + default_ () const {return base ().default_ ();} + + virtual string const& + options () const {return base ().options ();} + + public: + column& + base () const {return dynamic_cast<column&> (alters_->base ());} + + public: + alter_column (string const& id) + : column (id, "", false), alters_ (0), null_altered_ (false) {} + alter_column (alter_column const&, uscope&, graph&); + alter_column (xml::parser&, uscope&, graph&); + + virtual alter_column& + clone (uscope&, graph&) const; + + virtual string + kind () const + { + return "alter column"; + } + + virtual void + serialize (xml::serializer&) const; + + virtual void + add_edge_left (alters& a) + { + assert (alters_ == 0); + alters_ = &a; + } + + private: + alters* alters_; + + bool null_altered_; + }; + } +} + +#endif // ODB_SEMANTICS_RELATIONAL_COLUMN_HXX diff --git a/odb/odb/semantics/relational/deferrable.cxx b/odb/odb/semantics/relational/deferrable.cxx new file mode 100644 index 0000000..076ff69 --- /dev/null +++ b/odb/odb/semantics/relational/deferrable.cxx @@ -0,0 +1,55 @@ +// file : odb/semantics/relational/deferrable.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <ostream> +#include <istream> + +#include <odb/semantics/relational/deferrable.hxx> + +using namespace std; + +namespace semantics +{ + namespace relational + { + static const char* deferrable_[] = + { + "NOT DEFERRABLE", + "IMMEDIATE", + "DEFERRED" + }; + + string deferrable:: + string () const + { + return deferrable_[v_]; + } + + ostream& + operator<< (ostream& os, deferrable const& v) + { + return os << v.string (); + } + + istream& + operator>> (istream& is, deferrable& v) + { + string s; + is >> s; + + if (!is.fail ()) + { + if (s == "not_deferrable" || s == "NOT DEFERRABLE") + v = deferrable::not_deferrable; + else if (s == "immediate" || s == "IMMEDIATE") + v = deferrable::immediate; + else if (s == "deferred" || s == "DEFERRED") + v = deferrable::deferred; + else + is.setstate (istream::failbit); + } + + return is; + } + } +} diff --git a/odb/odb/semantics/relational/deferrable.hxx b/odb/odb/semantics/relational/deferrable.hxx new file mode 100644 index 0000000..b2f888d --- /dev/null +++ b/odb/odb/semantics/relational/deferrable.hxx @@ -0,0 +1,41 @@ +// file : odb/semantics/relational/deferrable.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_SEMANTICS_RELATIONAL_DEFERRABLE_HXX +#define ODB_SEMANTICS_RELATIONAL_DEFERRABLE_HXX + +#include <string> +#include <iosfwd> + +namespace semantics +{ + namespace relational + { + struct deferrable + { + enum value + { + not_deferrable, + immediate, + deferred + }; + + deferrable (value v = value (0)) : v_ (v) {} + operator value () const {return v_;} + + std::string + string () const; + + private: + value v_; + }; + + std::ostream& + operator<< (std::ostream&, deferrable const&); + + std::istream& + operator>> (std::istream&, deferrable&); + } +} + +#endif // ODB_SEMANTICS_RELATIONAL_DEFERRABLE_HXX diff --git a/odb/odb/semantics/relational/elements.cxx b/odb/odb/semantics/relational/elements.cxx new file mode 100644 index 0000000..de1878a --- /dev/null +++ b/odb/odb/semantics/relational/elements.cxx @@ -0,0 +1,179 @@ +// file : odb/semantics/relational/elements.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <libcutl/compiler/type-info.hxx> + +#include <odb/semantics/relational/elements.hxx> +#include <odb/semantics/relational/column.hxx> +#include <odb/semantics/relational/primary-key.hxx> + +namespace semantics +{ + namespace relational + { + string const xmlns = "http://www.codesynthesis.com/xmlns/odb/changelog"; + + // duplicate_name + // + template <> + duplicate_name:: + duplicate_name (uscope& s, unameable& o, unameable& d) + : scope (s), orig (o), dup (d), name (o.name ()) + { + } + + template <> + duplicate_name:: + duplicate_name (qscope& s, qnameable& o, qnameable& d) + : scope (s), orig (o), dup (d), name (o.name ().string ()) + { + } + + // scope<uname> + // + template <> + void scope<uname>:: + add_edge_left (names_type& e) + { + nameable_type& n (e.nameable ()); + name_type const& name (e.name ()); + + typename names_map::iterator i (names_map_.find (name)); + + if (i == names_map_.end ()) + { + typename names_list::iterator i; + + // We want the order to be add/alter columns first, then the + // primary key, then other keys, and finnally drop columns. + // + if (n.is_a<column> () || + n.is_a<add_column> () || + n.is_a<alter_column> ()) + { + i = names_.insert (first_key_, &e); + } + else if (!n.is_a<drop_column> ()) + { + if (n.is_a<primary_key> ()) + first_key_ = i = names_.insert ( + first_key_ != names_.end () ? first_key_ : first_drop_column_, + &e); + else + { + i = names_.insert (first_drop_column_, &e); + + if (first_key_ == names_.end ()) + first_key_ = i; + } + } + else + { + i = names_.insert (names_.end (), &e); + + if (first_drop_column_ == names_.end ()) + first_drop_column_ = i; + } + + names_map_[name] = i; + iterator_map_[&e] = i; + } + else + throw duplicate_name (*this, (*i->second)->nameable (), n); + } + + template <> + void scope<uname>:: + remove_edge_left (names_type& e) + { + typename names_iterator_map::iterator i (iterator_map_.find (&e)); + assert (i != iterator_map_.end ()); + + // If we are removing the first key, then move to the next key (or + // the end which means there are no keys). + // + if (first_key_ == i->second) + first_key_++; + + // The same for the first drop column. + // + if (first_drop_column_ == i->second) + first_drop_column_++; + + names_.erase (i->second); + names_map_.erase (e.name ()); + iterator_map_.erase (i); + } + + // type info + // + namespace + { + struct init + { + init () + { + using compiler::type_info; + + // node + // + insert (type_info (typeid (node))); + + // edge + // + insert (type_info (typeid (edge))); + + // alters + // + { + type_info ti (typeid (alters)); + ti.add_base (typeid (edge)); + insert (ti); + } + + // names + // + { + type_info ti (typeid (unames)); + ti.add_base (typeid (edge)); + insert (ti); + } + + { + type_info ti (typeid (qnames)); + ti.add_base (typeid (edge)); + insert (ti); + } + + // nameable + // + { + type_info ti (typeid (unameable)); + ti.add_base (typeid (node)); + insert (ti); + } + + { + type_info ti (typeid (qnameable)); + ti.add_base (typeid (node)); + insert (ti); + } + + // scope + // + { + type_info ti (typeid (uscope)); + ti.add_base (typeid (node)); + insert (ti); + } + + { + type_info ti (typeid (qscope)); + ti.add_base (typeid (node)); + insert (ti); + } + } + } init_; + } + } +} diff --git a/odb/odb/semantics/relational/elements.hxx b/odb/odb/semantics/relational/elements.hxx new file mode 100644 index 0000000..06ec552 --- /dev/null +++ b/odb/odb/semantics/relational/elements.hxx @@ -0,0 +1,468 @@ +// file : odb/semantics/relational/elements.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_SEMANTICS_RELATIONAL_ELEMENTS_HXX +#define ODB_SEMANTICS_RELATIONAL_ELEMENTS_HXX + +#include <map> +#include <list> +#include <vector> +#include <string> +#include <cassert> + +#include <libcutl/container/graph.hxx> +#include <libcutl/container/pointer-iterator.hxx> +#include <libcutl/compiler/context.hxx> + +#ifdef ODB_BUILD2 +#include <libstudxml/parser.hxx> +#include <libstudxml/serializer.hxx> +#else +#include <libcutl/xml/parser.hxx> +#include <libcutl/xml/serializer.hxx> +namespace cutl {namespace xml {typedef parser content;}} +#endif + +#include <odb/semantics/relational/name.hxx> + +namespace semantics +{ + namespace relational + { + using namespace cutl; + + using std::string; + + using container::pointer_iterator; + using compiler::context; + + typedef unsigned long long version; + + // + // + extern string const xmlns; + + // + // + class node; + class edge; + + typedef container::graph<node, edge> graph; + + // + // + class edge: public context + { + public: + template <typename X> + bool + is_a () const + { + return dynamic_cast<X const*> (this) != 0; + } + + public: + virtual + ~edge () {} + }; + + // + // + class node: public context + { + // Return name of the node. + // + public: + virtual string + kind () const = 0; + + public: + template <typename X> + bool + is_a () const + { + return dynamic_cast<X const*> (this) != 0; + } + + public: + virtual + ~node () {} + + // XML serialization. + // + virtual void + serialize (xml::serializer&) const = 0; + + // Sink functions that allow extensions in the form of one-way + // edges. + // + void + add_edge_right (edge&) {} + + void + remove_edge_right (edge&) {} + }; + + // + // + class alters: public edge + { + public: + node& + base () const {return *base_;} + + node& + modifier () const {return *modifier_;} + + public: + alters () : base_ (0), modifier_ (0) {} + + void + set_left_node (node& m) + { + assert (modifier_ == 0); + modifier_ = &m; + } + + void + set_right_node (node& b) + { + assert (base_ == 0); + base_ = &b; + } + + void + clear_left_node (node& m) + { + assert (modifier_ == &m); + modifier_ = 0; + } + + void + clear_right_node (node& b) + { + assert (base_ == &b); + base_ = 0; + } + + protected: + node* base_; + node* modifier_; + }; + + // + // + template <typename N> + class scope; + + template <typename N> + class nameable; + + // + // + template <typename N> + class names: public edge + { + public: + typedef N name_type; + typedef relational::scope<N> scope_type; + typedef relational::nameable<N> nameable_type; + + name_type const& + name () const + { + return name_; + } + + scope_type& + scope () const + { + return *scope_; + } + + nameable_type& + nameable () const + { + return *nameable_; + } + + public: + names (name_type const& name): name_ (name) {} + + void + set_left_node (scope_type& n) + { + scope_ = &n; + } + + void + set_right_node (nameable_type& n) + { + nameable_ = &n; + } + + void + clear_left_node (scope_type& n) + { + assert (scope_ == &n); + scope_ = 0; + } + + void + clear_right_node (nameable_type& n) + { + assert (nameable_ == &n); + nameable_ = 0; + } + + protected: + name_type name_; + scope_type* scope_; + nameable_type* nameable_; + }; + + typedef names<uname> unames; + typedef names<qname> qnames; + + // + // + template <typename N> + class nameable: public virtual node + { + public: + typedef N name_type; + typedef relational::names<N> names_type; + typedef relational::scope<N> scope_type; + + name_type const& + name () const {return named_->name ();} + + scope_type& + scope () const {return named ().scope ();} + + names_type& + named () const {return *named_;} + + string const& + id () const {return id_;} + + public: + // Id identifies the C++ node (e.g., a class or a data member) that + // this model node corresponds to. The ids are not necessarily unique + // (e.g., there can be a foreign key and an index with the same id that + // correspond to a container member). However, in any given scope, the + // {id,typeid} must be unique. This becomes important when we try to + // find correspondance between nodes during model diff'ing. + // + nameable (string const& id): id_ (id), named_ (0) {} + + virtual nameable& + clone (scope_type&, graph&) const = 0; + + // Virtual because we call it via nameable interface (e.g., in copy). + // + virtual void + add_edge_right (names_type& e) + { + assert (named_ == 0); + named_ = &e; + } + + virtual void + remove_edge_right (names_type& e) + { + assert (named_ == &e); + named_ = 0; + } + + using node::add_edge_right; + using node::remove_edge_right; + + protected: + nameable (nameable const&, graph& g); + nameable (xml::parser&, graph& g); + + void + serialize_attributes (xml::serializer&) const; + + public: + typedef void (*parser_func) (xml::parser&, scope_type&, graph&); + typedef std::map<std::string, parser_func> parser_map; + static parser_map parser_map_; + + template <typename T> + static void + parser_impl (xml::parser&, scope_type&, graph&); + + private: + string id_; + names_type* named_; + }; + + typedef nameable<uname> unameable; + typedef nameable<qname> qnameable; + + + // + // + struct duplicate_name + { + template <typename N> + duplicate_name (relational::scope<N>&, + relational::nameable<N>& orig, + relational::nameable<N>& dup); + + node& scope; + node& orig; + node& dup; + + string name; + }; + + template <typename N> + class scope: public virtual node + { + protected: + typedef N name_type; + typedef relational::names<N> names_type; + typedef relational::nameable<N> nameable_type; + + typedef std::list<names_type*> names_list; + typedef std::map<name_type, typename names_list::iterator> names_map; + typedef + std::map<names_type const*, typename names_list::iterator> + names_iterator_map; + + public: + typedef pointer_iterator<typename names_list::iterator> names_iterator; + typedef + pointer_iterator<typename names_list::const_iterator> + names_const_iterator; + + public: + // Iteration. + // + names_iterator + names_begin () + { + return names_.begin (); + } + + names_iterator + names_end () + { + return names_.end (); + } + + names_const_iterator + names_begin () const + { + return names_.begin (); + } + + names_const_iterator + names_end () const + { + return names_.end (); + } + + bool + names_empty () const + { + return names_.empty (); + } + + // Find (this scope only). + // + template <typename T> + T* + find (name_type const&); + + names_iterator + find (name_type const&); + + names_const_iterator + find (name_type const&) const; + + names_iterator + find (names_type const&); + + names_const_iterator + find (names_type const&) const; + + // Lookup in this and all altered scopes until we find what we are + // looking for or hit a stop node of type S (e.g., drop_*). + // + template <typename T, typename S> + T* + lookup (name_type const&); + + public: + scope* + base () const + { + return alters_ != 0 ? &dynamic_cast<scope&> (alters_->base ()) : 0; + } + + public: + scope () + : first_key_ (names_.end ()), + first_drop_column_ (names_.end ()), + alters_ (0) {} + + // Virtual because we call it via scope interface (e.g., in copy). + // + virtual void + add_edge_left (alters& a) + { + assert (alters_ == 0); + alters_ = &a; + } + + virtual void + remove_edge_left (alters& a) + { + assert (alters_ == &a); + alters_ = 0; + } + + virtual void + add_edge_left (names_type&); + + virtual void + remove_edge_left (names_type&); + + protected: + scope (scope const&, scope* base, graph&); + scope (xml::parser&, scope* base, graph&); + + void + serialize_content (xml::serializer&) const; + + protected: + names_list names_; + names_map names_map_; + names_iterator_map iterator_map_; + + typename names_list::iterator first_key_; + typename names_list::iterator first_drop_column_; + + alters* alters_; + }; + + template <> + void scope<uname>:: + add_edge_left (names_type&); + + template <> + void scope<uname>:: + remove_edge_left (names_type&); + + typedef scope<uname> uscope; + typedef scope<qname> qscope; + } +} + +#include <odb/semantics/relational/elements.txx> + +#endif // ODB_SEMANTICS_RELATIONAL_ELEMENTS_HXX diff --git a/odb/odb/semantics/relational/elements.txx b/odb/odb/semantics/relational/elements.txx new file mode 100644 index 0000000..2362d48 --- /dev/null +++ b/odb/odb/semantics/relational/elements.txx @@ -0,0 +1,215 @@ +// file : odb/semantics/relational/elements.txx +// license : GNU GPL v3; see accompanying LICENSE file + +namespace semantics +{ + namespace relational + { + // nameable + // + template <typename N> + typename nameable<N>::parser_map nameable<N>::parser_map_; + + template <typename N> + template <typename T> + void nameable<N>:: + parser_impl (xml::parser& p, scope_type& s, graph& g) + { + name_type n (p.attribute ("name", name_type ())); + T& x (g.new_node<T> (p, s, g)); + g.new_edge<names_type> (s, x, n); + } + + template <typename N> + nameable<N>:: + nameable (nameable const& n, graph&) + : id_ (n.id_), named_ (0) + { + } + + template <typename N> + nameable<N>:: + nameable (xml::parser&, graph&) + // : id_ (p.attribute<string> ("id")) + : named_ (0) + { + // The name attribute is handled in parser_impl(). + } + + template <typename N> + void nameable<N>:: + serialize_attributes (xml::serializer& s) const + { + // Omit empty names (e.g., a primary key). + // + name_type const& n (name ()); + if (!n.empty ()) + s.attribute ("name", n); + + //s.attribute ("id", id_); + } + + // scope + // + + template <typename N> + template <typename T, typename S> + T* scope<N>:: + lookup (name_type const& name) + { + if (T* r = find<T> (name)) + return r; + + if (scope* b = base ()) + { + if (find<S> (name) == 0) + return b->lookup<T, S> (name); + } + + return 0; + } + + template <typename N> + template <typename T> + T* scope<N>:: + find (name_type const& name) + { + typename names_map::iterator i (names_map_.find (name)); + return i != names_map_.end () + ? dynamic_cast<T*> (&(*i->second)->nameable ()) + : 0; + } + + template <typename N> + typename scope<N>::names_iterator scope<N>:: + find (name_type const& name) + { + typename names_map::iterator i (names_map_.find (name)); + + if (i == names_map_.end ()) + return names_.end (); + else + return i->second; + } + + template <typename N> + typename scope<N>::names_const_iterator scope<N>:: + find (name_type const& name) const + { + typename names_map::const_iterator i (names_map_.find (name)); + + if (i == names_map_.end ()) + return names_.end (); + else + return names_const_iterator (i->second); + } + + template <typename N> + typename scope<N>::names_iterator scope<N>:: + find (names_type const& e) + { + typename names_iterator_map::iterator i (iterator_map_.find (&e)); + return i != iterator_map_.end () ? i->second : names_.end (); + } + + template <typename N> + typename scope<N>::names_const_iterator scope<N>:: + find (names_type const& e) const + { + typename names_iterator_map::const_iterator i (iterator_map_.find (&e)); + return i != iterator_map_.end () ? i->second : names_.end (); + } + + template <typename N> + scope<N>:: + scope (scope const& s, scope* base, graph& g) + : first_key_ (names_.end ()), + first_drop_column_ (names_.end ()), + alters_ (0) + { + // Set the alters edge for lookup. + // + if (base != 0) + g.new_edge<alters> (*this, *base); + + for (names_const_iterator i (s.names_begin ()); + i != s.names_end (); ++i) + { + nameable_type& n (i->nameable ().clone (*this, g)); + g.new_edge<names_type> (*this, n, i->name ()); + } + } + + template <typename N> + scope<N>:: + scope (xml::parser& p, scope* base, graph& g) + : first_key_ (names_.end ()), + first_drop_column_ (names_.end ()), + alters_ (0) + { + // Set the alters edge for lookup. + // + if (base != 0) + g.new_edge<alters> (*this, *base); + + using namespace xml; + p.content (content::complex); + + for (parser::event_type e (p.peek ()); + e == parser::start_element; + e = p.peek ()) + { + typename nameable_type::parser_map::iterator i ( + nameable_type::parser_map_.find (p.name ())); + + if (p.namespace_ () != xmlns || i == nameable_type::parser_map_.end ()) + break; // Not one of our elements. + + p.next (); + i->second (p, *this, g); + p.next_expect (parser::end_element); + } + } + + template <typename N> + void scope<N>:: + serialize_content (xml::serializer& s) const + { + for (names_const_iterator i (names_begin ()); i != names_end (); ++i) + i->nameable ().serialize (s); + } + + class column; + class primary_key; + + template <typename N> + void scope<N>:: + add_edge_left (names_type& e) + { + name_type const& name (e.name ()); + + typename names_map::iterator i (names_map_.find (name)); + + if (i == names_map_.end ()) + { + typename names_list::iterator i (names_.insert (names_.end (), &e)); + names_map_[name] = i; + iterator_map_[&e] = i; + } + else + throw duplicate_name (*this, (*i->second)->nameable (), e.nameable ()); + } + + template <typename N> + void scope<N>:: + remove_edge_left (names_type& e) + { + typename names_iterator_map::iterator i (iterator_map_.find (&e)); + assert (i != iterator_map_.end ()); + + names_.erase (i->second); + names_map_.erase (e.name ()); + iterator_map_.erase (i); + } + } +} diff --git a/odb/odb/semantics/relational/foreign-key.cxx b/odb/odb/semantics/relational/foreign-key.cxx new file mode 100644 index 0000000..0357d95 --- /dev/null +++ b/odb/odb/semantics/relational/foreign-key.cxx @@ -0,0 +1,218 @@ +// file : odb/semantics/relational/foreign-key.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <ostream> +#include <istream> + +#include <libcutl/compiler/type-info.hxx> + +#include <odb/semantics/relational/foreign-key.hxx> + +using namespace std; + +namespace semantics +{ + namespace relational + { + static const char* action_str[] = {"NO ACTION", "CASCADE", "SET NULL"}; + + ostream& + operator<< (ostream& os, foreign_key::action_type v) + { + return os << action_str[v]; + } + + istream& + operator>> (istream& is, foreign_key::action_type& v) + { + string s; + getline (is, s); + + if (!is.eof ()) + is.setstate (istream::failbit); + + if (!is.fail ()) + { + if (s == "NO ACTION") + v = foreign_key::no_action; + else if (s == "CASCADE") + v = foreign_key::cascade; + else if (s == "SET NULL") + v = foreign_key::set_null; + else + is.setstate (istream::failbit); + } + + return is; + } + + foreign_key:: + foreign_key (foreign_key const& k, uscope& s, graph& g) + : key (k, s, g), + referenced_table_ (k.referenced_table_), + referenced_columns_ (k.referenced_columns_), + deferrable_ (k.deferrable_), + on_delete_ (k.on_delete_) + { + } + + foreign_key:: + foreign_key (xml::parser& p, uscope& s, graph& g) + : key (p, s, g), + deferrable_ (p.attribute ("deferrable", deferrable_type ())), + on_delete_ (p.attribute ("on-delete", no_action)) + { + using namespace xml; + + p.next_expect (parser::start_element, xmlns, "references"); + referenced_table_ = p.attribute<qname> ("table"); + p.content (content::complex); + + for (parser::event_type e (p.peek ()); + e == parser::start_element; + e = p.peek ()) + { + if (p.qname () != xml::qname (xmlns, "column")) + break; // Not our elements. + + p.next (); + referenced_columns_.push_back (p.attribute<uname> ("name")); + p.content (content::empty); + p.next_expect (parser::end_element); + } + + p.next_expect (parser::end_element); + } + + foreign_key& foreign_key:: + clone (uscope& s, graph& g) const + { + return g.new_node<foreign_key> (*this, s, g); + } + + void foreign_key:: + serialize_attributes (xml::serializer& s) const + { + key::serialize_attributes (s); + + if (deferrable () != deferrable_type::not_deferrable) + s.attribute ("deferrable", deferrable ()); + + if (on_delete () != no_action) + s.attribute ("on-delete", on_delete ()); + } + + void foreign_key:: + serialize_content (xml::serializer& s) const + { + key::serialize_content (s); + + // Referenced columns. + // + s.start_element (xmlns, "references"); + s.attribute ("table", referenced_table ()); + + for (columns::const_iterator i (referenced_columns_.begin ()); + i != referenced_columns_.end (); ++i) + { + s.start_element (xmlns, "column"); + s.attribute ("name", *i); + s.end_element (); + } + + s.end_element (); // references + } + + void foreign_key:: + serialize (xml::serializer& s) const + { + s.start_element (xmlns, "foreign-key"); + serialize_attributes (s); + serialize_content (s); + s.end_element (); // foreign-key + } + + // add_foreign_key + // + add_foreign_key& add_foreign_key:: + clone (uscope& s, graph& g) const + { + return g.new_node<add_foreign_key> (*this, s, g); + } + + void add_foreign_key:: + serialize (xml::serializer& s) const + { + s.start_element (xmlns, "add-foreign-key"); + foreign_key::serialize_attributes (s); + foreign_key::serialize_content (s); + s.end_element (); + } + + // drop_foreign_key + // + drop_foreign_key:: + drop_foreign_key (xml::parser& p, uscope&, graph& g) + : unameable (p, g) + { + p.content (xml::content::empty); + } + + drop_foreign_key& drop_foreign_key:: + clone (uscope& s, graph& g) const + { + return g.new_node<drop_foreign_key> (*this, s, g); + } + + void drop_foreign_key:: + serialize (xml::serializer& s) const + { + s.start_element (xmlns, "drop-foreign-key"); + unameable::serialize_attributes (s); + s.end_element (); + } + + // type info + // + namespace + { + struct init + { + init () + { + unameable::parser_map& m (unameable::parser_map_); + + m["foreign-key"] = &unameable::parser_impl<foreign_key>; + m["add-foreign-key"] = &unameable::parser_impl<add_foreign_key>; + m["drop-foreign-key"] = &unameable::parser_impl<drop_foreign_key>; + + using compiler::type_info; + + // foreign_key + // + { + type_info ti (typeid (foreign_key)); + ti.add_base (typeid (key)); + insert (ti); + } + + // add_foreign_key + // + { + type_info ti (typeid (add_foreign_key)); + ti.add_base (typeid (foreign_key)); + insert (ti); + } + + // drop_foreign_key + // + { + type_info ti (typeid (drop_foreign_key)); + ti.add_base (typeid (unameable)); + insert (ti); + } + } + } init_; + } + } +} diff --git a/odb/odb/semantics/relational/foreign-key.hxx b/odb/odb/semantics/relational/foreign-key.hxx new file mode 100644 index 0000000..32179fa --- /dev/null +++ b/odb/odb/semantics/relational/foreign-key.hxx @@ -0,0 +1,152 @@ +// file : odb/semantics/relational/foreign-key.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_SEMANTICS_RELATIONAL_FOREIGN_KEY_HXX +#define ODB_SEMANTICS_RELATIONAL_FOREIGN_KEY_HXX + +#include <iosfwd> + +#include <odb/semantics/relational/deferrable.hxx> +#include <odb/semantics/relational/elements.hxx> +#include <odb/semantics/relational/key.hxx> + +namespace semantics +{ + namespace relational + { + class foreign_key: public key + { + public: + qname const& + referenced_table () const + { + return referenced_table_; + } + + typedef std::vector<string> columns; + + columns const& + referenced_columns () const + { + return referenced_columns_; + } + + columns& + referenced_columns () + { + return referenced_columns_; + } + + public: + typedef relational::deferrable deferrable_type; + + deferrable_type + deferrable () const {return deferrable_;} + + bool + not_deferrable () const + { + return deferrable_ == deferrable_type::not_deferrable; + } + + enum action_type + { + no_action, + cascade, + set_null + }; + + action_type + on_delete () const {return on_delete_;} + + public: + foreign_key (string const& id, + qname const& referenced_table, + deferrable_type deferrable, + action_type on_delete = no_action) + : key (id), + referenced_table_ (referenced_table), + deferrable_ (deferrable), + on_delete_ (on_delete) + { + } + + foreign_key (foreign_key const&, uscope&, graph&); + foreign_key (xml::parser&, uscope&, graph&); + + virtual foreign_key& + clone (uscope&, graph&) const; + + virtual string + kind () const + { + return "foreign key"; + } + + virtual void + serialize (xml::serializer&) const; + + protected: + void + serialize_attributes (xml::serializer&) const; + + void + serialize_content (xml::serializer&) const; + + private: + qname referenced_table_; + columns referenced_columns_; + deferrable_type deferrable_; + action_type on_delete_; + }; + + std::ostream& + operator<< (std::ostream&, foreign_key::action_type); + + std::istream& + operator>> (std::istream&, foreign_key::action_type&); + + class add_foreign_key: public foreign_key + { + public: + add_foreign_key (string const& id, + qname const& rt, + deferrable_type d, + action_type od = no_action) + : foreign_key (id, rt, d, od) {} + add_foreign_key (foreign_key const& fk, uscope& s, graph& g) + : foreign_key (fk, s, g) {} + add_foreign_key (xml::parser& p, uscope& s, graph& g) + : foreign_key (p, s, g) {} + + virtual add_foreign_key& + clone (uscope&, graph&) const; + + virtual string + kind () const {return "add foreign key";} + + virtual void + serialize (xml::serializer&) const; + }; + + class drop_foreign_key: public unameable + { + public: + drop_foreign_key (string const& id): unameable (id) {} + drop_foreign_key (drop_foreign_key const& dfk, uscope&, graph& g) + : unameable (dfk, g) {} + drop_foreign_key (xml::parser&, uscope&, graph&); + + virtual drop_foreign_key& + clone (uscope&, graph&) const; + + virtual string + kind () const {return "drop foreign key";} + + virtual void + serialize (xml::serializer&) const; + }; + } +} + +#endif // ODB_SEMANTICS_RELATIONAL_FOREIGN_KEY_HXX diff --git a/odb/odb/semantics/relational/index.cxx b/odb/odb/semantics/relational/index.cxx new file mode 100644 index 0000000..2329f3a --- /dev/null +++ b/odb/odb/semantics/relational/index.cxx @@ -0,0 +1,145 @@ +// file : odb/semantics/relational/index.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <libcutl/compiler/type-info.hxx> + +#include <odb/semantics/relational/index.hxx> + +namespace semantics +{ + namespace relational + { + // index + // + index:: + index (index const& i, uscope& s, graph& g) + : key (i, s, g), + type_ (i.type_), + method_ (i.method_), + options_ (i.options_) + { + } + + index:: + index (xml::parser& p, uscope& s, graph& g) + : key (p, s, g), + type_ (p.attribute ("type", string ())), + method_ (p.attribute ("method", string ())), + options_ (p.attribute ("options", string ())) + { + } + + index& index:: + clone (uscope& s, graph& g) const + { + return g.new_node<index> (*this, s, g); + } + + void index:: + serialize_attributes (xml::serializer& s) const + { + key::serialize_attributes (s); + + if (!type ().empty ()) + s.attribute ("type", type ()); + + if (!method ().empty ()) + s.attribute ("method", method ()); + + if (!options ().empty ()) + s.attribute ("options", options ()); + } + + void index:: + serialize (xml::serializer& s) const + { + s.start_element (xmlns, "index"); + serialize_attributes (s); + key::serialize_content (s); + s.end_element (); + } + + // add_index + // + add_index& add_index:: + clone (uscope& s, graph& g) const + { + return g.new_node<add_index> (*this, s, g); + } + + void add_index:: + serialize (xml::serializer& s) const + { + s.start_element (xmlns, "add-index"); + index::serialize_attributes (s); + index::serialize_content (s); + s.end_element (); + } + + // drop_index + // + drop_index:: + drop_index (xml::parser& p, uscope&, graph& g) + : unameable (p, g) + { + p.content (xml::content::empty); + } + + drop_index& drop_index:: + clone (uscope& s, graph& g) const + { + return g.new_node<drop_index> (*this, s, g); + } + + void drop_index:: + serialize (xml::serializer& s) const + { + s.start_element (xmlns, "drop-index"); + unameable::serialize_attributes (s); + s.end_element (); + } + + // type info + // + namespace + { + struct init + { + init () + { + unameable::parser_map& m (unameable::parser_map_); + + m["index"] = &unameable::parser_impl<index>; + m["add-index"] = &unameable::parser_impl<add_index>; + m["drop-index"] = &unameable::parser_impl<drop_index>; + + using compiler::type_info; + + // index + // + { + type_info ti (typeid (index)); + ti.add_base (typeid (key)); + insert (ti); + } + + // add_index + // + { + type_info ti (typeid (add_index)); + ti.add_base (typeid (index)); + insert (ti); + } + + // drop_index + // + { + type_info ti (typeid (drop_index)); + ti.add_base (typeid (unameable)); + insert (ti); + } + } + } init_; + } + } +} diff --git a/odb/odb/semantics/relational/index.hxx b/odb/odb/semantics/relational/index.hxx new file mode 100644 index 0000000..68648cb --- /dev/null +++ b/odb/odb/semantics/relational/index.hxx @@ -0,0 +1,100 @@ +// file : odb/semantics/relational/index.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_SEMANTICS_RELATIONAL_INDEX_HXX +#define ODB_SEMANTICS_RELATIONAL_INDEX_HXX + +#include <odb/semantics/relational/elements.hxx> +#include <odb/semantics/relational/key.hxx> + +namespace semantics +{ + namespace relational + { + // Note that in our model indexes are defined in the table scope. + // + class index: public key + { + public: + string const& + type () const {return type_;} + + string const& + method () const {return method_;} + + string const& + options () const {return options_;} + + public: + index (string const& id, + string const& t = string (), + string const& m = string (), + string const& o = string ()) + : key (id), type_ (t), method_ (m), options_ (o) {} + index (index const&, uscope&, graph&); + index (xml::parser&, uscope&, graph&); + + virtual index& + clone (uscope&, graph&) const; + + virtual string + kind () const + { + return "index"; + } + + virtual void + serialize (xml::serializer&) const; + + protected: + void + serialize_attributes (xml::serializer&) const; + + private: + string type_; // E.g., "UNIQUE", etc. + string method_; // E.g., "BTREE", etc. + string options_; // Database-specific index options. + }; + + class add_index: public index + { + public: + add_index (string const& id, + string const& t = string (), + string const& m = string (), + string const& o = string ()) + : index (id, t, m, o) {} + add_index (index const& i, uscope& s, graph& g): index (i, s, g) {} + add_index (xml::parser& p, uscope& s, graph& g): index (p, s, g) {} + + virtual add_index& + clone (uscope&, graph&) const; + + virtual string + kind () const {return "add index";} + + virtual void + serialize (xml::serializer&) const; + }; + + class drop_index: public unameable + { + public: + drop_index (string const& id): unameable (id) {} + drop_index (drop_index const& di, uscope&, graph& g) + : unameable (di, g) {} + drop_index (xml::parser&, uscope&, graph&); + + virtual drop_index& + clone (uscope&, graph&) const; + + virtual string + kind () const {return "drop index";} + + virtual void + serialize (xml::serializer&) const; + }; + } +} + +#endif // ODB_SEMANTICS_RELATIONAL_INDEX_HXX diff --git a/odb/odb/semantics/relational/key.cxx b/odb/odb/semantics/relational/key.cxx new file mode 100644 index 0000000..3511618 --- /dev/null +++ b/odb/odb/semantics/relational/key.cxx @@ -0,0 +1,97 @@ +// file : odb/semantics/relational/key.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <libcutl/compiler/type-info.hxx> + +#include <odb/semantics/relational/key.hxx> +#include <odb/semantics/relational/column.hxx> + +namespace semantics +{ + namespace relational + { + key:: + key (key const& k, uscope& s, graph& g) + : unameable (k, g) + { + for (contains_iterator i (k.contains_begin ()); + i != k.contains_end (); ++i) + { + column* c (s.lookup<column, drop_column> (i->column ().name ())); + assert (c != 0); + g.new_edge<contains> (*this, *c, i->options ()); + } + } + + key:: + key (xml::parser& p, uscope& s, graph& g) + : unameable (p, g) + { + using namespace xml; + p.content (content::complex); + + for (parser::event_type e (p.peek ()); + e == parser::start_element; + e = p.peek ()) + { + if (p.qname () != xml::qname (xmlns, "column")) + break; // Not our elements. + + p.next (); + p.content (content::empty); + + uname n (p.attribute<uname> ("name")); + column* c (s.lookup<column, drop_column> (n)); + if (c == 0) + throw parsing (p, "invalid column name in the 'name' attribute"); + + string o (p.attribute ("options", string ())); + g.new_edge<contains> (*this, *c, o); + + p.next_expect (parser::end_element); + } + } + + void key:: + serialize_content (xml::serializer& s) const + { + for (contains_iterator i (contains_begin ()); i != contains_end (); ++i) + { + s.start_element (xmlns, "column"); + s.attribute ("name", i->column ().name ()); + if (!i->options ().empty ()) + s.attribute ("options", i->options ()); + s.end_element (); + } + } + + // type info + // + namespace + { + struct init + { + init () + { + using compiler::type_info; + + // contains + // + { + type_info ti (typeid (contains)); + ti.add_base (typeid (edge)); + insert (ti); + } + + // key + // + { + type_info ti (typeid (key)); + ti.add_base (typeid (node)); + insert (ti); + } + } + } init_; + } + } +} diff --git a/odb/odb/semantics/relational/key.hxx b/odb/odb/semantics/relational/key.hxx new file mode 100644 index 0000000..814d2ec --- /dev/null +++ b/odb/odb/semantics/relational/key.hxx @@ -0,0 +1,104 @@ +// file : odb/semantics/relational/key.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_SEMANTICS_RELATIONAL_KEY_HXX +#define ODB_SEMANTICS_RELATIONAL_KEY_HXX + +#include <odb/semantics/relational/elements.hxx> +#include <odb/semantics/relational/table.hxx> + +namespace semantics +{ + namespace relational + { + class key; + class column; + + class contains: public edge + { + public: + typedef relational::key key_type; + typedef relational::column column_type; + + key_type& + key () const {return *key_;} + + column_type& + column () const {return *column_;} + + string const& + options () const {return options_;} + + public: + contains (string const& o = string ()) : options_ (o) {} + + void + set_left_node (key_type& n) + { + key_ = &n; + } + + void + set_right_node (column_type& n) + { + column_ = &n; + } + + protected: + key_type* key_; + column_type* column_; + string options_; + }; + + class key: public unameable + { + typedef std::vector<contains*> contains_list; + + public: + typedef contains_list::size_type contains_size_type; + + typedef + pointer_iterator<contains_list::const_iterator> + contains_iterator; + + contains_iterator + contains_begin () const {return contains_.begin ();} + + contains_iterator + contains_end () const {return contains_.end ();} + + contains_size_type + contains_size () const {return contains_.size ();} + + contains& + contains_at (contains_size_type i) const {return *contains_[i];} + + public: + typedef relational::table table_type; + + table_type& + table () const {return dynamic_cast<table_type&> (scope ());} + + public: + key (std::string const& id): unameable (id) {} + + void + add_edge_left (contains& e) + { + contains_.push_back (&e); + } + + protected: + key (key const&, uscope&, graph&); + key (xml::parser&, uscope&, graph&); + + void + serialize_content (xml::serializer&) const; + + private: + contains_list contains_; + }; + } +} + +#endif // ODB_SEMANTICS_RELATIONAL_KEY_HXX diff --git a/odb/odb/semantics/relational/model.cxx b/odb/odb/semantics/relational/model.cxx new file mode 100644 index 0000000..8763045 --- /dev/null +++ b/odb/odb/semantics/relational/model.cxx @@ -0,0 +1,54 @@ +// file : odb/semantics/relational/model.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <libcutl/compiler/type-info.hxx> + +#include <odb/semantics/relational/model.hxx> + +namespace semantics +{ + namespace relational + { + model:: + model (model const& m, graph& g) + : qscope (m, 0, g), + version_ (m.version_) + { + } + + model:: + model (xml::parser& p, graph& g) + : qscope (p, 0, g), + version_ (p.attribute<version_type> ("version")) + { + } + + void model:: + serialize (xml::serializer& s) const + { + s.start_element (xmlns, "model"); + s.attribute ("version", version_); + qscope::serialize_content (s); + s.end_element (); + } + + // type info + // + namespace + { + struct init + { + init () + { + using compiler::type_info; + + { + type_info ti (typeid (model)); + ti.add_base (typeid (qscope)); + insert (ti); + } + } + } init_; + } + } +} diff --git a/odb/odb/semantics/relational/model.hxx b/odb/odb/semantics/relational/model.hxx new file mode 100644 index 0000000..02d1863 --- /dev/null +++ b/odb/odb/semantics/relational/model.hxx @@ -0,0 +1,49 @@ +// file : odb/semantics/relational/model.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_SEMANTICS_RELATIONAL_MODEL_HXX +#define ODB_SEMANTICS_RELATIONAL_MODEL_HXX + +#include <odb/semantics/relational/elements.hxx> + +namespace semantics +{ + namespace relational + { + class model: public graph, public qscope + { + public: + typedef relational::version version_type; + + version_type + version () const {return version_;} + + void + version (version_type v) {version_ = v;} + + public: + model (version_type v): version_ (v) {} + model (model const&, graph&); + model (xml::parser&, graph&); + + virtual string + kind () const {return "model";} + + virtual void + serialize (xml::serializer&) const; + + public: + using qscope::add_edge_left; + using qscope::add_edge_right; + + private: + model (model const&); + model& operator= (model const&); + + private: + version_type version_; + }; + } +} + +#endif // ODB_SEMANTICS_RELATIONAL_MODEL_HXX diff --git a/odb/odb/semantics/relational/name.cxx b/odb/odb/semantics/relational/name.cxx new file mode 100644 index 0000000..6eb2e16 --- /dev/null +++ b/odb/odb/semantics/relational/name.cxx @@ -0,0 +1,89 @@ +// file : odb/semantics/relational/name.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <ostream> +#include <istream> + +#include <odb/semantics/relational/name.hxx> + +using namespace std; + +namespace semantics +{ + namespace relational + { + string qname:: + string () const + { + std::string r; + + bool f (true); + for (iterator i (begin ()); i < end (); ++i) + { + if (i->empty ()) + continue; + + if (f) + f = false; + else + r += '.'; + + r += *i; + } + + return r; + } + + qname qname:: + from_string (std::string const& s) + { + using std::string; + + qname n; + + string::size_type p (string::npos); + + for (size_t i (0); i < s.size (); ++i) + { + char c (s[i]); + + if (c == '.') + { + if (p == string::npos) + n.append (string (s, 0, i)); + else + n.append (string (s, p + 1, i - p - 1)); + + p = i; + } + } + + if (p == string::npos) + n.append (s); + else + n.append (string (s, p + 1, string::npos)); + + return n; + } + + ostream& + operator<< (ostream& os, qname const& n) + { + return os << n.string (); + } + + istream& + operator>> (istream& is, qname& n) + { + string s; + is >> s; + + if (!is.fail ()) + n = qname::from_string (s); + else + n.clear (); + + return is; + } + } +} diff --git a/odb/odb/semantics/relational/name.hxx b/odb/odb/semantics/relational/name.hxx new file mode 100644 index 0000000..5268b4a --- /dev/null +++ b/odb/odb/semantics/relational/name.hxx @@ -0,0 +1,159 @@ +// file : odb/semantics/relational/name.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_SEMANTICS_RELATIONAL_NAME_HXX +#define ODB_SEMANTICS_RELATIONAL_NAME_HXX + +#include <string> +#include <vector> +#include <iosfwd> + +namespace semantics +{ + namespace relational + { + typedef std::string uname; + + class qname + { + public: + typedef relational::uname uname_type; + + qname () {} + + explicit + qname (uname_type const& n) {append (n);} + + template <typename I> + qname (I begin, I end) + { + for (; begin != end; ++begin) + append (*begin); + } + + qname& + operator= (uname_type const& n) + { + components_.resize (1); + components_[0] = n; + return *this; + } + + void + append (uname_type const& n) {components_.push_back (n);} + + void + append (qname const& n) + { + components_.insert (components_.end (), + n.components_.begin (), + n.components_.end ()); + } + + void + clear () {components_.clear ();} + + // Append a string to the last component. + // + qname& + operator+= (std::string const& s) + { + if (empty ()) + append (s); + else + uname () += s; + + return *this; + } + + friend qname + operator+ (qname const& n, std::string const& s) + { + qname r (n); + + if (r.empty ()) + r.append (s); + else + r.uname () += s; + + return r; + } + + void + swap (qname& n) {components_.swap (n.components_);} + + public: + bool + empty () const {return components_.empty ();} + + bool + qualified () const {return components_.size () > 1;} + + bool + fully_qualified () const + { + return qualified () && components_.front ().empty (); + } + + public: + typedef std::vector<uname_type> components; + typedef components::const_iterator iterator; + + iterator + begin () const {return components_.begin ();} + + iterator + end () const {return components_.end ();} + + uname_type& + uname () {return components_.back ();} + + uname_type const& + uname () const {return components_.back ();} + + qname + qualifier () const + { + return empty () + ? qname () + : qname (components_.begin (), components_.end () - 1); + } + + std::string + string () const; + + static qname + from_string (std::string const&); + + public: + friend bool + operator== (qname const& x, qname const& y) + { + return x.components_ == y.components_; + } + + friend bool + operator!= (qname const& x, qname const& y) + { + return x.components_ != y.components_; + } + + friend bool + operator< (qname const& x, qname const& y) + { + return x.components_ < y.components_; + } + + private: + components components_; + }; + + std::ostream& + operator<< (std::ostream&, qname const&); + + std::istream& + operator>> (std::istream&, qname&); + } +} + +#endif // ODB_SEMANTICS_RELATIONAL_NAME_HXX diff --git a/odb/odb/semantics/relational/primary-key.cxx b/odb/odb/semantics/relational/primary-key.cxx new file mode 100644 index 0000000..235340f --- /dev/null +++ b/odb/odb/semantics/relational/primary-key.cxx @@ -0,0 +1,80 @@ +// file : odb/semantics/relational/primary-key.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <libcutl/compiler/type-info.hxx> + +#include <odb/semantics/relational/primary-key.hxx> + +namespace semantics +{ + namespace relational + { + primary_key:: + primary_key (primary_key const& k, uscope& s, graph& g) + : key (k, s, g), auto__ (k.auto__), extra_map_ (k.extra_map_) + { + } + + primary_key:: + primary_key (xml::parser& p, uscope& s, graph& g) + : key (p, s, g), + auto__ (p.attribute ("auto", false)) + { + // All unhandled attributes go into the extra map. + // + typedef xml::parser::attribute_map_type attr_map; + attr_map const& am (p.attribute_map ()); + + for (attr_map::const_iterator i (am.begin ()); i != am.end (); ++i) + { + if (!i->second.handled) + extra_map_[i->first.name ()] = i->second.value; + } + } + + primary_key& primary_key:: + clone (uscope& s, graph& g) const + { + return g.new_node<primary_key> (*this, s, g); + } + + void primary_key:: + serialize (xml::serializer& s) const + { + s.start_element (xmlns, "primary-key"); + key::serialize_attributes (s); + + if (auto_ ()) + s.attribute ("auto", true); + + for (extra_map::const_iterator i (extra_map_.begin ()); + i != extra_map_.end (); ++i) + s.attribute (i->first, i->second); + + key::serialize_content (s); + s.end_element (); + } + + // type info + // + namespace + { + struct init + { + init () + { + unameable::parser_map_["primary-key"] = + &unameable::parser_impl<primary_key>; + + using compiler::type_info; + + { + type_info ti (typeid (primary_key)); + ti.add_base (typeid (key)); + insert (ti); + } + } + } init_; + } + } +} diff --git a/odb/odb/semantics/relational/primary-key.hxx b/odb/odb/semantics/relational/primary-key.hxx new file mode 100644 index 0000000..114f682 --- /dev/null +++ b/odb/odb/semantics/relational/primary-key.hxx @@ -0,0 +1,62 @@ +// file : odb/semantics/relational/primary-key.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_SEMANTICS_RELATIONAL_PRIMARY_KEY_HXX +#define ODB_SEMANTICS_RELATIONAL_PRIMARY_KEY_HXX + +#include <map> + +#include <odb/semantics/relational/elements.hxx> +#include <odb/semantics/relational/key.hxx> + +namespace semantics +{ + namespace relational + { + class primary_key: public key + { + public: + bool + auto_ () const {return auto__;} + + // Extra information. + // + public: + typedef std::map<string, string> extra_map; + + extra_map& + extra () {return extra_map_;} + + extra_map const& + extra () const {return extra_map_;} + + public: + primary_key (bool auto_) + : key (""), // Primary key has the implicit empty id. + auto__ (auto_) + { + } + + primary_key (primary_key const&, uscope&, graph&); + primary_key (xml::parser&, uscope&, graph&); + + virtual primary_key& + clone (uscope&, graph&) const; + + virtual string + kind () const + { + return "primary key"; + } + + virtual void + serialize (xml::serializer&) const; + + private: + bool auto__; + extra_map extra_map_; + }; + } +} + +#endif // ODB_SEMANTICS_RELATIONAL_PRIMARY_KEY_HXX diff --git a/odb/odb/semantics/relational/table.cxx b/odb/odb/semantics/relational/table.cxx new file mode 100644 index 0000000..3bf763d --- /dev/null +++ b/odb/odb/semantics/relational/table.cxx @@ -0,0 +1,183 @@ +// file : odb/semantics/relational/table.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <libcutl/compiler/type-info.hxx> + +#include <odb/semantics/relational/table.hxx> + +namespace semantics +{ + namespace relational + { + // table + // + table:: + table (table const& t, qscope& s, graph& g, bool b) + : qnameable (t, g), + uscope (t, (b ? s.lookup<table, drop_table> (t.name ()) : 0), g), + options_ (t.options_), + extra_map_ (t.extra_map_) + { + } + + table:: + table (xml::parser& p, qscope& s, graph& g, bool b) + : qnameable (p, g), + uscope ( + p, + (b ? s.lookup<table, drop_table> ( + p.attribute<qnameable::name_type> ("name")) : 0), + g), + options_ (p.attribute ("options", string ())) + { + // All unhandled attributes go into the extra map. + // + typedef xml::parser::attribute_map_type attr_map; + attr_map const& am (p.attribute_map ()); + + for (attr_map::const_iterator i (am.begin ()); i != am.end (); ++i) + { + if (!i->second.handled) + extra_map_[i->first.name ()] = i->second.value; + } + } + + table& table:: + clone (qscope& s, graph& g) const + { + return g.new_node<table> (*this, s, g); + } + + void table:: + serialize_attributes (xml::serializer& s) const + { + qnameable::serialize_attributes (s); + + if (!options_.empty ()) + s.attribute ("options", options_); + + for (extra_map::const_iterator i (extra_map_.begin ()); + i != extra_map_.end (); ++i) + s.attribute (i->first, i->second); + } + + void table:: + serialize (xml::serializer& s) const + { + s.start_element (xmlns, "table"); + serialize_attributes (s); + uscope::serialize_content (s); + s.end_element (); + } + + // add_table + // + add_table& add_table:: + clone (qscope& s, graph& g) const + { + return g.new_node<add_table> (*this, s, g); + } + + void add_table:: + serialize (xml::serializer& s) const + { + s.start_element (xmlns, "add-table"); + table::serialize_attributes (s); + table::serialize_content (s); + s.end_element (); + } + + // drop_table + // + drop_table:: + drop_table (xml::parser& p, qscope&, graph& g) + : qnameable (p, g) + { + p.content (xml::content::empty); + } + + drop_table& drop_table:: + clone (qscope& s, graph& g) const + { + return g.new_node<drop_table> (*this, s, g); + } + + void drop_table:: + serialize (xml::serializer& s) const + { + s.start_element (xmlns, "drop-table"); + qnameable::serialize_attributes (s); + s.end_element (); + } + + // alter_table + // + alter_table& alter_table:: + clone (qscope& s, graph& g) const + { + return g.new_node<alter_table> (*this, s, g); + } + + void alter_table:: + serialize (xml::serializer& s) const + { + s.start_element (xmlns, "alter-table"); + table::serialize_attributes (s); + table::serialize_content (s); + s.end_element (); + } + + // type info + // + namespace + { + struct init + { + init () + { + qnameable::parser_map& m (qnameable::parser_map_); + + m["table"] = &qnameable::parser_impl<table>; + m["add-table"] = &qnameable::parser_impl<add_table>; + m["drop-table"] = &qnameable::parser_impl<drop_table>; + m["alter-table"] = &qnameable::parser_impl<alter_table>; + + using compiler::type_info; + + // table + // + { + type_info ti (typeid (table)); + ti.add_base (typeid (qnameable)); + ti.add_base (typeid (uscope)); + insert (ti); + } + + // add_table + // + { + type_info ti (typeid (add_table)); + ti.add_base (typeid (table)); + insert (ti); + } + + // drop_table + // + { + type_info ti (typeid (drop_table)); + ti.add_base (typeid (qnameable)); + insert (ti); + } + + // alter_table + // + { + type_info ti (typeid (alter_table)); + ti.add_base (typeid (table)); + insert (ti); + } + } + } init_; + } + } +} diff --git a/odb/odb/semantics/relational/table.hxx b/odb/odb/semantics/relational/table.hxx new file mode 100644 index 0000000..1c4efcf --- /dev/null +++ b/odb/odb/semantics/relational/table.hxx @@ -0,0 +1,115 @@ +// file : odb/semantics/relational/table.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_SEMANTICS_RELATIONAL_TABLE_HXX +#define ODB_SEMANTICS_RELATIONAL_TABLE_HXX + +#include <odb/semantics/relational/elements.hxx> + +namespace semantics +{ + namespace relational + { + class table: public qnameable, public uscope + { + public: + virtual string const& + options () const {return options_;} + + virtual void + options (string const& o) {options_ = o;} + + // Extra information. + // + public: + typedef std::map<string, string> extra_map; + + extra_map& + extra () {return extra_map_;} + + extra_map const& + extra () const {return extra_map_;} + + public: + table (string const& id): qnameable (id) {} + table (table const&, qscope&, graph&, bool base = false); + table (xml::parser&, qscope&, graph&, bool base = false); + + virtual table& + clone (qscope&, graph&) const; + + virtual string + kind () const {return "table";} + + virtual void + serialize (xml::serializer&) const; + + // Resolve ambiguity. + // + using qnameable::scope; + + protected: + void + serialize_attributes (xml::serializer&) const; + + protected: + string options_; + extra_map extra_map_; + }; + + class add_table: public table + { + public: + add_table (string const& id): table (id) {} + add_table (table const& t, qscope& s, graph& g): table (t, s, g) {} + add_table (xml::parser& p, qscope& s, graph& g): table (p, s, g) {} + + virtual add_table& + clone (qscope&, graph&) const; + + virtual string + kind () const {return "add table";} + + virtual void + serialize (xml::serializer&) const; + }; + + class drop_table: public qnameable + { + public: + drop_table (string const& id): qnameable (id) {} + drop_table (drop_table const& t, qscope&, graph& g): qnameable (t, g) {} + drop_table (xml::parser&, qscope&, graph&); + + virtual drop_table& + clone (qscope&, graph&) const; + + virtual string + kind () const {return "drop table";} + + virtual void + serialize (xml::serializer&) const; + }; + + class alter_table: public table + { + public: + alter_table (string const& id): table (id) {} + alter_table (alter_table const& at, qscope& s, graph& g) + : table (at, s, g, true) {} + alter_table (xml::parser& p, qscope& s, graph& g) + : table (p, s, g, true) {} + + virtual alter_table& + clone (qscope&, graph&) const; + + virtual string + kind () const {return "alter table";} + + virtual void + serialize (xml::serializer&) const; + }; + } +} + +#endif // ODB_SEMANTICS_RELATIONAL_TABLE_HXX diff --git a/odb/odb/semantics/template.cxx b/odb/odb/semantics/template.cxx new file mode 100644 index 0000000..f492be0 --- /dev/null +++ b/odb/odb/semantics/template.cxx @@ -0,0 +1,88 @@ +// file : odb/semantics/template.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <libcutl/compiler/type-info.hxx> +#include <odb/semantics/template.hxx> + +namespace semantics +{ + template_:: + template_ () + { + } + + instantiates:: + instantiates () + { + } + + instantiation:: + instantiation () + { + } + + type_template:: + type_template () + { + } + + type_instantiation:: + type_instantiation () + { + } + + // type info + // + namespace + { + struct init + { + init () + { + using compiler::type_info; + + // template_ + // + { + type_info ti (typeid (template_)); + ti.add_base (typeid (nameable)); + insert (ti); + } + + // instantiates + // + { + type_info ti (typeid (instantiates)); + ti.add_base (typeid (edge)); + insert (ti); + } + + // instantiation + // + { + type_info ti (typeid (instantiation)); + ti.add_base (typeid (node)); + insert (ti); + } + + // type_template + // + { + type_info ti (typeid (type_template)); + ti.add_base (typeid (template_)); + insert (ti); + } + + // type_instantiation + // + { + type_info ti (typeid (type_instantiation)); + ti.add_base (typeid (type)); + ti.add_base (typeid (instantiation)); + insert (ti); + } + + } + } init_; + } +} diff --git a/odb/odb/semantics/template.hxx b/odb/odb/semantics/template.hxx new file mode 100644 index 0000000..11fe340 --- /dev/null +++ b/odb/odb/semantics/template.hxx @@ -0,0 +1,146 @@ +// file : odb/semantics/template.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_SEMANTICS_TEMPLATE_HXX +#define ODB_SEMANTICS_TEMPLATE_HXX + +#include <vector> +#include <odb/semantics/elements.hxx> + +namespace semantics +{ + // + // + class instantiates; + + class template_: public virtual nameable + { + typedef std::vector<instantiates*> instantiated; + + public: + typedef + pointer_iterator<instantiated::const_iterator> + instantiated_iterator; + + instantiated_iterator + instantiated_begin () const + { + return instantiated_.begin (); + } + + instantiated_iterator + instantiated_end () const + { + return instantiated_.end (); + } + + public: + void + add_edge_right (instantiates& e) + { + instantiated_.push_back (&e); + } + + using nameable::add_edge_right; + + protected: + template_ (); + + private: + instantiated instantiated_; + }; + + // + // + class instantiation; + + class instantiates: public edge + { + public: + typedef semantics::template_ template_type; + typedef semantics::instantiation instantiation_type; + + template_type& + template_ () const + { + return *template__; + } + + instantiation_type& + instantiation () const + { + return *instantiation_; + } + + public: + instantiates (); + + void + set_left_node (instantiation_type& n) + { + instantiation_ = &n; + } + + void + set_right_node (template_type& n) + { + template__ = &n; + } + + private: + template_type* template__; + instantiation_type* instantiation_; + }; + + // + // + class instantiation: public virtual node + { + public: + typedef semantics::template_ template_type; + typedef semantics::instantiates instantiates_type; + + template_type& + template_ () const + { + return instantiates_->template_ (); + } + + instantiates_type& + instantiates () const + { + return *instantiates_; + } + + public: + void + add_edge_left (instantiates_type& e) + { + instantiates_ = &e; + } + + protected: + instantiation (); + + private: + instantiates_type* instantiates_; + }; + + // + // Type template and instantiation. + // + + class type_template: public template_ + { + protected: + type_template (); + }; + + class type_instantiation: public virtual type, public instantiation + { + protected: + type_instantiation (); + }; +} + +#endif // ODB_SEMANTICS_TEMPLATE_HXX diff --git a/odb/odb/semantics/union-template.cxx b/odb/odb/semantics/union-template.cxx new file mode 100644 index 0000000..21fc9c0 --- /dev/null +++ b/odb/odb/semantics/union-template.cxx @@ -0,0 +1,54 @@ +// file : odb/semantics/union-template.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <libcutl/compiler/type-info.hxx> +#include <odb/semantics/union-template.hxx> + +namespace semantics +{ + union_template:: + union_template (path const& file, size_t line, size_t column, tree tn) + : node (file, line, column, tn) + { + } + + union_instantiation:: + union_instantiation (path const& file, + size_t line, + size_t column, + tree tn) + : node (file, line, column, tn) + { + } + + // type info + // + namespace + { + struct init + { + init () + { + using compiler::type_info; + + // union_template + // + { + type_info ti (typeid (union_template)); + ti.add_base (typeid (type_template)); + ti.add_base (typeid (scope)); + insert (ti); + } + + // union_instantiation + // + { + type_info ti (typeid (union_instantiation)); + ti.add_base (typeid (union_)); + ti.add_base (typeid (type_instantiation)); + insert (ti); + } + } + } init_; + } +} diff --git a/odb/odb/semantics/union-template.hxx b/odb/odb/semantics/union-template.hxx new file mode 100644 index 0000000..3e719b7 --- /dev/null +++ b/odb/odb/semantics/union-template.hxx @@ -0,0 +1,33 @@ +// file : odb/semantics/union-template.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_SEMANTICS_UNION_TEMPLATE_HXX +#define ODB_SEMANTICS_UNION_TEMPLATE_HXX + +#include <odb/semantics/elements.hxx> +#include <odb/semantics/union.hxx> +#include <odb/semantics/template.hxx> + +namespace semantics +{ + class union_template: public type_template, public scope + { + public: + union_template (path const&, size_t line, size_t column, tree); + + // Resolve conflict between scope::scope and nameable::scope. + // + using nameable::scope; + }; + + class union_instantiation: public union_, public type_instantiation + { + public: + union_instantiation (path const&, size_t line, size_t column, tree); + + using union_::add_edge_left; + using type_instantiation::add_edge_left; + }; +} + +#endif // ODB_SEMANTICS_UNION_TEMPLATE_HXX diff --git a/odb/odb/semantics/union.cxx b/odb/odb/semantics/union.cxx new file mode 100644 index 0000000..007ef57 --- /dev/null +++ b/odb/odb/semantics/union.cxx @@ -0,0 +1,36 @@ +// file : odb/semantics/union.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <libcutl/compiler/type-info.hxx> +#include <odb/semantics/union.hxx> + +namespace semantics +{ + union_:: + union_ (path const& file, size_t line, size_t column, tree tn) + : node (file, line, column, tn) + { + } + + // type info + // + namespace + { + struct init + { + init () + { + using compiler::type_info; + + // union_ + // + { + type_info ti (typeid (union_)); + ti.add_base (typeid (type)); + ti.add_base (typeid (scope)); + insert (ti); + } + } + } init_; + } +} diff --git a/odb/odb/semantics/union.hxx b/odb/odb/semantics/union.hxx new file mode 100644 index 0000000..79adc42 --- /dev/null +++ b/odb/odb/semantics/union.hxx @@ -0,0 +1,27 @@ +// file : odb/semantics/union.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_SEMANTICS_UNION_HXX +#define ODB_SEMANTICS_UNION_HXX + +#include <odb/semantics/elements.hxx> + +namespace semantics +{ + class union_: public virtual type, public scope + { + public: + union_ (path const&, size_t line, size_t column, tree); + + // Resolve conflict between scope::scope and nameable::scope. + // + using nameable::scope; + + protected: + union_ () + { + } + }; +} + +#endif // ODB_SEMANTICS_UNION_HXX diff --git a/odb/odb/semantics/unit.cxx b/odb/odb/semantics/unit.cxx new file mode 100644 index 0000000..4f92aed --- /dev/null +++ b/odb/odb/semantics/unit.cxx @@ -0,0 +1,42 @@ +// file : odb/semantics/unit.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <odb/gcc.hxx> + +#include <libcutl/compiler/type-info.hxx> +#include <odb/semantics/unit.hxx> + +namespace semantics +{ + unit:: + unit (path const& file) + : node (file, 1, 1, global_namespace), graph_ (*this) + { + // Use a special edge to get this->name() return the global + // namespace name (""). + // + new_edge<global_names> (*this, *this); + node::unit (*this); + } + + // type info + // + namespace + { + struct init + { + init () + { + using compiler::type_info; + + // unit + // + { + type_info ti (typeid (unit)); + ti.add_base (typeid (namespace_)); + insert (ti); + } + } + } init_; + } +} diff --git a/odb/odb/semantics/unit.hxx b/odb/odb/semantics/unit.hxx new file mode 100644 index 0000000..cfccbff --- /dev/null +++ b/odb/odb/semantics/unit.hxx @@ -0,0 +1,183 @@ +// file : odb/semantics/unit.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_SEMANTICS_UNIT_HXX +#define ODB_SEMANTICS_UNIT_HXX + +#include <map> + +#include <odb/semantics/elements.hxx> +#include <odb/semantics/namespace.hxx> + +namespace semantics +{ + class unit: public graph<node, edge>, public namespace_ + { + public: + unit (path const&); + + private: + unit (unit const&); + unit& operator= (unit const&); + + // Mapping from tree nodes to semantic graph nodes. + // + public: + node* + find (tree key) const + { + tree_node_map::const_iterator i (tree_node_map_.find (key)); + return i != tree_node_map_.end () ? i->second : 0; + } + + void + insert (tree key, node& value) + { + tree_node_map_[key] = &value; + } + + using namespace_::find; + + // Mapping from tree nodes to name hints. + // + public: + names* + find_hint (tree key) const + { + name_hint_map::const_iterator i (name_hint_map_.find (key)); + return i != name_hint_map_.end () ? i->second : 0; + } + + void + insert_hint (tree key, names& name) + { + name_hint_map_[key] = &name; + } + + public: + template <class T> + T& + new_node (path const& file, size_t line, size_t column) + { + T& r (graph_.new_node<T> (file, line, column)); + r.unit (*this); + return r; + } + + template <class T, class A0> + T& + new_node (path const& file, size_t line, size_t column, A0 const& a0) + { + T& r (graph_.new_node<T> (file, line, column, a0)); + r.unit (*this); + return r; + } + + template <class T, class A0, class A1> + T& + new_node (path const& file, size_t line, size_t column, + A0 const& a0, A1 const& a1) + { + T& r (graph_.new_node<T> (file, line, column, a0, a1)); + r.unit (*this); + return r; + } + + template <class T, class A0, class A1, class A2> + T& + new_node (path const& file, size_t line, size_t column, + A0 const& a0, A1 const& a1, A2 const& a2) + { + T& r (graph_.new_node<T> (file, line, column, a0, a1, a2)); + r.unit (*this); + return r; + } + + template <class T, class A0, class A1, class A2, class A3> + T& + new_node (path const& file, size_t line, size_t column, + A0 const& a0, A1 const& a1, A2 const& a2, A3 const& a3) + { + T& r (graph_.new_node<T> (file, line, column, a0, a1, a2, a3)); + r.unit (*this); + return r; + } + + template <class T, class A0, class A1, class A2, class A3, class A4> + T& + new_node (path const& file, size_t line, size_t column, + A0 const& a0, A1 const& a1, A2 const& a2, A3 const& a3, + A4 const& a4) + { + T& r (graph_.new_node<T> (file, line, column, a0, a1, a2, a3, a4)); + r.unit (*this); + return r; + } + + // For fundamental types. + // + template <class T> + T& + new_fund_node (tree tn) + { + T& r (graph_.new_node<T> (tn)); + r.unit (*this); + return r; + } + + protected: + // Special names edge for the global namespace. + // + class global_names: public names + { + public: + global_names () + : names ("") + { + scope_ = 0; + } + + void + set_left_node (unit&) + { + } + + void + set_right_node (nameable& n) + { + named_ = &n; + } + + void + clear_left_node (unit&) + { + } + + void + clear_right_node (nameable& n) + { + assert (named_ == &n); + named_ = 0; + } + }; + + public: + void + add_edge_left (global_names&) + { + } + + using namespace_::add_edge_right; + + private: + graph<node, edge>& graph_; + + typedef std::map<tree, node*> tree_node_map; + tree_node_map tree_node_map_; + + typedef std::map<tree, names*> name_hint_map; + name_hint_map name_hint_map_; + }; +} + +#endif // ODB_SEMANTICS_UNIT_HXX diff --git a/odb/odb/source.cxx b/odb/odb/source.cxx new file mode 100644 index 0000000..b2a39be --- /dev/null +++ b/odb/odb/source.cxx @@ -0,0 +1,151 @@ +// file : odb/source.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <odb/common.hxx> +#include <odb/context.hxx> +#include <odb/generate.hxx> +#include <odb/diagnostics.hxx> + +using namespace std; + +namespace source +{ + struct class_: traversal::class_, virtual context + { + class_ () + : typedefs_ (false), + query_columns_type_ (false, false, false), + view_query_columns_type_ (false) + { + *this >> defines_ >> *this; + *this >> typedefs_ >> *this; + } + + virtual void + traverse (type& c) + { + class_kind_type ck (class_kind (c)); + + if (ck == class_other || + (!options.at_once () && class_file (c) != unit.file ())) + return; + + names (c); + + switch (ck) + { + case class_object: traverse_object (c); break; + case class_view: traverse_view (c); break; + default: break; + } + } + + void + traverse_object (type&); + + void + traverse_view (type&); + + private: + traversal::defines defines_; + typedefs typedefs_; + + instance<query_columns_type> query_columns_type_; + instance<view_query_columns_type> view_query_columns_type_; + }; +} + +void source::class_:: +traverse_object (type& c) +{ + bool poly (polymorphic (c)); + bool abst (abstract (c)); + bool reuse_abst (abst && !poly); + + // The rest only applies to dynamic milti-database support. + // + if (!multi_dynamic) + return; + + os << "// " << class_name (c) << endl + << "//" << endl + << endl; + + // query_columns + // + if (options.generate_query ()) + query_columns_type_->traverse (c); + + // The rest does not apply to reuse-abstract objects. + // + if (reuse_abst) + return; + + string const& type (class_fq_name (c)); + string traits ("access::object_traits_impl< " + type + ", id_common >"); + + os << "const " << traits << "::" << endl + << "function_table_type*" << endl + << traits << "::" << endl + << "function_table[database_count];" + << endl; +} + +void source::class_:: +traverse_view (type& c) +{ + // The rest only applies to dynamic milti-database support. + // + if (!multi_dynamic) + return; + + os << "// " << class_name (c) << endl + << "//" << endl + << endl; + + if (c.get<size_t> ("object-count") != 0) + view_query_columns_type_->traverse (c); + + string const& type (class_fq_name (c)); + string traits ("access::view_traits_impl< " + type + ", id_common >"); + + os << "const " << traits << "::" << endl + << "function_table_type*" << endl + << traits << "::" << endl + << "function_table[database_count];" + << endl; +} + +namespace source +{ + void + generate () + { + context ctx; + ostream& os (ctx.os); + + traversal::unit unit; + traversal::defines unit_defines; + typedefs unit_typedefs (false); + traversal::namespace_ ns; + class_ c; + + unit >> unit_defines >> ns; + unit_defines >> c; + unit >> unit_typedefs >> c; + + traversal::defines ns_defines; + typedefs ns_typedefs (false); + + ns >> ns_defines >> ns; + ns_defines >> c; + ns >> ns_typedefs >> c; + + os << "namespace odb" + << "{"; + + unit.dispatch (ctx.unit); + + os << "}"; + } +} diff --git a/odb/odb/sql-lexer.cxx b/odb/odb/sql-lexer.cxx new file mode 100644 index 0000000..ab6f549 --- /dev/null +++ b/odb/odb/sql-lexer.cxx @@ -0,0 +1,250 @@ +// file : odb/sql-lexer.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <iostream> + +#include <odb/sql-lexer.hxx> + +using namespace std; + +sql_lexer:: +sql_lexer () + : loc_ ("C"), buf_ (0, 0, 0) +{ +} + +sql_lexer:: +sql_lexer (std::string const& sql) + : loc_ ("C"), buf_ (0, 0, 0) +{ + lex (sql); +} + +void sql_lexer:: +lex (std::string const& sql) +{ + is_.str (sql); + is_.clear (); + + l_ = c_ = 1; + buf_ = xchar (0, 0, 0); + eos_ = false; + unget_ = false; +} + +sql_lexer::xchar sql_lexer:: +peek () +{ + if (unget_) + return buf_; + else + { + if (eos_) + return xchar (xchar::traits_type::eof (), l_, c_); + else + { + xchar::int_type i (is_.peek ()); + + if (i == xchar::traits_type::eof ()) + eos_ = true; + + return xchar (i, l_, c_); + } + } +} + +sql_lexer::xchar sql_lexer:: +get () +{ + if (unget_) + { + unget_ = false; + return buf_; + } + else + { + // When is_.get () returns eof, the failbit is also set (stupid, + // isn't?) which may trigger an exception. To work around this + // we will call peek() first and only call get() if it is not + // eof. But we can only call peek() on eof once; any subsequent + // calls will spoil the failbit (even more stupid). + // + xchar c (peek ()); + + if (!is_eos (c)) + { + is_.get (); + + if (c == '\n') + { + l_++; + c_ = 1; + } + else + c_++; + } + + return c; + } +} + +void sql_lexer:: +unget (xchar c) +{ + // Because iostream::unget cannot work once eos is reached, + // we have to provide our own implementation. + // + buf_ = c; + unget_ = true; +} + +sql_token sql_lexer:: +next () +{ + skip_spaces (); + + xchar c (get ()); + + if (is_eos (c)) + return sql_token (); + + switch (c) + { + case '\'': + { + return string_literal (c); + } + case '\"': + { + return string_literal (c); + } + case '`': + { + return string_literal (c); + } + case ';': + { + return sql_token (sql_token::p_semi); + } + case ',': + { + return sql_token (sql_token::p_comma); + } + case '(': + { + return sql_token (sql_token::p_lparen); + } + case ')': + { + return sql_token (sql_token::p_rparen); + } + case '=': + { + return sql_token (sql_token::p_eq); + } + case '-': + { + return int_literal (get (), true); + } + case '+': + { + return int_literal (get (), false); + } + } + + if (is_alpha (c) || c == '_') + { + return identifier (c); + } + + if (is_dec_digit (c)) + { + return int_literal (c); + } + + ostringstream msg; + msg << "unexpected character '" << c << "'"; + throw invalid_input (c.line (), c.column (), msg.str ()); +} + +void sql_lexer:: +skip_spaces () +{ + for (xchar c (peek ());; c = peek ()) + { + if (is_eos (c) || !is_space (c)) + break; + + get (); + } +} + +sql_token sql_lexer:: +identifier (xchar c) +{ + string lexeme; + lexeme += c; + + for (c = peek (); + !is_eos (c) && (is_alnum (c) || c == '_'); + c = peek ()) + { + get (); + lexeme += c; + } + + return sql_token (sql_token::t_identifier, lexeme); +} + +sql_token sql_lexer:: +int_literal (xchar c, bool neg, size_t /*ml*/, size_t /*mc*/) +{ + //size_t ln (neg ? ml : c.line ()), cl (neg ? mc : c.column ()); + string lexeme; + + if (neg) + lexeme += '-'; + + lexeme += c; + + for (c = peek (); !is_eos (c) && is_dec_digit (c); c = peek ()) + { + get (); + lexeme += c; + } + + return sql_token (sql_token::t_int_lit, lexeme); +} + +sql_token sql_lexer:: +string_literal (xchar c) +{ + //size_t ln (c.line ()), cl (c.column ()); + char q (c); + string lexeme; + lexeme += c; + + while (true) + { + xchar c = get (); + + if (is_eos (c)) + throw invalid_input ( + c.line (), c.column (), "unterminated quoted string"); + + lexeme += c; + + // In SQL, double-quote is used to encode a single quote inside + // a string. + // + if (c == q) + { + if (peek () == q) + get (); + else + break; + } + } + + return sql_token (sql_token::t_string_lit, lexeme); +} diff --git a/odb/odb/sql-lexer.hxx b/odb/odb/sql-lexer.hxx new file mode 100644 index 0000000..9d24233 --- /dev/null +++ b/odb/odb/sql-lexer.hxx @@ -0,0 +1,129 @@ +// file : odb/sql-lexer.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_SQL_LEXER_HXX +#define ODB_SQL_LEXER_HXX + +#include <string> +#include <locale> +#include <cstddef> // std::size_t +#include <sstream> + +#include <odb/sql-token.hxx> + +class sql_lexer +{ +public: + sql_lexer (); + sql_lexer (std::string const& sql); + + void lex (std::string const& sql); + + struct invalid_input + { + invalid_input (std::size_t l, std::size_t c, std::string const& m) + : line (l), column (c), message (m) + { + } + + std::size_t line; + std::size_t column; + std::string const message; + }; + + sql_token + next (); + +protected: + class xchar + { + public: + typedef std::char_traits<char> traits_type; + typedef traits_type::int_type int_type; + typedef traits_type::char_type char_type; + + xchar (int_type v, std::size_t l, std::size_t c); + + operator char_type () const; + + int_type + value () const; + + std::size_t + line () const; + + std::size_t + column () const; + + private: + int_type v_; + std::size_t l_; + std::size_t c_; + }; + + xchar + peek (); + + xchar + get (); + + void + unget (xchar); + +protected: + void + skip_spaces (); + + sql_token + identifier (xchar); + + sql_token + int_literal (xchar, + bool neg = false, + std::size_t ml = 0, + std::size_t mc = 0); + + sql_token + string_literal (xchar); + +protected: + bool + is_alpha (char c) const; + + bool + is_oct_digit (char c) const; + + bool + is_dec_digit (char c) const; + + bool + is_hex_digit (char c) const; + + bool + is_alnum (char c) const; + + bool + is_space (char c) const; + + bool + is_eos (xchar const& c) const; + + char + to_upper (char c) const; + +private: + std::locale loc_; + std::istringstream is_; + std::size_t l_; + std::size_t c_; + + bool eos_; + bool include_; + + xchar buf_; + bool unget_; +}; + +#include <odb/sql-lexer.ixx> + +#endif // ODB_SQL_LEXER_HXX diff --git a/odb/odb/sql-lexer.ixx b/odb/odb/sql-lexer.ixx new file mode 100644 index 0000000..9179804 --- /dev/null +++ b/odb/odb/sql-lexer.ixx @@ -0,0 +1,84 @@ +// file : odb/sql-lexer.ixx +// license : GNU GPL v3; see accompanying LICENSE file + +// sql_lexer::xchar +// +inline sql_lexer::xchar:: +xchar (int_type v, std::size_t l, std::size_t c) + : v_ (v), l_ (l), c_ (c) +{ +} + +inline sql_lexer::xchar:: +operator char_type () const +{ + return traits_type::to_char_type (v_); +} + +inline sql_lexer::xchar::int_type sql_lexer::xchar:: +value () const +{ + return v_; +} + +inline std::size_t sql_lexer::xchar:: +line () const +{ + return l_; +} + +inline std::size_t sql_lexer::xchar:: +column () const +{ + return c_; +} + +// lexer +// +inline bool sql_lexer:: +is_alpha (char c) const +{ + return std::isalpha (c, loc_); +} + +inline bool sql_lexer:: +is_oct_digit (char c) const +{ + return std::isdigit (c, loc_) && c != '8' && c != '9'; +} + +inline bool sql_lexer:: +is_dec_digit (char c) const +{ + return std::isdigit (c, loc_); +} + +inline bool sql_lexer:: +is_hex_digit (char c) const +{ + return std::isxdigit (c, loc_); +} + +inline bool sql_lexer:: +is_alnum (char c) const +{ + return std::isalnum (c, loc_); +} + +inline bool sql_lexer:: +is_space (char c) const +{ + return std::isspace (c, loc_); +} + +inline bool sql_lexer:: +is_eos (xchar const& c) const +{ + return c.value () == xchar::traits_type::eof (); +} + +inline char sql_lexer:: +to_upper (char c) const +{ + return std::toupper (c, loc_); +} diff --git a/odb/odb/sql-token.cxx b/odb/odb/sql-token.cxx new file mode 100644 index 0000000..da9ecb2 --- /dev/null +++ b/odb/odb/sql-token.cxx @@ -0,0 +1,44 @@ +// file : odb/sql-token.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <ostream> + +#include <odb/sql-token.hxx> + +using namespace std; + +static char punctuation_literals[] = {';', ',', '(', ')', '='}; + +string sql_token:: +string () const +{ + switch (type ()) + { + case sql_token::t_eos: + { + return "<end-of-stream>"; + } + case sql_token::t_identifier: + { + return identifier (); + } + case sql_token::t_punctuation: + { + return std::string (1, punctuation_literals[punctuation ()]); + } + case sql_token::t_string_lit: + case sql_token::t_int_lit: + case sql_token::t_float_lit: + { + return literal (); + } + } + + return ""; +} + +ostream& +operator<< (ostream& os, sql_token const& t) +{ + return os << t.string (); +} diff --git a/odb/odb/sql-token.hxx b/odb/odb/sql-token.hxx new file mode 100644 index 0000000..c34de21 --- /dev/null +++ b/odb/odb/sql-token.hxx @@ -0,0 +1,89 @@ +// file : odb/sql-token.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_SQL_TOKEN_HXX +#define ODB_SQL_TOKEN_HXX + +#include <string> +#include <iosfwd> +#include <cstddef> // std::size_t + +class sql_token +{ +public: + enum token_type + { + t_eos, + t_identifier, + t_punctuation, + t_string_lit, + t_int_lit, + t_float_lit, + }; + + token_type + type () const; + + // Identifier + // +public: + std::string const& + identifier () const; + + // Punctuation + // +public: + enum punctuation_type + { + // Keep synched with punctuation_literals in source file. + // + p_semi, + p_comma, + p_lparen, + p_rparen, + p_eq, + p_invalid + }; + + // Return the punctuation id if type is t_punctuation and p_invalid + // otherwise. + // + punctuation_type + punctuation () const; + + // Literals. + // +public: + std::string const& + literal () const; + + // Human-readable string representation. + // +public: + std::string + string () const; + + // C-tors. + // +public: + // EOS and punctuations. + // + sql_token (); + sql_token (punctuation_type p); + + // Identifier and literals. + // + sql_token (token_type t, std::string const& s); + +private: + token_type type_; + punctuation_type punctuation_; + std::string str_; +}; + +std::ostream& +operator<< (std::ostream&, sql_token const&); + +#include <odb/sql-token.ixx> + +#endif // ODB_SQL_TOKEN_HXX diff --git a/odb/odb/sql-token.ixx b/odb/odb/sql-token.ixx new file mode 100644 index 0000000..5118c9f --- /dev/null +++ b/odb/odb/sql-token.ixx @@ -0,0 +1,44 @@ +// file : odb/sql-token.ixx +// license : GNU GPL v3; see accompanying LICENSE file + +inline sql_token::token_type sql_token:: +type () const +{ + return type_; +} + +inline std::string const& sql_token:: +identifier () const +{ + return str_; +} + +inline sql_token::punctuation_type sql_token:: +punctuation () const +{ + return type_ == t_punctuation ? punctuation_ : p_invalid; +} + +inline std::string const& sql_token:: +literal () const +{ + return str_; +} + +inline sql_token:: +sql_token () + : type_ (t_eos) +{ +} + +inline sql_token:: +sql_token (punctuation_type p) + : type_ (t_punctuation), punctuation_ (p) +{ +} + +inline sql_token:: +sql_token (token_type t, std::string const& s) + : type_ (t), str_ (s) +{ +} diff --git a/odb/odb/traversal.hxx b/odb/odb/traversal.hxx new file mode 100644 index 0000000..7f421b1 --- /dev/null +++ b/odb/odb/traversal.hxx @@ -0,0 +1,19 @@ +// file : odb/traversal.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_TRAVERSAL_HXX +#define ODB_TRAVERSAL_HXX + +#include <odb/traversal/class.hxx> +#include <odb/traversal/class-template.hxx> +#include <odb/traversal/derived.hxx> +#include <odb/traversal/elements.hxx> +#include <odb/traversal/enum.hxx> +#include <odb/traversal/fundamental.hxx> +#include <odb/traversal/namespace.hxx> +#include <odb/traversal/template.hxx> +#include <odb/traversal/union.hxx> +#include <odb/traversal/union-template.hxx> +#include <odb/traversal/unit.hxx> + +#endif // ODB_TRAVERSAL_HXX diff --git a/odb/odb/traversal/class-template.cxx b/odb/odb/traversal/class-template.cxx new file mode 100644 index 0000000..b04b625 --- /dev/null +++ b/odb/odb/traversal/class-template.cxx @@ -0,0 +1,62 @@ +// file : odb/traversal/class-template.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <odb/traversal/class-template.hxx> + +namespace traversal +{ + // + // + void class_template:: + traverse (type& c) + { + inherits (c); + names (c); + } + + void class_template:: + inherits (type& c) + { + inherits (c, *this); + } + + void class_template:: + inherits (type& c, edge_dispatcher& d) + { + iterate_and_dispatch (c.inherits_begin (), c.inherits_end (), d); + } + + // + // + void class_instantiation:: + traverse (type& c) + { + instantiates (c); + inherits (c); + names (c); + } + + void class_instantiation:: + instantiates (type& c) + { + instantiates (c, *this); + } + + void class_instantiation:: + instantiates (type& c, edge_dispatcher& d) + { + d.dispatch (c.instantiates ()); + } + + void class_instantiation:: + inherits (type& c) + { + inherits (c, *this); + } + + void class_instantiation:: + inherits (type& c, edge_dispatcher& d) + { + iterate_and_dispatch (c.inherits_begin (), c.inherits_end (), d); + } +} diff --git a/odb/odb/traversal/class-template.hxx b/odb/odb/traversal/class-template.hxx new file mode 100644 index 0000000..18e1e5b --- /dev/null +++ b/odb/odb/traversal/class-template.hxx @@ -0,0 +1,45 @@ +// file : odb/traversal/class-template.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_TRAVERSAL_CLASS_TEMPLATE_HXX +#define ODB_TRAVERSAL_CLASS_TEMPLATE_HXX + +#include <odb/semantics/class-template.hxx> + +#include <odb/traversal/elements.hxx> +#include <odb/traversal/class.hxx> + +namespace traversal +{ + struct class_template: scope_template<semantics::class_template> + { + virtual void + traverse (type&); + + virtual void + inherits (type&); + + virtual void + inherits (type&, edge_dispatcher&); + }; + + struct class_instantiation: scope_template<semantics::class_instantiation> + { + virtual void + traverse (type&); + + virtual void + instantiates (type&); + + virtual void + instantiates (type&, edge_dispatcher&); + + virtual void + inherits (type&); + + virtual void + inherits (type&, edge_dispatcher&); + }; +} + +#endif // ODB_TRAVERSAL_CLASS_TEMPLATE_HXX diff --git a/odb/odb/traversal/class.cxx b/odb/odb/traversal/class.cxx new file mode 100644 index 0000000..80c8b80 --- /dev/null +++ b/odb/odb/traversal/class.cxx @@ -0,0 +1,34 @@ +// file : odb/traversal/class.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <odb/traversal/class.hxx> + +namespace traversal +{ + void inherits:: + traverse (type& i) + { + dispatch (i.base ()); + } + + // + // + void class_:: + traverse (type& c) + { + inherits (c); + names (c); + } + + void class_:: + inherits (type& c) + { + inherits (c, *this); + } + + void class_:: + inherits (type& c, edge_dispatcher& d) + { + iterate_and_dispatch (c.inherits_begin (), c.inherits_end (), d); + } +} diff --git a/odb/odb/traversal/class.hxx b/odb/odb/traversal/class.hxx new file mode 100644 index 0000000..de86cc0 --- /dev/null +++ b/odb/odb/traversal/class.hxx @@ -0,0 +1,40 @@ +// file : odb/traversal/class.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_TRAVERSAL_CLASS_HXX +#define ODB_TRAVERSAL_CLASS_HXX + +#include <odb/semantics/class.hxx> +#include <odb/traversal/elements.hxx> + +namespace traversal +{ + struct inherits: edge<semantics::inherits> + { + inherits () + { + } + + inherits (node_dispatcher& n) + { + node_traverser (n); + } + + virtual void + traverse (type&); + }; + + struct class_: scope_template<semantics::class_> + { + virtual void + traverse (type&); + + virtual void + inherits (type&); + + virtual void + inherits (type&, edge_dispatcher&); + }; +} + +#endif // ODB_TRAVERSAL_CLASS_HXX diff --git a/odb/odb/traversal/derived.cxx b/odb/odb/traversal/derived.cxx new file mode 100644 index 0000000..a0acab8 --- /dev/null +++ b/odb/odb/traversal/derived.cxx @@ -0,0 +1,111 @@ +// file : odb/traversal/derived.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <odb/traversal/derived.hxx> + +namespace traversal +{ + void qualifies:: + traverse (type& e) + { + dispatch (e.type ()); + } + + void points:: + traverse (type& e) + { + dispatch (e.type ()); + } + + void references:: + traverse (type& e) + { + dispatch (e.type ()); + } + + void contains:: + traverse (type& e) + { + dispatch (e.type ()); + } + + // + // + void qualifier:: + traverse (type& q) + { + qualifies (q); + } + + void qualifier:: + qualifies (type& q) + { + qualifies (q, *this); + } + + void qualifier:: + qualifies (type& q, edge_dispatcher& d) + { + d.dispatch (q.qualifies ()); + } + + // + // + void pointer:: + traverse (type& p) + { + points (p); + } + + void pointer:: + points (type& p) + { + points (p, *this); + } + + void pointer:: + points (type& p, edge_dispatcher& d) + { + d.dispatch (p.points ()); + } + + // + // + void reference:: + traverse (type& r) + { + references (r); + } + + void reference:: + references (type& r) + { + references (r, *this); + } + + void reference:: + references (type& r, edge_dispatcher& d) + { + d.dispatch (r.references ()); + } + + // + // + void array:: + traverse (type& a) + { + contains (a); + } + + void array:: + contains (type& a) + { + contains (a, *this); + } + + void array:: + contains (type& a, edge_dispatcher& d) + { + d.dispatch (a.contains ()); + } +} diff --git a/odb/odb/traversal/derived.hxx b/odb/odb/traversal/derived.hxx new file mode 100644 index 0000000..b7648aa --- /dev/null +++ b/odb/odb/traversal/derived.hxx @@ -0,0 +1,130 @@ +// file : odb/traversal/derived.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_TRAVERSAL_DERIVED_HXX +#define ODB_TRAVERSAL_DERIVED_HXX + +#include <odb/semantics/derived.hxx> +#include <odb/traversal/elements.hxx> + +namespace traversal +{ + // + // Edges. + // + struct qualifies: edge<semantics::qualifies> + { + qualifies () + { + } + + qualifies (node_dispatcher& n) + { + node_traverser (n); + } + + virtual void + traverse (type&); + }; + + struct points: edge<semantics::points> + { + points () + { + } + + points (node_dispatcher& n) + { + node_traverser (n); + } + + virtual void + traverse (type&); + }; + + struct references: edge<semantics::references> + { + references () + { + } + + references (node_dispatcher& n) + { + node_traverser (n); + } + + virtual void + traverse (type&); + }; + + struct contains: edge<semantics::contains> + { + contains () + { + } + + contains (node_dispatcher& n) + { + node_traverser (n); + } + + virtual void + traverse (type&); + }; + + + // + // Nodes. + // + struct derived_type: node<semantics::derived_type> {}; + + struct qualifier: node<semantics::qualifier> + { + virtual void + traverse (type&); + + virtual void + qualifies (type&); + + virtual void + qualifies (type&, edge_dispatcher&); + }; + + struct pointer: node<semantics::pointer> + { + virtual void + traverse (type&); + + virtual void + points (type&); + + virtual void + points (type&, edge_dispatcher&); + }; + + struct reference: node<semantics::reference> + { + virtual void + traverse (type&); + + virtual void + references (type&); + + virtual void + references (type&, edge_dispatcher&); + }; + + struct array: node<semantics::array> + { + virtual void + traverse (type&); + + virtual void + contains (type&); + + virtual void + contains (type&, edge_dispatcher&); + }; +} + +#endif // ODB_TRAVERSAL_DERIVED_HXX diff --git a/odb/odb/traversal/elements.cxx b/odb/odb/traversal/elements.cxx new file mode 100644 index 0000000..f95917a --- /dev/null +++ b/odb/odb/traversal/elements.cxx @@ -0,0 +1,77 @@ +// file : odb/traversal/elements.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <odb/traversal/elements.hxx> + +namespace traversal +{ + void names:: + traverse (type& e) + { + dispatch (e.named ()); + } + + void declares:: + traverse (type& e) + { + dispatch (e.named ()); + } + + void defines:: + traverse (type& e) + { + dispatch (e.named ()); + } + + void typedefs:: + traverse (type& e) + { + dispatch (e.named ()); + } + + void belongs:: + traverse (type& e) + { + dispatch (e.type ()); + } + + // instance + // + void instance:: + traverse (type& i) + { + belongs (i); + } + + void instance:: + belongs (type& i) + { + belongs (i, *this); + } + + void instance:: + belongs (type& i, edge_dispatcher& d) + { + d.dispatch (i.belongs ()); + } + + // data_member + // + void data_member:: + traverse (type& m) + { + belongs (m); + } + + void data_member:: + belongs (type& m) + { + belongs (m, *this); + } + + void data_member:: + belongs (type& m, edge_dispatcher& d) + { + d.dispatch (m.belongs ()); + } +} diff --git a/odb/odb/traversal/elements.hxx b/odb/odb/traversal/elements.hxx new file mode 100644 index 0000000..d67a6d8 --- /dev/null +++ b/odb/odb/traversal/elements.hxx @@ -0,0 +1,238 @@ +// file : odb/traversal/elements.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_TRAVERSAL_ELEMENTS_HXX +#define ODB_TRAVERSAL_ELEMENTS_HXX + +#include <libcutl/compiler/traversal.hxx> +#include <odb/semantics/elements.hxx> + +namespace traversal +{ + using namespace cutl; + + // + // + typedef compiler::dispatcher<semantics::node> node_dispatcher; + typedef compiler::dispatcher<semantics::edge> edge_dispatcher; + + // + // + struct node_base: node_dispatcher, edge_dispatcher + { + void + edge_traverser (edge_dispatcher& d) + { + edge_dispatcher::traverser (d); + } + + edge_dispatcher& + edge_traverser () + { + return *this; + } + + using node_dispatcher::dispatch; + using edge_dispatcher::dispatch; + + using edge_dispatcher::iterate_and_dispatch; + }; + + struct edge_base: edge_dispatcher, node_dispatcher + { + void + node_traverser (node_dispatcher& d) + { + node_dispatcher::traverser (d); + } + + node_dispatcher& + node_traverser () + { + return *this; + } + + using edge_dispatcher::dispatch; + using node_dispatcher::dispatch; + + using node_dispatcher::iterate_and_dispatch; + }; + + inline edge_base& + operator>> (node_base& n, edge_base& e) + { + n.edge_traverser (e); + return e; + } + + inline node_base& + operator>> (edge_base& e, node_base& n) + { + e.node_traverser (n); + return n; + } + + // + // + template <typename X> + struct node: compiler::traverser_impl<X, semantics::node>, + virtual node_base + { + }; + + template <typename X> + struct edge: compiler::traverser_impl<X, semantics::edge>, + virtual edge_base + { + }; + + // + // Edges + // + + struct names: edge<semantics::names> + { + names () + { + } + + names (node_dispatcher& n) + { + node_traverser (n); + } + + virtual void + traverse (type&); + }; + + struct declares: edge<semantics::declares> + { + declares () + { + } + + declares (node_dispatcher& n) + { + node_traverser (n); + } + + virtual void + traverse (type&); + }; + + struct defines: edge<semantics::defines> + { + defines () + { + } + + defines (node_dispatcher& n) + { + node_traverser (n); + } + + virtual void + traverse (type&); + }; + + struct typedefs: edge<semantics::typedefs> + { + typedefs () + { + } + + typedefs (node_dispatcher& n) + { + node_traverser (n); + } + + virtual void + traverse (type&); + }; + + struct belongs: edge<semantics::belongs> + { + belongs () + { + } + + belongs (node_dispatcher& n) + { + node_traverser (n); + } + + virtual void + traverse (type&); + }; + + // + // Nodes + // + + struct nameable: node<semantics::nameable> {}; + + // + // + template <typename T> + struct scope_template: node<T> + { + public: + virtual void + traverse (T& s) + { + names (s); + } + + virtual void + names (T& s) + { + names (s, *this); + } + + virtual void + names (T& s, edge_dispatcher& d) + { + this->iterate_and_dispatch (s.names_begin (), s.names_end (), d); + } + }; + + struct scope: scope_template<semantics::scope> {}; + + // + // + struct type: node<semantics::type> {}; + + // + // + struct instance: node<semantics::instance> + { + virtual void + traverse (type&); + + virtual void + belongs (type&); + + virtual void + belongs (type&, edge_dispatcher&); + }; + + // + // + struct data_member: node<semantics::data_member> + { + virtual void + traverse (type&); + + virtual void + belongs (type&); + + virtual void + belongs (type&, edge_dispatcher&); + }; + + // + // + struct unsupported_type: node<semantics::unsupported_type> {}; +} + +#endif // ODB_TRAVERSAL_ELEMENTS_HXX diff --git a/odb/odb/traversal/enum.cxx b/odb/odb/traversal/enum.cxx new file mode 100644 index 0000000..fa33f92 --- /dev/null +++ b/odb/odb/traversal/enum.cxx @@ -0,0 +1,54 @@ +// file : odb/traversal/enum.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <odb/traversal/enum.hxx> + +namespace traversal +{ + void enumerates:: + traverse (type& e) + { + dispatch (e.enumerator ()); + } + + // + // + void underlies:: + traverse (type& u) + { + dispatch (u.type ()); + } + + // + // + void enum_:: + traverse (type& e) + { + underlied (e); + enumerates (e); + } + + void enum_:: + underlied (type& e) + { + underlied (e, *this); + } + + void enum_:: + underlied (type& e, edge_dispatcher& d) + { + d.dispatch (e.underlied ()); + } + + void enum_:: + enumerates (type& e) + { + enumerates (e, *this); + } + + void enum_:: + enumerates (type& e, edge_dispatcher& d) + { + iterate_and_dispatch (e.enumerates_begin (), e.enumerates_end (), d); + } +} diff --git a/odb/odb/traversal/enum.hxx b/odb/odb/traversal/enum.hxx new file mode 100644 index 0000000..cd141f2 --- /dev/null +++ b/odb/odb/traversal/enum.hxx @@ -0,0 +1,57 @@ +// file : odb/traversal/enum.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_TRAVERSAL_ENUM_HXX +#define ODB_TRAVERSAL_ENUM_HXX + +#include <odb/semantics/enum.hxx> +#include <odb/traversal/elements.hxx> + +namespace traversal +{ + struct enumerates: edge<semantics::enumerates> + { + enumerates () {} + enumerates (node_dispatcher& n) + { + node_traverser (n); + } + + virtual void + traverse (type&); + }; + + struct enumerator: node<semantics::enumerator> {}; + + struct underlies: edge<semantics::underlies> + { + underlies () {} + underlies (node_dispatcher& n) + { + node_traverser (n); + } + + virtual void + traverse (type&); + }; + + struct enum_: node<semantics::enum_> + { + virtual void + traverse (type&); + + virtual void + underlied (type&); + + virtual void + underlied (type&, edge_dispatcher&); + + virtual void + enumerates (type&); + + virtual void + enumerates (type&, edge_dispatcher&); + }; +} + +#endif // ODB_TRAVERSAL_ENUM_HXX diff --git a/odb/odb/traversal/fundamental.hxx b/odb/odb/traversal/fundamental.hxx new file mode 100644 index 0000000..974e74f --- /dev/null +++ b/odb/odb/traversal/fundamental.hxx @@ -0,0 +1,39 @@ +// file : odb/traversal/fundamental.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_TRAVERSAL_FUNDAMENTAL_HXX +#define ODB_TRAVERSAL_FUNDAMENTAL_HXX + +#include <odb/semantics/fundamental.hxx> +#include <odb/traversal/elements.hxx> + +namespace traversal +{ + struct fund_type: node<semantics::fund_type> {}; + + struct fund_void: node<semantics::fund_void> {}; + struct fund_bool: node<semantics::fund_bool> {}; + + // Integral. + // + struct fund_char: node<semantics::fund_char> {}; + struct fund_wchar: node<semantics::fund_wchar> {}; + struct fund_signed_char: node<semantics::fund_signed_char> {}; + struct fund_unsigned_char: node<semantics::fund_unsigned_char> {}; + struct fund_short: node<semantics::fund_short> {}; + struct fund_unsigned_short: node<semantics::fund_unsigned_short> {}; + struct fund_int: node<semantics::fund_int> {}; + struct fund_unsigned_int: node<semantics::fund_unsigned_int> {}; + struct fund_long: node<semantics::fund_long> {}; + struct fund_unsigned_long: node<semantics::fund_unsigned_long> {}; + struct fund_long_long: node<semantics::fund_long_long> {}; + struct fund_unsigned_long_long: node<semantics::fund_unsigned_long_long> {}; + + // Real. + // + struct fund_float: node<semantics::fund_float> {}; + struct fund_double: node<semantics::fund_double> {}; + struct fund_long_double: node<semantics::fund_long_double> {}; +} + +#endif // ODB_TRAVERSAL_FUNDAMENTAL_HXX diff --git a/odb/odb/traversal/namespace.hxx b/odb/odb/traversal/namespace.hxx new file mode 100644 index 0000000..223322b --- /dev/null +++ b/odb/odb/traversal/namespace.hxx @@ -0,0 +1,15 @@ +// file : odb/traversal/namespace.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_TRAVERSAL_NAMESPACE_HXX +#define ODB_TRAVERSAL_NAMESPACE_HXX + +#include <odb/semantics/namespace.hxx> +#include <odb/traversal/elements.hxx> + +namespace traversal +{ + struct namespace_: scope_template<semantics::namespace_> {}; +} + +#endif // ODB_TRAVERSAL_NAMESPACE_HXX diff --git a/odb/odb/traversal/relational.hxx b/odb/odb/traversal/relational.hxx new file mode 100644 index 0000000..a78e26b --- /dev/null +++ b/odb/odb/traversal/relational.hxx @@ -0,0 +1,18 @@ +// file : odb/traversal/relational.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_TRAVERSAL_RELATIONAL_HXX +#define ODB_TRAVERSAL_RELATIONAL_HXX + +#include <odb/traversal/relational/changelog.hxx> +#include <odb/traversal/relational/changeset.hxx> +#include <odb/traversal/relational/column.hxx> +#include <odb/traversal/relational/elements.hxx> +#include <odb/traversal/relational/foreign-key.hxx> +#include <odb/traversal/relational/index.hxx> +#include <odb/traversal/relational/key.hxx> +#include <odb/traversal/relational/model.hxx> +#include <odb/traversal/relational/primary-key.hxx> +#include <odb/traversal/relational/table.hxx> + +#endif // ODB_TRAVERSAL_RELATIONAL_HXX diff --git a/odb/odb/traversal/relational/changelog.cxx b/odb/odb/traversal/relational/changelog.cxx new file mode 100644 index 0000000..149d93d --- /dev/null +++ b/odb/odb/traversal/relational/changelog.cxx @@ -0,0 +1,63 @@ +// file : odb/traversal/relational/changelog.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <odb/traversal/relational/changelog.hxx> +#include <odb/traversal/relational/model.hxx> +#include <odb/traversal/relational/changeset.hxx> + +namespace traversal +{ + namespace relational + { + // contains_model + // + void contains_model:: + traverse (type& c) + { + dispatch (c.model ()); + } + + // contains_changeset + // + void contains_changeset:: + traverse (type& c) + { + dispatch (c.changeset ()); + } + + // changelog + // + void changelog:: + traverse (type& cl) + { + contains_model (cl); + contains_changeset (cl); + } + + void changelog:: + contains_model (type& cl) + { + contains_model (cl, *this); + } + + void changelog:: + contains_model (type& cl, edge_dispatcher& d) + { + d.dispatch (cl.contains_model ()); + } + + void changelog:: + contains_changeset (type& cl) + { + contains_changeset (cl, *this); + } + + void changelog:: + contains_changeset (type& cl, edge_dispatcher& d) + { + iterate_and_dispatch (cl.contains_changeset_begin (), + cl.contains_changeset_end (), + d); + } + } +} diff --git a/odb/odb/traversal/relational/changelog.hxx b/odb/odb/traversal/relational/changelog.hxx new file mode 100644 index 0000000..4b7f18f --- /dev/null +++ b/odb/odb/traversal/relational/changelog.hxx @@ -0,0 +1,53 @@ +// file : odb/traversal/relational/changelog.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_TRAVERSAL_RELATIONAL_CHANGELOG_HXX +#define ODB_TRAVERSAL_RELATIONAL_CHANGELOG_HXX + +#include <odb/semantics/relational/changelog.hxx> +#include <odb/traversal/relational/elements.hxx> + +namespace traversal +{ + namespace relational + { + struct contains_model: edge<semantics::relational::contains_model> + { + contains_model () {} + contains_model (node_dispatcher& n) {node_traverser (n);} + + virtual void + traverse (type&); + }; + + struct contains_changeset: edge<semantics::relational::contains_changeset> + { + contains_changeset () {} + contains_changeset (node_dispatcher& n) {node_traverser (n);} + + virtual void + traverse (type&); + }; + + struct changelog: node<semantics::relational::changelog> + { + public: + virtual void + traverse (type&); + + virtual void + contains_model (type&); + + virtual void + contains_model (type&, edge_dispatcher&); + + virtual void + contains_changeset (type&); + + virtual void + contains_changeset (type&, edge_dispatcher&); + }; + } +} + +#endif // ODB_TRAVERSAL_RELATIONAL_CHANGELOG_HXX diff --git a/odb/odb/traversal/relational/changeset.hxx b/odb/odb/traversal/relational/changeset.hxx new file mode 100644 index 0000000..3cc522a --- /dev/null +++ b/odb/odb/traversal/relational/changeset.hxx @@ -0,0 +1,18 @@ +// file : odb/traversal/relational/changeset.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_TRAVERSAL_RELATIONAL_CHANGESET_HXX +#define ODB_TRAVERSAL_RELATIONAL_CHANGESET_HXX + +#include <odb/semantics/relational/changeset.hxx> +#include <odb/traversal/relational/elements.hxx> + +namespace traversal +{ + namespace relational + { + struct changeset: scope_template<semantics::relational::changeset> {}; + } +} + +#endif // ODB_TRAVERSAL_RELATIONAL_CHANGESET_HXX diff --git a/odb/odb/traversal/relational/column.hxx b/odb/odb/traversal/relational/column.hxx new file mode 100644 index 0000000..b9c586d --- /dev/null +++ b/odb/odb/traversal/relational/column.hxx @@ -0,0 +1,21 @@ +// file : odb/traversal/relational/column.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_TRAVERSAL_RELATIONAL_COLUMN_HXX +#define ODB_TRAVERSAL_RELATIONAL_COLUMN_HXX + +#include <odb/semantics/relational/column.hxx> +#include <odb/traversal/relational/elements.hxx> + +namespace traversal +{ + namespace relational + { + struct column: node<semantics::relational::column> {}; + struct add_column: node<semantics::relational::add_column> {}; + struct drop_column: node<semantics::relational::drop_column> {}; + struct alter_column: node<semantics::relational::alter_column> {}; + } +} + +#endif // ODB_TRAVERSAL_RELATIONAL_COLUMN_HXX diff --git a/odb/odb/traversal/relational/elements.hxx b/odb/odb/traversal/relational/elements.hxx new file mode 100644 index 0000000..2b43ab0 --- /dev/null +++ b/odb/odb/traversal/relational/elements.hxx @@ -0,0 +1,162 @@ +// file : odb/traversal/relational/elements.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_TRAVERSAL_RELATIONAL_ELEMENTS_HXX +#define ODB_TRAVERSAL_RELATIONAL_ELEMENTS_HXX + +#include <libcutl/compiler/traversal.hxx> +#include <odb/semantics/relational/elements.hxx> + +namespace traversal +{ + namespace relational + { + using namespace cutl; + + // + // + typedef compiler::dispatcher<semantics::relational::node> node_dispatcher; + typedef compiler::dispatcher<semantics::relational::edge> edge_dispatcher; + + // + // + struct node_base: node_dispatcher, edge_dispatcher + { + void + edge_traverser (edge_dispatcher& d) + { + edge_dispatcher::traverser (d); + } + + edge_dispatcher& + edge_traverser () + { + return *this; + } + + using node_dispatcher::dispatch; + using edge_dispatcher::dispatch; + + using edge_dispatcher::iterate_and_dispatch; + }; + + struct edge_base: edge_dispatcher, node_dispatcher + { + void + node_traverser (node_dispatcher& d) + { + node_dispatcher::traverser (d); + } + + node_dispatcher& + node_traverser () + { + return *this; + } + + using edge_dispatcher::dispatch; + using node_dispatcher::dispatch; + + using node_dispatcher::iterate_and_dispatch; + }; + + inline edge_base& + operator>> (node_base& n, edge_base& e) + { + n.edge_traverser (e); + return e; + } + + inline node_base& + operator>> (edge_base& e, node_base& n) + { + e.node_traverser (n); + return n; + } + + // + // + template <typename X> + struct node: compiler::traverser_impl<X, semantics::relational::node>, + virtual node_base + { + virtual void + traverse (X&) {} + }; + + template <typename X> + struct edge: compiler::traverser_impl<X, semantics::relational::edge>, + virtual edge_base + { + }; + + // + // Edges + // + + template <typename N> + struct names: edge<semantics::relational::names<N> > + { + names () + { + } + + names (node_dispatcher& n) + { + this->node_traverser (n); + } + + virtual void + traverse (semantics::relational::names<N>& e) + { + this->dispatch (e.nameable ()); + } + }; + + typedef names<semantics::relational::uname> unames; + typedef names<semantics::relational::qname> qnames; + + // + // Nodes + // + + template <typename N> + struct nameable: node<semantics::relational::nameable<N> > {}; + + typedef nameable<semantics::relational::uname> unameable; + typedef nameable<semantics::relational::qname> qnameable; + + // + // + template <typename T> + struct scope_template: node<T> + { + public: + virtual void + traverse (T& s) + { + names (s); + } + + virtual void + names (T& s) + { + names (s, *this); + } + + virtual void + names (T& s, edge_dispatcher& d) + { + this->iterate_and_dispatch (s.names_begin (), s.names_end (), d); + } + }; + + template <typename N> + struct scope: scope_template<semantics::relational::scope<N> > {}; + + typedef scope<semantics::relational::uname> uscope; + typedef scope<semantics::relational::qname> qscope; + } +} + +#endif // ODB_TRAVERSAL_RELATIONAL_ELEMENTS_HXX diff --git a/odb/odb/traversal/relational/foreign-key.hxx b/odb/odb/traversal/relational/foreign-key.hxx new file mode 100644 index 0000000..b8ccba3 --- /dev/null +++ b/odb/odb/traversal/relational/foreign-key.hxx @@ -0,0 +1,21 @@ +// file : odb/traversal/relational/foreign-key.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_TRAVERSAL_RELATIONAL_FOREIGN_KEY_HXX +#define ODB_TRAVERSAL_RELATIONAL_FOREIGN_KEY_HXX + +#include <odb/semantics/relational/foreign-key.hxx> +#include <odb/traversal/relational/key.hxx> + +namespace traversal +{ + namespace relational + { + struct foreign_key: key_template<semantics::relational::foreign_key> {}; + struct add_foreign_key: + key_template<semantics::relational::add_foreign_key> {}; + struct drop_foreign_key: node<semantics::relational::drop_foreign_key> {}; + } +} + +#endif // ODB_TRAVERSAL_RELATIONAL_FOREIGN_KEY_HXX diff --git a/odb/odb/traversal/relational/index.hxx b/odb/odb/traversal/relational/index.hxx new file mode 100644 index 0000000..2277fee --- /dev/null +++ b/odb/odb/traversal/relational/index.hxx @@ -0,0 +1,20 @@ +// file : odb/traversal/relational/index.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_TRAVERSAL_RELATIONAL_INDEX_HXX +#define ODB_TRAVERSAL_RELATIONAL_INDEX_HXX + +#include <odb/semantics/relational/index.hxx> +#include <odb/traversal/relational/key.hxx> + +namespace traversal +{ + namespace relational + { + struct index: key_template<semantics::relational::index> {}; + struct add_index: key_template<semantics::relational::add_index> {}; + struct drop_index: node<semantics::relational::drop_index> {}; + } +} + +#endif // ODB_TRAVERSAL_RELATIONAL_INDEX_HXX diff --git a/odb/odb/traversal/relational/key.cxx b/odb/odb/traversal/relational/key.cxx new file mode 100644 index 0000000..15a131c --- /dev/null +++ b/odb/odb/traversal/relational/key.cxx @@ -0,0 +1,17 @@ +// file : odb/traversal/relational/key.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <odb/traversal/relational/key.hxx> +#include <odb/traversal/relational/column.hxx> + +namespace traversal +{ + namespace relational + { + void contains:: + traverse (type& c) + { + dispatch (c.column ()); + } + } +} diff --git a/odb/odb/traversal/relational/key.hxx b/odb/odb/traversal/relational/key.hxx new file mode 100644 index 0000000..d0ba2d7 --- /dev/null +++ b/odb/odb/traversal/relational/key.hxx @@ -0,0 +1,50 @@ +// file : odb/traversal/relational/key.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_TRAVERSAL_RELATIONAL_KEY_HXX +#define ODB_TRAVERSAL_RELATIONAL_KEY_HXX + +#include <odb/semantics/relational/key.hxx> +#include <odb/traversal/relational/elements.hxx> + +namespace traversal +{ + namespace relational + { + struct contains: edge<semantics::relational::contains> + { + contains () {} + contains (node_dispatcher& n) {node_traverser (n);} + + virtual void + traverse (type&); + }; + + template <typename T> + struct key_template: node<T> + { + public: + virtual void + traverse (T& k) + { + contains (k); + } + + virtual void + contains (T& k) + { + contains (k, *this); + } + + virtual void + contains (T& k, edge_dispatcher& d) + { + this->iterate_and_dispatch (k.contains_begin (), k.contains_end (), d); + } + }; + + struct key: key_template<semantics::relational::key> {}; + } +} + +#endif // ODB_TRAVERSAL_RELATIONAL_KEY_HXX diff --git a/odb/odb/traversal/relational/model.hxx b/odb/odb/traversal/relational/model.hxx new file mode 100644 index 0000000..03d70b1 --- /dev/null +++ b/odb/odb/traversal/relational/model.hxx @@ -0,0 +1,18 @@ +// file : odb/traversal/relational/model.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_TRAVERSAL_RELATIONAL_MODEL_HXX +#define ODB_TRAVERSAL_RELATIONAL_MODEL_HXX + +#include <odb/semantics/relational/model.hxx> +#include <odb/traversal/relational/elements.hxx> + +namespace traversal +{ + namespace relational + { + struct model: scope_template<semantics::relational::model> {}; + } +} + +#endif // ODB_TRAVERSAL_RELATIONAL_MODEL_HXX diff --git a/odb/odb/traversal/relational/primary-key.hxx b/odb/odb/traversal/relational/primary-key.hxx new file mode 100644 index 0000000..dcefab6 --- /dev/null +++ b/odb/odb/traversal/relational/primary-key.hxx @@ -0,0 +1,18 @@ +// file : odb/traversal/relational/primary-key.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_TRAVERSAL_RELATIONAL_PRIMARY_KEY_HXX +#define ODB_TRAVERSAL_RELATIONAL_PRIMARY_KEY_HXX + +#include <odb/semantics/relational/primary-key.hxx> +#include <odb/traversal/relational/key.hxx> + +namespace traversal +{ + namespace relational + { + struct primary_key: key_template<semantics::relational::primary_key> {}; + } +} + +#endif // ODB_TRAVERSAL_RELATIONAL_PRIMARY_KEY_HXX diff --git a/odb/odb/traversal/relational/table.hxx b/odb/odb/traversal/relational/table.hxx new file mode 100644 index 0000000..d80e37e --- /dev/null +++ b/odb/odb/traversal/relational/table.hxx @@ -0,0 +1,21 @@ +// file : odb/traversal/relational/table.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_TRAVERSAL_RELATIONAL_TABLE_HXX +#define ODB_TRAVERSAL_RELATIONAL_TABLE_HXX + +#include <odb/semantics/relational/table.hxx> +#include <odb/traversal/relational/elements.hxx> + +namespace traversal +{ + namespace relational + { + struct table: scope_template<semantics::relational::table> {}; + struct add_table: scope_template<semantics::relational::add_table> {}; + struct drop_table: node<semantics::relational::drop_table> {}; + struct alter_table: scope_template<semantics::relational::alter_table> {}; + } +} + +#endif // ODB_TRAVERSAL_RELATIONAL_TABLE_HXX diff --git a/odb/odb/traversal/template.cxx b/odb/odb/traversal/template.cxx new file mode 100644 index 0000000..21a846d --- /dev/null +++ b/odb/odb/traversal/template.cxx @@ -0,0 +1,53 @@ +// file : odb/traversal/template.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <odb/traversal/template.hxx> + +namespace traversal +{ + void instantiates:: + traverse (type& i) + { + dispatch (i.template_ ()); + } + + // + // + void instantiation:: + traverse (type& i) + { + instantiates (i); + } + + void instantiation:: + instantiates (type& i) + { + instantiates (i, *this); + } + + void instantiation:: + instantiates (type& i, edge_dispatcher& d) + { + d.dispatch (i.instantiates ()); + } + + // + // + void type_instantiation:: + traverse (type& i) + { + instantiates (i); + } + + void type_instantiation:: + instantiates (type& i) + { + instantiates (i, *this); + } + + void type_instantiation:: + instantiates (type& i, edge_dispatcher& d) + { + d.dispatch (i.instantiates ()); + } +} diff --git a/odb/odb/traversal/template.hxx b/odb/odb/traversal/template.hxx new file mode 100644 index 0000000..28a64d5 --- /dev/null +++ b/odb/odb/traversal/template.hxx @@ -0,0 +1,56 @@ +// file : odb/traversal/template.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_TRAVERSAL_TEMPLATE_HXX +#define ODB_TRAVERSAL_TEMPLATE_HXX + +#include <odb/semantics/template.hxx> +#include <odb/traversal/elements.hxx> + +namespace traversal +{ + struct instantiates: edge<semantics::instantiates> + { + instantiates () + { + } + + instantiates (node_dispatcher& n) + { + node_traverser (n); + } + + virtual void + traverse (type&); + }; + + struct template_: node<semantics::template_> {}; + + struct instantiation: node<semantics::instantiation> + { + virtual void + traverse (type&); + + virtual void + instantiates (type&); + + virtual void + instantiates (type&, edge_dispatcher&); + }; + + struct type_template: node<semantics::type_template> {}; + + struct type_instantiation: node<semantics::type_instantiation> + { + virtual void + traverse (type&); + + virtual void + instantiates (type&); + + virtual void + instantiates (type&, edge_dispatcher&); + }; +} + +#endif // ODB_TRAVERSAL_TEMPLATE_HXX diff --git a/odb/odb/traversal/union-template.cxx b/odb/odb/traversal/union-template.cxx new file mode 100644 index 0000000..9d72fcd --- /dev/null +++ b/odb/odb/traversal/union-template.cxx @@ -0,0 +1,26 @@ +// file : odb/traversal/union-template.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <odb/traversal/union-template.hxx> + +namespace traversal +{ + void union_instantiation:: + traverse (type& u) + { + instantiates (u); + names (u); + } + + void union_instantiation:: + instantiates (type& u) + { + instantiates (u, *this); + } + + void union_instantiation:: + instantiates (type& u, edge_dispatcher& d) + { + d.dispatch (u.instantiates ()); + } +} diff --git a/odb/odb/traversal/union-template.hxx b/odb/odb/traversal/union-template.hxx new file mode 100644 index 0000000..10d1d49 --- /dev/null +++ b/odb/odb/traversal/union-template.hxx @@ -0,0 +1,29 @@ +// file : odb/traversal/union-template.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_TRAVERSAL_UNION_TEMPLATE_HXX +#define ODB_TRAVERSAL_UNION_TEMPLATE_HXX + +#include <odb/semantics/union-template.hxx> + +#include <odb/traversal/elements.hxx> +#include <odb/traversal/union.hxx> + +namespace traversal +{ + struct union_template: scope_template<semantics::union_template> {}; + + struct union_instantiation: scope_template<semantics::union_instantiation> + { + virtual void + traverse (type&); + + virtual void + instantiates (type&); + + virtual void + instantiates (type&, edge_dispatcher&); + }; +} + +#endif // ODB_TRAVERSAL_UNION_TEMPLATE_HXX diff --git a/odb/odb/traversal/union.hxx b/odb/odb/traversal/union.hxx new file mode 100644 index 0000000..2d1a7af --- /dev/null +++ b/odb/odb/traversal/union.hxx @@ -0,0 +1,15 @@ +// file : odb/traversal/union.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_TRAVERSAL_UNION_HXX +#define ODB_TRAVERSAL_UNION_HXX + +#include <odb/semantics/union.hxx> +#include <odb/traversal/elements.hxx> + +namespace traversal +{ + struct union_: scope_template<semantics::union_> {}; +} + +#endif // ODB_TRAVERSAL_UNION_HXX diff --git a/odb/odb/traversal/unit.hxx b/odb/odb/traversal/unit.hxx new file mode 100644 index 0000000..a90418a --- /dev/null +++ b/odb/odb/traversal/unit.hxx @@ -0,0 +1,15 @@ +// file : odb/traversal/unit.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_TRAVERSAL_UNIT_HXX +#define ODB_TRAVERSAL_UNIT_HXX + +#include <odb/semantics/unit.hxx> +#include <odb/traversal/elements.hxx> + +namespace traversal +{ + struct unit: scope_template<semantics::unit> {}; +} + +#endif // ODB_TRAVERSAL_UNIT_HXX diff --git a/odb/odb/validator.cxx b/odb/odb/validator.cxx new file mode 100644 index 0000000..bf9aa6b --- /dev/null +++ b/odb/odb/validator.cxx @@ -0,0 +1,1912 @@ +// file : odb/validator.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <odb/gcc.hxx> + +#include <set> +#include <iostream> + +#include <odb/traversal.hxx> +#include <odb/common.hxx> +#include <odb/context.hxx> +#include <odb/diagnostics.hxx> +#include <odb/validator.hxx> +#include <odb/cxx-lexer.hxx> + +#include <odb/relational/validator.hxx> + +using namespace std; + +namespace +{ + // Resolve null overrides. + // + static void + override_null (semantics::node& n, string const& prefix = "") + { + string p (prefix.empty () ? prefix : prefix + '-'); + + if (n.count (p + "null") && n.count (p + "not-null")) + { + if (n.get<location_t> (p + "null-location") < + n.get<location_t> (p + "not-null-location")) + { + n.remove (p + "null"); + n.remove (p + "null-location"); + } + else + { + n.remove (p + "not-null"); + n.remove (p + "not-null-location"); + } + } + } + + // + // Pass 1. + // + + struct data_member1: traversal::data_member, context + { + data_member1 (bool& valid) + : valid_ (valid) + { + } + + virtual void + traverse (type& m) + { + semantics::class_& c (dynamic_cast<semantics::class_&> (m.scope ())); + bool obj (object (c)); + + // If the class is marked transient, then mark each non-virtual + // data member as transient. + // + { + bool t (transient (m)); + + if (!t && c.count ("transient") && !m.count ("virtual")) + { + m.set ("transient", true); + t = true; + } + + if (t) + return; + } + + semantics::names* hint; + semantics::type& t (utype (m, hint)); + + if (t.fq_anonymous (hint)) + { + os << m.file () << ":" << m.line () << ":" << m.column () << ":" + << " error: unnamed type in data member declaration" << endl; + + os << m.file () << ":" << m.line () << ":" << m.column () << ":" + << " info: use 'typedef' to name this type" << endl; + + valid_ = false; + } + + // Make sure id or inverse member is not marked readonly since we + // depend on these three sets not having overlaps. + // + if (m.count ("readonly")) // context::readonly() also checks the class. + { + if (id (m)) + { + os << m.file () << ":" << m.line () << ":" << m.column () << ":" + << " error: object id should not be declared readonly" << endl; + + valid_ = false; + } + + if (inverse (m)) + { + os << m.file () << ":" << m.line () << ":" << m.column () << ":" + << " error: inverse object pointer should not be declared " + << "readonly" << endl; + + valid_ = false; + } + } + + // Make sure a member of a section is an immediate member of an object. + // The same for the section member itself. + // + bool section (t.fq_name () == "::odb::section"); + + if (!obj) + { + if (m.count ("section-member")) + { + os << m.file () << ":" << m.line () << ":" << m.column () << ": " << + "error: data member belonging to a section can only be a " << + "direct member of a persistent class" << endl; + valid_ = false; + } + + if (section) + { + os << m.file () << ":" << m.line () << ":" << m.column () << ": " << + "error: section data member can only be a direct member of a " << + "persistent class" << endl; + valid_ = false; + } + } + + // Make sure the load and update pragmas are only specified on + // section members. + // + if (!section) + { + if (m.count ("section-load")) + { + location_t loc (m.get<location_t> ("section-load-location")); + error (loc) << "'#pragma db load' can only be specified for " + "a section data member" << endl; + valid_ = false; + } + + if (m.count ("section-update")) + { + location_t loc (m.get<location_t> ("section-update-location")); + error (loc) << "'#pragma db update' can only be specified for " + "a section data member" << endl; + valid_ = false; + } + } + + // Check that the addition version makes sense. + // + unsigned long long av (m.get<unsigned long long> ("added", 0)); + if (av != 0) + { + location_t l (m.get<location_t> ("added-location")); + + if (id (m)) + { + error (l) << "object id cannod be soft-added" << endl; + valid_ = false; + } + + if (version (m)) + { + error (l) << "optimistic concurrency version cannod be " + "soft-added" << endl; + valid_ = false; + } + + if (!versioned ()) + { + error (l) << "added data member in a non-versioned object " << + "model" << endl; + valid_ = false; + } + else + { + model_version const& mv (version ()); + + if (av > mv.current) + { + error (l) << "addition version is greater than the current " << + "model version" << endl; + valid_ = false; + } + else if (av <= mv.base) + { + error (l) << "addition version is less than or equal to the " << + "base model version" << endl; + info (l) << "delete this pragma since migration to version " << + av << " is no longer possible" << endl; + valid_ = false; + } + } + } + + // Check that the deletion version makes sense. + // + unsigned long long dv (m.get<unsigned long long> ("deleted", 0)); + if (dv != 0) + { + location_t l (m.get<location_t> ("deleted-location")); + + if (id (m)) + { + error (l) << "object id cannod be soft-deleted" << endl; + valid_ = false; + } + + if (version (m)) + { + error (l) << "optimistic concurrency version cannod be " + "soft-deleted" << endl; + valid_ = false; + } + + if (!versioned ()) + { + error (l) << "deleted data member in a non-versioned object " << + "model" << endl; + valid_ = false; + } + else + { + model_version const& mv (version ()); + + if (dv > mv.current) + { + error (l) << "deletion version is greater than the current " << + "model version" << endl; + valid_ = false; + } + else if (dv <= mv.base) + { + error (l) << "deletion version is less than or equal to the " << + "base model version" << endl; + info (c.location ()) << "delete this data member since " << + "migration to version " << dv << " is no longer possible" << + endl; + valid_ = false; + } + } + } + + // Make sure that addition and deletion versions are properly ordered. + // We can have both the [av, dv] (added then deleted) and [dv, av] + // (deleted then re-added) intervals. + // + if (av != 0 && dv != 0 && av == dv) + { + location_t al (m.get<location_t> ("added-location")); + location_t dl (m.get<location_t> ("deleted-location")); + + error (al) << "addition and deletion versions are the same" << endl; + info (dl) << "deletion version is specified here" << endl; + valid_ = false; + } + + if (section) + return; // Section data member is transient. + + // Resolve null overrides. + // + override_null (m); + override_null (m, "value"); + } + + bool& valid_; + }; + + // Find special members (id, version). + // + struct special_members: traversal::class_, context + { + special_members (class_kind_type kind, + bool& valid, + semantics::data_member*& id, + semantics::data_member*& optimistic) + : kind_ (kind), member_ (valid, id, optimistic) + { + if (kind != class_view) + *this >> inherits_ >> *this; + + *this >> names_ >> member_; + } + + virtual void + traverse (semantics::class_& c) + { + // Skip transient bases. + // + switch (kind_) + { + case class_object: + { + if (!object (c)) + return; + break; + } + case class_view: + { + break; + } + case class_composite: + { + if (!composite (c)) + return; + break; + } + case class_other: + { + assert (false); + break; + } + } + + // Views don't have bases. + // + if (kind_ != class_view) + inherits (c); + + names (c); + } + + private: + struct member: traversal::data_member, context + { + member (bool& valid, + semantics::data_member*& id, + semantics::data_member*& optimistic) + : valid_ (valid), id_ (id), optimistic_ (optimistic) + { + } + + virtual void + traverse (semantics::data_member& m) + { + if (m.count ("id")) + { + if (id_ == 0) + id_ = &m; + else + { + os << m.file () << ":" << m.line () << ":" << m.column () << ":" + << " error: multiple object id members" << endl; + + os << id_->file () << ":" << id_->line () << ":" << id_->column () + << ": info: previous id member is declared here" << endl; + + valid_ = false; + } + } + + if (version (m)) + { + if (optimistic_ == 0) + optimistic_ = &m; + else + { + os << m.file () << ":" << m.line () << ":" << m.column () << ":" + << " error: multiple version members" << endl; + + semantics::data_member& o (*optimistic_); + + os << o.file () << ":" << o.line () << ":" << o.column () + << ": info: previous version member is declared here" << endl; + + valid_ = false; + } + } + } + + bool& valid_; + semantics::data_member*& id_; + semantics::data_member*& optimistic_; + }; + + class_kind_type kind_; + member member_; + traversal::names names_; + traversal::inherits inherits_; + }; + + // + // + struct value_type: traversal::type, context + { + virtual void + traverse (semantics::type& t) + { + // Resolve null overrides. + // + override_null (t); + override_null (t, "value"); + } + }; + + struct typedefs1: typedefs + { + typedefs1 (traversal::declares& d) + : typedefs (true), declares_ (d) + { + } + + void + traverse (semantics::typedefs& t) + { + if (check (t)) + traversal::typedefs::traverse (t); + else + declares_.traverse (t); + } + + private: + traversal::declares& declares_; + }; + + // + // + struct class1: traversal::class_, context + { + class1 (bool& valid) + : valid_ (valid), typedefs_ (declares_), member_ (valid) + { + *this >> defines_ >> *this; + *this >> typedefs_ >> *this; + *this >> declares_ >> vt_; + + names_member_ >> member_; + } + + virtual void + traverse (type& c) + { + class_kind_type ck (class_kind (c)); + + if (ck != class_other) + { + for (type::inherits_iterator i (c.inherits_begin ()); + i != c.inherits_end (); ++i) + { + type& b (i->base ()); + + if (class_kind (b) == class_other) + continue; + + location_t cl; + location_t bl; + + // If we are in the same file, then use real locations (as + // opposed to definition locations) since that's the order + // in which we will be generating the code. + // + if (class_file (c) == class_file (b)) + { + cl = class_real_location (c); + bl = class_real_location (b); + } + else + { + cl = class_location (c); + bl = class_location (b); + } + + if (cl < bl) + { + // We cannot use class_name() for b since it might not have + // yet been processed (tree-hint). + // + error (cl) << "base class " << b.name () << " must " + << "be defined before derived class " << class_name (c) + << endl; + + info (bl) << "class " << b.name () << " is define here" + << endl; + + valid_ = false; + } + } + } + + switch (ck) + { + case class_object: + names (c); + traverse_object (c); + break; + case class_view: + names (c); + traverse_view (c); + break; + case class_composite: + names (c); + traverse_composite (c); + // Fall through. + case class_other: + vt_.dispatch (c); + break; + } + } + + virtual void + traverse_object (type& c) + { + // Check that the deletion version makes sense. + // + if (unsigned long long v = c.get<unsigned long long> ("deleted", 0)) + { + location_t l (c.get<location_t> ("deleted-location")); + + if (!versioned ()) + { + error (l) << "deleted class in a non-versioned object model" << endl; + valid_ = false; + } + else + { + model_version const& mv (version ()); + + if (v > mv.current) + { + error (l) << "deletion version is greater than the current " << + "model version" << endl; + valid_ = false; + } + else if (v <= mv.base) + { + error (l) << "deletion version is less than or equal to the " << + "base model version" << endl; + info (c.location ()) << "delete this class since migration to " << + "version " << v << " is no longer possible" << endl; + valid_ = false; + } + } + } + + // Check that the callback function exist. + // + if (c.count ("callback")) + { + string name (c.get<string> ("callback")); + tree decl ( + lookup_qualified_name ( + c.tree_node (), get_identifier (name.c_str ()), false, false)); + + if (decl != error_mark_node && TREE_CODE (decl) == BASELINK) + { + // Figure out if we have a const version of the callback. OVL_* + // macros work for both FUNCTION_DECL and OVERLOAD. + // +#if BUILDING_GCC_MAJOR >= 8 + for (ovl_iterator i (BASELINK_FUNCTIONS (decl)); i; ++i) +#else + for (tree o (BASELINK_FUNCTIONS (decl)); o != 0; o = OVL_NEXT (o)) +#endif + { +#if BUILDING_GCC_MAJOR >= 8 + tree f (*i); +#else + tree f (OVL_CURRENT (o)); +#endif + if (DECL_CONST_MEMFUNC_P (f)) + { + c.set ("callback-const", true); + break; + } + } + + //@@ Would be nice to check the signature of the function(s) + // instead of postponing it until the C++ compilation. Though + // we may still get C++ compilation errors because of const + // mismatch. + // + } + else + { + os << c.file () << ":" << c.line () << ":" << c.column () << ": " + << "error: unable to resolve member function '" << name << "' " + << "specified with '#pragma db callback' for class '" + << class_name (c) << "'" << endl; + + valid_ = false; + } + } + + // Check bases. + // + type* poly_root (0); + + for (type::inherits_iterator i (c.inherits_begin ()); + i != c.inherits_end (); + ++i) + { + type& b (i->base ()); + + if (object (b)) + { + if (type* r = polymorphic (b)) + { + if (poly_root == 0) + { + poly_root = r; + c.set ("polymorphic-base", &static_cast<semantics::class_&> (b)); + } + // If poly_root and r are the same, then we have virtual + // inheritance. Though we don't support it at the moment. + // + else //if (poly_root != r) + { + os << c.file () << ":" << c.line () << ":" << c.column () << ":" + << " error: persistent class '" << class_name (c) << "' " + << "derives from multiple polymorphic bases" << endl; + + type& a (*poly_root); + os << a.file () << ":" << a.line () << ":" << a.column () << ":" + << " info: first polymorphic base defined here" << endl; + + type& b (*r); + os << b.file () << ":" << b.line () << ":" << b.column () << ":" + << " info: second polymorphic base defined here" << endl; + + valid_ = false; + } + } + } + else if (view (b) || composite (b)) + { + // @@ Should we use hint here? + // + string name (class_fq_name (b)); + + os << c.file () << ":" << c.line () << ":" << c.column () << ":" + << " error: base class '" << name << "' is a view or value type" + << endl; + + os << c.file () << ":" << c.line () << ":" << c.column () << ":" + << " info: object types cannot derive from view or value " + << "types" + << endl; + + os << b.file () << ":" << b.line () << ":" << b.column () << ":" + << " info: class '" << name << "' is defined here" << endl; + + valid_ = false; + } + } + + // Check members. + // + names (c, names_member_); + + // Check special members. + // + using semantics::data_member; + + data_member* id (0); + data_member* optimistic (0); + { + special_members t (class_object, valid_, id, optimistic); + t.traverse (c); + } + + if (id == 0) + { + // An object without an id should either be reuse-abstract + // or explicitly marked as such. We check that it is not + // polymorphic below. + // + if (!(c.count ("id") || abstract (c))) + { + os << c.file () << ":" << c.line () << ":" << c.column () << ":" + << " error: no data member designated as an object id" << endl; + + os << c.file () << ":" << c.line () << ":" << c.column () << ":" + << " info: use '#pragma db id' to specify an object id member" + << endl; + + os << c.file () << ":" << c.line () << ":" << c.column () << ":" + << " info: or explicitly declare that this persistent class " + << "has no object id with '#pragma db object no_id'" << endl; + + valid_ = false; + } + } + else + { + // Convert id to a member path. This has to happen early since + // a lot of code that runs next (e.g., processor, pass 1) depends + // on this information being available. + // + data_member_path& idp (c.set ("id-member", data_member_path ())); + idp.push_back (id); + + // See if we have a member path that we need to resolve. + // + const string& name (id->get<string> ("id")); + location_t l (id->get<location_t> ("id-location")); + + if (!name.empty ()) + { + if (id->count ("auto")) + { + error (l) << "nested id cannot be automatically assigned" << endl; + valid_ = false; + } + + if (semantics::class_* comp = utype (*id).is_a<semantics::class_> ()) + { + try + { + resolve_data_members (idp, *comp, name, l, lex_); + } + catch (const operation_failed&) {valid_ = false;} + } + else + { + error (l) << "nested id requires composite member" << endl; + valid_ = false; + } + + // Mark the whole member as readonly. + // + id->set ("readonly", true); + } + + data_member* idf (idp.front ()); + data_member* idb (idp.back ()); + + // Complain if an id member has a default value (default value + // for the id's type is ok -- we will ignore it). + // + if (idb->count ("default")) + { + error (l) << "object id member cannot have default value" << endl; + valid_ = false; + } + + // Complain if an id member is in a section. + // + if (idf->count ("section-member")) + { + error (l) << "object id member cannot be in a section" << endl; + valid_ = false; + } + + // Automatically mark the id member as not null. If we already have + // an explicit null pragma for this member, issue an error. + // + if (idb->count ("null")) + { + error (l) << "object id member cannot be null" << endl; + valid_ = false; + } + else + idf->set ("not-null", true); + } + + if (optimistic != 0) + { + semantics::data_member& m (*optimistic); + + // Make sure we have the class declared optimistic. + // + if (&m.scope () == &c && !c.count ("optimistic")) + { + os << m.file () << ":" << m.line () << ":" << m.column () << ":" + << " error: version data member in a class not declared " + << "optimistic" << endl; + + os << c.file () << ":" << c.line () << ":" << c.column () << ":" + << " info: use '#pragma db optimistic' to declare this " + << "class optimistic" << endl; + + valid_ = false; + } + + // Make sure we have object id. + // + if (id == 0) + { + os << c.file () << ":" << c.line () << ":" << c.column () << ":" + << " error: optimistic class without an object id" << endl; + + valid_ = false; + } + + // Make sure id and version members are in the same class. The + // current architecture relies on that. + // + if (id != 0 && &id->scope () != &m.scope ()) + { + os << c.file () << ":" << c.line () << ":" << c.column () << ":" + << " error: object id and version members are in different " + << "classes" << endl; + + os << c.file () << ":" << c.line () << ":" << c.column () << ":" + << " info: object id and version members must be in the same " + << "class" << endl; + + os << id->file () << ":" << id->line () << ":" << id->column () + << ": info: object id member is declared here" << endl; + + os << m.file () << ":" << m.line () << ":" << m.column () << ":" + << " error: version member is declared here" << endl; + + valid_ = false; + } + + // Make sure this class is not readonly. + // + if (readonly (c)) + { + os << c.file () << ":" << c.line () << ":" << c.column () << ":" + << " error: optimistic class cannot be readonly" << endl; + + valid_ = false; + } + + // Complain if the version member is in a section. + // + if (m.count ("section-member")) + { + os << m.file () << ":" << m.line () << ":" << m.column () + << ": error: version member cannot be in a section" << endl; + valid_ = false; + } + + // This takes care of also marking derived classes as optimistic. + // + c.set ("optimistic-member", optimistic); + } + else + { + // Make sure there is a version member if the class declared + // optimistic. + // + if (c.count ("optimistic")) + { + os << c.file () << ":" << c.line () << ":" << c.column () << ":" + << " error: optimistic class without a version member" << endl; + + os << c.file () << ":" << c.line () << ":" << c.column () << ":" + << " info: use '#pragma db version' to declare on of the " + << "data members as a version" << endl; + + valid_ = false; + } + } + + // Polymorphic inheritance. + // + if (c.count ("polymorphic") && poly_root == 0) + { + // Root of the hierarchy. + // + + if (id == 0) + { + os << c.file () << ":" << c.line () << ":" << c.column () << ":" + << " error: polymorphic class without an object id" << endl; + + valid_ = false; + } + + if (!TYPE_POLYMORPHIC_P (c.tree_node ())) + { + os << c.file () << ":" << c.line () << ":" << c.column () << ":" + << " error: non-polymorphic class (class without virtual " + << "functions) cannot be declared polymorphic" << endl; + + valid_ = false; + } + + poly_root = &c; + } + + if (poly_root != 0) + c.set ("polymorphic-root", poly_root); + + // Sectionable objects. + // + if (c.count ("sectionable")) + { + if (optimistic == 0) + { + location_t l (c.get<location_t> ("sectionable-location")); + error (l) << "only optimistic class can be sectionable" << endl; + valid_ = false; + } + else if (&optimistic->scope () != &c && poly_root != &c) + { + location l (c.get<location_t> ("sectionable-location")); + error (l) << "only optimistic class that declares the version " << + "data member or that is a root of a polymorphic hierarchy can " << + "be sectionable" << endl; + info (optimistic->location ()) << "version member is declared " << + "here" << endl; + valid_ = false; + } + } + + // Update features set based on this object. + // + if (options.at_once () || class_file (c) == unit.file ()) + { + if (poly_root != 0) + features.polymorphic_object = true; + else if (id == 0 && !abstract (c)) + features.no_id_object = true; + else + features.simple_object = true; + } + } + + virtual void + traverse_view (type& c) + { + // Views require query support. + // + if (!options.generate_query ()) + { + os << c.file () << ":" << c.line () << ":" << c.column () << ":" + << " error: query support is required when using views" + << endl; + + os << c.file () << ":" << c.line () << ":" << c.column () << ":" + << " info: use the --generate-query option to enable query " + << "support" + << endl; + + valid_ = false; + } + + // Check that the callback function exist. + // + if (c.count ("callback")) + { + string name (c.get<string> ("callback")); + tree decl ( + lookup_qualified_name ( + c.tree_node (), get_identifier (name.c_str ()), false, false)); + + if (decl == error_mark_node || TREE_CODE (decl) != BASELINK) + { + os << c.file () << ":" << c.line () << ":" << c.column () << ": " + << "error: unable to resolve member function '" << name << "' " + << "specified with '#pragma db callback' for class '" + << class_name (c) << "'" << endl; + + valid_ = false; + } + + // No const version for views. + + //@@ Would be nice to check the signature of the function(s) + // instead of postponing it until the C++ compilation. Though + // we may still get C++ compilation errors because of const + // mismatch. + // + } + + // Check bases. + // + for (type::inherits_iterator i (c.inherits_begin ()); + i != c.inherits_end (); + ++i) + { + type& b (i->base ()); + + if (object (b) || view (b) || composite (b)) + { + // @@ Should we use hint here? + // + string name (class_fq_name (b)); + + os << c.file () << ":" << c.line () << ":" << c.column () << ":" + << " error: base class '" << name << "' is an object, " + << "view, or value type" + << endl; + + os << c.file () << ":" << c.line () << ":" << c.column () << ":" + << " info: view types cannot derive from view, object or " + << "value types" + << endl; + + os << b.file () << ":" << b.line () << ":" << b.column () << ":" + << " info: class '" << name << "' is defined here" << endl; + + valid_ = false; + } + } + + // Check members. + // + names (c, names_member_); + + // Check id. + // + semantics::data_member* id (0); + semantics::data_member* optimistic (0); + { + special_members t (class_view, valid_, id, optimistic); + t.traverse (c); + } + + if (id != 0) + { + os << id->file () << ":" << id->line () << ":" << id->column () + << ": error: view type data member cannot be designated as an " + << "object id" << endl; + + valid_ = false; + } + + if (optimistic != 0) + { + semantics::data_member& o (*optimistic); + + os << o.file () << ":" << o.line () << ":" << o.column () + << ": error: view type data member cannot be designated as a " + << "version" << endl; + + valid_ = false; + } + + // Update features set based on this view. + // + if (options.at_once () || class_file (c) == unit.file ()) + { + features.view = true; + } + } + + virtual void + traverse_composite (type& c) + { + for (type::inherits_iterator i (c.inherits_begin ()); + i != c.inherits_end (); + ++i) + { + type& b (i->base ()); + + if (object (b) || view (b)) + { + // @@ Should we use hint here? + // + string name (class_fq_name (b)); + + os << c.file () << ":" << c.line () << ":" << c.column () << ":" + << " error: base class '" << name << "' is a view or object " + << "type" + << endl; + + os << c.file () << ":" << c.line () << ":" << c.column () << ":" + << " info: composite value types cannot derive from object " + << "or view types" << endl; + + os << b.file () << ":" << b.line () << ":" << b.column () << ":" + << " info: class '" << name << "' is defined here" << endl; + + valid_ = false; + } + } + + // Check members. + // + names (c, names_member_); + + // Check id. + // + semantics::data_member* id (0); + semantics::data_member* optimistic (0); + { + special_members t (class_composite, valid_, id, optimistic); + t.traverse (c); + } + + if (id != 0) + { + os << id->file () << ":" << id->line () << ":" << id->column () + << ": error: value type data member cannot be designated as an " + << "object id" << endl; + + valid_ = false; + } + + if (optimistic != 0) + { + semantics::data_member& o (*optimistic); + + os << o.file () << ":" << o.line () << ":" << o.column () + << ": error: value type data member cannot be designated as a " + << "version" << endl; + + valid_ = false; + } + } + + bool& valid_; + + traversal::defines defines_; + traversal::declares declares_; + typedefs1 typedefs_; + + value_type vt_; + data_member1 member_; + traversal::names names_member_; + + cxx_string_lexer lex_; + }; + + // + // Pass 2. + // + + struct data_member2: traversal::data_member, context + { + data_member2 (bool& valid): valid_ (valid) {} + + // Verify that composite value 'c' that is used by data member + // 'm' is defined before (or inside) class 's'. + // + void + verify_composite_location (semantics::class_& c, + semantics::class_& s, + semantics::data_member& m) + { + if (c.in_scope (s)) + return; + + location_t cl; + location_t sl; + + // If we are in the same file, then use real locations (as + // opposed to definition locations) since that's the order + // in which we will be generating the code. + // + if (class_file (c) == class_file (s)) + { + cl = class_real_location (c); + sl = class_real_location (s); + } + else + { + cl = class_location (c); + sl = class_location (s); + } + + if (sl < cl) + { + const string& cn (class_name (c)); + + error (sl) + << "composite value type " << class_fq_name (c) << " must " + << "be defined before being used in class " << class_fq_name (s) + << endl; + + info (m.location ()) + << "data member " << m.name () << " uses " << cn << endl; + + info (cl) << "class " << cn << " is define here" << endl; + + valid_ = false; + } + } + + virtual void + traverse (type& m) + { + using semantics::class_; + + semantics::type& t (utype (m)); + class_& s (dynamic_cast<class_&> (m.scope ())); + + // Validate pointed-to objects. + // + class_* c; + if ((c = object_pointer (t)) || (c = points_to (m))) + { + bool poly (polymorphic (*c)); + bool abst (abstract (*c)); + + // Make sure the pointed-to class is complete. + // + if (!c->complete ()) + { + os << m.file () << ":" << m.line () << ":" << m.column () << ": " + << "error: pointed-to class '" << class_fq_name (*c) << "' " + << "is incomplete" << endl; + + os << c->file () << ":" << c->line () << ":" << c->column () << ": " + << "info: class '" << class_name (*c) << "' is declared here" + << endl; + + os << c->file () << ":" << c->line () << ":" << c->column () << ": " + << "info: consider including its definition with the " + << "--odb-epilogue option" << endl; + + valid_ = false; + return; + } + + // Make sure the pointed-to class is not reuse-abstract. + // + if (abst && !poly) + { + os << m.file () << ":" << m.line () << ":" << m.column () << ": " + << "error: pointed-to class '" << class_fq_name (*c) << "' " + << "is abstract" << endl; + + os << c->file () << ":" << c->line () << ":" << c->column () << ": " + << "info: class '" << class_name (*c) << "' is defined here" + << endl; + + throw operation_failed (); + } + + // Make sure the pointed-to class has object id unless it is in a + // view where we can load no-id objects. + // + if (data_member_path* id = id_member (*c)) + { + semantics::type& idt (utype (*id)); + + // Make sure composite id is defined before this pointer. Failed + // that we get non-obvious C++ compiler errors in generated code. + // + if (class_* comp = composite_wrapper (idt)) + verify_composite_location (*comp, s, m); + } + else if (!view_member (m)) + { + os << m.file () << ":" << m.line () << ":" << m.column () << ": " + << "error: pointed-to class '" << class_fq_name (*c) << "' " + << "has no object id" << endl; + + os << c->file () << ":" << c->line () << ":" << c->column () << ": " + << "info: class '" << class_name (*c) << "' is defined here" + << endl; + + valid_ = false; + return; + } + + // Make sure the pointed-to class has a default ctor. Since we will + // use database::load() in the generated code, lack of a default ctor + // will lead to uncompilable generated code. Poly-abstract is Ok. + // + if (!c->default_ctor () && !(abst && poly)) + { + os << m.file () << ":" << m.line () << ":" << m.column () << ": " + << "error: pointed-to class '" << class_fq_name (*c) << "' " + << "has no default constructor" << endl; + + os << c->file () << ":" << c->line () << ":" << c->column () << ": " + << "info: class '" << class_name (*c) << "' is defined here" + << endl; + + valid_ = false; + return; + } + } + + // Make sure composite type is defined before (or inside) + // this class. Failed that we get non-obvious C++ compiler + // errors in generated code. + // + if (class_* comp = composite_wrapper (t)) + verify_composite_location (*comp, s, m); + + // Check that containers with composite value element types don't have + // any nested containers. + // + if (semantics::type* c = container (m)) + { + class_* vt (0); + class_* kt (0); + + switch (container_kind (*c)) + { + case ck_map: + case ck_multimap: + { + kt = composite_wrapper (container_kt (m)); + } + // Fall through. + default: + { + vt = composite_wrapper (container_vt (m)); + } + } + + if (vt != 0 && has_a (*vt, test_container)) + { + os << m.file () << ":" << m.line () << ":" << m.column () << ": " + << "error: containers of containers not supported" << endl; + + os << vt->file () << ":" << vt->line () << ":" << vt->column () + << ": info: composite element value " << class_fq_name (*vt) + << " contains container(s)" << endl; + + valid_ = false; + } + + if (kt != 0 && has_a (*kt, test_container)) + { + os << m.file () << ":" << m.line () << ":" << m.column () << ": " + << "error: containers of containers not supported" << endl; + + os << kt->file () << ":" << kt->line () << ":" << kt->column () + << ": info: composite element key " << class_fq_name (*kt) + << " contains container(s)" << endl; + + valid_ = false; + } + } + } + + bool& valid_; + }; + + // Make sure soft-delete versions make sense for dependent entities. + // We don't seem to need anything for soft-add since if an entity is + // not added (e.g., an object), then we cannot reference it in the + // C++ sense (e.g., a pointer). + // + struct version_dependencies: object_members_base + { + version_dependencies (bool& valid): valid_ (valid) {} + + virtual void + traverse_object (semantics::class_& c) + { + // For reuse inheritance we allow the base to be soft-deleted. In + // a sense, it becomes like an abstract class with the added + // functionality of being able to load old data. + // + // For polymorphism inheritance things are a lot more complicated + // and we don't allow a base to be soft-deleted since there is a + // link (foreign key) from the derived table. + // + semantics::class_* poly (polymorphic (c)); + if (poly != 0 && poly != &c) + { + semantics::class_& base (polymorphic_base (c)); + check_strict ( + c, base, "polymorphic derived object", "polymorphic base"); + } + + // Don't go into bases. + // + names (c); + } + + virtual void + traverse_simple (semantics::data_member& m) + { + semantics::class_& c (dynamic_cast<semantics::class_&> (m.scope ())); + + switch (class_kind (c)) + { + case class_object: + { + // Member shouldn't be deleted after the object that contains it. + // + check_lax (m, c, "data member", "object"); + break; + } + // We leave it up to the user to make sure the view is consistent + // with the database schema it is being run on. + // + default: + break; + } + } + + virtual void + traverse_composite (semantics::data_member* m, semantics::class_& c) + { + if (m != 0) + traverse_simple (*m); // Do simple value tests. + else + names (c); // Don't go into bases. + } + + virtual void + traverse_pointer (semantics::data_member& m, semantics::class_& c) + { + traverse_simple (m); // Do simple value tests. + + // Pointer must be deleted before the pointed-to object. + // + check_strict (m, c, "object pointer", "pointed-to object"); + + // Inverse pointer must be deleted before the direct side. + // + if (data_member_path* imp = inverse (m)) + check_strict (m, *imp, "inverse object pointer", "direct pointer"); + } + + virtual void + traverse_container (semantics::data_member& m, semantics::type&) + { + traverse_simple (m); // Do simple value tests. + + if (semantics::class_* c = object_pointer (container_vt (m))) + { + // Pointer must be deleted before the pointed-to object. + // + check_strict (m, *c, "object pointer", "pointed-to object"); + + // Inverse pointer must be deleted before the direct side. + // + if (data_member_path* imp = inverse (m, "value")) + check_strict (m, *imp, "inverse object pointer", "direct pointer"); + } + } + + private: + // Check for harmless things like a data member deleted after the + // object. + // + template <typename D, typename P> + void + check_lax (D& dep, P& pre, char const* dname, char const* pname) + { + unsigned long long dv (deleted (dep)); + unsigned long long pv (deleted (pre)); + + if (pv == 0 || // Prerequisite never deleted. + dv == 0 || // Dependent never deleted. + dv <= pv) // Dependent deleted before prerequisite. + return; + + location_t dl (dep.template get<location_t> ("deleted-location")); + location_t pl (pre.template get<location_t> ("deleted-location")); + + error (dl) << dname << " is deleted after " << dname << endl; + info (pl) << pname << " deletion version is specified here" << endl; + + valid_ = false; + } + + // Check for harmful things that will result in an invalid database + // schema, like a pointer pointing to a deleted object. + // + template <typename D, typename P> + void + check_strict (D& dep, P& pre, char const* dname, char const* pname) + { + location_t dl (0), pl (0); + unsigned long long dv (deleted (dep, &dl)); + unsigned long long pv (deleted (pre, &pl)); + + if (pv == 0) // Prerequisite never deleted. + return; + + if (dv == 0) // Dependent never deleted. + { + location dl (dep.location ()); + + error (dl) << dname << " is not deleted" << endl; + info (pl) << pname << " is deleted here" << endl; + + valid_ = false; + } + else if (pv > dv) // Prerequisite deleted before dependent. + { + error (dl) << dname << " is deleted after " << pname << endl; + info (pl) << pname << " deletion version is specified here" << endl; + + valid_ = false; + } + } + + private: + bool& valid_; + }; + + struct class2: traversal::class_, context + { + class2 (bool& valid) + : valid_ (valid), has_lt_operator_ (0), typedefs_ (true), member_ (valid) + { + // Find the has_lt_operator function template. + // + tree odb ( + lookup_qualified_name ( + global_namespace, get_identifier ("odb"), false, false)); + + if (odb != error_mark_node) + { + tree compiler ( + lookup_qualified_name ( + odb, get_identifier ("compiler"), false, false)); + + if (compiler != error_mark_node) + { + has_lt_operator_ = lookup_qualified_name ( + compiler, get_identifier ("has_lt_operator"), false, false); + + if (has_lt_operator_ != error_mark_node) + { +#if BUILDING_GCC_MAJOR >= 8 + has_lt_operator_ = OVL_FIRST (has_lt_operator_); +#else + has_lt_operator_ = OVL_CURRENT (has_lt_operator_); +#endif + } + else + { + os << unit.file () << ": error: unable to resolve has_lt_operator " + << "function template inside odb::compiler" << endl; + has_lt_operator_ = 0; + } + } + else + os << unit.file () << ": error: unable to resolve compiler " + << "namespace inside odb" << endl; + } + else + os << unit.file () << ": error: unable to resolve odb namespace" + << endl; + + if (has_lt_operator_ == 0) + valid_ = false; + + *this >> defines_ >> *this; + *this >> typedefs_ >> *this; + + names_member_ >> member_; + } + + virtual void + traverse (type& c) + { + class_kind_type ck (class_kind (c)); + + if (ck == class_other) + return; + + names (c); + + switch (ck) + { + case class_object: traverse_object (c); break; + case class_view: traverse_view (c); break; + case class_composite: traverse_composite (c); break; + default: return; + } + + // Check members. + // + names (c, names_member_); + + // Check version dependencies. + // + { + version_dependencies vd (valid_); + vd.traverse (c); + } + } + + virtual void + traverse_object (type& c) + { + bool abst (abstract (c)); + bool poly (polymorphic (c)); + + // Make sure we have no empty or pointless sections unless we + // are reuse-abstract or polymorphic. + // + if (!poly && !abst) + { + user_sections& uss (c.get<user_sections> ("user-sections")); + + for (user_sections::iterator i (uss.begin ()); i != uss.end (); ++i) + { + user_section& s (*i); + + // Skip the special version update section (we always treat it + // as abstract in reuse inheritance). + // + if (s.special == user_section::special_version) + continue; + + semantics::data_member& m (*s.member); + location const& l (m.location ()); + + if (s.total == 0 && !s.containers) + { + error (l) << "empty section" << endl; + + if (&m.scope () != &c) + info (c.location ()) << "as seen in this non-abstract " << + "persistent class" << endl; + + valid_ = false; + continue; + } + + // Eager-loaded section with readonly members. + // + if (s.load == user_section::load_eager && s.update_empty ()) + { + error (l) << "eager-loaded section with readonly members is " << + "pointless" << endl; + + if (&m.scope () != &c) + info (c.location ()) << "as seen in this non-abstract " << + "persistent class" << endl; + + valid_ = false; + } + } + } + + if (data_member_path* id = id_member (c)) + { + semantics::type& t (utype (*id)); + + // If this is a session object, make sure that the id type can + // be compared. + // + if (session (c) && has_lt_operator_ != 0) + { + tree args (make_tree_vec (1)); + TREE_VEC_ELT (args, 0) = t.tree_node (); + + tree inst ( + instantiate_template ( + has_lt_operator_, args, tf_none)); + + bool v (inst != error_mark_node); + + if (v && + DECL_TEMPLATE_INSTANTIATION (inst) && + !DECL_TEMPLATE_INSTANTIATED (inst)) + { + // Instantiate this function template to see if the value type + // provides operator<. Unfortunately, GCC instantiate_decl() + // does not provide any control over the diagnostics it issues + // in case of an error. To work around this, we are going to + // temporarily redirect diagnostics to /dev/null, which is + // where asm_out_file points to (see plugin.cxx). + // + // Needless to say, this is very hacky and we should quickly fail + // (as we do below) if there were errors. + // + int ec (errorcount); + FILE* s (global_dc->printer->buffer->stream); + global_dc->printer->buffer->stream = asm_out_file; + + instantiate_decl (inst, false, false); + + global_dc->printer->buffer->stream = s; + v = (ec == errorcount); + } + + if (!v) + { + semantics::data_member& idm (*id->front ()); + + os << t.file () << ":" << t.line () << ":" << t.column () + << ": error: value type that is used as object id in " + << "persistent class with session support does not define " + << "the less than (<) comparison" << endl; + + os << t.file () << ":" << t.line () << ":" << t.column () + << ": info: provide operator< for this value type" << endl; + + os << idm.file () << ":" << idm.line () << ":" << idm.column () + << ": info: id member is defined here" << endl; + + os << c.file () << ":" << c.line () << ":" << c.column () + << ": info: persistent class is defined here" << endl; + + valid_ = false; + } + } + } + else + { + // Make sure an object without id has no sections. + // + user_sections& uss (c.get<user_sections> ("user-sections")); + + if (!uss.empty ()) + { + semantics::data_member& m (*uss.front ().member); + os << m.file () << ":" << m.line () << ":" << m.column () + << ": error: object without id cannot have sections" << endl; + valid_ = false; + } + } + + // Allow all the members to be deleted as long as there is no + // schema for this class. + // + column_count_type const& cc (column_count (c)); + size_t cont (has_a (c, test_container)); + size_t dcont (cont - has_a (c, test_container | exclude_deleted)); + + if ((cc.total == 0 && cont == 0) || + (cc.total == cc.deleted && cont == dcont && !(abst || deleted (c)))) + { + os << c.file () << ":" << c.line () << ":" << c.column () << ":" + << " error: no persistent data members in the class" << endl; + valid_ = false; + } + } + + virtual void + traverse_view (type& c) + { + // We don't check for the column count here since we may want to + // allow certain kinds of empty views. Instead, this is handled + // in relational::validation. + + // See if any of the associated objects are polymorphic. If so, + // we need to include polymorphism-specific headers. Also, if the + // view is loading the object, then we will need the corresponding + // statements. + // + if (c.count ("objects")) + { + view_objects& objs (c.get<view_objects> ("objects")); + + for (view_objects::iterator i (objs.begin ()); i != objs.end (); ++i) + { + if (i->kind == view_object::object) + { + if (polymorphic (*i->obj)) + features.polymorphic_object = true; + else if (i->ptr != 0) + { + (id_member (*i->obj) != 0 + ? features.simple_object + : features.no_id_object) = true; + } + } + } + } + } + + virtual void + traverse_composite (type& c) + { + // Allow all the members to be deleted. + // + column_count_type const& cc (column_count (c)); + size_t cont (has_a (c, test_container)); + + if (cc.total == 0 && cont == 0) + { + os << c.file () << ":" << c.line () << ":" << c.column () << ":" + << " error: no persistent data members in the class" << endl; + valid_ = false; + } + } + + bool& valid_; + tree has_lt_operator_; + + traversal::defines defines_; + typedefs typedefs_; + + data_member2 member_; + traversal::names names_member_; + }; +} + +void +validate (options const& ops, + features& f, + semantics::unit& u, + semantics::path const& p, + unsigned short pass) +{ + bool valid (true); + database db (ops.database ()[0]); + + // Validate options. + // + if (ops.generate_schema_only () && + ops.schema_format ()[db].count (schema_format::embedded)) + { + cerr << "error: --generate-schema-only is only valid when generating " << + "schema as a standalone SQL or separate C++ file" << endl; + valid = false; + } + + // Multi-database support options. + // + if (ops.multi_database () == multi_database::dynamic && + ops.default_database_specified () && + ops.default_database () != database::common) + { + cerr << "error: when dynamic multi-database support is used, the " << + "default database can only be 'common'" << endl; + valid = false; + } + + if (db == database::common && + ops.multi_database () == multi_database::disabled) + { + cerr << "error: 'common' database is only valid with multi-database " << + "support enabled" << endl; + valid = false; + } + + // Changelog options. + // + if (ops.changelog_in ().count (db) != ops.changelog_out ().count (db)) + { + cerr << "error: both --changelog-in and --changelog-out must be " << + "specified" << endl; + valid = false; + } + + if (!valid) + throw validator_failed (); + + unique_ptr<context> ctx (create_context (cerr, u, ops, f, 0)); + + if (pass == 1) + { + traversal::unit unit; + traversal::defines unit_defines; + traversal::declares unit_declares; + typedefs1 unit_typedefs (unit_declares); + traversal::namespace_ ns; + value_type vt; + class1 c (valid); + + unit >> unit_defines >> ns; + unit_defines >> c; + unit >> unit_declares >> vt; + unit >> unit_typedefs >> c; + + traversal::defines ns_defines; + traversal::declares ns_declares; + typedefs1 ns_typedefs (ns_declares); + + ns >> ns_defines >> ns; + ns_defines >> c; + ns >> ns_declares >> vt; + ns >> ns_typedefs >> c; + + unit.dispatch (u); + } + else + { + traversal::unit unit; + traversal::defines unit_defines; + typedefs unit_typedefs (true); + traversal::namespace_ ns; + class2 c (valid); + + unit >> unit_defines >> ns; + unit_defines >> c; + unit >> unit_typedefs >> c; + + traversal::defines ns_defines; + typedefs ns_typedefs (true); + + ns >> ns_defines >> ns; + ns_defines >> c; + ns >> ns_typedefs >> c; + + unit.dispatch (u); + } + + if (!valid) + throw validator_failed (); + + switch (db) + { + case database::common: + { + break; + } + case database::mssql: + case database::mysql: + case database::oracle: + case database::pgsql: + case database::sqlite: + { + try + { + relational::validate (ops, f, u, p, pass); + } + catch (operation_failed const&) + { + throw validator_failed (); + } + + break; + } + } +} diff --git a/odb/odb/validator.hxx b/odb/odb/validator.hxx new file mode 100644 index 0000000..d0aeac9 --- /dev/null +++ b/odb/odb/validator.hxx @@ -0,0 +1,23 @@ +// file : odb/validator.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_VALIDATOR_HXX +#define ODB_VALIDATOR_HXX + +#include <odb/options.hxx> +#include <odb/features.hxx> +#include <odb/semantics/unit.hxx> + +class validator_failed {}; + +// The first pass is performed after processing pass 1 but before +// processing pass 2. The second -- after pass 2. +// +void +validate (options const&, + features&, + semantics::unit&, + semantics::path const&, + unsigned short pass); + +#endif // ODB_VALIDATOR_HXX diff --git a/odb/odb/version.hxx b/odb/odb/version.hxx new file mode 100644 index 0000000..4ad389a --- /dev/null +++ b/odb/odb/version.hxx @@ -0,0 +1,37 @@ +// file : odb/version.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_VERSION_HXX +#define ODB_VERSION_HXX + +// Version format is AABBCCDD where +// +// AA - major version number +// BB - minor version number +// CC - bugfix version number +// DD - alpha / beta (DD + 50) version number +// +// When DD is not 00, 1 is subtracted from AABBCC. For example: +// +// Version AABBCCDD +// 2.0.0 02000000 +// 2.1.0 02010000 +// 2.1.1 02010100 +// 2.2.0.a1 02019901 +// 3.0.0.b2 02999952 +// + +// ODB interface version: minor, major, and alpha/beta versions. +// +#define ODB_VERSION 20476 +#define ODB_VERSION_STR "2.5-b.26" + +// ODB compiler version: interface version plus the bugfix version. +// +// NOTE: remember to update metadata to full version when switching to +// version.hxx.in. +// +#define ODB_COMPILER_VERSION 2049976 +#define ODB_COMPILER_VERSION_STR "2.5.0-b.26" + +#endif // ODB_VERSION_HXX |