#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#include <stdint.h>
#include <libxml/tree.h>
#include <libxml/parser.h>
#include <libxml/parserInternals.h>
#ifdef _WIN32
#include <mysql.h>
#else
#include <mysql/mysql.h>
#endif
#include "hashtable.h"
#include "string_buf.h"
#define X(type, name) type,
#define Y(type, name) X(type, name)
enum field_type {
#include "field_types.c"
};
const enum field_type field_type_ids[] = {
#include "field_types.c"
};
#undef X
#undef Y
#define X(type, name) [type] = #type,
#define Y(type, name) X(type, name)
static const char *const field_enum_names[] = {
#include "field_types.c"
};
#undef X
#undef Y
#define X(type, name) [type] = name,
#define Y(type, name) X(type, name)
static const char *const field_names[] = {
#include "field_types.c"
};
#undef X
#undef Y
static inline bool is_rel_type(enum field_type type)
{
return type == ONE_TO_ONE_REL || type == MANY_TO_ONE_REL ||
type == MANY_TO_MANY_REL || type == BROKEN_TO_ONE_REL ||
type == BROKEN_TO_MANY_REL;
}
static inline bool is_text_type(enum field_type type)
{
return type == CHAR_FIELD || type == FILE_FIELD || type == TEXT_FIELD;
}
/* All keys and values are static, ht_destroy() is enough to free this. */
static int make_field_names_ht(hashtable_t *ht)
{
int ret;
int i;
ret = ht_string_init(ht);
if (ret)
return ret;
for (i = 0; i < sizeof(field_type_ids) / sizeof(*field_type_ids); i++) {
ret = ht_add(ht, field_names[i], &field_type_ids[i]);
if (ret) {
ht_destroy(ht);
break;
}
}
return ret;
}
inline static void *create_struct_with_string(size_t minimum_size,
size_t char_field_offset,
const char *string)
{
size_t string_size = strlen(string) + 1;
size_t dynamic_size = char_field_offset + string_size;
char *mem;
mem = malloc(dynamic_size > minimum_size ? dynamic_size : minimum_size);
if (mem)
memcpy(mem + char_field_offset, string, string_size);
return mem;
}
#define STRUCT_WITH_STRING_CREATOR(fun_name, type, char_field) \
static type *fun_name(const char *string) \
{ \
return (type*) create_struct_with_string \
(sizeof(type), \
(size_t) ((type*) 0)->char_field, \
string); \
}
struct field {
struct field *next;
union field_data {
size_t max_length;
char *target_table; /* NULL means mtm table creation failure */
bool array_has_data;
} spec_data;
uint8_t type;
char name[1];
};
STRUCT_WITH_STRING_CREATOR(create_field, struct field, name)
struct table {
struct field *fields;
size_t all_rows;
size_t inserted_rows;
size_t insertion_errors;
bool creation_error;
char name[1];
};
STRUCT_WITH_STRING_CREATOR(create_table, struct table, name)
struct restore {
size_t errors_count;
enum {
PASS_1 = 1,
PASS_2 = 2
} pass;
int nesting;
bool fatal_error;
bool creating_table;
struct table *current_table;
struct field *current_field;
char *characters_buf;
size_t characters_size;
size_t characters_count; /* in PASS_1 used without _buf and _size */
char *pk_buf;
size_t pk_size;
size_t pk_filled;
char *stmt_buf;
size_t stmt_size;
size_t stmt_filled;
hashtable_t field_names;
hashtable_t tables;
MYSQL *mysql;
FILE *raport_stream;
};
/*
* I'm not sure how I should interpret the 'bigger' and 'smaller' when it comes
* to chars, that may or may not be signed. Anyways, no need to be coherent with
* the original version because in practice this function is only ever used
* to check equality.
*/
static int strcmp_replacing_dot(const char *s1, const char *s2)
{
size_t i = 0;
int c1, c2;
while (true) {
c1 = (int) (unsigned char) s1[i];
c2 = (int) (unsigned char) s2[i];
if (c1 == '.')
c1 = '_';
if (c2 == '.')
c2 = '_';
if (c1 != c2)
return c1 - c2;
if (!c1)
return 0;
i++;
}
}
static size_t string_hash_replacing_dot(const void *key_)
{
size_t i = 0, hash = (size_t) 0xa1bad2dead3beef4;
char c, shift;
const char *key = key_;
do {
c = key[i++];
if (c == '.')
c = '_';
shift = ((unsigned char) c) % sizeof(size_t);
hash += ((hash >> shift) |
(hash << (sizeof(size_t) - shift))) ^ c;
} while (c);
return hash;
}
static void replace_dot(char *s)
{
do {
if (*s == '.')
*s = '_';
} while (*(s++));
}
static int init_restore_data(struct restore *data, const char *raport_path)
{
data->errors_count = 0;
data->pass = PASS_1;
data->nesting = 0;
data->fatal_error = false;
data->creating_table = false;
data->current_table = NULL;
data->current_field = NULL;
data->characters_buf = NULL;
data->characters_size = 0;
data->characters_count = 0;
data->stmt_buf = NULL;
data->stmt_size = 0;
data->stmt_filled = 0;
data->pk_buf = NULL;
data->pk_size = 0;
data->pk_filled = 0;
if (make_field_names_ht(&data->field_names))
return -1;
if (ht_init(&data->tables, string_hash_replacing_dot,
(int (*)(const void*, const void*)) strcmp_replacing_dot)) {
ht_destroy(&data->field_names);
return -1;
}
data->raport_stream = raport_path ? fopen(raport_path, "w+") : NULL;
if (!data->raport_stream) {
if (raport_path) {
fprintf(stderr,
"Nie udalo sie utworzyc pliku raportu\n\n");
}
data->raport_stream = stdout;
}
data->mysql = NULL;
return 0;
}
static void free_field(struct field *field)
{
if (!field)
return;
if (is_rel_type(field->type))
free(field->spec_data.target_table);
free(field);
}
static void free_table(struct table *table)
{
struct field *field, *tmp;
if (!table)
return;
field = table->fields;
while (field) {
tmp = field->next;
free_field(field);
field = tmp;
}
free(table);
}
static void free_table_entry(void *key, void *val, void *dummy_arg)
{
free_table(val);
}
static void destroy_restore_data(struct restore *data)
{
ht_destroy(&data->field_names);
ht_map_destroy(&data->tables, NULL, free_table_entry);
free(data->characters_buf);
free(data->stmt_buf);
free(data->pk_buf);
if (data->raport_stream != stderr)
fclose(data->raport_stream);
}
static void out_of_memory_error(struct restore *data)
{
/*
* TODO: dodac informacje na jakim etapie dzialania stanelo
* i wypisać jakies informacje o dotychczas zrobionych rzeczach.
*/
data->fatal_error = true;
fprintf(data->raport_stream, "Brak pamieci.\n");
}
static void semantic_error(struct restore *data, const char *msg, ...)
{
va_list ap;
data->fatal_error = true;
fprintf(data->raport_stream, "Blad semantyczny xml: ");
va_start(ap, msg);
vfprintf(data->raport_stream, msg, ap);
va_end(ap);
fputc('\n', data->raport_stream);
}
struct sb_mysql_arg {
MYSQL *mysql;
const char *string;
};
static int sb_mysql_escape(char **buf, size_t *buf_len, size_t *buf_filled,
void *arg_)
{
struct sb_mysql_arg *arg = arg_;
size_t len = strlen(arg->string);
if (extend_buf(buf, buf_len, buf_filled, len * 2))
return -1;
*buf_filled += mysql_real_escape_string(arg->mysql, *buf + *buf_filled,
arg->string, len);
return 0;
}
static int sb_name_escape(char **buf, size_t *buf_len, size_t *buf_filled,
void *name_)
{
const unsigned char *name = name_;
size_t i;
for (i = 0; name[i]; i++) {
if (name[i] == '`') {
if (sb_bytes(buf, buf_len, buf_filled, name, i + 1))
return -1;
name += i;
i = 0;
}
}
return sb_bytes(buf, buf_len, buf_filled, name, i);
}
static bool is_field_compatible(struct restore *data, const char *name,
enum field_type type, const char *target_table)
{
struct field *previous = data->current_field;
return
!strcmp(previous->name, (const char*) name) &&
previous->type == type &&
(!is_rel_type(type) ||
!strcmp_replacing_dot(target_table,
previous->spec_data.target_table));
}
static void new_field(struct restore *data, const char *name,
enum field_type type, const char *target_table)
{
struct field *field = NULL, **dst;
size_t table_name_size;
field = create_field(name);
if (!field)
goto fail;
field->next = NULL;
field->type = type;
if (is_rel_type(type)) {
table_name_size = strlen(target_table) + 1;
field->spec_data.target_table = malloc(table_name_size);
if (!field->spec_data.target_table)
goto fail;
memcpy(field->spec_data.target_table,
target_table, table_name_size);
replace_dot(field->spec_data.target_table);
} else if (type == ARRAY_FIELD) {
field->spec_data.array_has_data = false;
} else {
field->spec_data.max_length = 0;
}
dst = data->current_field ? &data->current_field->next :
&data->current_table->fields;
*dst = field;
data->current_field = field;
return;
fail:
free_field(field);
out_of_memory_error(data);
}
static void new_table(struct restore *data, const char *table_name)
{
struct table *table = NULL;
table = create_table(table_name);
if (!table)
goto fail;
replace_dot(table->name);
table->creation_error = false;
table->all_rows = 0;
table->inserted_rows = 0;
table->insertion_errors = 0;
table->fields = NULL;
if (ht_add(&data->tables, table->name, table))
goto fail;
data->creating_table = true;
data->current_table = table;
data->current_field = NULL;
return;
fail:
free_table(table);
out_of_memory_error(data);
}
static int find_attrs(const char **attrs, int to_find,
const char *const *searched, const char **found)
{
int i, j; /* `i' iterates through attrs[], `j' through searched */
int found_count = 0;
if (!attrs)
return to_find;
for (j = 0; j < to_find; j++)
found[j] = NULL;
for (i = 0; attrs[i]; i += 2) {
for (j = 0; j < to_find; j++) {
if (!found[j] && !strcmp(attrs[i], searched[j])) {
found_count++;
found[j] = attrs[i + 1];
if (to_find == found_count)
return 0;
}
}
}
return to_find - found_count;
}
/*
* TODO: change handle_field(), handle_entry() and other functions below and
* above not to call exit(), but rather let control return to main().
*/
static void handle_field(struct restore *data, const char *name,
const char **attrs)
{
const char *searched[4] = {"name", "type", "rel", "to"};
const char *found[4];
const char *type_name;
const enum field_type *type;
data->characters_count = 0;
if (data->pass == PASS_2)
return;
if (strcmp(name, "field")) {
semantic_error(data, "tag '%s' zamiast 'field' na poziomie 2",
name);
return;
}
find_attrs(attrs, 4, searched, found);
if (!found[0]) {
semantic_error(data, "brak atrybutu 'name' w polu modelu `%s`",
data->current_table->name);
return;
}
type_name = found[2] ? found[2] : found[1] ? found[1] : NULL;
if (!type_name) {
semantic_error(data,
"brak atrybutu 'type' w polu `%s` modelu `%s`",
found[0], data->current_table->name);
return;
}
if (ht_get(&data->field_names, type_name, NULL, (void**) &type)) {
fprintf(data->raport_stream,
"nieznany typ '%s' kolumny `%s` tabeli `%s`.\n",
type_name, found[0], data->current_table->name);
type = &field_type_ids[UNKNOWN_FIELD];
}
if (is_rel_type(*type) && !found[3]) {
semantic_error(data,
"brak atrybutu 'to' w polu `%s` modelu `%s`",
found[0], data->current_table->name);
return;
}
if (data->creating_table) {
new_field(data, found[0], *type, found[3]);
} else if (!data->current_field) {
semantic_error(data,
"zmienna liczba pol w rekordach modelu `%s`",
data->current_table->name);
return;
} else if (!is_field_compatible(data, found[0], *type, found[3])){
semantic_error(data, "niekompatybilne rekordy modelu `%s`",
data->current_table->name);
return;
}
return;
}
static void handle_entry(struct restore *data, const char *name,
const char **attrs)
{
const char *searched[2] = {"model", "pk"};
const char *found[2];
struct table *table;
struct sb_mysql_arg sb_arg;
int ret;
if (strcmp(name, "object")) {
semantic_error(data, "tag '%s' zamiast 'object' na poziomie 1",
name);
return;
}
find_attrs(attrs, 2, searched, found);
if (!found[0]) {
semantic_error(data, "brak atrybutu 'model' w obiekcie");
return;
}
if (!found[1]) {
semantic_error(data, "brak atrybutu 'pk' w obiekcie");
return;
}
ret = ht_get(&data->tables, found[0], NULL, (void**) &table);
if (ret == HT_KEY_ABSENT) {
switch (data->pass) {
case PASS_1:
new_table(data, found[0]);
break;
case PASS_2:
goto fail;
}
} else {
data->creating_table = false;
if (data->pass == PASS_2 && data->current_table != table)
printf("Wypelnianie tabeli `%s`\n", table->name);
data->current_table = table; /* not really needed in PASS_1 */
data->current_field = table->fields;
}
if (data->pass != PASS_2)
return;
data->pk_filled = 0;
if (sb_string(&data->pk_buf, &data->pk_size,
&data->pk_filled, found[1]))
goto fail;
data->stmt_filled = 0;
sb_arg = (struct sb_mysql_arg) {data->mysql, found[1]};
if (sb_sprintf(&data->stmt_buf, &data->stmt_size,
&data->stmt_filled,
"INSERT INTO `%_` VALUES(\n"
"'%_'",
sb_name_escape, table->name, sb_mysql_escape, &sb_arg))
goto fail;
return;
fail:
data->fatal_error = true;
}
static int insert_to_connector_table(struct restore *data, const char **attrs)
{
int res = -1;
struct field *field = data->current_field;
const char *tab1 = data->current_table->name;
const char *tab2 = field->spec_data.target_table;
const char *pk_attr = "pk";
const char *pk;
char *stmt_buf = NULL;
size_t stmt_size = 0, stmt_filled = 0;
struct sb_mysql_arg sb_arg1, sb_arg2;
if (!tab2) /* Means error during creation of conenctor table */
return 0;
find_attrs(attrs, 1, &pk_attr, &pk);
if (!pk) {
semantic_error(data,
"Brak atrybutu 'pk' w tagu w relacji wiele-do-wielu.\n");
goto fail;
}
sb_arg1 = (struct sb_mysql_arg) {data->mysql, data->pk_buf};
sb_arg2 = (struct sb_mysql_arg) {data->mysql, pk};
if (sb_sprintf(&stmt_buf, &stmt_size, &stmt_filled,
"INSERT INTO `%__mtm_%_` VALUES(\n"
"'%_',\n"
"'%_');",
sb_name_escape, tab1, sb_name_escape, tab2,
sb_mysql_escape, &sb_arg1,
sb_mysql_escape, &sb_arg2))
goto fail;
if (mysql_real_query(data->mysql, stmt_buf, stmt_filled)) {
data->errors_count++;
if (!data->current_table->insertion_errors++) {
fprintf(data->raport_stream,
"Nie udalo sie wstawic elementu do tabeli lacznikowej:\n"
"Blad %u (%s): %s\n",
mysql_errno(data->mysql),
mysql_sqlstate(data->mysql),
mysql_error(data->mysql));
}
}
res = 0;
fail:
free(stmt_buf);
return res;
}
static void startElement_callback(void *user_data, const xmlChar *name_,
const xmlChar **attrs_)
{
const char *name = (const char*) name_;
const char **attrs = (const char**) attrs_;
struct restore *data = user_data;
if (data->fatal_error)
return;
switch(data->nesting++) {
case 0:
/* We have no interest in the root element */
return;
case 1:
handle_entry(data, name, attrs);
break;
case 2:
handle_field(data, name, attrs);
break;
case 3:
if (data->pass == PASS_2 &&
(data->current_field->type == MANY_TO_MANY_REL ||
data->current_field->type == BROKEN_TO_MANY_REL) &&
insert_to_connector_table(data, attrs))
goto fail;
default:
break;
}
return;
fail:
data->fatal_error = true;
}
static int insert_CHAR_FIELD(struct restore *data)
{
struct sb_mysql_arg sb_arg;
if (!data->characters_count &&
sb_string(&data->stmt_buf, &data->stmt_size,
&data->stmt_filled, ",\nNULL"))
return -1;
sb_arg = (struct sb_mysql_arg)
{data->mysql, data->characters_buf};
if (data->characters_count &&
sb_sprintf(&data->stmt_buf, &data->stmt_size,
&data->stmt_filled,
",\n'%_'",
sb_mysql_escape, &sb_arg))
return -1;
return 0;
}
#define insert_FILE_FIELD insert_CHAR_FIELD
#define insert_TEXT_FIELD insert_CHAR_FIELD
#define insert_FILE_FIELD insert_CHAR_FIELD
#define insert_INT_FIELD insert_CHAR_FIELD
#define insert_SMALL_INT_FIELD insert_CHAR_FIELD
#define insert_POSITIVE_INT_FIELD insert_CHAR_FIELD
#define insert_BIG_INT_FIELD insert_CHAR_FIELD
#define insert_DECIMAL_FIELD insert_CHAR_FIELD
#define insert_TIME_FIELD insert_CHAR_FIELD
#define insert_DATETIME_FIELD insert_CHAR_FIELD
#define insert_DATE_FIELD insert_CHAR_FIELD
#define insert_JSON_FIELD insert_CHAR_FIELD
#define insert_ONE_TO_ONE_REL insert_CHAR_FIELD
#define insert_MANY_TO_ONE_REL insert_CHAR_FIELD
#define insert_BROKEN_TO_ONE_REL insert_CHAR_FIELD
#define insert_UNKNOWN_FIELD insert_CHAR_FIELD
static int insert_BOOL_FIELD(struct restore *data)
{
const char *value;
if (data->characters_count && strstr(data->characters_buf, "False"))
value = ",\nFALSE";
else if (data->characters_count && strstr(data->characters_buf, "True"))
value = ",\nTRUE";
else
value = ",\nNULL";
if (sb_string(&data->stmt_buf, &data->stmt_size,
&data->stmt_filled, value))
return -1;
return 0;
}
static int insert_array_element(struct restore *data, const char *element,
int *current_id)
{
int res = -1;
char *stmt_buf = NULL;
size_t stmt_size = 0, stmt_filled = 0;
struct sb_mysql_arg sb_arg1, sb_arg2;
sb_arg1 = (struct sb_mysql_arg) {data->mysql, data->pk_buf};
sb_arg2 = (struct sb_mysql_arg) {data->mysql, element};
if (sb_sprintf(&stmt_buf, &stmt_size, &stmt_filled,
"INSERT INTO `%__array_%_` VALUES(\n"
"%d,\n"
"'%_',\n"
"'%_');",
sb_name_escape, data->current_table->name,
sb_name_escape, data->current_field->name,
(*current_id)++,
sb_mysql_escape, &sb_arg1,
sb_mysql_escape, &sb_arg2))
goto fail;
if (mysql_real_query(data->mysql, stmt_buf, stmt_filled)) {
data->errors_count++;
if (!data->current_table->insertion_errors++) {
fprintf(data->raport_stream,
"Nie udalo sie wstawic elementu danej ARRAY:\n"
"Blad %u (%s): %s\n",
mysql_errno(data->mysql),
mysql_sqlstate(data->mysql),
mysql_error(data->mysql));
}
}
res = 0;
fail:
free(stmt_buf);
return res;
}
static int insert_ARRAY_FIELD(struct restore *data)
{
char *pos, *string_start;
unsigned char c;
int current_id = 0;
enum {
NOT_STARTED,
BEFORE_FIRST_STRING,
BEFORE_STRING,
IN_STRING,
AFTER_STRING,
FINISHED
} state = NOT_STARTED;
for (pos = data->characters_buf; *pos; pos++) {
c = *pos;
switch (state) {
case NOT_STARTED:
if (isspace(c))
break;
if (c == '[') {
state = BEFORE_FIRST_STRING;
break;
}
goto syntax_fail;
case BEFORE_FIRST_STRING:
case BEFORE_STRING:
if (isspace(c))
break;
if (c == '"') {
state = IN_STRING;
string_start = pos + 1;
break;
}
if (c == ']' && state == BEFORE_FIRST_STRING) {
state = FINISHED;
break;
}
goto syntax_fail;
case IN_STRING:
if (c != '"' || pos[-1] == '\\')
break;
*pos = '\0';
if (insert_array_element(data, string_start,
¤t_id))
goto fail;
state = AFTER_STRING;
break;
case AFTER_STRING:
if (isspace(c))
break;
if (c == ',') {
state = BEFORE_STRING;
break;
}
if (c == ']') {
state = FINISHED;
break;
}
goto syntax_fail;
case FINISHED:
if (!isspace(c))
goto syntax_fail;
}
}
if (state != FINISHED)
goto fail;
return 0;
syntax_fail:
fprintf(data->raport_stream,
"Blad syntaktyczny w danej typu array:\n%s\n",
data->characters_buf);
fail:
return -1;
}
static int no_insert_field(struct restore *data)
{
return 0;
}
/* Deliberately omitted */
#define insert_EMPTY_ARRAY_FIELD no_insert_field
/* Inserts to connector tables happen elsewhere */
#define insert_MANY_TO_MANY_REL no_insert_field
#define insert_BROKEN_TO_MANY_REL no_insert_field
#define X(type, name) [type] = insert_##type,
#define Y(type, name) X(type, name)
static int (*field_inserters[])(struct restore *) = {
#include "field_types.c"
};
#undef X
#undef Y
static int finalize_insert(struct restore *data)
{
if (sb_string(&data->stmt_buf, &data->stmt_size,
&data->stmt_filled, ");"))
return -1;
data->current_table->all_rows++;
if (mysql_real_query(data->mysql, data->stmt_buf, data->stmt_filled)) {
data->errors_count++;
if (!data->current_table->insertion_errors++) {
fprintf(data->raport_stream,
"Nie udalo sie wstawic rekordu do bazy:\n"
"Blad %u (%s): %s\n"
"Zapytanie:\n"
"%s\n",
mysql_errno(data->mysql),
mysql_sqlstate(data->mysql),
mysql_error(data->mysql),
data->stmt_buf);
}
} else {
data->current_table->inserted_rows++;
}
return 0;
}
static void endElement_callback(void *user_data, const xmlChar *name)
{
struct restore *data = user_data;
struct field *field = data->current_field;
if (data->fatal_error)
return;
switch (data->nesting--) {
case 3:
break;
case 2:
if (data->pass == PASS_2 &&
!data->current_table->creation_error &&
finalize_insert(data))
goto fail;
default:
return;
}
switch (data->pass) {
case PASS_1:
if (!field)
goto out;
if (is_text_type(field->type) &&
field->spec_data.max_length < data->characters_count)
field->spec_data.max_length = data->characters_count;
if (field->type == ARRAY_FIELD &&
strchr(data->characters_buf, '"'))
field->spec_data.array_has_data = true;
break;
case PASS_2:
if (!field)
goto fail;
if (field_inserters[field->type](data))
goto fail;
}
if (!data->creating_table)
data->current_field = field->next;
out:
return;
fail:
data->fatal_error = true;
}
static void characters_callback(void *user_data, const xmlChar *characters,
int len)
{
struct restore *data = user_data;
if (data->fatal_error)
return;
/* Should not happen, but... */
if (!data->current_field) {
if (data->pass == PASS_1)
fprintf(data->raport_stream,
"Znaki poza tagiem 'field': '%.*s'\n",
len, characters);
return;
}
switch (data->pass) {
case PASS_1:
if (data->current_field->type != ARRAY_FIELD) {
data->characters_count += len;
break;
}
case PASS_2:
if (sb_bytes(&data->characters_buf, &data->characters_size,
&data->characters_count, characters, len))
data->fatal_error = true;
}
}
static void error_callback(void *user_data, const char *msg, ...)
{
struct restore *data = user_data;
va_list ap;
int c;
if (data->fatal_error)
return;
fprintf(data->raport_stream, "Blad xml: ");
va_start(ap, msg);
vfprintf(data->raport_stream, msg, ap);
va_end(ap);
if (!data->errors_count++) {
fprintf(stderr, "Blad syntaktyczny xml, kontynuowac? y/N: ");
fflush(stderr);
c = getchar();
if (c != 'y' && c != 'Y')
data->fatal_error = true;
}
}
static int clear_database(struct restore *data, const char *dbname)
{
int res = -1;
char *stmt_buf = NULL;
size_t buf_len = 0, buf_filled = 0;
struct sb_mysql_arg sb_arg;
MYSQL_RES *query_res = NULL;
size_t i;
my_ulonglong rows;
MYSQL_ROW *rows_array = NULL;
sb_arg = (struct sb_mysql_arg) {data->mysql, dbname};
if (sb_sprintf(&stmt_buf, &buf_len, &buf_filled,
"SELECT table_name "
"FROM information_schema.tables "
"WHERE table_schema = '%_';",
sb_mysql_escape, &sb_arg))
goto fail;
if (mysql_real_query(data->mysql, stmt_buf, buf_filled)) {
fprintf(data->raport_stream,
"Nie udalo sie pobrac nazw istniejacych tabel:\n"
"Blad %u (%s): %s\n"
"Zapytanie:\n"
"%s\n",
mysql_errno(data->mysql),
mysql_sqlstate(data->mysql),
mysql_error(data->mysql), stmt_buf);
goto fail;
}
query_res = mysql_store_result(data->mysql);
if (!query_res)
goto fail;
rows = mysql_num_rows(query_res);
rows_array = malloc(sizeof(MYSQL_ROW) * rows);
if (!rows_array)
goto fail;
for (i = 0; i < rows; i++) {
rows_array[i] = mysql_fetch_row(query_res);
if (!rows_array[i])
goto fail;
}
for (i = 0; i < rows; i++) {
printf("Usuwanie tabeli `%s`.\n", rows_array[i][0]);
buf_filled = 0;
/* Assume fetched table names are not malicious */
if (sb_sprintf(&stmt_buf, &buf_len, &buf_filled,
"DROP TABLE IF EXISTS `%_`;",
sb_name_escape, rows_array[i][0]))
goto fail;
if (mysql_real_query(data->mysql, stmt_buf, buf_filled)) {
fprintf(data->raport_stream,
"Nie udalo sie usunac instniejacej tabeli:\n"
"Blad %u (%s): %s\n",
mysql_errno(data->mysql),
mysql_sqlstate(data->mysql),
mysql_error(data->mysql));
goto fail;
}
}
putchar('\n');
res = 0;
fail:
free(rows_array);
if (query_res)
mysql_free_result(query_res);
free(stmt_buf);
return res;
}
static int sb_field_name(char **stmt_buf, size_t *buf_len,
size_t *buf_filled, struct field *field)
{
if (sb_sprintf(stmt_buf, buf_len, buf_filled,
",\n"
"`%_` ",
sb_name_escape, field->name))
return -1;
return 0;
}
#define SIMPLE_FIELD_DECLARATOR(type, sql_name) \
static int sb_##type(char **stmt_buf, size_t *buf_len, \
size_t *buf_filled, struct field *field) \
{ \
if (sb_field_name(stmt_buf, buf_len, \
buf_filled, field) || \
sb_string(stmt_buf, buf_len, buf_filled, sql_name)) \
return -1; \
\
return 0; \
}
SIMPLE_FIELD_DECLARATOR(TEXT_FIELD, "TEXT")
SIMPLE_FIELD_DECLARATOR(INT_FIELD, "INT")
SIMPLE_FIELD_DECLARATOR(SMALL_INT_FIELD, "SMALLINT")
SIMPLE_FIELD_DECLARATOR(POSITIVE_INT_FIELD, "INT UNSIGNED")
SIMPLE_FIELD_DECLARATOR(BIG_INT_FIELD, "BIGINT")
SIMPLE_FIELD_DECLARATOR(DECIMAL_FIELD, "DECIMAL")
SIMPLE_FIELD_DECLARATOR(BOOL_FIELD, "BOOL")
SIMPLE_FIELD_DECLARATOR(TIME_FIELD, "TIME")
SIMPLE_FIELD_DECLARATOR(DATETIME_FIELD, "DATETIME")
SIMPLE_FIELD_DECLARATOR(DATE_FIELD, "DATE")
SIMPLE_FIELD_DECLARATOR(JSON_FIELD, "TEXT")
/* We do foreign key constraints later, now we just create an int column */
SIMPLE_FIELD_DECLARATOR(ONE_TO_ONE_REL, "INT")
SIMPLE_FIELD_DECLARATOR(MANY_TO_ONE_REL, "INT")
SIMPLE_FIELD_DECLARATOR(BROKEN_TO_ONE_REL, "INT")
SIMPLE_FIELD_DECLARATOR(UNKNOWN_FIELD, "TEXT")
static int sb_CHAR_FIELD(char **stmt_buf, size_t *buf_len,
size_t *buf_filled, struct field *field)
{
if (sb_field_name(stmt_buf, buf_len, buf_filled, field) ||
sb_sprintf(stmt_buf, buf_len, buf_filled, "VARCHAR(%u)",
(unsigned) field->spec_data.max_length))
return -1;
return 0;
}
#define sb_FILE_FIELD sb_CHAR_FIELD
static int no_sb_field(char **stmt_buf, size_t *buf_len,
size_t *buf_filled, struct field *field)
{
return 0;
}
/* Deliberately omitted */
#define sb_EMPTY_ARRAY_FIELD no_sb_field
/* Realized in terms of separate tables */
#define sb_ARRAY_FIELD no_sb_field
#define sb_MANY_TO_MANY_REL no_sb_field
#define sb_BROKEN_TO_MANY_REL no_sb_field
#define X(type, name) [type] = sb_##type,
#define Y(type, name) X(type, name)
static int (*field_declarators[])(char**, size_t*, size_t*, struct field*) = {
#include "field_types.c"
};
#undef X
#undef Y
static void create_mysql_table(const void *key, void *val, void *data_)
{
bool fatal_error = true;
struct table *table = val;
struct field *field;
struct restore *data = data_;
char *stmt_buf = NULL;
size_t buf_len = 0, buf_filled = 0;
if (data->fatal_error)
return;
printf("Tworzenie tabeli `%s`.\n", table->name);
if (sb_sprintf(&stmt_buf, &buf_len, &buf_filled,
"CREATE TABLE `%_` (\n"
"ID INT PRIMARY KEY",
sb_name_escape, table->name))
goto fail;
for (field = table->fields; field; field = field->next) {
if (field_declarators[field->type](&stmt_buf, &buf_len,
&buf_filled, field))
goto fail;
}
if (sb_string(&stmt_buf, &buf_len, &buf_filled, ");"))
goto fail;
if (mysql_real_query(data->mysql, stmt_buf, buf_filled)) {
data->errors_count++;
fprintf(data->raport_stream,
"Nie udalo sie utworzyc tabeli `%s`:\n"
"Blad %u (%s): %s\n"
"Zapytanie:\n"
"%s\n",
table->name,
mysql_errno(data->mysql),
mysql_sqlstate(data->mysql),
mysql_error(data->mysql),
stmt_buf);
table->creation_error = true;
}
fatal_error = false;
fail:
free(stmt_buf);
if (fatal_error)
data->fatal_error = true;
}
static int create_mysql_tables(struct restore *data)
{
ht_map(&data->tables, data, create_mysql_table);
if (data->fatal_error)
return -1;
putchar('\n');
return 0;
}
static void check_table_relations_correctness(const void *key, void *val,
void *data_)
{
struct restore *data = data_;
struct table *table = val, *target_table;
struct field *field;
for (field = table->fields; field; field = field->next) {
if (field->type == ARRAY_FIELD &&
!field->spec_data.array_has_data) {
fprintf(data->raport_stream,
"Pole `%s` typu ARRAY tabeli `%s` nie zawiera danych, bedzie ono pominiete w odtworzonej bazie.\n",
field->name, table->name);
field->type = EMPTY_ARRAY_FIELD;
}
if (!is_rel_type(field->type))
continue;
target_table = NULL;
/*
* I wrote that hashtable. I can assume certain ht api functions
* are re-entrant... Can I?
*/
if (!ht_get(&data->tables, field->spec_data.target_table,
NULL, (void**) &target_table) &&
!target_table->creation_error)
continue;
fprintf(data->raport_stream,
"Relacja %s z tabeli `%s` (pole `%s`) do nie%s tabeli `%s`\n",
field_enum_names[field->type],
table->name, field->name,
target_table ? "dodanej" : "istniejacej",
field->spec_data.target_table);
field->type = field->type == MANY_TO_MANY_REL ?
BROKEN_TO_MANY_REL : BROKEN_TO_ONE_REL;
}
}
static void check_tables_relations(struct restore *data)
{
/* It's a bit of a cheat - assuming I can call ht_get() while mapping */
ht_map(&data->tables, data, check_table_relations_correctness);
fputc('\n', data->raport_stream);
}
static void fprint_field(FILE *os, const struct field *field)
{
fprintf(os, " `%s` %s", field->name, field_enum_names[field->type]);
if (is_rel_type(field->type))
fprintf(os, " to `%s`", field->spec_data.target_table);
if (is_text_type(field->type)) {
fprintf(os, " max %lu",
(unsigned long) field->spec_data.max_length);
}
fputc('\n', os);
}
static void fprint_table(FILE *os, const struct table *table)
{
struct field *field;
fprintf(os, "Tabela `%s`\n", table->name);
for (field = table->fields; field; field = field->next)
fprint_field(os, field);
putc('\n', os);
}
static void fprint_table_entry(const void *key, void *val, void *os)
{
fprint_table(os, val);
}
static void fprint_all_tables(FILE *os, hashtable_t *tables)
{
ht_map(tables, os, fprint_table_entry);
}
static int add_fk_constraint(struct restore *data, struct table *table,
struct field *field)
{
int res = -1;
char *stmt_buf = NULL;
size_t buf_len = 0, buf_filled = 0;
printf("Nakladanie ograniczenia na klucz obcy `%s` w tabeli `%s`.\n",
field->name, table->name);
if (sb_sprintf(&stmt_buf, &buf_len, &buf_filled,
"ALTER TABLE `%_`\n"
"ADD CONSTRAINT `%__%__FK` FOREIGN KEY (`%_`)\n"
"REFERENCES `%_`(id);",
sb_name_escape, table->name, sb_name_escape, table->name,
sb_name_escape, field->name, sb_name_escape, field->name,
sb_name_escape, field->spec_data.target_table))
goto out;
if (mysql_real_query(data->mysql, stmt_buf, buf_filled)) {
data->errors_count++;
fprintf(data->raport_stream,
"Nie udalo sie dodac ograniczenia klucza obcego na kolumne `%s` tabeli `%s`:\n"
"Blad %u (%s): %s\n",
field->name, table->name,
mysql_errno(data->mysql),
mysql_sqlstate(data->mysql),
mysql_error(data->mysql));
}
res = 0;
out:
free(stmt_buf);
return res;
}
static int sb_maybe_references(char **buf, size_t *buf_len,
size_t *buf_filled, void *table_name)
{
if (!table_name)
return 0;
return sb_sprintf(buf, buf_len, buf_filled,
" REFERENCES `%_`(id)", sb_name_escape, table_name);
}
static int create_mysql_mtm_table(struct restore *data, struct table *table,
struct field *field)
{
int res = -1;
char *stmt_buf = NULL;
size_t buf_len = 0, buf_filled = 0;
char *tab1 = table->name, *tab2 = field->spec_data.target_table;
printf("Tworzenie tabeli lacznikowej `%s_mtm_%s`.\n", tab1, tab2);
if (sb_sprintf(&stmt_buf, &buf_len, &buf_filled,
"CREATE TABLE `%__mtm_%_` (\n"
"`%__id` INT%_,\n"
"`%__%__id` INT%_,\n"
"PRIMARY KEY(`%__id`, `%__%__id`));",
sb_name_escape, tab1, sb_name_escape, tab2,
sb_name_escape, tab1,
sb_maybe_references,
table->creation_error ? NULL : tab1,
sb_name_escape, field->name, sb_name_escape, tab2,
sb_maybe_references,
field->type == BROKEN_TO_MANY_REL ? NULL : tab2,
sb_name_escape, tab1,
sb_name_escape, field->name, sb_name_escape, tab2))
goto fail;
if (mysql_real_query(data->mysql, stmt_buf, buf_filled)) {
data->errors_count++;
fprintf(data->raport_stream,
"Nie udalo sie utworzyc tabeli lacznikowej `%s_mtm_%s`:\n"
"Blad %u (%s): %s\n"
"Zapytanie:\n"
"%s\n",
tab1, tab2,
mysql_errno(data->mysql),
mysql_sqlstate(data->mysql),
mysql_error(data->mysql),
stmt_buf);
field->spec_data.target_table = NULL;
}
res = 0;
fail:
free(stmt_buf);
return res;
}
static int create_mysql_array(struct restore *data, struct table *table,
struct field *field)
{
int res = -1;
char *stmt_buf = NULL;
size_t buf_len = 0, buf_filled = 0;
printf("Tworzenie tabeli `%s_array_%s` do zrealizowania pola ARRAY.\n",
table->name, field->name);
if (sb_sprintf(&stmt_buf, &buf_len, &buf_filled,
"CREATE TABLE `%__array_%_` (\n"
"id_of_element INT,\n"
"`%__id` INT%_,\n"
"element VARCHAR(255),\n"
"PRIMARY KEY(`%__id`, id_of_element));",
sb_name_escape, table->name, sb_name_escape, field->name,
sb_name_escape, table->name,
sb_maybe_references,
table->creation_error ? NULL : table->name,
sb_name_escape, table->name))
goto fail;
if (mysql_real_query(data->mysql, stmt_buf, buf_filled)) {
data->errors_count++;
fprintf(data->raport_stream,
"Nie udalo sie utworzyc tabeli `%s_array%s` realizujacej pole typu ARRAY:\n"
"Blad %u (%s): %s\n"
"Zapytanie:\n"
"%s\n",
table->name, field->name,
mysql_errno(data->mysql),
mysql_sqlstate(data->mysql),
mysql_error(data->mysql),
stmt_buf);
field->type = EMPTY_ARRAY_FIELD;
}
res = 0;
fail:
free(stmt_buf);
return res;
}
static void create_mysql_table_relations(const void *key, void *val,
void *data_)
{
struct table *table = val;
struct field *field;
struct restore *data = data_;
if (data->fatal_error)
return;
for (field = table->fields; field; field = field->next) {
if ((field->type == MANY_TO_MANY_REL ||
field->type == BROKEN_TO_MANY_REL) &&
create_mysql_mtm_table(data, table, field))
goto fail;
if (field->type == ARRAY_FIELD &&
create_mysql_array(data, table, field))
goto fail;
if (table->creation_error)
continue;
if ((field->type == ONE_TO_ONE_REL ||
field->type == MANY_TO_ONE_REL) &&
add_fk_constraint(data, table, field))
goto fail;
}
return;
fail:
data->fatal_error = true;
}
static int complete_mysql_tables(struct restore *data)
{
ht_map(&data->tables, data, create_mysql_table_relations);
if (data->fatal_error)
return -1;
putchar('\n');
return 0;
}
static void print_table_summary(const void *key, void *val, void *data_)
{
struct table *table = val;
struct restore *data = data_;
if (!table->all_rows) {
fprintf(data->raport_stream,
"Nie wstawiono zadnych rekordow do tabeli `table->name`.\n");
return;
}
fprintf(data->raport_stream,
"Rekordy wstawione do tabeli `%s`: %ld/%ld\n",
table->name, (long) table->inserted_rows,
(long) table->all_rows);
if (!table->insertion_errors)
return;
fprintf(data->raport_stream,
"Bledy przy wypelnianiu tabeli `%s`: %ld\n",
table->name, (long) table->insertion_errors);
}
static void print_entire_summary(struct restore *data)
{
ht_map(&data->tables, data, print_table_summary);
fputc('\n', data->raport_stream);
}
static inline void error_exit(const char *msg, ...)
{
va_list ap;
va_start(ap, msg);
vfprintf(stderr, msg, ap);
va_end(ap);
fputc('\n', stderr);
exit(-1);
}
const char foreign_checks_off[] = "SET FOREIGN_KEY_CHECKS = 0;";
const char foreign_checks_on[] = "SET FOREIGN_KEY_CHECKS = 1;";
const char set_utf8[] = "SET NAMES 'utf8';";
int main(int argc, char **argv)
{
const char *xml_path, *db_host, *db_user, *db_password, *db_name,
*raport_path = NULL;
int db_port;
xmlSAXHandler sax1, sax2;
struct restore data;
int i;
if (argc < 7) {
error_exit("Zbyt malo argumentów. Oczekiwane:\n"
"restore SCIEZKA_PLIKU_XML ADRES_BAZY PORT UZYTKOWNIK HASLO NAZWA_BAZY [SCIEZKA_PLIKU_RAPORTU]");
}
xml_path = argv[1];
db_host = argv[2];
db_port = atoi(argv[3]);
db_user = argv[4];
db_password = argv[5];
db_name = argv[6];
if (argc >= 8)
raport_path = argv[7];
memset(&sax1, 0, sizeof(sax1));
if (init_restore_data(&data, raport_path))
error_exit("Blad inicjalizacji wewnetrznych struktur.");
sax1.initialized = XML_SAX2_MAGIC;
sax1.startElement = startElement_callback;
sax1.endElement = endElement_callback;
sax1.characters = characters_callback;
sax2 = sax1;
sax1.error = error_callback;
data.pass = PASS_1;
xmlSAXUserParseFile(&sax1, &data, xml_path);
if (data.fatal_error || !data.tables.entries)
error_exit("Nie udalo sie ustalic schematu bazy.");
if (data.errors_count) {
fprintf(data.raport_stream,
"Byly bledy podczas czytania pliku xml (%ld).\n",
(long) data.errors_count);
}
putc('\n', data.raport_stream);
/* initialize connection handler */
data.mysql = mysql_init(NULL);
if (!data.mysql)
error_exit("Blad inicializacji libmysql (prawdopodobnie brak pamieci).");
/* connect to server */
if (!mysql_real_connect(data.mysql,
db_host, db_user, db_password, db_name, db_port,
NULL, 0)) {
fprintf(data.raport_stream,
"Nie udalo sie polaczyc z baza.\n"
"Blad %u (%s): %s\n",
mysql_errno(data.mysql),
mysql_sqlstate(data.mysql),
mysql_error(data.mysql));
mysql_close(data.mysql);
return -1;
}
printf("Polaczono z baza\n\n");
/* Re-enabling foreign checks later is not needed. */
if (mysql_real_query(data.mysql, foreign_checks_off,
sizeof(foreign_checks_off) - 1)) {
fprintf(data.raport_stream,
"Nie udalo sie wylaczyc sprawdzania spojnosci relacji.\n"
"Blad %u (%s): %s\n",
mysql_errno(data.mysql),
mysql_sqlstate(data.mysql),
mysql_error(data.mysql));
error_exit("Blad podczas wylaczania sprawdzania spojnosci relacji.");
}
/* Setting utf-8 for communication between database and the client. */
if (mysql_real_query(data.mysql, set_utf8, sizeof(set_utf8) - 1)) {
data.errors_count++;
fprintf(data.raport_stream,
"Nie udalo sie ustawic kodowania utf8.\n"
"Blad %u (%s): %s\n",
mysql_errno(data.mysql),
mysql_sqlstate(data.mysql),
mysql_error(data.mysql));
}
if (clear_database(&data, "nowabaze")) {
error_exit("Blad podczas usuwania starej zawartosci bazy.");
}
if (create_mysql_tables(&data))
error_exit("Blad podczas tworzenia tabel.");
check_tables_relations(&data);
fprint_all_tables(data.raport_stream, &data.tables);
if (complete_mysql_tables(&data))
error_exit("Blad podczas uzupelniania utworzonych tabel.");
data.pass = 2;
xmlSAXUserParseFile(&sax2, &data, xml_path);
putchar('\n');
fputc('\n', data.raport_stream);
print_entire_summary(&data);
if (data.fatal_error)
error_exit("Fatalny blad podczas umieszczania danych w bazie.");
#define OS(n) ((n) == 0 ? data.raport_stream : stdout)
for (i = data.raport_stream == stdout ? 0 : 1; i >= 0; i--) {
fprintf(OS(i), "Operacja zakonczona");
if (data.errors_count) {
fprintf(OS(i), "; bledow lacznie: %ld\n",
(long) data.errors_count);
} else {
fprintf(OS(i), " bez bledow.\n");
}
}
mysql_close(data.mysql);
destroy_restore_data(&data);
return 0;
}