/* MDB Tools - A library for reading MS Access database file * Copyright (C) 2000 Brian Bruns * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "mdbsql.h" #ifdef DMALLOC #include "dmalloc.h" #endif #ifdef HAVE_WORDEXP_H #define HAVE_WORDEXP #include #endif char *g_input_ptr; /* Prevent warnings from -Wmissing-prototypes. */ #ifdef YYPARSE_PARAM #if defined __STDC__ || defined __cplusplus int yyparse (void *YYPARSE_PARAM); #else int yyparse (); #endif #else /* ! YYPARSE_PARAM */ #if defined __STDC__ || defined __cplusplus int yyparse (void); #else int yyparse (); #endif #endif /* ! YYPARSE_PARAM */ void mdb_sql_error(MdbSQL* sql, char *fmt, ...) { va_list ap; va_start(ap, fmt); vsnprintf(sql->error_msg, sizeof(sql->error_msg), fmt, ap); va_end(ap); fprintf(stderr, "%s\n", sql->error_msg); } int mdb_sql_yyinput(char *buf, int need) { int cplen, have; have = strlen(g_input_ptr); cplen = need > have ? have : need; if (cplen>0) { memcpy(buf, g_input_ptr, cplen); g_input_ptr += cplen; } return cplen; } MdbSQL *mdb_sql_init() { MdbSQL *sql; sql = (MdbSQL *) g_malloc0(sizeof(MdbSQL)); sql->columns = g_ptr_array_new(); sql->tables = g_ptr_array_new(); sql->sarg_tree = NULL; sql->sarg_stack = NULL; sql->max_rows = -1; return sql; } #ifndef _ #define _(x) x #endif /** * mdb_sql_run_query: * @sql: MDB SQL object to execute the query on. * @querystr: SQL query string to execute. * * Parses @querystr and executes it within the given @sql object. * * Returns: the updated MDB SQL object, or NULL on error **/ MdbSQL* mdb_sql_run_query (MdbSQL* sql, const gchar* querystr) { g_return_val_if_fail (sql, NULL); g_return_val_if_fail (querystr, NULL); g_input_ptr = (gchar*) querystr; /* calls to yyparse should be serialized for thread safety */ /* begin unsafe */ _mdb_sql (sql); sql->error_msg[0]='\0'; if (yyparse()) { /* end unsafe */ mdb_sql_error (sql, _("Could not parse '%s' command"), querystr); mdb_sql_reset (sql); return NULL; } if (sql->cur_table == NULL) { /* Invalid column name? (should get caught by mdb_sql_select, * but it appeared to happen anyway with 0.5) */ mdb_sql_error (sql, _("Got no result for '%s' command"), querystr); return NULL; } mdb_sql_bind_all (sql); return sql; } void mdb_sql_set_maxrow(MdbSQL *sql, int maxrow) { sql->max_rows = maxrow; } static void mdb_sql_free_columns(GPtrArray *columns) { unsigned int i; if (!columns) return; for (i=0; ilen; i++) { MdbSQLColumn *c = (MdbSQLColumn *)g_ptr_array_index(columns, i); g_free(c->name); g_free(c); } g_ptr_array_free(columns, TRUE); } static void mdb_sql_free_tables(GPtrArray *tables) { unsigned int i; if (!tables) return; for (i=0; ilen; i++) { MdbSQLTable *t = (MdbSQLTable *)g_ptr_array_index(tables, i); g_free(t->name); g_free(t); } g_ptr_array_free(tables, TRUE); } void mdb_sql_close(MdbSQL *sql) { if (sql->mdb) { mdb_close(sql->mdb); sql->mdb = NULL; } else { mdb_sql_error(sql, "Not connected."); } } MdbHandle *mdb_sql_open(MdbSQL *sql, char *db_name) { char *db_namep = db_name; #ifdef HAVE_WORDEXP wordexp_t words; if (wordexp(db_name, &words, 0)==0) { if (words.we_wordc>0) db_namep = words.we_wordv[0]; } #endif sql->mdb = mdb_open(db_namep, MDB_NOFLAGS); if ((!sql->mdb) && (!strstr(db_namep, ".mdb"))) { char *tmpstr = (char *) g_strconcat(db_namep, ".mdb", NULL); sql->mdb = mdb_open(tmpstr, MDB_NOFLAGS); g_free(tmpstr); } if (!sql->mdb) { mdb_sql_error(sql, "Unable to locate database %s", db_name); } #ifdef HAVE_WORDEXP wordfree(&words); #endif return sql->mdb; } MdbSargNode * mdb_sql_alloc_node() { return (MdbSargNode *) g_malloc0(sizeof(MdbSargNode)); } void mdb_sql_free_tree(MdbSargNode *tree) { if (tree->left) mdb_sql_free_tree(tree->left); if (tree->right) mdb_sql_free_tree(tree->right); if (tree->parent) g_free(tree->parent); g_free(tree); } void mdb_sql_push_node(MdbSQL *sql, MdbSargNode *node) { sql->sarg_stack = g_list_append(sql->sarg_stack, node); /* * Tree builds from bottom to top, so we should be left with * the correct tree root when done */ sql->sarg_tree = node; } MdbSargNode * mdb_sql_pop_node(MdbSQL *sql) { GList *glist; MdbSargNode *node; glist = g_list_last(sql->sarg_stack); if (!glist) return NULL; node = glist->data; #if 0 if (node->op==MDB_EQUAL) printf("popping %d\n", node->value.i); else printf("popping %s\n", node->op == MDB_OR ? "OR" : "AND"); #endif sql->sarg_stack = g_list_remove(sql->sarg_stack, node); return node; } void mdb_sql_add_not(MdbSQL *sql) { MdbSargNode *node, *left; left = mdb_sql_pop_node(sql); if (!left) { mdb_sql_error(sql, "parse error near 'NOT'"); mdb_sql_reset(sql); return; } node = mdb_sql_alloc_node(); node->op = MDB_NOT; node->left = left; mdb_sql_push_node(sql, node); } void mdb_sql_add_or(MdbSQL *sql) { MdbSargNode *node, *left, *right; left = mdb_sql_pop_node(sql); right = mdb_sql_pop_node(sql); if (!left || !right) { mdb_sql_error(sql, "parse error near 'OR'"); mdb_sql_reset(sql); return; } node = mdb_sql_alloc_node(); node->op = MDB_OR; node->left = left; node->right = right; mdb_sql_push_node(sql, node); } void mdb_sql_add_and(MdbSQL *sql) { MdbSargNode *node, *left, *right; left = mdb_sql_pop_node(sql); right = mdb_sql_pop_node(sql); if (!left || !right) { mdb_sql_error(sql, "parse error near 'AND'"); mdb_sql_reset(sql); return; } node = mdb_sql_alloc_node(); node->op = MDB_AND; node->left = left; node->right = right; mdb_sql_push_node(sql, node); } void mdb_sql_dump_node(MdbSargNode *node, int level) { int i; int mylevel = level+1; if (!level) printf("root "); for (i=0;i"); switch (node->op) { case MDB_OR: printf(" or\n"); break; case MDB_AND: printf(" and\n"); break; case MDB_NOT: printf(" not\n"); break; case MDB_LT: printf(" < %d\n", node->value.i); break; case MDB_GT: printf(" < %d\n", node->value.i); break; case MDB_LIKE: printf(" like %s\n", node->value.s); break; case MDB_EQUAL: printf(" = %d\n", node->value.i); break; } if (node->left) { printf("left "); mdb_sql_dump_node(node->left, mylevel); } if (node->right) { printf("right "); mdb_sql_dump_node(node->right, mylevel); } } /* evaluate a expression involving 2 constants and add answer to the stack */ int mdb_sql_eval_expr(MdbSQL *sql, char *const1, int op, char *const2) { long val1, val2, value, compar; unsigned char illop = 0; MdbSargNode *node; if (const1[0]=='\'' && const2[0]=='\'') { value = strcmp(const1, const2); switch (op) { case MDB_EQUAL: compar = (value ? 0 : 1); break; case MDB_GT: compar = (value > 0); break; case MDB_GTEQ: compar = (value >= 0); break; case MDB_LT: compar = (value < 0); break; case MDB_LTEQ: compar = (value <= 0); break; case MDB_LIKE: compar = mdb_like_cmp(const1,const2); break; default: illop = 1; } } else if (const1[0]!='\'' && const2[0]!='\'') { val1 = atol(const1); val2 = atol(const2); switch (op) { case MDB_EQUAL: compar = (val1 == val2); break; case MDB_GT: compar = (val1 > val2); break; case MDB_GTEQ: compar = (val1 >= val2); break; case MDB_LT: compar = (val1 < val2); break; case MDB_LTEQ: compar = (val1 <= val2); break; default: illop = 1; } } else { mdb_sql_error(sql, "Comparison of strings and numbers not allowed."); /* the column and table names are no good now */ mdb_sql_reset(sql); return 1; } if (illop) { mdb_sql_error(sql, "Illegal operator used for comparision of literals."); /* the column and table names are no good now */ mdb_sql_reset(sql); return 1; } node = mdb_sql_alloc_node(); node->op = MDB_EQUAL; node->col = NULL; node->value.i = (compar ? 1 : 0); mdb_sql_push_node(sql, node); return 0; } int mdb_sql_add_sarg(MdbSQL *sql, char *col_name, int op, char *constant) { int lastchar; MdbSargNode *node; node = mdb_sql_alloc_node(); node->op = op; /* stash the column name until we finish with the grammar */ node->parent = (void *) g_strdup(col_name); if (!constant) { /* XXX - do we need to check operator? */ mdb_sql_push_node(sql, node); return 0; } /* FIX ME -- we should probably just be storing the ascii value until the ** column definition can be checked for validity */ if (constant[0]=='\'') { lastchar = strlen(constant) > 256 ? 256 : strlen(constant); strncpy(node->value.s, &constant[1], lastchar - 2);; node->value.s[lastchar - 1]='\0'; } else { node->value.i = atoi(constant); } mdb_sql_push_node(sql, node); return 0; } void mdb_sql_all_columns(MdbSQL *sql) { sql->all_columns=1; } int mdb_sql_add_column(MdbSQL *sql, char *column_name) { MdbSQLColumn *c; c = (MdbSQLColumn *) g_malloc0(sizeof(MdbSQLColumn)); c->name = g_strdup(column_name); g_ptr_array_add(sql->columns, c); sql->num_columns++; return 0; } int mdb_sql_add_table(MdbSQL *sql, char *table_name) { MdbSQLTable *t; t = (MdbSQLTable *) g_malloc0(sizeof(MdbSQLTable)); t->name = g_strdup(table_name); t->alias = NULL; g_ptr_array_add(sql->tables, t); sql->num_tables++; return 0; } void mdb_sql_dump(MdbSQL *sql) { unsigned int i; MdbSQLColumn *c; MdbSQLTable *t; for (i=0;inum_columns;i++) { c = g_ptr_array_index(sql->columns,i); printf("column = %s\n",c->name); } for (i=0;inum_tables;i++) { t = g_ptr_array_index(sql->tables,i); printf("table = %s\n",t->name); } } void mdb_sql_exit(MdbSQL *sql) { mdb_sql_reset(sql); // Free memory if (sql->mdb) mdb_close(sql->mdb); } void mdb_sql_reset(MdbSQL *sql) { if (sql->cur_table) { mdb_index_scan_free(sql->cur_table); if (sql->cur_table->sarg_tree) { mdb_sql_free_tree(sql->cur_table->sarg_tree); sql->cur_table->sarg_tree = NULL; } mdb_free_tabledef(sql->cur_table); sql->cur_table = NULL; } /* Reset columns */ mdb_sql_free_columns(sql->columns); sql->num_columns = 0; sql->columns = g_ptr_array_new(); /* Reset tables */ mdb_sql_free_tables(sql->tables); sql->num_tables = 0; sql->tables = g_ptr_array_new(); /* Reset sargs */ if (sql->sarg_tree) { mdb_sql_free_tree(sql->sarg_tree); sql->sarg_tree = NULL; } g_list_free(sql->sarg_stack); sql->sarg_stack = NULL; sql->all_columns = 0; sql->max_rows = -1; } static void print_break(int sz, int first) { int i; if (first) { fprintf(stdout,"+"); } for (i=0;i= vlen ? ' ' : v[i]); } fprintf(stdout,"|"); } void mdb_sql_listtables(MdbSQL *sql) { unsigned int i; MdbCatalogEntry *entry; MdbHandle *mdb = sql->mdb; MdbField fields[1]; unsigned char row_buffer[MDB_PGSIZE]; int row_size; MdbTableDef *ttable; gchar tmpstr[100]; int tmpsiz; if (!mdb) { mdb_sql_error(sql, "You must connect to a database first"); return; } mdb_read_catalog (mdb, MDB_TABLE); ttable = mdb_create_temp_table(mdb, "#listtables"); mdb_sql_add_temp_col(sql, ttable, 0, "Tables", MDB_TEXT, 30, 0); /* add all user tables in catalog to list */ for (i=0; i < mdb->num_catalog; i++) { entry = g_ptr_array_index (mdb->catalog, i); if (mdb_is_user_table(entry)) { //col = g_ptr_array_index(table->columns,0); tmpsiz = mdb_ascii2unicode(mdb, entry->object_name, 0, tmpstr, 100); mdb_fill_temp_field(&fields[0],tmpstr, tmpsiz, 0,0,0,0); row_size = mdb_pack_row(ttable, row_buffer, 1, fields); mdb_add_row_to_pg(ttable,row_buffer, row_size); ttable->num_rows++; } } sql->cur_table = ttable; } int mdb_sql_add_temp_col(MdbSQL *sql, MdbTableDef *ttable, int col_num, char *name, int col_type, int col_size, int is_fixed) { MdbColumn tcol; MdbSQLColumn *sqlcol; mdb_fill_temp_col(&tcol, name, col_size, col_type, is_fixed); mdb_temp_table_add_col(ttable, &tcol); mdb_sql_add_column(sql, name); sqlcol = g_ptr_array_index(sql->columns,col_num); sqlcol->disp_size = mdb_col_disp_size(&tcol); return 0; } void mdb_sql_describe_table(MdbSQL *sql) { MdbTableDef *ttable, *table = NULL; MdbSQLTable *sql_tab; MdbHandle *mdb = sql->mdb; MdbColumn *col; unsigned int i; MdbField fields[3]; char tmpstr[256]; unsigned char row_buffer[MDB_PGSIZE]; int row_size; gchar col_name[100], col_type[100], col_size[100]; int tmpsiz; if (!mdb) { mdb_sql_error(sql, "You must connect to a database first"); return; } sql_tab = g_ptr_array_index(sql->tables,0); table = mdb_read_table_by_name(mdb, sql_tab->name, MDB_TABLE); if (!table) { mdb_sql_error(sql, "%s is not a table in this database", sql_tab->name); /* the column and table names are no good now */ mdb_sql_reset(sql); return; } mdb_read_columns(table); ttable = mdb_create_temp_table(mdb, "#describe"); mdb_sql_add_temp_col(sql, ttable, 0, "Column Name", MDB_TEXT, 30, 0); mdb_sql_add_temp_col(sql, ttable, 1, "Type", MDB_TEXT, 20, 0); mdb_sql_add_temp_col(sql, ttable, 2, "Size", MDB_TEXT, 10, 0); for (i=0;inum_cols;i++) { col = g_ptr_array_index(table->columns,i); tmpsiz = mdb_ascii2unicode(mdb, col->name, 0, col_name, 100); mdb_fill_temp_field(&fields[0],col_name, tmpsiz, 0,0,0,0); strcpy(tmpstr, mdb_get_colbacktype_string(col)); tmpsiz = mdb_ascii2unicode(mdb, tmpstr, 0, col_type, 100); mdb_fill_temp_field(&fields[1],col_type, tmpsiz, 0,0,0,1); sprintf(tmpstr,"%d",col->col_size); tmpsiz = mdb_ascii2unicode(mdb, tmpstr, 0, col_size, 100); mdb_fill_temp_field(&fields[2],col_size, tmpsiz, 0,0,0,2); row_size = mdb_pack_row(ttable, row_buffer, 3, fields); mdb_add_row_to_pg(ttable,row_buffer, row_size); ttable->num_rows++; } /* the column and table names are no good now */ //mdb_sql_reset(sql); sql->cur_table = ttable; } int mdb_sql_find_sargcol(MdbSargNode *node, gpointer data) { MdbTableDef *table = data; unsigned int i; MdbColumn *col; if (!mdb_is_relational_op(node->op)) return 0; if (!node->parent) return 0; for (i=0;inum_cols;i++) { col=g_ptr_array_index(table->columns,i); if (!strcasecmp(col->name, (char *)node->parent)) { node->col = col; break; } } return 0; } void mdb_sql_select(MdbSQL *sql) { unsigned int i,j; MdbHandle *mdb = sql->mdb; MdbTableDef *table = NULL; MdbSQLTable *sql_tab; MdbColumn *col; MdbSQLColumn *sqlcol; int found = 0; if (!mdb) { mdb_sql_error(sql, "You must connect to a database first"); return; } sql_tab = g_ptr_array_index(sql->tables,0); table = mdb_read_table_by_name(mdb, sql_tab->name, MDB_TABLE); if (!table) { mdb_sql_error(sql, "%s is not a table in this database", sql_tab->name); /* the column and table names are no good now */ mdb_sql_reset(sql); return; } mdb_read_columns(table); mdb_read_indices(table); mdb_rewind_table(table); if (sql->all_columns) { for (i=0;inum_cols;i++) { col = g_ptr_array_index(table->columns,i); mdb_sql_add_column(sql, col->name); } } /* verify all specified columns exist in this table */ for (i=0;inum_columns;i++) { sqlcol = g_ptr_array_index(sql->columns,i); found=0; for (j=0;jnum_cols;j++) { col=g_ptr_array_index(table->columns,j); if (!strcasecmp(sqlcol->name, col->name)) { sqlcol->disp_size = mdb_col_disp_size(col); found=1; break; } } if (!found) { mdb_sql_error(sql, "Column %s not found",sqlcol->name); mdb_sql_reset(sql); return; } } /* * resolve column names to MdbColumn structs */ if (sql->sarg_tree) { mdb_sql_walk_tree(sql->sarg_tree, mdb_sql_find_sargcol, table); mdb_sql_walk_tree(sql->sarg_tree, mdb_find_indexable_sargs, NULL); } /* * move the sarg_tree. * XXX - this won't work when we implement joins */ table->sarg_tree = sql->sarg_tree; sql->sarg_tree = NULL; sql->cur_table = table; mdb_index_scan_init(mdb, table); } void mdb_sql_bind_column(MdbSQL *sql, int colnum, void *varaddr, int *len_ptr) { MdbSQLColumn *sqlcol; /* sql columns are traditionally 1 based, so decrement colnum */ sqlcol = g_ptr_array_index(sql->columns,colnum - 1); mdb_bind_column_by_name(sql->cur_table, sqlcol->name, varaddr, len_ptr); } void mdb_sql_bind_all(MdbSQL *sql) { unsigned int i; for (i=0;inum_columns;i++) { sql->bound_values[i] = g_malloc0(MDB_BIND_SIZE); mdb_sql_bind_column(sql, i+1, sql->bound_values[i], NULL); } } /* * mdb_sql_fetch_row is now just a wrapper around mdb_fetch_row. * It is left here only for backward compatibility. */ int mdb_sql_fetch_row(MdbSQL *sql, MdbTableDef *table) { return mdb_fetch_row(table); } void mdb_sql_dump_results(MdbSQL *sql) { unsigned int j; MdbSQLColumn *sqlcol; /* print header */ for (j=0;jnum_columns;j++) { sqlcol = g_ptr_array_index(sql->columns,j); print_break(sqlcol->disp_size, !j); } fprintf(stdout,"\n"); for (j=0;jnum_columns;j++) { sqlcol = g_ptr_array_index(sql->columns,j); print_value(sqlcol->name,sqlcol->disp_size,!j); } fprintf(stdout,"\n"); for (j=0;jnum_columns;j++) { sqlcol = g_ptr_array_index(sql->columns,j); print_break(sqlcol->disp_size, !j); } fprintf(stdout,"\n"); /* print each row */ while(mdb_fetch_row(sql->cur_table)) { for (j=0;jnum_columns;j++) { sqlcol = g_ptr_array_index(sql->columns,j); print_value(sql->bound_values[j],sqlcol->disp_size,!j); } fprintf(stdout,"\n"); } /* footer */ for (j=0;jnum_columns;j++) { sqlcol = g_ptr_array_index(sql->columns,j); print_break(sqlcol->disp_size, !j); } fprintf(stdout,"\n"); /* clean up */ for (j=0;jnum_columns;j++) { g_free(sql->bound_values[j]); } /* the column and table names are no good now */ mdb_sql_reset(sql); }