1 /* MDB Tools - A library for reading MS Access database files
2  * Copyright (C) 2000-2011 Brian Bruns and others
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public
15  * License along with this library; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
17  */
18 
19 /*
20 ** functions to deal with different backend database engines
21 */
22 
23 #include "mdbtools.h"
24 #include "mdbprivate.h"
25 
26 /*    Access data types */
27 static const MdbBackendType mdb_access_types[] = {
28     [MDB_BOOL] = { .name = "Boolean" },
29     [MDB_BYTE] = { .name = "Byte" },
30     [MDB_INT] = { .name = "Integer" },
31     [MDB_LONGINT] = { .name = "Long Integer" },
32     [MDB_MONEY] = { .name = "Currency" },
33     [MDB_FLOAT] = { .name = "Single" },
34     [MDB_DOUBLE] = { .name = "Double" },
35     [MDB_DATETIME] = { .name = "DateTime" },
36     [MDB_BINARY] = { .name = "Binary" },
37     [MDB_TEXT] = { .name = "Text", .needs_char_length = 1 },
38     [MDB_OLE] = { .name = "OLE", .needs_byte_length = 1 },
39     [MDB_MEMO] = { .name = "Memo/Hyperlink", .needs_char_length = 1 },
40     [MDB_REPID] = { .name = "Replication ID" },
41     [MDB_NUMERIC] = { .name = "Numeric", .needs_precision = 1, .needs_scale = 1 },
42 };
43 
44 /*    Oracle data types */
45 static const MdbBackendType mdb_oracle_types[] = {
46     [MDB_BOOL] = { .name = "NUMBER(1)" },
47     [MDB_BYTE] = { .name = "NUMBER(3)" },
48     [MDB_INT] = { .name = "NUMBER(5)" },
49     [MDB_LONGINT] = { .name = "NUMBER(11)" },
50     [MDB_MONEY] = { .name = "NUMBER(15,2)" },
51     [MDB_FLOAT] = { .name = "FLOAT" },
52     [MDB_DOUBLE] = { .name = "FLOAT" },
53     [MDB_DATETIME] = { .name = "TIMESTAMP" },
54     [MDB_BINARY] = { .name = "BINARY" },
55     [MDB_TEXT] = { .name = "VARCHAR2", .needs_char_length= 1 },
56     [MDB_OLE] = { .name = "BLOB" },
57     [MDB_MEMO] = { .name = "CLOB" },
58     [MDB_REPID] = { .name = "NUMBER", .needs_precision = 1 },
59     [MDB_NUMERIC] = { .name = "NUMBER", .needs_precision = 1 },
60 };
61 static const MdbBackendType mdb_oracle_shortdate_type =
62 		    { .name = "DATE" };
63 
64 /*    Sybase/MSSQL data types */
65 static const MdbBackendType mdb_sybase_types[] = {
66     [MDB_BOOL] = { .name = "bit" },
67     [MDB_BYTE] = { .name = "char", .needs_byte_length = 1 },
68     [MDB_INT] = { .name = "smallint" },
69     [MDB_LONGINT] = { .name = "int" },
70     [MDB_MONEY] = { .name = "money" },
71     [MDB_FLOAT] = { .name = "real" },
72     [MDB_DOUBLE] = { .name = "float" },
73     [MDB_DATETIME] = { .name = "smalldatetime" },
74     [MDB_BINARY] = { .name = "varbinary", .needs_byte_length = 1 },
75     [MDB_TEXT] = { .name = "nvarchar", .needs_char_length = 1 },
76     [MDB_OLE] = { .name = "varbinary(max)" },
77     [MDB_MEMO] = { .name = "nvarchar(max)" },
78     [MDB_REPID] = { .name = "Sybase_Replication ID" },
79     [MDB_NUMERIC] = { .name = "numeric", .needs_precision = 1, .needs_scale = 1 },
80 };
81 static const MdbBackendType mdb_sybase_shortdate_type =
82 		    { .name = "DATE" };
83 
84 /*    Postgres data types */
85 static const MdbBackendType mdb_postgres_types[] = {
86     [MDB_BOOL] = { .name = "BOOLEAN" },
87     [MDB_BYTE] = { .name = "SMALLINT" },
88     [MDB_INT] = { .name = "INTEGER" },
89     [MDB_LONGINT] = { .name = "INTEGER" }, /* bigint */
90     [MDB_MONEY] = { .name = "NUMERIC(15,2)" }, /* money deprecated ? */
91     [MDB_FLOAT] = { .name = "REAL" },
92     [MDB_DOUBLE] = { .name = "DOUBLE PRECISION" },
93     [MDB_DATETIME] = { .name = "TIMESTAMP WITHOUT TIME ZONE" },
94     [MDB_BINARY] = { .name = "BYTEA" },
95     [MDB_TEXT] = { .name = "VARCHAR", .needs_char_length = 1 },
96     [MDB_OLE] = { .name = "BYTEA" },
97     [MDB_MEMO] = { .name = "TEXT" },
98     [MDB_REPID] = { .name = "UUID" },
99     [MDB_NUMERIC] = { .name = "NUMERIC", .needs_precision = 1, .needs_scale = 1 },
100 };
101 static const MdbBackendType mdb_postgres_shortdate_type =
102 		    { .name = "DATE" };
103 static const MdbBackendType mdb_postgres_serial_type =
104 		    { .name = "SERIAL" };
105 
106 /*    MySQL data types */
107 static const MdbBackendType mdb_mysql_types[] = {
108     [MDB_BOOL] = { .name = "boolean" },
109     [MDB_BYTE] = { .name = "tinyint" },
110     [MDB_INT] = { .name = "smallint" },
111     [MDB_LONGINT] = { .name = "int" },
112     [MDB_MONEY] = { .name = "float" },
113     [MDB_FLOAT] = { .name = "float" },
114     [MDB_DOUBLE] = { .name = "float" },
115     [MDB_DATETIME] = { .name = "datetime" },
116     [MDB_BINARY] = { .name = "blob" },
117     [MDB_TEXT] = { .name = "varchar", .needs_char_length = 1 },
118     [MDB_OLE] = { .name = "blob" },
119     [MDB_MEMO] = { .name = "text" },
120     [MDB_REPID] = { .name = "char(38)" },
121     [MDB_NUMERIC] = { .name = "numeric", .needs_precision = 1, .needs_scale = 1 },
122 };
123 static const MdbBackendType mdb_mysql_shortdate_type =
124 		    { .name = "date" };
125 /* We can't use the MySQL SERIAL type because that uses a bigint which
126  * is 64 bits wide, whereas MDB long ints are 32 bits */
127 static const MdbBackendType mdb_mysql_serial_type =
128 		    { .name = "int not null auto_increment unique" };
129 
130 /*    sqlite data types */
131 static const MdbBackendType mdb_sqlite_types[] = {
132     [MDB_BOOL] = { .name = "INTEGER" },
133     [MDB_BYTE] = { .name = "INTEGER" },
134     [MDB_INT] = { .name = "INTEGER" },
135     [MDB_LONGINT] = { .name = "INTEGER" },
136     [MDB_MONEY] = { .name = "REAL" },
137     [MDB_FLOAT] = { .name = "REAL" },
138     [MDB_DOUBLE] = { .name = "REAL" },
139     [MDB_DATETIME] = { .name = "DateTime" },
140     [MDB_BINARY] = { .name = "BLOB" },
141     [MDB_TEXT] = { .name = "varchar" },
142     [MDB_OLE] = { .name = "BLOB" },
143     [MDB_MEMO] = { .name = "TEXT" },
144     [MDB_REPID] = { .name = "INTEGER" },
145     [MDB_NUMERIC] = { .name = "INTEGER" },
146 };
147 
148 enum {
149 	MDB_BACKEND_ACCESS = 1,
150 	MDB_BACKEND_ORACLE,
151 	MDB_BACKEND_SYBASE,
152 	MDB_BACKEND_POSTGRES,
153 	MDB_BACKEND_MYSQL,
154 	MDB_BACKEND_SQLITE,
155 };
156 
157 static void mdb_drop_backend(gpointer key, gpointer value, gpointer data);
158 
159 
passthrough_unchanged(const gchar * str)160 static gchar *passthrough_unchanged(const gchar *str) {
161     return (gchar *)str;
162 }
163 
to_lower_case(const gchar * str)164 static gchar *to_lower_case(const gchar *str) {
165     return g_utf8_strdown(str, -1);
166 }
167 
168 /**
169  * Convenience function to replace an input string with its database specific normalised version.
170  *
171  * This function throws away the input string after normalisation, freeing its memory, and replaces it with a new
172  * normalised version allocated on the stack.
173  *
174  * @param mdb  Database specific MDB handle containing pointers to utility methods
175  * @param str string to normalise
176  * @return a pointer to the normalised version of the input string
177  */
mdb_normalise_and_replace(MdbHandle * mdb,gchar ** str)178 gchar *mdb_normalise_and_replace(MdbHandle *mdb, gchar **str) {
179     gchar *normalised_str = mdb->default_backend->normalise_case(*str);
180     if (normalised_str != *str) {
181         /* Free and replace the old string only and only if a new string was created at a different memory location
182          * so that we can account for the case where strings a just passed through unchanged.
183          */
184         free(*str);
185         *str = normalised_str;
186     }
187     return *str;
188 }
189 
190 static gchar*
quote_generic(const gchar * value,gchar quote_char,gchar escape_char)191 quote_generic(const gchar *value, gchar quote_char, gchar escape_char) {
192 	gchar *result, *pr;
193 	unsigned char c;
194 
195 	pr = result = g_malloc(1+4*strlen(value)+2); // worst case scenario
196 
197 	*pr++ = quote_char;
198 	while ((c=*(unsigned char*)value++)) {
199 		if (c<32) {
200 			sprintf(pr, "\\%03o", c);
201 			pr+=4;
202 			continue;
203 		}
204 		else if (c == quote_char) {
205 			*pr++ = escape_char;
206 		}
207 		*pr++ = c;
208 	}
209 	*pr++ = quote_char;
210 	*pr++ = '\0';
211 	return result;
212 }
213 static gchar*
quote_schema_name_bracket_merge(const gchar * schema,const gchar * name)214 quote_schema_name_bracket_merge(const gchar* schema, const gchar *name) {
215 	if (schema)
216 		return g_strconcat("[", schema, "_", name, "]", NULL);
217 	else
218 		return g_strconcat("[", name, "]", NULL);
219 }
220 
221 /*
222  * For backends that really does support schema
223  * returns "name" or "schema"."name"
224  */
225 static gchar*
quote_schema_name_dquote(const gchar * schema,const gchar * name)226 quote_schema_name_dquote(const gchar* schema, const gchar *name)
227 {
228 	if (schema) {
229 		gchar *frag1 = quote_generic(schema, '"', '"');
230 		gchar *frag2 = quote_generic(name, '"', '"');
231 		gchar *result = g_strconcat(frag1, ".", frag2, NULL);
232 		g_free(frag1);
233 		g_free(frag2);
234 		return result;
235 	}
236 	return quote_generic(name, '"', '"');
237 }
238 
239 /*
240  * For backends that really do NOT support schema
241  * returns "name" or "schema_name"
242  */
243 /*
244 static gchar*
245 quote_schema_name_dquote_merge(const gchar* schema, const gchar *name)
246 {
247 	if (schema) {
248 		gchar *combined = g_strconcat(schema, "_", name, NULL);
249 		gchar *result = quote_generic(combined, '"', '"');
250 		g_free(combined);
251 		return result;
252 	}
253 	return quote_generic(name, '"', '"');
254 }*/
255 
256 static gchar*
quote_schema_name_rquotes_merge(const gchar * schema,const gchar * name)257 quote_schema_name_rquotes_merge(const gchar* schema, const gchar *name)
258 {
259 	if (schema) {
260 		gchar *combined = g_strconcat(schema, "_", name, NULL);
261 		gchar *result = quote_generic(combined, '`', '`');
262 		g_free(combined);
263 		return result;
264 	}
265 	return quote_generic(name, '`', '`');
266 }
267 
268 static gchar*
quote_with_squotes(const gchar * value)269 quote_with_squotes(const gchar* value)
270 {
271 	return quote_generic(value, '\'', '\'');
272 }
273 
274 const MdbBackendType*
mdb_get_colbacktype(const MdbColumn * col)275 mdb_get_colbacktype(const MdbColumn *col) {
276 	MdbBackend *backend = col->table->entry->mdb->default_backend;
277 	int col_type = col->col_type;
278 	if (col_type > MDB_NUMERIC)
279 		return NULL;
280 	if (col_type == MDB_LONGINT && col->is_long_auto && backend->type_autonum)
281 		return backend->type_autonum;
282 	if (col_type == MDB_DATETIME && backend->type_shortdate) {
283 		if (mdb_col_is_shortdate(col))
284 			return backend->type_shortdate;
285 	}
286 	if (!backend->types_table[col_type].name[0])
287 		return NULL;
288 	return &backend->types_table[col_type];
289 }
290 
291 const char *
mdb_get_colbacktype_string(const MdbColumn * col)292 mdb_get_colbacktype_string(const MdbColumn *col)
293 {
294 	const MdbBackendType *type = mdb_get_colbacktype(col);
295 	if (!type) {
296    		// return NULL;
297 		static TLS char buf[16];
298 		snprintf(buf, sizeof(buf), "Unknown_%04x", col->col_type);
299 		return buf;
300 	}
301 	return type->name;
302 }
303 int
mdb_colbacktype_takes_length(const MdbColumn * col)304 mdb_colbacktype_takes_length(const MdbColumn *col)
305 {
306 	const MdbBackendType *type = mdb_get_colbacktype(col);
307 	if (!type) return 0;
308 	return type->needs_precision || type->needs_char_length || type->needs_byte_length;
309 }
310 static int
mdb_colbacktype_takes_length_in_characters(const MdbColumn * col)311 mdb_colbacktype_takes_length_in_characters(const MdbColumn *col)
312 {
313 	const MdbBackendType *type = mdb_get_colbacktype(col);
314 	if (!type) return 0;
315 	return type->needs_char_length;
316 }
317 
318 /**
319  * mdb_init_backends
320  *
321  * Initializes the mdb_backends hash and loads the builtin backends.
322  * Use mdb_remove_backends() to destroy this hash when done.
323  */
mdb_init_backends(MdbHandle * mdb)324 void mdb_init_backends(MdbHandle *mdb)
325 {
326     if (mdb->backends) {
327         mdb_remove_backends(mdb);
328     }
329 	mdb->backends = g_hash_table_new(g_str_hash, g_str_equal);
330 
331 	mdb_register_backend(mdb, "access",
332 		MDB_SHEXP_DROPTABLE|MDB_SHEXP_CST_NOTNULL|MDB_SHEXP_DEFVALUES,
333 		mdb_access_types, NULL, NULL,
334 		"Date()", "Date()",
335 		NULL,
336 		NULL,
337 		"-- That file uses encoding %s\n",
338 		"DROP TABLE %s;\n",
339 		NULL,
340 		NULL,
341 		NULL,
342 		NULL,
343 		NULL,
344 		quote_schema_name_bracket_merge);
345 	mdb_register_backend(mdb, "sybase",
346 		MDB_SHEXP_DROPTABLE|MDB_SHEXP_CST_NOTNULL|MDB_SHEXP_CST_NOTEMPTY|MDB_SHEXP_COMMENTS|MDB_SHEXP_DEFVALUES,
347 		mdb_sybase_types, &mdb_sybase_shortdate_type, NULL,
348 		"getdate()", "getdate()",
349 		NULL,
350 		NULL,
351 		"-- That file uses encoding %s\n",
352 		"DROP TABLE %s;\n",
353 		"ALTER TABLE %s ADD CHECK (%s <>'');\n",
354 		"COMMENT ON COLUMN %s.%s IS %s;\n",
355 		NULL,
356 		"COMMENT ON TABLE %s IS %s;\n",
357 		NULL,
358 		quote_schema_name_dquote);
359 	mdb_register_backend(mdb, "oracle",
360 		MDB_SHEXP_DROPTABLE|MDB_SHEXP_CST_NOTNULL|MDB_SHEXP_COMMENTS|MDB_SHEXP_INDEXES|MDB_SHEXP_RELATIONS|MDB_SHEXP_DEFVALUES,
361 		mdb_oracle_types, &mdb_oracle_shortdate_type, NULL,
362 		"current_date", "sysdate",
363 		NULL,
364 		NULL,
365 		"-- That file uses encoding %s\n",
366 		"DROP TABLE %s;\n",
367 		NULL,
368 		"COMMENT ON COLUMN %s.%s IS %s;\n",
369 		NULL,
370 		"COMMENT ON TABLE %s IS %s;\n",
371 		NULL,
372 		quote_schema_name_dquote);
373 	mdbi_register_backend2(mdb, "postgres",
374 		MDB_SHEXP_DROPTABLE|MDB_SHEXP_CST_NOTNULL|MDB_SHEXP_CST_NOTEMPTY|MDB_SHEXP_COMMENTS|MDB_SHEXP_INDEXES|MDB_SHEXP_RELATIONS|MDB_SHEXP_DEFVALUES|MDB_SHEXP_BULK_INSERT,
375 		mdb_postgres_types, &mdb_postgres_shortdate_type, &mdb_postgres_serial_type,
376 		"current_date", "now()",
377 		"%Y-%m-%d %H:%M:%S",
378 		"%Y-%m-%d",
379 		"SET client_encoding = '%s';\n",
380         "CREATE TABLE IF NOT EXISTS %s\n",
381 		"DROP TABLE IF EXISTS %s;\n",
382 		"ALTER TABLE %s ADD CHECK (%s <>'');\n",
383 		"COMMENT ON COLUMN %s.%s IS %s;\n",
384 		NULL,
385 		"COMMENT ON TABLE %s IS %s;\n",
386 		NULL,
387 		quote_schema_name_dquote,
388         to_lower_case);
389 	mdb_register_backend(mdb, "mysql",
390 		MDB_SHEXP_DROPTABLE|MDB_SHEXP_CST_NOTNULL|MDB_SHEXP_CST_NOTEMPTY|MDB_SHEXP_INDEXES|MDB_SHEXP_RELATIONS|MDB_SHEXP_DEFVALUES|MDB_SHEXP_BULK_INSERT,
391 		mdb_mysql_types, &mdb_mysql_shortdate_type, &mdb_mysql_serial_type,
392 		"current_date", "now()",
393 		"%Y-%m-%d %H:%M:%S",
394 		"%Y-%m-%d",
395 		"-- That file uses encoding %s\n",
396 		"DROP TABLE IF EXISTS %s;\n",
397 		"ALTER TABLE %s ADD CHECK (%s <>'');\n",
398 		NULL,
399 		"COMMENT %s",
400 		NULL,
401 		"COMMENT %s",
402 		quote_schema_name_rquotes_merge);
403 	mdb_register_backend(mdb, "sqlite",
404 		MDB_SHEXP_DROPTABLE|MDB_SHEXP_DEFVALUES|MDB_SHEXP_BULK_INSERT,
405 		mdb_sqlite_types, NULL, NULL,
406 		"date('now')", "date('now')",
407 		"%Y-%m-%d %H:%M:%S",
408 		"%Y-%m-%d",
409 		"-- That file uses encoding %s\n",
410 		"DROP TABLE IF EXISTS %s;\n",
411 		NULL,
412 		NULL,
413 		NULL,
414 		NULL,
415 		NULL,
416 		quote_schema_name_rquotes_merge);
417 }
418 
mdbi_register_backend2(MdbHandle * mdb,char * backend_name,guint32 capabilities,const MdbBackendType * backend_type,const MdbBackendType * type_shortdate,const MdbBackendType * type_autonum,const char * short_now,const char * long_now,const char * date_fmt,const char * shortdate_fmt,const char * charset_statement,const char * create_table_statement,const char * drop_statement,const char * constraint_not_empty_statement,const char * column_comment_statement,const char * per_column_comment_statement,const char * table_comment_statement,const char * per_table_comment_statement,gchar * (* quote_schema_name)(const gchar *,const gchar *),gchar * (* normalise_case)(const gchar *))419 MdbBackend *mdbi_register_backend2(MdbHandle *mdb, char *backend_name, guint32 capabilities,
420         const MdbBackendType *backend_type, const MdbBackendType *type_shortdate, const MdbBackendType *type_autonum,
421         const char *short_now, const char *long_now,
422         const char *date_fmt, const char *shortdate_fmt,
423         const char *charset_statement,
424         const char *create_table_statement,
425         const char *drop_statement,
426         const char *constraint_not_empty_statement,
427         const char *column_comment_statement,
428         const char *per_column_comment_statement,
429         const char *table_comment_statement,
430         const char *per_table_comment_statement,
431         gchar* (*quote_schema_name)(const gchar*, const gchar*),
432         gchar* (*normalise_case)(const gchar*)) {
433 	MdbBackend *backend = g_malloc0(sizeof(MdbBackend));
434 	backend->capabilities = capabilities;
435 	backend->types_table = backend_type;
436 	backend->type_shortdate = type_shortdate;
437 	backend->type_autonum = type_autonum;
438 	backend->short_now = short_now;
439 	backend->long_now = long_now;
440 	backend->date_fmt = date_fmt;
441 	backend->shortdate_fmt = shortdate_fmt;
442 	backend->charset_statement = charset_statement;
443 	backend->create_table_statement = create_table_statement;
444 	backend->drop_statement = drop_statement;
445 	backend->constaint_not_empty_statement = constraint_not_empty_statement;
446 	backend->column_comment_statement = column_comment_statement;
447 	backend->per_column_comment_statement = per_column_comment_statement;
448 	backend->table_comment_statement = table_comment_statement;
449 	backend->per_table_comment_statement = per_table_comment_statement;
450 	backend->quote_schema_name  = quote_schema_name;
451 	backend->normalise_case = normalise_case;
452 	g_hash_table_insert(mdb->backends, backend_name, backend);
453     return backend;
454 }
455 
mdb_register_backend(MdbHandle * mdb,char * backend_name,guint32 capabilities,const MdbBackendType * backend_type,const MdbBackendType * type_shortdate,const MdbBackendType * type_autonum,const char * short_now,const char * long_now,const char * date_fmt,const char * shortdate_fmt,const char * charset_statement,const char * drop_statement,const char * constraint_not_empty_statement,const char * column_comment_statement,const char * per_column_comment_statement,const char * table_comment_statement,const char * per_table_comment_statement,gchar * (* quote_schema_name)(const gchar *,const gchar *))456 void mdb_register_backend(MdbHandle *mdb, char *backend_name, guint32 capabilities,
457         const MdbBackendType *backend_type, const MdbBackendType *type_shortdate, const MdbBackendType *type_autonum,
458         const char *short_now, const char *long_now,
459         const char *date_fmt, const char *shortdate_fmt,
460         const char *charset_statement,
461         const char *drop_statement,
462         const char *constraint_not_empty_statement,
463         const char *column_comment_statement,
464         const char *per_column_comment_statement,
465         const char *table_comment_statement,
466         const char *per_table_comment_statement,
467         gchar* (*quote_schema_name)(const gchar*, const gchar*))
468 {
469     mdbi_register_backend2(mdb, backend_name, capabilities,
470             backend_type, type_shortdate, type_autonum,
471             short_now, long_now,
472             date_fmt, shortdate_fmt,
473             charset_statement,
474             "CREATE TABLE %s\n",
475             drop_statement,
476             constraint_not_empty_statement,
477             column_comment_statement,
478             per_column_comment_statement,
479             table_comment_statement,
480             per_table_comment_statement,
481             quote_schema_name,
482             passthrough_unchanged);
483 }
484 
485 /**
486  * mdb_remove_backends
487  *
488  * Removes all entries from and destroys the mdb_backends hash.
489  */
490 void
mdb_remove_backends(MdbHandle * mdb)491 mdb_remove_backends(MdbHandle *mdb)
492 {
493 	g_hash_table_foreach(mdb->backends, mdb_drop_backend, NULL);
494 	g_hash_table_destroy(mdb->backends);
495 }
mdb_drop_backend(gpointer key,gpointer value,gpointer data)496 static void mdb_drop_backend(gpointer key, gpointer value, gpointer data)
497 {
498 	MdbBackend *backend = (MdbBackend *)value;
499 	g_free (backend);
500 }
501 
502 /**
503  * mdb_set_default_backend
504  * @mdb: Handle to open MDB database file
505  * @backend_name: Name of the backend to set as default
506  *
507  * Sets the default backend of the handle @mdb to @backend_name.
508  *
509  * Returns: 1 if successful, 0 if unsuccessful.
510  */
mdb_set_default_backend(MdbHandle * mdb,const char * backend_name)511 int mdb_set_default_backend(MdbHandle *mdb, const char *backend_name)
512 {
513 	MdbBackend *backend;
514 
515     if (!mdb->backends) {
516         mdb_init_backends(mdb);
517     }
518 	backend = (MdbBackend *) g_hash_table_lookup(mdb->backends, backend_name);
519 	if (backend) {
520 		mdb->default_backend = backend;
521 		g_free(mdb->backend_name); // NULL is ok
522 		mdb->backend_name = (char *) g_strdup(backend_name);
523 		mdb->relationships_table = NULL;
524 		if (backend->date_fmt) {
525 			mdb_set_date_fmt(mdb, backend->date_fmt);
526 		} else {
527 			mdb_set_date_fmt(mdb, "%x %X");
528 		}
529 		if (backend->shortdate_fmt) {
530 			mdb_set_shortdate_fmt(mdb, backend->shortdate_fmt);
531 		} else {
532 			mdb_set_shortdate_fmt(mdb, "%x");
533 		}
534 	}
535 	return (backend != NULL);
536 }
537 
538 
539 /**
540  * Generates index name based on backend.
541  *
542  * You should free() the returned value once you are done with it.
543  *
544  * @param backend backend we are generating indexes for
545  * @param table table being processed
546  * @param idx index being processed
547  * @return the index name
548  */
549 static char *
mdb_get_index_name(int backend,MdbTableDef * table,MdbIndex * idx)550 mdb_get_index_name(int backend, MdbTableDef *table, MdbIndex *idx)
551 {
552 	char *index_name;
553 
554 	switch(backend){
555 		case MDB_BACKEND_MYSQL:
556 			// appending table name to index often makes it too long for mysql
557 			if (idx->index_type==1)
558 				// for mysql name of primary key is not used
559 				index_name = g_strdup("_pkey");
560 			else {
561 				index_name = g_strdup(idx->name);
562 			}
563 			break;
564 		default:
565 			if (idx->index_type==1)
566 				index_name = g_strconcat(table->name, "_pkey", NULL);
567 			else {
568 				index_name = g_strconcat(table->name, "_", idx->name, "_idx", NULL);
569 			}
570 	}
571 
572 	return index_name;
573 }
574 
575 /**
576  * mdb_print_indexes
577  * @output: Where to print the sql
578  * @table: Table to process
579  * @dbnamespace: Target namespace/schema name
580  */
581 static void
mdb_print_indexes(FILE * outfile,MdbTableDef * table,char * dbnamespace)582 mdb_print_indexes(FILE* outfile, MdbTableDef *table, char *dbnamespace)
583 {
584 	unsigned int i, j;
585 	char* quoted_table_name;
586 	char* index_name;
587 	char* quoted_name;
588 	int backend;
589 	MdbHandle* mdb = table->entry->mdb;
590 	MdbIndex *idx;
591 	MdbColumn *col;
592 
593 	if (!strcmp(mdb->backend_name, "postgres")) {
594 		backend = MDB_BACKEND_POSTGRES;
595 	} else if (!strcmp(mdb->backend_name, "mysql")) {
596 		backend = MDB_BACKEND_MYSQL;
597 	} else if (!strcmp(mdb->backend_name, "oracle")) {
598 		backend = MDB_BACKEND_ORACLE;
599 	} else {
600 		fprintf(outfile, "-- Indexes are not implemented for %s\n\n", mdb->backend_name);
601 		return;
602 	}
603 
604 	/* read indexes */
605 	mdb_read_indices(table);
606 
607 	fprintf (outfile, "-- CREATE INDEXES ...\n");
608 
609 	quoted_table_name = mdb->default_backend->quote_schema_name(dbnamespace, table->name);
610 	quoted_table_name = mdb->default_backend->normalise_case(quoted_table_name);
611 
612 	for (i=0;i<table->num_idxs;i++) {
613 		idx = g_ptr_array_index (table->indices, i);
614 		if (idx->index_type==2)
615 			continue;
616 
617 		index_name = mdb_get_index_name(backend, table, idx);
618 		switch (backend) {
619 			case MDB_BACKEND_POSTGRES:
620 				/* PostgreSQL index and constraint names are
621                                  * never namespaced in DDL (they are always
622                                  * created in same namespace as table), so
623                                  * omit namespace.
624                                  */
625 				quoted_name = mdb->default_backend->quote_schema_name(NULL, index_name);
626 				break;
627 
628                          default:
629 				quoted_name = mdb->default_backend->quote_schema_name(dbnamespace, index_name);
630 		}
631 
632 		quoted_name = mdb_normalise_and_replace(mdb, &quoted_name);
633 
634 		if (idx->index_type==1) {
635 			switch (backend) {
636 				case MDB_BACKEND_ORACLE:
637 				case MDB_BACKEND_POSTGRES:
638 					fprintf (outfile, "ALTER TABLE %s ADD CONSTRAINT %s PRIMARY KEY (", quoted_table_name, quoted_name);
639 					break;
640 				case MDB_BACKEND_MYSQL:
641 					fprintf (outfile, "ALTER TABLE %s ADD PRIMARY KEY (", quoted_table_name);
642 					break;
643 			}
644 		} else {
645 			switch (backend) {
646 				case MDB_BACKEND_ORACLE:
647 				case MDB_BACKEND_POSTGRES:
648 					fprintf(outfile, "CREATE");
649 					if (idx->flags & MDB_IDX_UNIQUE)
650 						fprintf (outfile, " UNIQUE");
651 					fprintf(outfile, " INDEX %s ON %s (", quoted_name, quoted_table_name);
652 					break;
653 				case MDB_BACKEND_MYSQL:
654 					fprintf(outfile, "ALTER TABLE %s ADD", quoted_table_name);
655 					if (idx->flags & MDB_IDX_UNIQUE)
656 						fprintf (outfile, " UNIQUE");
657 					fprintf(outfile, " INDEX %s (", quoted_name);
658 					break;
659 			}
660 		}
661 		g_free(quoted_name);
662 		free(index_name);
663 
664 		for (j=0;j<idx->num_keys;j++) {
665 			if (j)
666 				fprintf(outfile, ", ");
667 			col=g_ptr_array_index(table->columns,idx->key_col_num[j]-1);
668 			quoted_name = mdb->default_backend->quote_schema_name(NULL, col->name);
669 			quoted_name = mdb_normalise_and_replace(mdb, &quoted_name);
670 			fprintf (outfile, "%s", quoted_name);
671 			if (idx->index_type!=1 && idx->key_col_order[j])
672 				/* no DESC for primary keys */
673 				fprintf(outfile, " DESC");
674 
675 			g_free(quoted_name);
676 
677 		}
678 		fprintf (outfile, ");\n");
679 	}
680 	fputc ('\n', outfile);
681 
682 	g_free(quoted_table_name);
683 }
684 
685 /**
686  * mdb_get_relationships
687  * @mdb: Handle to open MDB database file
688  * @tablename: Name of the table to process. Process all tables if NULL.
689  *
690  * Generates relationships by reading the MSysRelationships table.
691  *   'szColumn' contains the column name of the child table.
692  *   'szObject' contains the table name of the child table.
693  *   'szReferencedColumn' contains the column name of the parent table.
694  *   'szReferencedObject' contains the table name of the parent table.
695  *   'grbit' contains integrity constraints.
696  *
697  * Returns: a string stating that relationships are not supported for the
698  *   selected backend, or a string containing SQL commands for setting up
699  *   the relationship, tailored for the selected backend.
700  *   Returns NULL on last iteration.
701  *   The caller is responsible for freeing this string.
702  */
703 static char *
mdb_get_relationships(MdbHandle * mdb,const gchar * dbnamespace,const char * tablename)704 mdb_get_relationships(MdbHandle *mdb, const gchar *dbnamespace, const char* tablename)
705 {
706 	unsigned int i;
707 	gchar *text = NULL;  /* String to be returned */
708 	char **bound = mdb->relationships_values;  /* Bound values */
709 	int backend = 0;
710 	char *quoted_table_1, *quoted_column_1,
711 	     *quoted_table_2, *quoted_column_2,
712 	     *constraint_name, *quoted_constraint_name;
713 	long grbit;
714 
715 	if (!strcmp(mdb->backend_name, "oracle")) {
716 		backend = MDB_BACKEND_ORACLE;
717 	} else if (!strcmp(mdb->backend_name, "postgres")) {
718 		backend = MDB_BACKEND_POSTGRES;
719 	} else if (!strcmp(mdb->backend_name, "mysql")) {
720 		backend = MDB_BACKEND_MYSQL;
721 	} else if (!mdb->relationships_table) {
722 		return NULL;
723 	}
724 
725 	if (!mdb->relationships_table) {
726 		mdb->relationships_table = mdb_read_table_by_name(mdb, "MSysRelationships", MDB_TABLE);
727 		if (!mdb->relationships_table || !mdb->relationships_table->num_rows) {
728 			fprintf(stderr, "No MSysRelationships\n");
729 			return NULL;
730 		}
731 		if (!mdb_read_columns(mdb->relationships_table)) {
732 			fprintf(stderr, "Unable to read columns of MSysRelationships\n");
733 			return NULL;
734 		}
735 		for (i=0;i<5;i++) {
736 			bound[i] = g_malloc0(mdb->bind_size);
737 		}
738 		mdb_bind_column_by_name(mdb->relationships_table, "szColumn", bound[0], NULL);
739 		mdb_bind_column_by_name(mdb->relationships_table, "szObject", bound[1], NULL);
740 		mdb_bind_column_by_name(mdb->relationships_table, "szReferencedColumn", bound[2], NULL);
741 		mdb_bind_column_by_name(mdb->relationships_table, "szReferencedObject", bound[3], NULL);
742 		mdb_bind_column_by_name(mdb->relationships_table, "grbit", bound[4], NULL);
743 		mdb_rewind_table(mdb->relationships_table);
744 	}
745     if (mdb->relationships_table->cur_row >= mdb->relationships_table->num_rows) {  /* past the last row */
746         for (i=0;i<5;i++)
747             g_free(bound[i]);
748         mdb->relationships_table = NULL;
749         return NULL;
750     }
751 
752 	while (1) {
753 		if (!mdb_fetch_row(mdb->relationships_table)) {
754 			for (i=0;i<5;i++)
755 				g_free(bound[i]);
756 			mdb->relationships_table = NULL;
757 			return NULL;
758 		}
759 		if (!tablename || !strcmp(bound[1], tablename))
760 			break;
761 	}
762 
763 	quoted_table_1 = mdb->default_backend->quote_schema_name(dbnamespace, bound[1]);
764 	quoted_table_2 = mdb->default_backend->quote_schema_name(dbnamespace, bound[3]);
765 	grbit = atoi(bound[4]);
766 	constraint_name = g_strconcat(bound[1], "_", bound[0], "_fk", NULL);
767 
768 	switch (backend) {
769 		case MDB_BACKEND_POSTGRES:
770 			/* PostgreSQL index and constraint names are
771 			 * never namespaced in DDL (they are always
772 			 * created in same namespace as table), so
773 			 * omit namespace.  Nor should column names
774                          * be namespaced.
775 			 */
776 			quoted_constraint_name = mdb->default_backend->quote_schema_name(NULL, constraint_name);
777 			quoted_constraint_name = mdb_normalise_and_replace(mdb, &quoted_constraint_name);
778 			quoted_column_1 = mdb->default_backend->quote_schema_name(NULL, bound[0]);
779 			quoted_column_1 = mdb_normalise_and_replace(mdb, &quoted_column_1);
780 			quoted_column_2 = mdb->default_backend->quote_schema_name(NULL, bound[2]);
781 			quoted_column_2 = mdb_normalise_and_replace(mdb, &quoted_column_2);
782 			break;
783 
784 		default:
785 			/* Other databases, namespace constraint and
786 			 * column names.
787 			 */
788 			quoted_constraint_name = mdb->default_backend->quote_schema_name(dbnamespace, constraint_name);
789 			quoted_column_1 = mdb->default_backend->quote_schema_name(dbnamespace, bound[0]);
790 			quoted_column_2 = mdb->default_backend->quote_schema_name(dbnamespace, bound[2]);
791 			break;
792 	}
793 	g_free(constraint_name);
794 
795 	if (grbit & 0x00000002) {
796 		text = g_strconcat(
797 			"-- Relationship from ", quoted_table_1,
798 			" (", quoted_column_1, ")"
799 			" to ", quoted_table_2, "(", quoted_column_2, ")",
800 			" does not enforce integrity.\n", NULL);
801 	} else {
802 		switch (backend) {
803 		  case MDB_BACKEND_ORACLE:
804                         text = g_strconcat(
805                                 "ALTER TABLE ", quoted_table_1,
806                                 " ADD CONSTRAINT ", quoted_constraint_name,
807                                 " FOREIGN KEY (", quoted_column_1, ")"
808                                 " REFERENCES ", quoted_table_2, "(", quoted_column_2, ")",
809                                 (grbit & 0x00001000) ? " ON DELETE CASCADE" : "",
810                                 ";\n", NULL);
811 
812                         break;
813 		  case MDB_BACKEND_MYSQL:
814 			text = g_strconcat(
815 				"ALTER TABLE ", quoted_table_1,
816 				" ADD CONSTRAINT ", quoted_constraint_name,
817 				" FOREIGN KEY (", quoted_column_1, ")"
818 				" REFERENCES ", quoted_table_2, "(", quoted_column_2, ")",
819 				(grbit & 0x00000100) ? " ON UPDATE CASCADE" : "",
820 				(grbit & 0x00001000) ? " ON DELETE CASCADE" : "",
821 				";\n", NULL);
822 			break;
823 		  case MDB_BACKEND_POSTGRES:
824 			text = g_strconcat(
825 				"ALTER TABLE ", quoted_table_1,
826 				" ADD CONSTRAINT ", quoted_constraint_name,
827 				" FOREIGN KEY (", quoted_column_1, ")"
828 				" REFERENCES ", quoted_table_2, "(", quoted_column_2, ")",
829 				(grbit & 0x00000100) ? " ON UPDATE CASCADE" : "",
830 				(grbit & 0x00001000) ? " ON DELETE CASCADE" : "",
831 				/* On some databases (eg PostgreSQL) we also want to set
832 				 * the constraints to be optionally deferrable, to
833 				 * facilitate out of order bulk loading.
834 				 */
835 				" DEFERRABLE",
836 				" INITIALLY IMMEDIATE",
837 				";\n", NULL);
838 
839 			break;
840 		}
841 	}
842 	g_free(quoted_table_1);
843 	g_free(quoted_column_1);
844 	g_free(quoted_table_2);
845 	g_free(quoted_column_2);
846 	g_free(quoted_constraint_name);
847 
848 	return (char *)text;
849 }
850 
851 static void
generate_table_schema(FILE * outfile,MdbCatalogEntry * entry,char * dbnamespace,guint32 export_options)852 generate_table_schema(FILE *outfile, MdbCatalogEntry *entry, char *dbnamespace, guint32 export_options)
853 {
854 	MdbTableDef *table;
855 	MdbHandle *mdb = entry->mdb;
856 	MdbColumn *col;
857 	unsigned int i;
858 	char* quoted_table_name;
859 	char* quoted_name;
860 	MdbProperties *props;
861 	const char *prop_value;
862 
863 	quoted_table_name = mdb->default_backend->quote_schema_name(dbnamespace, entry->object_name);
864 	quoted_table_name = mdb_normalise_and_replace(mdb, &quoted_table_name);
865 
866 	/* drop the table if it exists */
867 	if (export_options & MDB_SHEXP_DROPTABLE)
868 		fprintf (outfile, mdb->default_backend->drop_statement, quoted_table_name);
869 
870 	/* create the table */
871 	fprintf (outfile, mdb->default_backend->create_table_statement, quoted_table_name);
872 	fprintf (outfile, " (\n");
873 
874 	table = mdb_read_table (entry);
875 
876 	/* get the columns */
877 	mdb_read_columns(table);
878 
879 	/* loop over the columns, dumping the names and types */
880 	for (i = 0; i < table->num_cols; i++) {
881 		col = g_ptr_array_index (table->columns, i);
882 
883 		quoted_name = mdb->default_backend->quote_schema_name(NULL, col->name);
884 		quoted_name = mdb_normalise_and_replace(mdb, &quoted_name);
885 		fprintf (outfile, "\t%s\t\t\t%s", quoted_name,
886 			mdb_get_colbacktype_string (col));
887 		g_free(quoted_name);
888 
889 		if (mdb_colbacktype_takes_length(col)) {
890 			/* more portable version from DW patch */
891 			if (col->col_size == 0)
892 				fputs(" (255)", outfile);
893 			else if (col->col_scale != 0)
894 				fprintf(outfile, " (%d, %d)", col->col_scale, col->col_prec);
895 			else if (!IS_JET3(mdb) && mdb_colbacktype_takes_length_in_characters(col))
896 				fprintf(outfile, " (%d)", col->col_size/2);
897 			else
898 				fprintf(outfile, " (%d)", col->col_size);
899 		}
900 
901 		if (mdb->default_backend->per_column_comment_statement && export_options & MDB_SHEXP_COMMENTS) {
902 			prop_value = mdb_col_get_prop(col, "Description");
903 			if (prop_value) {
904 				char *comment = quote_with_squotes(prop_value);
905 				fputs(" ", outfile);
906 				fprintf(outfile,
907 					mdb->default_backend->per_column_comment_statement,
908 					comment);
909 				free(comment);
910 			}
911 		}
912 
913 		if (export_options & MDB_SHEXP_CST_NOTNULL) {
914 			if (col->col_type == MDB_BOOL) {
915 				/* access booleans are never null */
916 				fputs(" NOT NULL", outfile);
917 			} else {
918 				const gchar *not_null = mdb_col_get_prop(col, "Required");
919 				if (not_null && not_null[0]=='y')
920 					fputs(" NOT NULL", outfile);
921 			}
922 		}
923 
924 		if (export_options & MDB_SHEXP_DEFVALUES) {
925 			int done = 0;
926 			if (col->props) {
927 				gchar *defval = g_hash_table_lookup(col->props->hash, "DefaultValue");
928 				if (defval) {
929 					size_t def_len = strlen(defval);
930 					fputs(" DEFAULT ", outfile);
931 					/* ugly hack to detect the type */
932 					if (defval[0]=='"' && defval[def_len-1]=='"') {
933 						/* this is a string */
934 						gchar *output_default = malloc(def_len-1);
935 						gchar *output_default_escaped;
936 						memcpy(output_default, defval+1, def_len-2);
937 						output_default[def_len-2] = 0;
938 						output_default_escaped = quote_with_squotes(output_default);
939 						fputs(output_default_escaped, outfile);
940 						g_free(output_default_escaped);
941 						free(output_default);
942 					} else if (!strcmp(defval, "Yes"))
943 						fputs("TRUE", outfile);
944 					else if (!strcmp(defval, "No"))
945 						fputs("FALSE", outfile);
946 					else if (!g_ascii_strcasecmp(defval, "date()")) {
947 						if (mdb_col_is_shortdate(col))
948 							fputs(mdb->default_backend->short_now, outfile);
949 						else
950 							fputs(mdb->default_backend->long_now, outfile);
951 					}
952 					else
953 						fputs(defval, outfile);
954 					done = 1;
955 				}
956 			}
957 			if (!done && col->col_type == MDB_BOOL)
958 				/* access booleans are false by default */
959 				fputs(" DEFAULT FALSE", outfile);
960 		}
961 		if (i < table->num_cols - 1)
962 			fputs(", \n", outfile);
963 		else
964 			fputs("\n", outfile);
965 	} /* for */
966 
967 	fputs(")", outfile);
968 	if (mdb->default_backend->per_table_comment_statement && export_options & MDB_SHEXP_COMMENTS) {
969 		prop_value = mdb_table_get_prop(table, "Description");
970 		if (prop_value) {
971 			char *comment = quote_with_squotes(prop_value);
972 			fputs(" ", outfile);
973 			fprintf(outfile, mdb->default_backend->per_table_comment_statement, comment);
974 			free(comment);
975 		}
976 	}
977 	fputs(";\n", outfile);
978 
979 	/* Add the constraints on columns */
980 	for (i = 0; i < table->num_cols; i++) {
981 		col = g_ptr_array_index (table->columns, i);
982 		props = col->props;
983 		if (!props)
984 			continue;
985 
986 		quoted_name = mdb->default_backend->quote_schema_name(NULL, col->name);
987 		quoted_name = mdb_normalise_and_replace(mdb, &quoted_name);
988 
989 		if (export_options & MDB_SHEXP_CST_NOTEMPTY) {
990 			prop_value = mdb_col_get_prop(col, "AllowZeroLength");
991 			if (prop_value && prop_value[0]=='n')
992 					fprintf(outfile,
993 						mdb->default_backend->constaint_not_empty_statement,
994 						quoted_table_name, quoted_name);
995 		}
996 
997 		if (mdb->default_backend->column_comment_statement && export_options & MDB_SHEXP_COMMENTS) {
998 			prop_value = mdb_col_get_prop(col, "Description");
999 			if (prop_value) {
1000 				char *comment = quote_with_squotes(prop_value);
1001 				fprintf(outfile,
1002 					mdb->default_backend->column_comment_statement,
1003 					quoted_table_name, quoted_name, comment);
1004 				g_free(comment);
1005 			}
1006 		}
1007 
1008 		g_free(quoted_name);
1009 	}
1010 
1011 	/* Add the constraints on table */
1012 	if (mdb->default_backend->table_comment_statement && export_options & MDB_SHEXP_COMMENTS) {
1013 		prop_value = mdb_table_get_prop(table, "Description");
1014 		if (prop_value) {
1015 			char *comment = quote_with_squotes(prop_value);
1016 			fprintf(outfile,
1017 				mdb->default_backend->table_comment_statement,
1018 				quoted_table_name, comment);
1019 			g_free(comment);
1020 		}
1021 	}
1022 	fputc('\n', outfile);
1023 
1024 
1025 	if (export_options & MDB_SHEXP_INDEXES)
1026 		// prints all the indexes of that table
1027 		mdb_print_indexes(outfile, table, dbnamespace);
1028 
1029 	g_free(quoted_table_name);
1030 
1031 	mdb_free_tabledef (table);
1032 }
1033 
1034 
1035 int
mdb_print_schema(MdbHandle * mdb,FILE * outfile,char * tabname,char * dbnamespace,guint32 export_options)1036 mdb_print_schema(MdbHandle *mdb, FILE *outfile, char *tabname, char *dbnamespace, guint32 export_options)
1037 {
1038 	unsigned int   i;
1039 	char		*the_relation;
1040 	MdbCatalogEntry *entry;
1041 	const char *charset;
1042 	int success = (tabname == NULL);
1043 
1044 	/* clear unsupported options */
1045 	export_options &= mdb->default_backend->capabilities;
1046 
1047 	/* Print out a little message to show that this came from mdb-tools.
1048 	   I like to know how something is generated. DW */
1049 	fputs("-- ----------------------------------------------------------\n"
1050 		"-- MDB Tools - A library for reading MS Access database files\n"
1051 		"-- Copyright (C) 2000-2011 Brian Bruns and others.\n"
1052 		"-- Files in libmdb are licensed under LGPL and the utilities under\n"
1053 		"-- the GPL, see COPYING.LIB and COPYING files respectively.\n"
1054 		"-- Check out http://mdbtools.sourceforge.net\n"
1055 		"-- ----------------------------------------------------------\n\n",
1056 		outfile);
1057 
1058 	charset = mdb_target_charset(mdb);
1059 	if (charset) {
1060 		fprintf(outfile, mdb->default_backend->charset_statement, charset);
1061 		fputc('\n', outfile);
1062 	}
1063 
1064 	for (i=0; i < mdb->num_catalog; i++) {
1065 		entry = g_ptr_array_index (mdb->catalog, i);
1066 		if (entry->object_type == MDB_TABLE) {
1067 			if ((tabname && !strcmp(entry->object_name, tabname))
1068 			 || (!tabname && mdb_is_user_table(entry))) {
1069 				generate_table_schema(outfile, entry, dbnamespace, export_options);
1070 				success = 1;
1071 			}
1072 		}
1073 	}
1074 	fprintf (outfile, "\n");
1075 
1076 	if (export_options & MDB_SHEXP_RELATIONS) {
1077 		fputs ("-- CREATE Relationships ...\n", outfile);
1078 		the_relation=mdb_get_relationships(mdb, dbnamespace, tabname);
1079 		if (!the_relation) {
1080 			fputs("-- relationships are not implemented for ", outfile);
1081 			fputs(mdb->backend_name, outfile);
1082 			fputs("\n", outfile);
1083 		} else {
1084 			do {
1085 				fputs(the_relation, outfile);
1086 				g_free(the_relation);
1087 			} while ((the_relation=mdb_get_relationships(mdb, dbnamespace, tabname)) != NULL);
1088 		}
1089 	}
1090 	return success;
1091 }
1092 
1093 #define MDB_BINEXPORT_MASK 0x0F
1094 #define is_binary_type(x) (x==MDB_OLE || x==MDB_BINARY || x==MDB_REPID)
1095 #define is_quote_type(x) (is_binary_type(x) || x==MDB_TEXT || x==MDB_MEMO || x==MDB_DATETIME)
1096 //#define DONT_ESCAPE_ESCAPE
1097 void
mdb_print_col(FILE * outfile,gchar * col_val,int quote_text,int col_type,int bin_len,char * quote_char,char * escape_char,int flags)1098 mdb_print_col(FILE *outfile, gchar *col_val, int quote_text, int col_type, int bin_len,
1099 		char *quote_char, char *escape_char, int flags)
1100 /* quote_text: Don't quote if 0.
1101  */
1102 {
1103 	size_t quote_len = strlen(quote_char); /* multibyte */
1104 
1105 	size_t orig_escape_len = escape_char ? strlen(escape_char) : 0;
1106 	int quoting = quote_text && is_quote_type(col_type);
1107     int bin_mode = (flags & MDB_BINEXPORT_MASK);
1108     int escape_cr_lf = !!(flags & MDB_EXPORT_ESCAPE_CONTROL_CHARS);
1109 
1110 	/* double the quote char if no escape char passed */
1111 	if (!escape_char)
1112 		escape_char = quote_char;
1113 
1114 	if (quoting)
1115 		fputs(quote_char, outfile);
1116 
1117 	while (1) {
1118 		if (is_binary_type(col_type)) {
1119 			if (bin_mode == MDB_BINEXPORT_STRIP)
1120 				break;
1121 			if (!bin_len--)
1122 				break;
1123 		} else /* use \0 sentry */
1124 			if (!*col_val)
1125 				break;
1126 
1127 		if (is_binary_type(col_type) && bin_mode == MDB_BINEXPORT_OCTAL) {
1128 			fprintf(outfile, "\\%03o", *(unsigned char*)col_val++);
1129 		} else if (is_binary_type(col_type) && bin_mode == MDB_BINEXPORT_HEXADECIMAL) {
1130 			fprintf(outfile, "%02X", *(unsigned char*)col_val++);
1131 		} else if (quoting && quote_len && !strncmp(col_val, quote_char, quote_len)) {
1132 			fprintf(outfile, "%s%s", escape_char, quote_char);
1133 			col_val += quote_len;
1134 #ifndef DONT_ESCAPE_ESCAPE
1135 		} else if (quoting && orig_escape_len && !strncmp(col_val, escape_char, orig_escape_len)) {
1136 			fprintf(outfile, "%s%s", escape_char, escape_char);
1137 			col_val += orig_escape_len;
1138 #endif
1139 		} else if (escape_cr_lf && is_quote_type(col_type) && *col_val=='\r') {
1140 			col_val++;
1141 			putc('\\', outfile);
1142 			putc('r', outfile);
1143 		} else if (escape_cr_lf && is_quote_type(col_type) && *col_val=='\n') {
1144 			col_val++;
1145 			putc('\\', outfile);
1146 			putc('n', outfile);
1147 		} else if (escape_cr_lf && is_quote_type(col_type) && *col_val=='\t') {
1148 			col_val++;
1149 			putc('\\', outfile);
1150 			putc('t', outfile);
1151 		} else if (escape_cr_lf && is_quote_type(col_type) && *col_val=='\\') {
1152 			col_val++;
1153 			putc('\\', outfile);
1154 			putc('\\', outfile);
1155 		} else
1156 			putc(*col_val++, outfile);
1157 	}
1158 	if (quoting)
1159 		fputs(quote_char, outfile);
1160 }
1161