From fe69d94f3d2dcb37d69ac2d7a0f88ad5fce2ad5c Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Tue, 1 Mar 2011 11:56:33 +0200 Subject: Add support for embedded database schemas New options: --schema-format, --default-schema. New example: schema/embedded. --- NEWS | 17 ++- doc/manual.xhtml | 100 +++++++++++++- doc/odb-prologue.1 | 4 +- doc/odb-prologue.xhtml | 4 +- odb/context.cxx | 188 +++++++++++++++++++++++++- odb/context.hxx | 16 ++- odb/database.cxx | 51 ------- odb/database.hxx | 37 ------ odb/emitter.cxx | 36 +++++ odb/emitter.hxx | 49 +++++++ odb/generator.cxx | 8 +- odb/makefile | 9 +- odb/mysql/context.cxx | 7 + odb/mysql/context.hxx | 1 + odb/mysql/header.cxx | 9 ++ odb/mysql/schema.cxx | 339 ----------------------------------------------- odb/mysql/schema.hxx | 314 ++++++++++++++++++++++++++++++++++++++++++- odb/mysql/source.cxx | 78 ++++++++++- odb/mysql/sql-schema.cxx | 98 ++++++++++++++ odb/mysql/sql-schema.hxx | 17 +++ odb/option-functions.cxx | 36 +++++ odb/option-functions.hxx | 14 ++ odb/option-types.cxx | 93 +++++++++++++ odb/option-types.hxx | 65 +++++++++ odb/options.cli | 40 +++++- odb/plugin.cxx | 9 +- 26 files changed, 1179 insertions(+), 460 deletions(-) delete mode 100644 odb/database.cxx delete mode 100644 odb/database.hxx create mode 100644 odb/emitter.cxx create mode 100644 odb/emitter.hxx create mode 100644 odb/mysql/sql-schema.cxx create mode 100644 odb/mysql/sql-schema.hxx create mode 100644 odb/option-functions.cxx create mode 100644 odb/option-functions.hxx create mode 100644 odb/option-types.cxx create mode 100644 odb/option-types.hxx diff --git a/NEWS b/NEWS index c489179..0a005a1 100644 --- a/NEWS +++ b/NEWS @@ -11,10 +11,19 @@ Version 1.2.0 using namespace odb; The new approach brings all the essential ODB names into the current - namespace without any of the auxiliary objects such as traits, etc., - which minimizes the likelihood of conflicts with other libraries. - Note that you can still use the odb namespace when qualifying - individual names, for example, odb::database. + namespace without any of the auxiliary objects such as traits, etc., which + minimizes the likelihood of conflicts with other libraries. Note that you + can still use the odb namespace when qualifying individual names, for + example, odb::database. + + * Support for embedded database schemas. The new option, --schema-format, + allows the selection of the schema format. The valid values for this + option are 'sql' for a standalone SQL file and 'embedded' for a schema + embedded into the generated C++ code. The new odb::schema_catalog class + provides support for accessing embedded schemas from within the + application. For details refer to Section 3.3, "Database" in the ODB + manual as well as the 'schema/embedded' example in the odb-examples + package. * New exceptions: odb::recoverbale, odb::connection_lost, and odb::timeout. The odb::recoverbale exception is a common base class for all recoverable diff --git a/doc/manual.xhtml b/doc/manual.xhtml index fd14163..f244dc5 100644 --- a/doc/manual.xhtml +++ b/doc/manual.xhtml @@ -1046,10 +1046,16 @@ mysql --user=odb_test --database=odb_test < person.sql

The above command will log in to a local MySQL server as user odb_test without a password and use the database - named odb_test. Note that after executing this + named odb_test. Beware that after executing this command, all the data stored in the odb_test database will be deleted.

+

Note also that using a standalone generated SQL file is not the + only way to create a database schema in ODB. We can also embed + the schema directly into our application or use custom schemas + that were not generated by the ODB compiler. Refer to + Section 3.3, "Database" for details.

+

Once the database schema is ready, we run our application using the same login and database name:

