// file : odb/relational/sqlite/schema.cxx // copyright : Copyright (c) 2009-2018 Code Synthesis Tools CC // license : GNU GPL v3; see accompanying LICENSE file #include #include #include 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 (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 (in.scope ())); sema_rel::qname n (t.name ().qualifier ()); n.append (in.name ()); return quote_id (n); } }; entry 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 (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 ( &i->nameable ())) { p = m.find (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& dkey (*t.find ("")); assert (rkey.contains_size () == dkey.contains_size ()); delete_ (p->name (), t.name (), rkey, dkey); } drop (t, migration); } }; entry 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 (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 (&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 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_; 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_; 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 (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 (in.scope ()).name ().uname ()); } }; entry 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_; // // 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 cc (*this); trav_rel::unames n (*cc); names (at, n); // SQLite does not support altering columns. // if (sema_rel::alter_column* ac = check (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 (&i->nameable ())); if (dfk == 0) continue; foreign_key& fk (find (*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_; 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 (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 (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 (&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_; // // 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_; } } }