LCOV - code coverage report
Current view: top level - snapwebsites - snap_tables.cpp (source / functions) Hit Total Coverage
Test: coverage.info Lines: 1 265 0.4 %
Date: 2019-12-15 17:13:15 Functions: 2 53 3.8 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : // Snap Websites Server -- read a table XML file and put definitions in structures
       2             : // Copyright (c) 2016-2019  Made to Order Software Corp.  All Rights Reserved
       3             : //
       4             : // https://snapwebsites.org/
       5             : // contact@m2osw.com
       6             : //
       7             : // This program is free software; you can redistribute it and/or modify
       8             : // it under the terms of the GNU General Public License as published by
       9             : // the Free Software Foundation; either version 2 of the License, or
      10             : // (at your option) any later version.
      11             : //
      12             : // This program is distributed in the hope that it will be useful,
      13             : // but WITHOUT ANY WARRANTY; without even the implied warranty of
      14             : // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      15             : // GNU General Public License for more details.
      16             : //
      17             : // You should have received a copy of the GNU General Public License
      18             : // along with this program; if not, write to the Free Software
      19             : // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
      20             : 
      21             : 
      22             : // self
      23             : //
      24             : #include "snapwebsites/snap_tables.h"
      25             : 
      26             : 
      27             : // snapwebsites
      28             : //
      29             : #include "snapwebsites/glob_dir.h"
      30             : #include "snapwebsites/log.h"
      31             : 
      32             : 
      33             : // snapdev lib
      34             : //
      35             : #include <snapdev/not_used.h>
      36             : 
      37             : 
      38             : // Qt lib
      39             : //
      40             : #include <QFile>
      41             : #include <QDomDocument>
      42             : 
      43             : 
      44             : // last include
      45             : //
      46             : #include <snapdev/poison.h>
      47             : 
      48             : 
      49             : 
      50             : namespace snap
      51             : {
      52             : 
      53             : 
      54             : namespace
      55             : {
      56             : 
      57             : 
      58             : struct model_name_value_t
      59             : {
      60             :     char const *            f_name;
      61             :     snap_tables::model_t    f_model;
      62             : };
      63             : 
      64             : 
      65             : model_name_value_t model_name_values[] =
      66             : {
      67             :     { "content", snap_tables::model_t::MODEL_CONTENT },
      68             :     { "data",    snap_tables::model_t::MODEL_DATA    },
      69             :     { "queue",   snap_tables::model_t::MODEL_QUEUE   },
      70             :     { "log",     snap_tables::model_t::MODEL_LOG     },
      71             :     { "session", snap_tables::model_t::MODEL_SESSION }
      72             : };
      73             : 
      74             : 
      75             : 
      76             : struct kind_name_value_t
      77             : {
      78             :     char const *            f_name;
      79             :     snap_tables::kind_t     f_kind;
      80             : };
      81             : 
      82             : 
      83             : kind_name_value_t kind_name_values[] =
      84             : {
      85             :     { "thrift",  snap_tables::kind_t::KIND_THRIFT },
      86             :     { "blob",    snap_tables::kind_t::KIND_BLOB   }
      87             : };
      88             : 
      89             : 
      90             : }
      91             : // no name namespace
      92             : 
      93             : 
      94             : 
      95             : /** \class snap_tables
      96             :  * \brief Describe one table from our XML files.
      97             :  *
      98             :  * Whenever you start a process that needs to access our Cassandra database
      99             :  * you can read the schema from our table XML files. These files describe
     100             :  * the model (i.e. how the table gets used) and the data in the table.
     101             :  *
     102             :  * The XML file includes descriptions, table names, data such as name/value
     103             :  * pairs, etc.
     104             :  *
     105             :  * We support several basic models. In the old days, we wanted to use sorted
     106             :  * columns to support sorted lists within our tables. In our newest models,
     107             :  * we instead use a name/value model for most tables. We also allow ourselves
     108             :  * to use more or less columns than were usable in thrift. This just allows
     109             :  * us to avoid having to handle complicated data concatenations.
     110             :  *
     111             :  * The old thrift model gave us what is viewed as three CQL columns:
     112             :  *
     113             :  * * `key` -- this is the key access a row, simile to an ID in a an SQL
     114             :  * database; it is always unique; the key is used by Cassandra to decide
     115             :  * on which set of machines the data will reside (i.e. if the murmur3 of
     116             :  * the key is XYZ, then it goes on the N machines that support the range
     117             :  * that includes XYZ.)
     118             :  *
     119             :  * * `column1` -- this "strangely" named column was used to sort values
     120             :  * within a table; this was important to gain access to many values.
     121             :  * However, the truth was that this would prevent `column1` from being
     122             :  * defined on multiple computers, so a row with many `column1` but a
     123             :  * single `key` would put all the data on a single set of computers.
     124             :  * Using such for an index can have some reverse effect and make the
     125             :  * database slow over time.
     126             :  *
     127             :  * * `value` -- the value of the variable named by `column1`. For example,
     128             :  * we had a `created_on` field; `created_on` would be in `column1` and
     129             :  * `value` would be the timestamp when that row was created.
     130             :  *
     131             :  * With CQL, `column1` is not required and since we want to shift to
     132             :  * using blobs instead of name/value pairs managed by CQL (because that's
     133             :  * just way too slow on large amount of pages) we can actually change
     134             :  * our table definitions to just two columns:
     135             :  *
     136             :  * * `key` -- as above, the key referencing this row (i.e. a URL at this
     137             :  * moment, although later we want to use integers, see the "tree" feature
     138             :  * where each name is transformed in a number along the way. So a domain
     139             :  * becomes a number, a URL becomes a number, a page is assigned a number,
     140             :  * etc.)
     141             :  *
     142             :  * * `value` -- the value is a blob; a blob is a serie of name/value pairs.
     143             :  * We still have to define the exact format and it probably needs to be
     144             :  * described somewhere else anyway; for sure I want to use binary for
     145             :  * anything which isn't text so that way we can easily save numbers without
     146             :  * any loss of precision.
     147             :  *
     148             :  * Finally, in the CQL realm it is possible to create secondary indexes.
     149             :  * This allows you to avoid the `ALLOW FILTERING` and thus make things
     150             :  * go faster on a `SELECT` against certain columns. For example, the
     151             :  * `indexes` table makes use of a secondary column for its `value` column.
     152             :  * That way we can search for pages that are indexed instead of only index
     153             :  * pages. (i.e. search from both sides.)
     154             :  */
     155             : 
     156             : 
     157             : 
     158             : 
     159             : 
     160           0 : void snap_tables::column_t::set_name(QString const & name)
     161             : {
     162           0 :     f_name = name;
     163           0 : }
     164             : 
     165             : 
     166           0 : QString const & snap_tables::column_t::get_name() const
     167             : {
     168           0 :     return f_name;
     169             : }
     170             : 
     171             : 
     172           0 : void snap_tables::column_t::set_type(dbutils::column_type_t type)
     173             : {
     174           0 :     f_type = type;
     175           0 : }
     176             : 
     177             : 
     178           0 : dbutils::column_type_t snap_tables::column_t::get_type() const
     179             : {
     180           0 :     return f_type;
     181             : }
     182             : 
     183             : 
     184           0 : void snap_tables::column_t::set_required(bool required)
     185             : {
     186           0 :     f_required = required;
     187           0 : }
     188             : 
     189             : 
     190           0 : bool snap_tables::column_t::get_required() const
     191             : {
     192           0 :     return f_required;
     193             : }
     194             : 
     195             : 
     196           0 : void snap_tables::column_t::set_description(QString const & description)
     197             : {
     198           0 :     f_description = description;
     199           0 : }
     200             : 
     201             : 
     202           0 : QString snap_tables::column_t::get_description() const
     203             : {
     204           0 :     return f_description;
     205             : }
     206             : 
     207             : 
     208           0 : bool snap_tables::column_t::has_default_value() const
     209             : {
     210           0 :     return f_has_default;
     211             : }
     212             : 
     213             : 
     214           0 : void snap_tables::column_t::set_default(QString const & default_value)
     215             : {
     216             :     // if you set the default value then this column is considered to
     217             :     // have a default value
     218             :     //
     219           0 :     f_has_default = true;
     220             : 
     221           0 :     f_default = default_value;
     222           0 : }
     223             : 
     224             : 
     225           0 : QString snap_tables::column_t::get_default() const
     226             : {
     227           0 :     return f_default;
     228             : }
     229             : 
     230             : 
     231           0 : void snap_tables::column_t::set_min_value(double const min)
     232             : {
     233           0 :     f_min_value = min;
     234           0 : }
     235             : 
     236             : 
     237           0 : double snap_tables::column_t::get_min_value() const
     238             : {
     239           0 :     return f_min_value;
     240             : }
     241             : 
     242             : 
     243           0 : void snap_tables::column_t::set_max_value(double const max)
     244             : {
     245           0 :     f_max_value = max;
     246           0 : }
     247             : 
     248             : 
     249           0 : double snap_tables::column_t::get_max_value() const
     250             : {
     251           0 :     return f_max_value;
     252             : }
     253             : 
     254             : 
     255           0 : void snap_tables::column_t::set_min_length(int64_t const min)
     256             : {
     257           0 :     f_min_length = min;
     258           0 : }
     259             : 
     260             : 
     261           0 : int64_t snap_tables::column_t::get_min_length() const
     262             : {
     263           0 :     return f_min_length;
     264             : }
     265             : 
     266             : 
     267           0 : void snap_tables::column_t::set_max_length(int64_t const max)
     268             : {
     269           0 :     f_max_length = max;
     270           0 : }
     271             : 
     272             : 
     273           0 : int64_t snap_tables::column_t::get_max_length() const
     274             : {
     275           0 :     return f_max_length;
     276             : }
     277             : 
     278             : 
     279           0 : void snap_tables::column_t::set_validation(QString const & validation)
     280             : {
     281           0 :     f_validation = validation;
     282           0 : }
     283             : 
     284             : 
     285           0 : QString snap_tables::column_t::get_validation() const
     286             : {
     287           0 :     return f_validation;
     288             : }
     289             : 
     290             : 
     291             : /** \brief Set whether the output of this column should be limited.
     292             :  *
     293             :  * When dealing with column that can end up being really large (over
     294             :  * 256 bytes), you should mark them as limited. That way we can
     295             :  * display the first \em few bytes and not cover 100 screens of data
     296             :  * which would not be useful to you.
     297             :  *
     298             :  * By default the entire value is printed (the limited flag is false.)
     299             :  *
     300             :  * \param[in] limited  Whether it should be limited or not.
     301             :  */
     302           0 : void snap_tables::column_t::set_limited(bool limited)
     303             : {
     304           0 :     f_limited = limited;
     305           0 : }
     306             : 
     307             : 
     308             : /** \brief Get whether the output should be limited or not.
     309             :  *
     310             :  * This function returns true if the output of this column should be
     311             :  * limited so as not to cover the entire screen with data which would
     312             :  * not be useful.
     313             :  *
     314             :  * You are welcome to ignore this flag.
     315             :  *
     316             :  * \return true if the limited flag was set to true, false otherwise.
     317             :  */
     318           0 : bool snap_tables::column_t::get_limited() const
     319             : {
     320           0 :     return f_limited;
     321             : }
     322             : 
     323             : 
     324             : 
     325             : 
     326             : /** \brief Set the secondary index name.
     327             :  *
     328             :  * This function saves the name of the secondary index. The name is not
     329             :  * required as it can be given a default name automatically.
     330             :  *
     331             :  * The default name is built from the table name, the column name, and the
     332             :  * word "index" at the end:
     333             :  *
     334             :  * \code
     335             :  *     set_name(table.get_name() + "_" + index.get_column_name() + "_index");
     336             :  * \endcode
     337             :  *
     338             :  * \param[in] name  The name of the secondary index.
     339             :  */
     340           0 : void snap_tables::secondary_index_t::set_name(QString const & name)
     341             : {
     342           0 :     f_name = name;
     343           0 : }
     344             : 
     345             : 
     346             : /** \brief Get the secondary index name.
     347             :  *
     348             :  * THis function returns the name of the secondary index. If the string is
     349             :  * empty, then the default name was used.
     350             :  *
     351             :  * \return The name of the secondary index or an empty string for the default.
     352             :  *
     353             :  * \sa set_name()
     354             :  */
     355           0 : QString const & snap_tables::secondary_index_t::get_name() const
     356             : {
     357           0 :     return f_name;
     358             : }
     359             : 
     360             : 
     361             : /** \brief Set the column name.
     362             :  *
     363             :  * This function is used to set the name of the column used to generate
     364             :  * the secondary index.
     365             :  *
     366             :  * Note that a secondary index can only have one column named in their
     367             :  * indexes. This allows for much faster read and write without locking
     368             :  * which is what NoSQL databases strive for.
     369             :  *
     370             :  * There is still a way to have a secondary index on multiple columns,
     371             :  * but you will be the one responsible for managing the contents of
     372             :  * these columns so that they get added to the database in a single
     373             :  * column.
     374             :  *
     375             :  * For example, you may want an extra index against a date and priority
     376             :  * pair of columns. Say you define the date as a Unix timestamp in
     377             :  * seconds and the priority is a number between 1 and 100. What you
     378             :  * can do is multiply the Unix timestamp by 100 and add the priority
     379             :  * minus one. That gives you both values in a single int64 value which
     380             :  * you can save in one column.
     381             :  *
     382             :  * If you can't merge the two parameters in such a way, you can always
     383             :  * use a binary buffer (a blob in Cassandra) and save the values one
     384             :  * after another in binary. Then it will sort everything as expected
     385             :  * in your single blob column.
     386             :  *
     387             :  * \param[in] column  The name of the column the index is for.
     388             :  */
     389           0 : void snap_tables::secondary_index_t::set_column(QString const & column)
     390             : {
     391           0 :     f_column = column;
     392           0 : }
     393             : 
     394             : 
     395             : /** \brief Get the column name.
     396             :  *
     397             :  * A secondary index in Cassandra supports a single column (i.e. you can
     398             :  * only index one column at a time.)
     399             :  *
     400             :  * \return The name of the column used as the index.
     401             :  *
     402             :  * \sa set_column()
     403             :  */
     404           0 : QString const & snap_tables::secondary_index_t::get_column() const
     405             : {
     406           0 :     return f_column;
     407             : }
     408             : 
     409             : 
     410             : 
     411             : 
     412             : 
     413             : 
     414             : 
     415             : /** \brief Define the name of this schema.
     416             :  *
     417             :  * Each table has a name. This name must be a valid Cassandra name.
     418             :  * Mainly, it has to start with a letter and be composed of
     419             :  * letters, digits, and underscores and not be a reserved keyword
     420             :  * such as `SELECT`, `INDEX` or `AND`. The name is not case sensitive
     421             :  * although it is customary to write it in lowercase only. (Cassandra
     422             :  * saves the name in lowercase anyway.)
     423             :  *
     424             :  * \param[in] name  The name of the table.
     425             :  *
     426             :  * \sa get_name()
     427             :  */
     428           0 : void snap_tables::table_schema_t::set_name(QString const & name)
     429             : {
     430           0 :     f_name = name;
     431           0 : }
     432             : 
     433             : 
     434             : /** \brief Retrieve the table name.
     435             :  *
     436             :  * This function returns the name of the table.
     437             :  *
     438             :  * See the set_name() about restrictions to the name of a table.
     439             :  *
     440             :  * \return The name of the table.
     441             :  *
     442             :  * \sa set_name()
     443             :  */
     444           0 : QString const & snap_tables::table_schema_t::get_name() const
     445             : {
     446           0 :     return f_name;
     447             : }
     448             : 
     449             : 
     450             : /** \brief Set the description.
     451             :  *
     452             :  * This function is used to save the table description.
     453             :  *
     454             :  * The description is just information about the table. It does not
     455             :  * get saved in Cassandra. It is expected to be useful to the developers.
     456             :  *
     457             :  * \param[in] description  The table description.
     458             :  */
     459           0 : void snap_tables::table_schema_t::set_description(QString const & description)
     460             : {
     461           0 :     f_description = description;
     462           0 : }
     463             : 
     464             : 
     465             : /** \brief Retrieve the description.
     466             :  *
     467             :  * This function returns the description of the table.
     468             :  *
     469             :  * \return The table description.
     470             :  */
     471           0 : QString const & snap_tables::table_schema_t::get_description() const
     472             : {
     473           0 :     return f_description;
     474             : }
     475             : 
     476             : 
     477             : /** \brief Set the model.
     478             :  *
     479             :  * This function is used to set the model of the table. The model defines how
     480             :  * the table is expected to be used in general terms. This is used to
     481             :  * improve the speed of the table when using it.
     482             :  *
     483             :  * The model is one of:
     484             :  *
     485             :  * \li MODEL_CONTENT
     486             :  *
     487             :  * The table is expected to hold content, a few writes and many reads.
     488             :  *
     489             :  * \li MODEL_DATA
     490             :  *
     491             :  * The data table is expected to be written to once and read many times.
     492             :  *
     493             :  * \li MODEL_QUEUE
     494             :  *
     495             :  * The table is to be used like a FIFO. If you have to use this model,
     496             :  * you probably need to rethink how your are using Cassandra. You
     497             :  * probably want to use a different technology to support queues.
     498             :  *
     499             :  * \li MODEL_LOG
     500             :  *
     501             :  * This model is like the MODEL_DATA only you do a single write and
     502             :  * some reads, but the table is really expected to be written to often.
     503             :  * Also no updates are ever expected.
     504             :  *
     505             :  * \li MODEL_SESSION
     506             :  *
     507             :  * The session model is expected to have many reads and writes and
     508             :  * especially a TTL to the data so it automatically gets deleted after
     509             :  * a while (i.e. sessions time out.)
     510             :  *
     511             :  * \param[in] model  The new table model.
     512             :  */
     513           0 : void snap_tables::table_schema_t::set_model(model_t model)
     514             : {
     515           0 :     f_model = model;
     516           0 : }
     517             : 
     518             : 
     519             : /** \brief Retrieve the model.
     520             :  *
     521             :  * This function returns the current table model.
     522             :  *
     523             :  * To transform the model to a string, you can use the model_to_string()
     524             :  * function.
     525             :  *
     526             :  * \return The model used by this table.
     527             :  */
     528           0 : snap_tables::model_t snap_tables::table_schema_t::get_model() const
     529             : {
     530           0 :     return f_model;
     531             : }
     532             : 
     533             : 
     534             : /** \brief Set the kind of table.
     535             :  *
     536             :  * This function is used to set the kind of table to be used. The
     537             :  * kind defines the CQL schema. We currently support two formats.
     538             :  * The old format is the thrift like format and the new format is
     539             :  * the blob format that allows us to save all the fields (columns)
     540             :  * in a single large blob which we can read at once instead of
     541             :  * reading the data one cell at a time.
     542             :  *
     543             :  * The kind is one of:
     544             :  *
     545             :  * \li KIND_THRIFT
     546             :  *
     547             :  * The table is created with a CQL schema as follow:
     548             :  *
     549             :  *     key blob,
     550             :  *     column1 blob,
     551             :  *     value blob,
     552             :  *     primary key (key, column1)
     553             :  *
     554             :  * \li KIND_BLOB
     555             :  *
     556             :  * The table is created with a CQL schema as follow:
     557             :  *
     558             :  *     key blob,
     559             :  *     value blob,
     560             :  *     primary key (key)
     561             :  *
     562             :  * \param[in] kind  The new table kind.
     563             :  *
     564             :  * \sa get_kind()
     565             :  */
     566           0 : void snap_tables::table_schema_t::set_kind(kind_t kind)
     567             : {
     568           0 :     f_kind = kind;
     569           0 : }
     570             : 
     571             : 
     572             : /** \brief Retrieve the kind.
     573             :  *
     574             :  * This function returns the current table kind.
     575             :  *
     576             :  * To transform the kind to a string, you can use the kind_to_string()
     577             :  * function.
     578             :  *
     579             :  * \return The kind used by this table.
     580             :  *
     581             :  * \sa set_kind()
     582             :  */
     583           0 : snap_tables::kind_t snap_tables::table_schema_t::get_kind() const
     584             : {
     585           0 :     return f_kind;
     586             : }
     587             : 
     588             : 
     589             : /** \brief Mark the table as being dropped.
     590             :  *
     591             :  * This function marks the table as dropped (true) or active (false).
     592             :  *
     593             :  * When set to true, the snapdbproxy will automatically drop that table.
     594             :  * This is done because just removing the corresponding `*-tables.xml`
     595             :  * would not tell us whether the table needs to be dropped. You may
     596             :  * remove a plugin from one machine and still have it on another
     597             :  * machine, for example. So instead of just remove the `*-tables.xml`
     598             :  * you need to change the setup and put `drop="drop"` in the table
     599             :  * definition.
     600             :  *
     601             :  * \param[in] drop  The new status of the drop flag.
     602             :  */
     603           0 : void snap_tables::table_schema_t::set_drop(bool drop)
     604             : {
     605           0 :     f_drop = drop;
     606           0 : }
     607             : 
     608             : 
     609             : /** \brief Check whether the table is marked as a drop table.
     610             :  *
     611             :  * This function returns true if the `drop="drop"` flag was specified
     612             :  * in the table definition.
     613             :  *
     614             :  * \return Whether the drop flag is set in the table definition.
     615             :  */
     616           0 : bool snap_tables::table_schema_t::get_drop() const
     617             : {
     618           0 :     return f_drop;
     619             : }
     620             : 
     621             : 
     622             : /** \brief Attach this column to the schema.
     623             :  *
     624             :  * This function saves the column in this schema. Columns have a name.
     625             :  * If you set a column that was already set, the old one gets
     626             :  * overwritten.
     627             :  *
     628             :  * \param[in] column  The column to add to this schema.
     629             :  */
     630           0 : void snap_tables::table_schema_t::set_column(column_t const & column)
     631             : {
     632           0 :     f_columns[column.get_name()] = column;
     633           0 : }
     634             : 
     635             : 
     636             : /** \brief Retrieve the list of columns.
     637             :  *
     638             :  * This function returns the map of columns that were added using the
     639             :  * set_column() function.
     640             :  *
     641             :  * \return The map of columns of this schema.
     642             :  */
     643           0 : snap_tables::column_t::map_t const & snap_tables::table_schema_t::get_columns() const
     644             : {
     645           0 :     return f_columns;
     646             : }
     647             : 
     648             : 
     649             : /** \brief Add a secondary index.
     650             :  *
     651             :  * This function adds a secondary index to this table.
     652             :  *
     653             :  * \param[in] index  The index to add to this table.
     654             :  *
     655             :  * \sa get_secondary_indexes()
     656             :  */
     657           0 : void snap_tables::table_schema_t::set_secondary_index(secondary_index_t const & index)
     658             : {
     659           0 :     f_secondary_indexes[index.get_name()] = index;
     660           0 : }
     661             : 
     662             : 
     663             : /** \brief Retrieve the map of secondary indexes.
     664             :  *
     665             :  * This function returns the map of all the secondary indexes part
     666             :  * to this table.
     667             :  *
     668             :  * \return The map of secondary index.
     669             :  */
     670           0 : snap_tables::secondary_index_t::map_t const & snap_tables::table_schema_t::get_secondary_indexes() const
     671             : {
     672           0 :     return f_secondary_indexes;
     673             : }
     674             : 
     675             : 
     676             : 
     677             : /** \brief Load a directory of XML files defining tables.
     678             :  *
     679             :  * This function goes through a list of files in a directory and reads
     680             :  * all these files as XML files defining tables.
     681             :  *
     682             :  * Here the schema is how we see the tables, whereas, the database
     683             :  * system may see it differently.
     684             :  *
     685             :  * The function can be called any number of times to read files from
     686             :  * various places. However, loading the same files more than once
     687             :  * is considered to be an error and the function returns with false.
     688             :  *
     689             :  * \param[in] path  The path to the directory to read files from.
     690             :  *
     691             :  * \return true if the load process worked, false otherwise.
     692             :  */
     693           0 : bool snap_tables::load(QString const & path)
     694             : {
     695             :     // create the pattern
     696             :     //
     697           0 :     QString const pattern(QString("%1/*.xml").arg(path));
     698             : 
     699             :     // read the list of files
     700             :     //
     701           0 :     bool success(true);
     702             : 
     703             :     try
     704             :     {
     705           0 :         glob_dir files;
     706           0 :         files.set_path( pattern, GLOB_ERR | GLOB_NOSORT | GLOB_NOESCAPE );
     707           0 :         files.enumerate_glob( [&]( QString the_path )
     708             :             {
     709           0 :                 success = success && load_xml(the_path);
     710           0 :             });
     711             :     }
     712           0 :     catch( std::exception const & x)
     713             :     {
     714           0 :         SNAP_LOG_ERROR("could not read \"")(pattern)("\" (what=")(x.what())(")!");
     715           0 :         success = false;
     716             :     }
     717           0 :     catch( ... )
     718             :     {
     719           0 :         SNAP_LOG_ERROR("could not read \"")(pattern)("\"! (got unknown exception)");
     720           0 :         success = false;
     721             :     }
     722             : 
     723           0 :     return success;
     724             : }
     725             : 
     726             : 
     727             : /** \brief Load one specific XML file.
     728             :  *
     729             :  * This function loads one specific XML file from a file on disk or in
     730             :  * Qt resources.
     731             :  *
     732             :  * The file must be valid XML. The format is defined in the tables.xml
     733             :  * file found in the library and which defines the core tables.
     734             :  *
     735             :  * \param[in] filename  The name of the XML file to load.
     736             :  *
     737             :  * \return true when the file loaded properly and the table definitions
     738             :  *         were all added successfully to this 'tables' class.
     739             :  */
     740           0 : bool snap_tables::load_xml(QString const & filename)
     741             : {
     742             :     // open the file
     743             :     //
     744           0 :     QFile file(filename);
     745           0 :     if(!file.open(QIODevice::ReadOnly))
     746             :     {
     747           0 :         SNAP_LOG_ERROR("tables::load_xml() could not open \"")
     748           0 :                       (filename)
     749           0 :                       ("\" resource file.");
     750           0 :         return false;
     751             :     }
     752             : 
     753             :     // read the file
     754             :     //
     755           0 :     QDomDocument doc;
     756           0 :     QString errmsg;
     757           0 :     int err_line(0);
     758           0 :     int err_column(0);
     759           0 :     if(!doc.setContent(&file, false, &errmsg, &err_line, &err_column))
     760             :     {
     761           0 :         SNAP_LOG_ERROR("could not read XML in \"")
     762           0 :                       (filename)
     763           0 :                       ("\", error:")
     764           0 :                       (err_line)
     765           0 :                       ("/")
     766           0 :                       (err_column)
     767           0 :                       (": ")
     768           0 :                       (errmsg)
     769           0 :                       (".");
     770           0 :         return false;
     771             :     }
     772             : 
     773             :     // get the list of <table> tags and go through them one by one
     774             :     //
     775           0 :     QDomNodeList table_tags(doc.elementsByTagName("table"));
     776           0 :     for(int t(0); t < table_tags.size(); ++t)
     777             :     {
     778           0 :         QDomElement table(table_tags.at(t).toElement());
     779           0 :         if(table.isNull())
     780             :         {
     781           0 :             continue;
     782             :         }
     783             : 
     784           0 :         table_schema_t schema;
     785           0 :         schema.set_name(table.attribute("name"));
     786             : 
     787             :         // make sure we are not loading a duplicate
     788             :         // if so we cannot be sure what to do so we throw
     789             :         //
     790           0 :         auto const it(f_schemas.find(schema.get_name()));
     791           0 :         if(it != f_schemas.end())
     792             :         {
     793             :             // TODO: we do not currently save where we found the first
     794             :             //       instance so the error is rather poor at this point...
     795             :             //
     796           0 :             SNAP_LOG_FATAL("found second definition of \"")
     797           0 :                           (schema.get_name())
     798           0 :                           ("\" in \"")
     799           0 :                           (filename)
     800           0 :                           ("\".");
     801           0 :             throw snap_table_invalid_xml_exception("snap_tables::load_xml(): table names loaded in snap::tables must all be unique and not be a reserved keyword");
     802             :         }
     803             : 
     804           0 :         schema.set_model(string_to_model(table.attribute("model")));
     805             : 
     806           0 :         if(table.hasAttribute("drop"))
     807             :         {
     808           0 :             schema.set_drop();
     809             :         }
     810             : 
     811           0 :         QDomElement description(table.firstChildElement("description"));
     812           0 :         if(!description.isNull())
     813             :         {
     814           0 :             schema.set_description(description.text());
     815             :         }
     816             : 
     817           0 :         QDomElement schema_tag(table.firstChildElement("schema"));
     818           0 :         if(schema_tag.isNull())
     819             :         {
     820           0 :             SNAP_LOG_FATAL("missing required <schema> tag.");
     821           0 :             throw snap_table_invalid_xml_exception("snap_tables::load_xml(): missing required <schema> tag.");
     822             :         }
     823             : 
     824           0 :         if(schema_tag.hasAttribute("kind"))
     825             :         {
     826           0 :             schema.set_kind(string_to_kind(schema_tag.attribute("kind")));
     827             :         }
     828             : 
     829           0 :         QDomNodeList column_tags(schema_tag.elementsByTagName("column"));
     830           0 :         for(int c(0); c < column_tags.size(); ++c)
     831             :         {
     832           0 :             QDomElement const column_info(column_tags.at(c).toElement());
     833           0 :             if(column_info.isNull())
     834             :             {
     835           0 :                 continue;
     836             :             }
     837             : 
     838           0 :             column_t column;
     839             : 
     840             :             // in a "thrift" kind of table, columns define the names
     841             :             // appearing in "column1" and the type of value in "value"
     842             :             //
     843             :             // in a "blob" kind of table, columns are all saved together
     844             :             // in one value; we're in charge of transforming the blob
     845             :             // data from a binary buffer to a map of named values
     846             :             //
     847           0 :             if(!column_info.hasAttribute("name"))
     848             :             {
     849           0 :                 SNAP_LOG_FATAL("a <column> must have a \"name\" attribute.");
     850           0 :                 throw snap_table_invalid_xml_exception("snap_tables::load_xml(): found a column without a \"name\" attribute.");
     851             :             }
     852             : 
     853           0 :             column.set_name(column_info.attribute("name"));
     854             : 
     855             :             // make sure that each column is unique by name
     856             :             //
     857           0 :             column_t::map_t columns(schema.get_columns());
     858           0 :             if(columns.find(column.get_name()) != columns.end())
     859             :             {
     860           0 :                 SNAP_LOG_FATAL("column \"")
     861           0 :                               (column.get_name())
     862           0 :                               ("\" is defined multiple times in table \"")
     863           0 :                               (schema.get_name())
     864           0 :                               ("\".");
     865           0 :                 throw snap_table_invalid_xml_exception("snap_tables::load_xml(): found two columns with the same name (see logs for details.)");
     866             :             }
     867             : 
     868           0 :             if(column_info.hasAttribute("type"))
     869             :             {
     870           0 :                 dbutils::column_type_t const column_type(dbutils::get_column_type(column_info.attribute("type")));
     871           0 :                 column.set_type(column_type);
     872             :             }
     873             : 
     874           0 :             if(column_info.hasAttribute("required")
     875           0 :             && column_info.attribute("required") == "true")
     876             :             {
     877           0 :                 column.set_required();
     878             :             }
     879             : 
     880           0 :             if(column_info.hasAttribute("limited")
     881           0 :             && column_info.attribute("limited") == "true")
     882             :             {
     883           0 :                 column.set_limited();
     884             :             }
     885             : 
     886           0 :             QDomElement const column_description(column_info.firstChildElement("description"));
     887           0 :             if(!column_description.isNull())
     888             :             {
     889           0 :                 column.set_description(column_description.text());
     890             :             }
     891             : 
     892           0 :             QDomElement const column_default(column_info.firstChildElement("default"));
     893           0 :             if(!column_default.isNull())
     894             :             {
     895           0 :                 column.set_default(column_default.text());
     896             :             }
     897             : 
     898           0 :             QDomElement const min_value(column_info.firstChildElement("min-value"));
     899           0 :             if(!min_value.isNull())
     900             :             {
     901           0 :                 column.set_min_value(min_value.text().toDouble());
     902             :             }
     903             : 
     904           0 :             QDomElement const max_value(column_info.firstChildElement("max-value"));
     905           0 :             if(!max_value.isNull())
     906             :             {
     907           0 :                 column.set_max_value(max_value.text().toDouble());
     908             :             }
     909             : 
     910           0 :             QDomElement const min_length(column_info.firstChildElement("min-length"));
     911           0 :             if(!min_length.isNull())
     912             :             {
     913           0 :                 column.set_min_length(min_length.text().toLongLong());
     914             :             }
     915             : 
     916           0 :             QDomElement const max_length(column_info.firstChildElement("max-length"));
     917           0 :             if(!max_length.isNull())
     918             :             {
     919           0 :                 column.set_max_length(max_length.text().toLongLong());
     920             :             }
     921             : 
     922           0 :             QDomElement const validation(column_info.firstChildElement("validation"));
     923           0 :             if(!validation.isNull())
     924             :             {
     925           0 :                 column.set_validation(validation.text());
     926             :             }
     927             : 
     928           0 :             schema.set_column(column);
     929             :         }
     930             : 
     931           0 :         QDomNodeList secondary_index_tags(table.elementsByTagName("secondary-index"));
     932           0 :         for(int i(0); i < secondary_index_tags.size(); ++i)
     933             :         {
     934           0 :             QDomElement const secondary_index_info(secondary_index_tags.at(i).toElement());
     935           0 :             if(secondary_index_info.isNull())
     936             :             {
     937           0 :                 continue;
     938             :             }
     939             : 
     940           0 :             secondary_index_t secondary_index;
     941             : 
     942             :             // get name (name is optional, use column name if name is not defined)
     943             :             //
     944           0 :             if(secondary_index_info.hasAttribute("name"))
     945             :             {
     946           0 :                 secondary_index.set_name(secondary_index_info.attribute("name"));
     947             :             }
     948             : 
     949             :             // get column
     950             :             //
     951           0 :             if(!secondary_index_info.hasAttribute("column"))
     952             :             {
     953           0 :                 SNAP_LOG_FATAL("an <secondary-index> must have a \"column\" attribute.");
     954           0 :                 throw snap_table_invalid_xml_exception("snap_tables::load_xml(): found a <secondary-index> without a \"column\" attribute.");
     955             :             }
     956             : 
     957           0 :             secondary_index.set_column(secondary_index_info.attribute("column"));
     958             : 
     959           0 :             schema.set_secondary_index(secondary_index);
     960             :         }
     961             : 
     962             :         // save the new schema in our map
     963             :         //
     964           0 :         f_schemas[schema.get_name()] = schema;
     965             :     }
     966             : 
     967           0 :     return true;
     968             : }
     969             : 
     970             : 
     971             : /** \brief Check whether the named table exists.
     972             :  *
     973             :  * This function can be used to check whether the definition of a
     974             :  * specific table is defined.
     975             :  *
     976             :  * \note
     977             :  * The function returns false for tables that are described and have
     978             :  * the drop="drop" attribute set.
     979             :  *
     980             :  * \param[in] name  The name of the table to search for.
     981             :  *
     982             :  * \return true if the table is defined in the list of schemas.
     983             :  */
     984           0 : bool snap_tables::has_table(QString const & name) const
     985             : {
     986           0 :     auto const it(f_schemas.find(name));
     987           0 :     if(it == f_schemas.end())
     988             :     {
     989           0 :         return false;
     990             :     }
     991           0 :     return !it->second.get_drop();
     992             : }
     993             : 
     994             : 
     995             : /** \brief Check whether the named table exists.
     996             :  *
     997             :  * This function can be used to check whether the definition of a
     998             :  * specific table is defined.
     999             :  *
    1000             :  * \note
    1001             :  * The function returns false for tables that are described and have
    1002             :  * the drop="drop" attribute set.
    1003             :  *
    1004             :  * \param[in] name  The name of the table to search for.
    1005             :  *
    1006             :  * \return true if the table is defined in the list of schemas.
    1007             :  */
    1008           0 : snap_tables::table_schema_t * snap_tables::get_table(QString const & name) const
    1009             : {
    1010           0 :     auto const it(f_schemas.find(name));
    1011           0 :     if(it == f_schemas.end())
    1012             :     {
    1013             :         throw snap_table_unknown_table_exception(
    1014             :                   "table \""
    1015           0 :                 + name
    1016           0 :                 + "\" does not exist. Please use has_table() first to determine whether you can call get_table().");
    1017             :     }
    1018             : 
    1019           0 :     return const_cast<snap::snap_tables::table_schema_t *>(&it->second);
    1020             : }
    1021             : 
    1022             : 
    1023             : /** \brief Retrieve all the schemas.
    1024             :  *
    1025             :  * This function returns a reference to the internal map of schemas.
    1026             :  * It can be used to go through the list of table definitions.
    1027             :  *
    1028             :  * \return A reference to the internal map.
    1029             :  */
    1030           0 : snap_tables::table_schema_t::map_t const & snap_tables::get_schemas() const
    1031             : {
    1032           0 :     return f_schemas;
    1033             : }
    1034             : 
    1035             : 
    1036             : /** \brief Transform a string to a model enumeration.
    1037             :  *
    1038             :  * This function transforms the specified \p model string in a
    1039             :  * number as defined in the model_t enumeration.
    1040             :  *
    1041             :  * \exception snap_table_invalid_xml_exception
    1042             :  * If the specified \p model string does not represent a valid name
    1043             :  * in the list of model_name_values, then this exception is raised.
    1044             :  *
    1045             :  * \param[in] model  The name of the model to transform.
    1046             :  *
    1047             :  * \return One of the model_t enumeration value.
    1048             :  */
    1049           0 : snap_tables::model_t snap_tables::string_to_model(QString const & model)
    1050             : {
    1051           0 :     for(size_t idx(0) ; idx < sizeof(model_name_values) / sizeof(model_name_values[0]); ++idx)
    1052             :     {
    1053           0 :         if(model_name_values[idx].f_name == model)
    1054             :         {
    1055           0 :             return model_name_values[idx].f_model;
    1056             :         }
    1057             :     }
    1058             : 
    1059             :     // we have to find it, no choice...
    1060             :     //
    1061           0 :     throw snap_table_invalid_xml_exception(QString("model named \"%1\" was not found, please verify spelling or Snap!'s versions").arg(model));
    1062             : }
    1063             : 
    1064             : 
    1065             : /** \brief Transform a model to a string.
    1066             :  *
    1067             :  * This function transforms the specified \p model enumeration number
    1068             :  * in a display string so it can be displayed.
    1069             :  *
    1070             :  * \exception snap_table_invalid_xml_exception
    1071             :  * If the specified \p model number is not valid this exception is raised.
    1072             :  *
    1073             :  * \param[in] model  The model to transform.
    1074             :  *
    1075             :  * \return A string representing the model.
    1076             :  */
    1077           0 : QString snap_tables::model_to_string(model_t model)
    1078             : {
    1079           0 :     size_t const idx(static_cast<size_t>(model));
    1080           0 :     if(idx < sizeof(model_name_values) / sizeof(model_name_values[0]))
    1081             :     {
    1082           0 :         return QString::fromUtf8(model_name_values[idx].f_name);
    1083             :     }
    1084             : 
    1085             :     // invalid enumeration number?
    1086             :     //
    1087           0 :     throw snap_table_invalid_xml_exception(QString("model_t \"%1\" is not a valid model enumeration").arg(static_cast<int>(model)));
    1088             : }
    1089             : 
    1090             : 
    1091             : /** \brief Transform a string to a kind enumeration.
    1092             :  *
    1093             :  * This function transforms the specified \p kind string in a
    1094             :  * number as defined in the kind_t enumeration.
    1095             :  *
    1096             :  * \exception snap_table_invalid_xml_exception
    1097             :  * If the specified \p kind string does not represent a valid name
    1098             :  * in the list of kind_name_values, then this exception is raised.
    1099             :  *
    1100             :  * \param[in] kind  The name of the kind to transform.
    1101             :  *
    1102             :  * \return One of the kind_t enumeration value.
    1103             :  */
    1104           0 : snap_tables::kind_t snap_tables::string_to_kind(QString const & kind)
    1105             : {
    1106           0 :     for(size_t idx(0) ; idx < sizeof(kind_name_values) / sizeof(kind_name_values[0]); ++idx)
    1107             :     {
    1108           0 :         if(kind_name_values[idx].f_name == kind)
    1109             :         {
    1110           0 :             return kind_name_values[idx].f_kind;
    1111             :         }
    1112             :     }
    1113             : 
    1114             :     // we have to find it, no choice...
    1115             :     //
    1116           0 :     throw snap_table_invalid_xml_exception(QString("kind named \"%1\" was not found, please verify spelling or Snap!'s versions").arg(kind));
    1117             : }
    1118             : 
    1119             : 
    1120             : /** \brief Transform a kind to a string.
    1121             :  *
    1122             :  * This function transforms the specified \p kind enumeration number
    1123             :  * in a display string so it can be displayed.
    1124             :  *
    1125             :  * \exception snap_table_invalid_xml_exception
    1126             :  * If the specified \p kind number is not valid this exception is raised.
    1127             :  *
    1128             :  * \param[in] kind  The kind to transform.
    1129             :  *
    1130             :  * \return A string representing the kind.
    1131             :  */
    1132           0 : QString snap_tables::kind_to_string(kind_t kind)
    1133             : {
    1134           0 :     size_t const idx(static_cast<size_t>(kind));
    1135           0 :     if(idx < sizeof(kind_name_values) / sizeof(kind_name_values[0]))
    1136             :     {
    1137           0 :         return QString::fromUtf8(kind_name_values[idx].f_name);
    1138             :     }
    1139             : 
    1140             :     // invalid enumeration number?
    1141             :     //
    1142           0 :     throw snap_table_invalid_xml_exception(QString("kind_t \"%1\" is not a valid kind enumeration").arg(static_cast<int>(kind)));
    1143             : }
    1144             : 
    1145             : 
    1146           6 : } // namespace snap
    1147             : // vim: ts=4 sw=4 et

Generated by: LCOV version 1.13