@@ -1916,6 +1922,80 @@ auto_ptr<odb::database> db ( system-specific database classes, refer to Chapter 10, "Database Systems".

+

Before we can persist our objects, the corresponding database schema has + to be created in the database. The schema contains table definitions and + other relational database artifacts that are used to store the state of + persistent objects in the database.

+ +

There are several ways to create the database schema. The easiest is to + instruct the ODB compiler to generate the corresponding schema from the + persistent classes (--generate-schema option). The ODB + compiler can generate the schema either as a standalone SQL file or + embedded into the generated C++ code (--schema-format + option). If we are using the SQL file to create the database schema, then + this file should be executed, normally only once, before the application + is started.

+ +

Alternatively, the schema can be embedded directly into the generated + code and we can use the odb::schema_catalog class to + create it in the database from within our application, + for example:

+ +
+#include <odb/schema-catalog.hxx>
+
+odb::transaction t (db->begin ());
+odb::schema_catalog::create_schema (*db);
+t.commit ();
+  
+ +

Refer to the next section for information on the + odb::transaction class. The complete version of the above + code fragment is available in the schema/embedded example in + the odb-examples package.

+ +

The odb::schema_catalog class has the following interface. + You will need to include the <odb/schema-catalog.hxx> + header file to make this class available in your application.

+ +
+namespace odb
+{
+  class schema_catalog
+  {
+  public:
+    static void
+    create_schema (database&, const std::string& name = "");
+  };
+}
+  
+ +

The first argument to the create_schema() function + is the database instance that we would like to create the schema in. + The second argument is the schema name. By default, the ODB + compiler generates all embedded schemas with the default schema + name (empty string). However, if your application needs to + have several separate schemas, you can use the + --default-schema ODB compiler option to assign + custom schema names and then use these names as a second argument + to create_schema(). If the schema is not found, + create_schema() throws the + odb::unknown_schema exception. The + create_schema() function should be called within + a transaction.

+ +

Finally, we can also use a custom database schema with ODB. This approach + can work similarly to the standalone SQL file described above except that + the database schema is hand-written or produced by another program. Or we + could execute custom SQL statements that create the schema directly from + our application. To map persistent classes to custom database schemas, ODB + provides a wide range of mapping customization pragmas, such + as db table, db column, + and db type (Chapter 9, "ODB Pragma + Language"). For sample code that shows how to perform such mapping + for various C++ constructs, refer to the schema/custom + example in the odb-examples package.

+

3.4 Transactions

A transaction is an atomic, consistent, isolated and durable @@ -2615,6 +2695,17 @@ namespace odb struct database_exception: exception { }; + + // Schema catalog exceptions. + // + struct unknown_schema: exception + { + const std::string& + name () const; + + virtual const char* + what () const throw (); + }; } @@ -2663,11 +2754,16 @@ namespace odb the query result class. Refer to Section 4.4, "Query Result" for details.

-

The database_exception is a base class for all +

The database_exception exception is a base class for all database system-specific exceptions that are thrown by the database system-specific runtime library. See Chapter 10, "Database Systems" for more information.

+

The unknown_schema exception is thrown by the + odb::schema_catalog class if a schema with the specified + name is not found. Refer to Section 3.3, "Database" + for details.

+

The odb::exception class is defined in the <odb/exception.hxx> header file. All the concrete ODB exceptions are defined in diff --git a/doc/odb-prologue.1 b/doc/odb-prologue.1 index 0eb3fb4..7610917 100644 --- a/doc/odb-prologue.1 +++ b/doc/odb-prologue.1 @@ -49,7 +49,9 @@ option is specified), and .B name-odb.cxx (source file). Additionally, if the .B --generate-schema -option is specified and the target database supports it, the +option is specified and the +.B sql +schema format is requested, the .B name.sql database schema file is generated. .\" diff --git a/doc/odb-prologue.xhtml b/doc/odb-prologue.xhtml index d3edc41..61826e2 100644 --- a/doc/odb-prologue.xhtml +++ b/doc/odb-prologue.xhtml @@ -74,7 +74,7 @@ name-odb.cxx (source file). Additionally, if the --generate-schema option is - specified and the target database supports it, the name.sql database - schema file is generated.

+ specified and the sql schema format is requested, + the name.sql database schema file is generated.

OPTIONS

