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
|