diff options
Diffstat (limited to 'odb/odb/relational/sqlite/schema.cxx')
-rw-r--r-- | odb/odb/relational/sqlite/schema.cxx | 455 |
1 files changed, 455 insertions, 0 deletions
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_; + } + } +} |