diff --git a/odb/context.cxx b/odb/context.cxx index 580b54c..8b07da5 100644 --- a/odb/context.cxx +++ b/odb/context.cxx @@ -3,6 +3,8 @@ // copyright : Copyright (c) 2009-2011 Code Synthesis Tools CC // license : GNU GPL v3; see accompanying LICENSE file +#include // std::is{alpha,upper,lower} + #include #include @@ -99,7 +101,9 @@ context (ostream& os_, os (os_), unit (u), options (ops), - keyword_set (data_->keyword_set_) + keyword_set (data_->keyword_set_), + embedded_schema (ops.generate_schema () && + ops.schema_format ().count (schema_format::embedded)) { for (size_t i (0); i < sizeof (keywords) / sizeof (char*); ++i) data_->keyword_set_.insert (keywords[i]); @@ -111,7 +115,19 @@ context (context& c) os (c.os), unit (c.unit), options (c.options), - keyword_set (c.keyword_set) + keyword_set (c.keyword_set), + embedded_schema (c.embedded_schema) +{ +} + +context:: +context (context& c, ostream& os_) + : data_ (c.data_), + os (os_), + unit (c.unit), + options (c.options), + keyword_set (c.keyword_set), + embedded_schema (c.embedded_schema) { } @@ -263,6 +279,29 @@ public_name (semantics::data_member& m) const } 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; @@ -336,6 +375,151 @@ escape (string const& name) const return 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 (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 (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 (u); + break; + } + } + } + else + { + // @@ Unrepresentable character. + // + r += '?'; + } + } + + r += '"'; + + return r; +} + +string context:: +strlit (string const& str) +{ + return strlit_ascii (str); +} + namespace { struct column_count_impl: object_members_base diff --git a/odb/context.hxx b/odb/context.hxx index 03a3871..b052a71 100644 --- a/odb/context.hxx +++ b/odb/context.hxx @@ -15,7 +15,6 @@ #include -#include #include #include #include @@ -148,11 +147,23 @@ public: string public_name (semantics::data_member&) 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; + // Return a string literal that can be used in C++ source code. It + // includes "". + // + string + strlit (string const&); + // Counts and other information. // public: @@ -302,6 +313,8 @@ public: typedef std::set keyword_set_type; keyword_set_type const& keyword_set; + bool embedded_schema; + struct db_type_type { db_type_type () {} @@ -355,6 +368,7 @@ public: options_type const&, data_ptr = data_ptr ()); context (context&); + context (context&, std::ostream&); virtual ~context (); diff --git a/odb/database.cxx b/odb/database.cxx deleted file mode 100644 index e5b9b24..0000000 --- a/odb/database.cxx +++ /dev/null @@ -1,51 +0,0 @@ -// file : odb/database.cxx -// author : Boris Kolpackov -// copyright : Copyright (c) 2009-2011 Code Synthesis Tools CC -// license : GNU GPL v3; see accompanying LICENSE file - -#include -#include -#include -#include // std::lower_bound - -#include - -using namespace std; - -static const char* str[] = -{ - "mysql", - "tracer" -}; - -const char* database:: -string () const -{ - return str[v_]; -} - -istream& -operator>> (istream& is, database& db) -{ - string s; - is >> s; - - if (!is.fail ()) - { - const char** e (str + sizeof (str) / sizeof (char*)); - const char** i (lower_bound (str, e, s)); - - if (i != e && *i == s) - db = database::value (i - str); - else - is.setstate (istream::failbit); - } - - return is; -} - -ostream& -operator<< (ostream& os, database db) -{ - return os << db.string (); -} diff --git a/odb/database.hxx b/odb/database.hxx deleted file mode 100644 index c47a9da..0000000 --- a/odb/database.hxx +++ /dev/null @@ -1,37 +0,0 @@ -// file : odb/database.hxx -// author : Boris Kolpackov -// copyright : Copyright (c) 2009-2011 Code Synthesis Tools CC -// license : GNU GPL v3; see accompanying LICENSE file - -#ifndef ODB_DATABASE_HXX -#define ODB_DATABASE_HXX - -#include - -struct database -{ - enum value - { - // Keep in alphabetic order. - // - mysql, - tracer - }; - - database (value v = value (0)) : v_ (v) {} - operator value () const {return v_;} - - const char* - string () const; - -private: - value v_; -}; - -std::istream& -operator>> (std::istream&, database&); - -std::ostream& -operator<< (std::ostream&, database); - -#endif // ODB_DATABASE_HXX diff --git a/odb/emitter.cxx b/odb/emitter.cxx new file mode 100644 index 0000000..6cfe64a --- /dev/null +++ b/odb/emitter.cxx @@ -0,0 +1,36 @@ +// file : odb/emitter.cxx +// author : Boris Kolpackov +// copyright : Copyright (c) 2009-2011 Code Synthesis Tools CC +// license : GNU GPL v3; see accompanying LICENSE file + +#include + +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); + } + + e_.line (s); + str (string ()); + return 0; +} diff --git a/odb/emitter.hxx b/odb/emitter.hxx new file mode 100644 index 0000000..f68253b --- /dev/null +++ b/odb/emitter.hxx @@ -0,0 +1,49 @@ +// file : odb/emitter.hxx +// author : Boris Kolpackov +// copyright : Copyright (c) 2009-2011 Code Synthesis Tools CC +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_EMITTER_HXX +#define ODB_EMITTER_HXX + +#include +#include + +// 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/generator.cxx b/odb/generator.cxx index ddeda2b..91548e4 100644 --- a/odb/generator.cxx +++ b/odb/generator.cxx @@ -29,10 +29,10 @@ #include #include -#include #include #include #include +#include using namespace std; using namespace cutl; @@ -201,9 +201,11 @@ generate (options const& ops, semantics::unit& unit, path const& p) // // + bool sql_schema (ops.generate_schema () && + ops.schema_format ().count (schema_format::sql)); ofstream sql; - if (ops.generate_schema ()) + if (sql_schema) { sql.open (sql_path.string ().c_str (), ios_base::out); @@ -429,7 +431,7 @@ generate (options const& ops, semantics::unit& unit, path const& p) // SQL // - if (ops.generate_schema ()) + if (sql_schema) { // Copy prologue. // diff --git a/odb/makefile b/odb/makefile index d73f007..ea9385a 100644 --- a/odb/makefile +++ b/odb/makefile @@ -12,6 +12,7 @@ cxx-lexer.cxx \ sql-lexer.cxx \ context.cxx \ common.cxx \ +emitter.cxx \ type-processor.cxx \ include.cxx \ header.cxx \ @@ -34,10 +35,11 @@ tracer/source.cxx cxx_ptun += \ mysql/context.cxx \ mysql/common.cxx \ -mysql/schema.cxx \ mysql/header.cxx \ mysql/inline.cxx \ -mysql/source.cxx +mysql/source.cxx \ +mysql/schema.cxx \ +mysql/sql-schema.cxx cxx_ptun += \ semantics/class.cxx \ @@ -68,7 +70,7 @@ cxx_dtun := odb.cxx # Common units. # -cxx_ctun := database.cxx profile.cxx +cxx_ctun := option-types.cxx option-functions.cxx profile.cxx # Options file. # @@ -118,6 +120,7 @@ gen := $(addprefix $(out_base)/,$(genf)) $(gen): $(cli) $(gen): cli := $(cli) $(gen): cli_options += \ +--generate-modifier \ --generate-specifier \ --generate-description \ --suppress-undocumented \ diff --git a/odb/mysql/context.cxx b/odb/mysql/context.cxx index 3b00c96..6a218af 100644 --- a/odb/mysql/context.cxx +++ b/odb/mysql/context.cxx @@ -77,6 +77,13 @@ namespace mysql { } + context:: + context (context& c, ostream& os) + : base_context (c, os), + data_ (c.data_) + { + } + namespace { struct has_grow: traversal::class_ diff --git a/odb/mysql/context.hxx b/odb/mysql/context.hxx index 0dfb3ba..05b6639 100644 --- a/odb/mysql/context.hxx +++ b/odb/mysql/context.hxx @@ -120,6 +120,7 @@ namespace mysql public: context (std::ostream&, semantics::unit&, options_type const&); context (context&); + context (context&, std::ostream&); }; } diff --git a/odb/mysql/header.cxx b/odb/mysql/header.cxx index 12f3e4d..59793b0 100644 --- a/odb/mysql/header.cxx +++ b/odb/mysql/header.cxx @@ -901,6 +901,15 @@ namespace mysql << "query (database&, const query_type&);" << endl; + // create_schema () + // + if (embedded_schema) + { + os << "static void" << endl + << "create_schema (database&);" + << endl; + } + // Implementation details. // os << "public:" << endl; diff --git a/odb/mysql/schema.cxx b/odb/mysql/schema.cxx index 686272c..b40a019 100644 --- a/odb/mysql/schema.cxx +++ b/odb/mysql/schema.cxx @@ -3,348 +3,9 @@ // copyright : Copyright (c) 2009-2011 Code Synthesis Tools CC // license : GNU GPL v3; see accompanying LICENSE file -#include - -#include #include -using namespace std; - namespace mysql { - namespace - { - typedef set tables; - - struct object_columns: object_columns_base, context - { - object_columns (context& c, string const& prefix = string ()) - : object_columns_base (c), context (c), prefix_ (prefix) - { - } - - virtual bool - column (semantics::data_member& m, string const& name, bool first) - { - // Ignore inverse object pointers. - // - if (inverse (m)) - return false; - - if (!first) - os << "," << endl; - - os << " `" << name << "` " << column_type (m, prefix_); - - if (m.count ("id")) - os << " PRIMARY KEY"; - - using semantics::class_; - if (class_* c = object_pointer (member_type (m, prefix_))) - { - os << " REFERENCES `" << table_name (*c) << "` (`" << - column_name (id_member (*c)) << "`)"; - } - - return true; - } - - private: - string prefix_; - }; - - struct member_create: object_members_base, context - { - member_create (context& c, semantics::class_& object, tables& t) - : object_members_base (c, false, true), - context (c), - object_ (object), - id_member_ (id_member (object)), - tables_ (t) - { - } - - virtual void - container (semantics::data_member& m) - { - using semantics::type; - using semantics::data_member; - - // Ignore inverse containers of object pointers. - // - if (inverse (m, "value")) - return; - - type& t (m.type ()); - container_kind_type ck (container_kind (t)); - type& vt (container_vt (t)); - - string const& name (table_name (m, table_prefix_)); - - if (tables_.count (name)) - return; - - os << "CREATE TABLE `" << name << "` (" << endl; - - // object_id (simple value) - // - string id_name (column_name (m, "id", "object_id")); - os << " `" << id_name << "` " << column_type (id_member_, "ref"); - - // index (simple value) - // - string index_name; - bool ordered (ck == ck_ordered && !unordered (m)); - if (ordered) - { - index_name = column_name (m, "index", "index"); - - os << "," << endl - << " `" << index_name << "` " << column_type (m, "index"); - } - - // key (simple or composite value) - // - if (ck == ck_map || ck == ck_multimap) - { - type& kt (container_kt (t)); - - os << "," << endl; - - if (semantics::class_* ckt = comp_value (kt)) - { - object_columns oc (*this); - oc.traverse_composite (m, *ckt, "key", "key"); - } - else - { - object_columns oc (*this, "key"); - string const& name (column_name (m, "key", "key")); - oc.column (m, name, true); - } - } - - // value (simple or composite value) - // - { - os << "," << endl; - - if (semantics::class_* cvt = comp_value (vt)) - { - object_columns oc (*this); - oc.traverse_composite (m, *cvt, "value", "value"); - } - else - { - object_columns oc (*this, "value"); - string const& name (column_name (m, "value", "value")); - oc.column (m, name, true); - } - } - - // object_id index - // - os << "," << endl - << " INDEX (`" << id_name << "`)"; - - // index index - // - if (ordered) - os << "," << endl - << " INDEX (`" << index_name << "`)"; - - os << ")"; - - string const& engine (options.mysql_engine ()); - - if (engine != "default") - os << endl - << " ENGINE=" << engine; - - os << ";" << endl - << endl; - - tables_.insert (name); - } - - private: - semantics::class_& object_; - semantics::data_member& id_member_; - tables& tables_; - }; - - struct class_create: traversal::class_, context - { - class_create (context& c) - : context (c) - { - } - - virtual void - traverse (type& c) - { - if (c.file () != unit.file ()) - return; - - if (!c.count ("object")) - return; - - string const& name (table_name (c)); - - // If the table with this name was already created, assume the - // user knows what they are doing and skip it. - // - if (tables_.count (name)) - return; - - os << "CREATE TABLE `" << name << "` (" << endl; - - { - object_columns oc (*this); - oc.traverse (c); - } - - os << ")"; - - string const& engine (options.mysql_engine ()); - - if (engine != "default") - os << endl - << " ENGINE=" << engine; - - os << ";" << endl - << endl; - - // Create tables for members. - // - { - member_create mc (*this, c, tables_); - mc.traverse (c); - } - - tables_.insert (name); - } - - private: - tables tables_; - }; - - struct member_drop: object_members_base, context - { - member_drop (context& c, semantics::class_& object, tables& t) - : object_members_base (c, false, true), - context (c), - object_ (object), - tables_ (t) - { - } - - virtual void - container (semantics::data_member& m) - { - // Ignore inverse containers of object pointers. - // - if (inverse (m, "value")) - return; - - string const& name (table_name (m, table_prefix_)); - - if (tables_.count (name)) - return; - - os << "DROP TABLE IF EXISTS `" << name << "`;" << endl; - tables_.insert (name); - } - - private: - semantics::class_& object_; - tables& tables_; - }; - - struct class_drop: traversal::class_, context - { - class_drop (context& c) - : context (c) - { - } - - virtual void - traverse (type& c) - { - if (c.file () != unit.file ()) - return; - - if (!c.count ("object")) - return; - - string const& name (table_name (c)); - - if (tables_.count (name)) - return; - - os << "DROP TABLE IF EXISTS `" << name << "`;" << endl; - - // Drop tables for members. - // - { - member_drop mc (*this, c, tables_); - mc.traverse (c); - } - - tables_.insert (name); - } - - private: - tables tables_; - }; - - static char const file_header[] = - "/* This file was generated by ODB, object-relational mapping (ORM)\n" - " * compiler for C++.\n" - " */\n\n"; - } - - void - generate_schema (context& ctx) - { - ctx.os << file_header; - - // Drop. - // - { - traversal::unit unit; - traversal::defines unit_defines; - traversal::namespace_ ns; - class_drop c (ctx); - - unit >> unit_defines >> ns; - unit_defines >> c; - - traversal::defines ns_defines; - - ns >> ns_defines >> ns; - ns_defines >> c; - unit.dispatch (ctx.unit); - } - - ctx.os << endl; - - // Create. - // - { - traversal::unit unit; - traversal::defines unit_defines; - traversal::namespace_ ns; - class_create c (ctx); - - unit >> unit_defines >> ns; - unit_defines >> c; - - traversal::defines ns_defines; - ns >> ns_defines >> ns; - ns_defines >> c; - unit.dispatch (ctx.unit); - } - } } diff --git a/odb/mysql/schema.hxx b/odb/mysql/schema.hxx index e221c92..1defad7 100644 --- a/odb/mysql/schema.hxx +++ b/odb/mysql/schema.hxx @@ -6,12 +6,320 @@ #ifndef ODB_MYSQL_SCHEMA_HXX #define ODB_MYSQL_SCHEMA_HXX -#include +#include + +#include +#include namespace mysql { - void - generate_schema (context&); + struct schema_context: context + { + typedef std::set tables; + + schema_context (context& c, std::ostream& os, emitter& e) + : context (c, os), e_ (e) + { + } + + schema_context (schema_context& c) : context (c), e_ (c.e_) {} + + emitter& e_; + }; + + // + // Drop. + // + + struct member_drop: object_members_base, schema_context + { + member_drop (schema_context& c, tables& t) + : object_members_base (c, false, true), + schema_context (c), + tables_ (t) + { + } + + virtual void + container (semantics::data_member& m) + { + // Ignore inverse containers of object pointers. + // + if (inverse (m, "value")) + return; + + string const& name (table_name (m, table_prefix_)); + + if (tables_.count (name)) + return; + + e_.pre (); + os << "DROP TABLE IF EXISTS `" << name << "`" << endl; + e_.post (); + + tables_.insert (name); + } + + private: + tables& tables_; + }; + + struct class_drop: traversal::class_, schema_context + { + class_drop (context& c, emitter& e) + : schema_context (c, os, e), os (e), + member_drop_ (*this, tables_) + { + } + + virtual void + traverse (type& c) + { + if (c.file () != unit.file ()) + return; + + if (!c.count ("object")) + return; + + string const& name (table_name (c)); + + if (tables_.count (name)) + return; + + e_.pre (); + os << "DROP TABLE IF EXISTS `" << name << "`" << endl; + e_.post (); + + tables_.insert (name); + + // Drop tables for members. + // + member_drop_.traverse (c); + } + + private: + tables tables_; + emitter_ostream os; + member_drop member_drop_; + }; + + // + // Create. + // + + struct object_columns: object_columns_base, schema_context + { + object_columns (schema_context& c, string const& prefix = string ()) + : object_columns_base (c), schema_context (c), prefix_ (prefix) + { + } + + virtual bool + column (semantics::data_member& m, string const& name, bool first) + { + // Ignore inverse object pointers. + // + if (inverse (m)) + return false; + + if (!first) + os << "," << endl; + + os << " `" << name << "` " << column_type (m, prefix_); + + if (m.count ("id")) + os << " PRIMARY KEY"; + + using semantics::class_; + if (class_* c = object_pointer (member_type (m, prefix_))) + { + os << " REFERENCES `" << table_name (*c) << "` (`" << + column_name (id_member (*c)) << "`)"; + } + + return true; + } + + private: + string prefix_; + }; + + struct member_create: object_members_base, schema_context + { + member_create (schema_context& c, semantics::class_& object, tables& t) + : object_members_base (c, false, true), + schema_context (c), + id_member_ (id_member (object)), + tables_ (t) + { + } + + virtual void + container (semantics::data_member& m) + { + using semantics::type; + using semantics::data_member; + + // Ignore inverse containers of object pointers. + // + if (inverse (m, "value")) + return; + + type& t (m.type ()); + container_kind_type ck (container_kind (t)); + type& vt (container_vt (t)); + + string const& name (table_name (m, table_prefix_)); + + if (tables_.count (name)) + return; + + e_.pre (); + os << "CREATE TABLE `" << name << "` (" << endl; + + // object_id (simple value) + // + string id_name (column_name (m, "id", "object_id")); + os << " `" << id_name << "` " << column_type (id_member_, "ref"); + + // index (simple value) + // + string index_name; + bool ordered (ck == ck_ordered && !unordered (m)); + if (ordered) + { + index_name = column_name (m, "index", "index"); + + os << "," << endl + << " `" << index_name << "` " << column_type (m, "index"); + } + + // key (simple or composite value) + // + if (ck == ck_map || ck == ck_multimap) + { + type& kt (container_kt (t)); + + os << "," << endl; + + if (semantics::class_* ckt = comp_value (kt)) + { + object_columns oc (*this); + oc.traverse_composite (m, *ckt, "key", "key"); + } + else + { + object_columns oc (*this, "key"); + string const& name (column_name (m, "key", "key")); + oc.column (m, name, true); + } + } + + // value (simple or composite value) + // + { + os << "," << endl; + + if (semantics::class_* cvt = comp_value (vt)) + { + object_columns oc (*this); + oc.traverse_composite (m, *cvt, "value", "value"); + } + else + { + object_columns oc (*this, "value"); + string const& name (column_name (m, "value", "value")); + oc.column (m, name, true); + } + } + + // object_id index + // + os << "," << endl + << " INDEX (`" << id_name << "`)"; + + // index index + // + if (ordered) + os << "," << endl + << " INDEX (`" << index_name << "`)"; + + os << ")"; + + string const& engine (options.mysql_engine ()); + + if (engine != "default") + os << endl + << " ENGINE=" << engine; + + os << endl; + e_.post (); + + tables_.insert (name); + } + + private: + semantics::data_member& id_member_; + tables& tables_; + }; + + struct class_create: traversal::class_, schema_context + { + class_create (context& c, emitter& e) + : schema_context (c, os, e), os (e) + { + } + + virtual void + traverse (type& c) + { + if (c.file () != unit.file ()) + return; + + if (!c.count ("object")) + return; + + string const& name (table_name (c)); + + // If the table with this name was already created, assume the + // user knows what they are doing and skip it. + // + if (tables_.count (name)) + return; + + e_.pre (); + os << "CREATE TABLE `" << name << "` (" << endl; + + { + object_columns oc (*this); + oc.traverse (c); + } + + os << ")"; + + string const& engine (options.mysql_engine ()); + + if (engine != "default") + os << endl + << " ENGINE=" << engine; + + os << endl; + e_.post (); + + tables_.insert (name); + + // Create tables for members. + // + { + member_create mc (*this, c, tables_); + mc.traverse (c); + } + } + + private: + tables tables_; + emitter_ostream os; + }; } #endif // ODB_MYSQL_SCHEMA_HXX diff --git a/odb/mysql/source.cxx b/odb/mysql/source.cxx index fece2e5..bc7340a 100644 --- a/odb/mysql/source.cxx +++ b/odb/mysql/source.cxx @@ -11,6 +11,7 @@ #include #include +#include #include using namespace std; @@ -19,6 +20,38 @@ namespace mysql { namespace { + struct schema_emitter: emitter, context + { + schema_emitter (context& c): context (c) {} + + virtual void + pre () + { + first_ = true; + os << "db.execute ("; + } + + virtual void + line (const std::string& l) + { + if (first_) + first_ = false; + else + os << endl; + + os << strlit (l); + } + + virtual void + post () + { + os << ");" << endl; + } + + private: + bool first_; + }; + struct object_columns: object_columns_base, context { object_columns (context& c, @@ -2398,7 +2431,11 @@ namespace mysql init_id_image_member_ (c, "id_", "id"), init_value_base_ (c), init_value_member_ (c), - init_id_value_member_ (c, "id") + init_id_value_member_ (c, "id"), + + schema_emitter_ (c), + schema_drop_ (c, schema_emitter_), + schema_create_ (c, schema_emitter_) { grow_base_inherits_ >> grow_base_; grow_member_names_ >> grow_member_; @@ -3008,8 +3045,7 @@ namespace mysql << "return result (r);" << "}"; - os << "void" << endl - << traits << "::" << endl + os << "void " << traits << "::" << endl << "query_ (database&," << endl << "const query_type& q," << endl << "mysql::object_statements< object_type >& sts," @@ -3034,6 +3070,26 @@ namespace mysql << "st->execute ();" << "}"; } + + // create_schema () + // + if (embedded_schema) + { + os << "void " << traits << "::" << endl + << "create_schema (database& db)" + << "{"; + + schema_drop_.traverse (c); + schema_create_.traverse (c); + + os << "}"; + + os << "static const schema_catalog_entry" << endl + << "schema_catalog_entry_" << flat_name (type) << "_ (" << endl + << strlit (options.default_schema ()) << "," << endl + << "&" << traits << "::create_schema);" + << endl; + } } virtual void @@ -3141,6 +3197,10 @@ namespace mysql init_value_member init_value_member_; traversal::names init_value_member_names_; init_value_member init_id_value_member_; + + schema_emitter schema_emitter_; + class_drop schema_drop_; + class_create schema_create_; }; } @@ -3160,9 +3220,17 @@ namespace mysql ns >> ns_defines >> ns; ns_defines >> c; - ctx.os << "#include " << endl - << endl; + // + // + ctx.os << "#include " << endl; + if (ctx.embedded_schema) + ctx.os << "#include " << endl; + + ctx.os << endl; + + // + // ctx.os << "#include " << endl << "#include " << endl << "#include " << endl diff --git a/odb/mysql/sql-schema.cxx b/odb/mysql/sql-schema.cxx new file mode 100644 index 0000000..7c71b24 --- /dev/null +++ b/odb/mysql/sql-schema.cxx @@ -0,0 +1,98 @@ +// file : odb/mysql/sql-schema.cxx +// author : Boris Kolpackov +// copyright : Copyright (c) 2009-2011 Code Synthesis Tools CC +// license : GNU GPL v3; see accompanying LICENSE file + +#include +#include +#include + +using namespace std; + +namespace mysql +{ + namespace + { + struct schema_emitter: emitter, context + { + schema_emitter (context& c): context (c) {} + + virtual void + pre () + { + first_ = true; + } + + virtual void + line (const std::string& l) + { + if (first_) + first_ = false; + else + os << endl; + + os << l; + } + + virtual void + post () + { + os << ';' << endl + << endl; + } + + private: + bool first_; + }; + + static char const file_header[] = + "/* This file was generated by ODB, object-relational mapping (ORM)\n" + " * compiler for C++.\n" + " */\n\n"; + } + + void + generate_schema (context& ctx) + { + ctx.os << file_header; + schema_emitter emitter (ctx); + + // Drop. + // + { + traversal::unit unit; + traversal::defines unit_defines; + traversal::namespace_ ns; + class_drop c (ctx, emitter); + + unit >> unit_defines >> ns; + unit_defines >> c; + + traversal::defines ns_defines; + + ns >> ns_defines >> ns; + ns_defines >> c; + unit.dispatch (ctx.unit); + } + + ctx.os << endl; + + // Create. + // + { + traversal::unit unit; + traversal::defines unit_defines; + traversal::namespace_ ns; + class_create c (ctx, emitter); + + unit >> unit_defines >> ns; + unit_defines >> c; + + traversal::defines ns_defines; + + ns >> ns_defines >> ns; + ns_defines >> c; + unit.dispatch (ctx.unit); + } + } +} diff --git a/odb/mysql/sql-schema.hxx b/odb/mysql/sql-schema.hxx new file mode 100644 index 0000000..fdbdf28 --- /dev/null +++ b/odb/mysql/sql-schema.hxx @@ -0,0 +1,17 @@ +// file : odb/mysql/sql-schema.hxx +// author : Boris Kolpackov +// copyright : Copyright (c) 2009-2011 Code Synthesis Tools CC +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_MYSQL_SQL_SCHEMA_HXX +#define ODB_MYSQL_SQL_SCHEMA_HXX + +#include + +namespace mysql +{ + void + generate_schema (context&); +} + +#endif // ODB_MYSQL_SQL_SCHEMA_HXX diff --git a/odb/option-functions.cxx b/odb/option-functions.cxx new file mode 100644 index 0000000..22a57f0 --- /dev/null +++ b/odb/option-functions.cxx @@ -0,0 +1,36 @@ +// file : odb/option-functions.cxx +// author : Boris Kolpackov +// copyright : Copyright (c) 2009-2011 Code Synthesis Tools CC +// license : GNU GPL v3; see accompanying LICENSE file + +#include + +#include + +using namespace std; + +void +process_options (options& o) +{ + // Set the default schema format depending on the database. + // + if (o.generate_schema () && o.schema_format ().empty ()) + { + set f; + + switch (o.database ()) + { + case database::mysql: + { + f.insert (schema_format::sql); + break; + } + case database::tracer: + { + break; + } + } + + o.schema_format (f); + } +} diff --git a/odb/option-functions.hxx b/odb/option-functions.hxx new file mode 100644 index 0000000..3d63992 --- /dev/null +++ b/odb/option-functions.hxx @@ -0,0 +1,14 @@ +// file : odb/option-functions.hxx +// author : Boris Kolpackov +// copyright : Copyright (c) 2009-2011 Code Synthesis Tools CC +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_OPTION_FUNCTIONS_HXX +#define ODB_OPTION_FUNCTIONS_HXX + +#include + +void +process_options (options&); + +#endif // ODB_OPTION_FUNCTIONS_HXX diff --git a/odb/option-types.cxx b/odb/option-types.cxx new file mode 100644 index 0000000..b05fead --- /dev/null +++ b/odb/option-types.cxx @@ -0,0 +1,93 @@ +// file : odb/option-types.cxx +// author : Boris Kolpackov +// copyright : Copyright (c) 2009-2011 Code Synthesis Tools CC +// license : GNU GPL v3; see accompanying LICENSE file + +#include +#include +#include +#include // std::lower_bound + +#include + +using namespace std; + +// database +// +static const char* database_[] = +{ + "mysql", + "tracer" +}; + +const char* database:: +string () const +{ + return database_[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 (); +} + +// schema_format +// +static const char* schema_format_[] = +{ + "embedded", + "sql" +}; + +const char* 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 (); +} diff --git a/odb/option-types.hxx b/odb/option-types.hxx new file mode 100644 index 0000000..dc09780 --- /dev/null +++ b/odb/option-types.hxx @@ -0,0 +1,65 @@ +// file : odb/option-types.hxx +// author : Boris Kolpackov +// copyright : Copyright (c) 2009-2011 Code Synthesis Tools CC +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_OPTION_TYPES_HXX +#define ODB_OPTION_TYPES_HXX + +#include + +struct database +{ + enum value + { + // Keep in alphabetic order. + // + mysql, + tracer + }; + + database (value v = value (0)) : v_ (v) {} + operator value () const {return v_;} + + const char* + string () const; + +private: + value v_; +}; + +std::istream& +operator>> (std::istream&, database&); + +std::ostream& +operator<< (std::ostream&, database); + +// +// +struct schema_format +{ + enum value + { + // Keep in alphabetic order. + // + embedded, + sql + }; + + schema_format (value v = value (0)) : v_ (v) {} + operator value () const {return v_;} + + const char* + string () const; + +private: + value v_; +}; + +std::istream& +operator>> (std::istream&, schema_format&); + +std::ostream& +operator<< (std::ostream&, schema_format); + +#endif // ODB_OPTION_TYPES_HXX diff --git a/odb/options.cli b/odb/options.cli index 562daa6..bb21095 100644 --- a/odb/options.cli +++ b/odb/options.cli @@ -3,10 +3,11 @@ // copyright : Copyright (c) 2009-2011 Code Synthesis Tools CC // license : GNU GPL v3; see accompanying LICENSE file -include ; +include ; include ; +include ; -include ; +include ; class options { @@ -59,10 +60,37 @@ class options bool --generate-schema { - "Generate database schema. The resulting SQL file creates database - tables required to store classes defined in the file being compiled. - Note that all the existing information stored in such tables will be - lost." + "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 database 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." + }; + + std::set< ::schema_format> --schema-format + { + "", + "Generate the database schema in the specified format. Pass \cb{sql} as + to generate the database schema as a standalone SQL file or + pass \cb{embedded} to embed the schema into the generated C++ code. + Repeat this option to generate the same database schema in multiple + formats." + }; + + std::string --default-schema = "" + { + "", + "Use as the default database schema name. Schema names are + primarily used for distinguishing between multiple embedded schemas in + the schema catalog. If this option is not specified, the empty name, + which corresponds to the default schema, is used." }; std::string --default-pointer = "*" diff --git a/odb/plugin.cxx b/odb/plugin.cxx index 47a524d..ede0235 100644 --- a/odb/plugin.cxx +++ b/odb/plugin.cxx @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -204,8 +205,14 @@ plugin_init (plugin_name_args* plugin_info, plugin_gcc_version*) cli::argv_file_scanner scan (argc, &argv[0], oi, 3); - options_.reset ( + auto_ptr ops ( new options (scan, cli::unknown_mode::fail, cli::unknown_mode::fail)); + + // Process options. + // + process_options (*ops); + + options_ = ops; } if (options_->trace ()) -- cgit v1.1