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, "ed_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, "ed_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, "ed_constraint_name);
778 quoted_column_1 = mdb->default_backend->quote_schema_name(NULL, bound[0]);
779 quoted_column_1 = mdb_normalise_and_replace(mdb, "ed_column_1);
780 quoted_column_2 = mdb->default_backend->quote_schema_name(NULL, bound[2]);
781 quoted_column_2 = mdb_normalise_and_replace(mdb, "ed_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, "ed_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, "ed_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, "ed_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