%{ /* * Implementation of the Microsoft Installer (msi.dll) * * Copyright 2002-2004 Mike McCormack for CodeWeavers * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "config.h" #include #include #include #include "windef.h" #include "winbase.h" #include "query.h" #include "wine/debug.h" #include "wine/unicode.h" #define YYLEX_PARAM info #define YYPARSE_PARAM info extern int SQL_error(const char *str); WINE_DEFAULT_DEBUG_CHANNEL(msi); typedef struct tag_SQL_input { MSIDATABASE *db; LPCWSTR command; DWORD n, len; MSIVIEW **view; /* view structure for the resulting query */ } SQL_input; static LPWSTR SQL_getstring( struct sql_str *str ); static INT SQL_getint( SQL_input *sql ); static int SQL_lex( void *SQL_lval, SQL_input *info); static MSIVIEW *do_one_select( MSIDATABASE *db, MSIVIEW *in, string_list *columns ); static MSIVIEW *do_order_by( MSIDATABASE *db, MSIVIEW *in, string_list *columns ); static BOOL SQL_MarkPrimaryKeys( create_col_info *cols, string_list *keys); static struct expr * EXPR_complex( struct expr *l, UINT op, struct expr *r ); static struct expr * EXPR_column( LPWSTR ); static struct expr * EXPR_ival( struct sql_str *); static struct expr * EXPR_sval( struct sql_str *); static struct expr * EXPR_wildcard(); %} %pure-parser %union { struct sql_str str; LPWSTR string; string_list *column_list; value_list *val_list; MSIVIEW *query; struct expr *expr; USHORT column_type; create_col_info *column_info; column_assignment update_col_info; } %token TK_ABORT TK_AFTER TK_AGG_FUNCTION TK_ALL TK_AND TK_AS TK_ASC %token TK_BEFORE TK_BEGIN TK_BETWEEN TK_BITAND TK_BITNOT TK_BITOR TK_BY %token TK_CASCADE TK_CASE TK_CHAR TK_CHECK TK_CLUSTER TK_COLLATE TK_COLUMN %token TK_COMMA TK_COMMENT TK_COMMIT TK_CONCAT TK_CONFLICT %token TK_CONSTRAINT TK_COPY TK_CREATE %token TK_DEFAULT TK_DEFERRABLE TK_DEFERRED TK_DELETE TK_DELIMITERS TK_DESC %token TK_DISTINCT TK_DOT TK_DROP TK_EACH %token TK_ELSE TK_END TK_END_OF_FILE TK_EQ TK_EXCEPT TK_EXPLAIN %token TK_FAIL TK_FLOAT TK_FOR TK_FOREIGN TK_FROM TK_FUNCTION %token TK_GE TK_GLOB TK_GROUP TK_GT %token TK_HAVING TK_HOLD %token TK_IGNORE TK_ILLEGAL TK_IMMEDIATE TK_IN TK_INDEX TK_INITIALLY %token TK_ID %token TK_INSERT TK_INSTEAD TK_INT %token TK_INTEGER %token TK_INTERSECT TK_INTO TK_IS %token TK_ISNULL %token TK_JOIN TK_JOIN_KW %token TK_KEY %token TK_LE TK_LIKE TK_LIMIT TK_LONG TK_LONGCHAR TK_LP TK_LSHIFT TK_LT %token TK_LOCALIZABLE %token TK_MATCH TK_MINUS %token TK_NE TK_NOT TK_NOTNULL TK_NULL %token TK_OBJECT TK_OF TK_OFFSET TK_ON TK_OR TK_ORACLE_OUTER_JOIN TK_ORDER %token TK_PLUS TK_PRAGMA TK_PRIMARY %token TK_RAISE TK_REFERENCES TK_REM TK_REPLACE TK_RESTRICT TK_ROLLBACK %token TK_ROW TK_RP TK_RSHIFT %token TK_SELECT TK_SEMI TK_SET TK_SHORT TK_SLASH TK_SPACE TK_STAR TK_STATEMENT %token TK_STRING %token TK_TABLE TK_TEMP TK_THEN TK_TRANSACTION TK_TRIGGER %token TK_UMINUS TK_UNCLOSED_STRING TK_UNION TK_UNIQUE %token TK_UPDATE TK_UPLUS TK_USING %token TK_VACUUM TK_VALUES TK_VIEW %token TK_WHEN TK_WHERE TK_WILDCARD /* * These are extra tokens used by the lexer but never seen by the * parser. We put them in a rule so that the parser generator will * add them to the parse.h output file. * */ %nonassoc END_OF_FILE ILLEGAL SPACE UNCLOSED_STRING COMMENT FUNCTION COLUMN AGG_FUNCTION. %type column table string_or_id %type selcollist %type from unorderedsel oneselect onequery onecreate oneinsert oneupdate %type expr val column_val const_val %type column_type data_type data_type_l data_count %type column_def table_def %type constlist %type column_assignment update_assign_list %% onequery: oneselect { SQL_input* sql = (SQL_input*) info; *sql->view = $1; } | onecreate { SQL_input* sql = (SQL_input*) info; *sql->view = $1; } | oneinsert { SQL_input* sql = (SQL_input*) info; *sql->view = $1; } | oneupdate { SQL_input* sql = (SQL_input*) info; *sql->view = $1; } ; oneinsert: TK_INSERT TK_INTO table TK_LP selcollist TK_RP TK_VALUES TK_LP constlist TK_RP { SQL_input *sql = (SQL_input*) info; MSIVIEW *insert = NULL; INSERT_CreateView( sql->db, &insert, $3, $5, $9, FALSE ); $$ = insert; } | TK_INSERT TK_INTO table TK_LP selcollist TK_RP TK_VALUES TK_LP constlist TK_RP TK_TEMP { SQL_input *sql = (SQL_input*) info; MSIVIEW *insert = NULL; INSERT_CreateView( sql->db, &insert, $3, $5, $9, TRUE ); $$ = insert; } ; onecreate: TK_CREATE TK_TABLE table TK_LP table_def TK_RP { SQL_input* sql = (SQL_input*) info; MSIVIEW *create = NULL; if( !$5 ) YYABORT; CREATE_CreateView( sql->db, &create, $3, $5, FALSE ); $$ = create; } | TK_CREATE TK_TABLE table TK_LP table_def TK_RP TK_HOLD { SQL_input* sql = (SQL_input*) info; MSIVIEW *create = NULL; if( !$5 ) YYABORT; CREATE_CreateView( sql->db, &create, $3, $5, TRUE ); $$ = create; } ; oneupdate: TK_UPDATE table TK_SET update_assign_list TK_WHERE expr { SQL_input* sql = (SQL_input*) info; MSIVIEW *update = NULL; UPDATE_CreateView( sql->db, &update, $2, &$4, $6 ); $$ = update; } ; table_def: column_def TK_PRIMARY TK_KEY selcollist { if( SQL_MarkPrimaryKeys( $1, $4 ) ) $$ = $1; else $$ = NULL; } ; column_def: column_def TK_COMMA column column_type { create_col_info *ci; for( ci = $1; ci->next; ci = ci->next ) ; ci->next = HeapAlloc( GetProcessHeap(), 0, sizeof *$$ ); if( !ci->next ) { /* FIXME: free $1 */ YYABORT; } ci->next->colname = $3; ci->next->type = $4; ci->next->next = NULL; $$ = $1; } | column column_type { $$ = HeapAlloc( GetProcessHeap(), 0, sizeof *$$ ); if( ! $$ ) YYABORT; $$->colname = $1; $$->type = $2; $$->next = NULL; } ; column_type: data_type_l { $$ = $1 | MSITYPE_VALID; } | data_type_l TK_LOCALIZABLE { FIXME("LOCALIZABLE ignored\n"); $$ = $1 | MSITYPE_VALID; } ; data_type_l: data_type { $$ |= MSITYPE_NULLABLE; } | data_type TK_NOT TK_NULL { $$ = $1; } ; data_type: TK_CHAR { $$ = MSITYPE_STRING | 1; } | TK_CHAR TK_LP data_count TK_RP { $$ = MSITYPE_STRING | 0x400 | $3; } | TK_LONGCHAR { $$ = 2; } | TK_SHORT { $$ = 2; } | TK_INT { $$ = 2; } | TK_LONG { $$ = 4; } | TK_OBJECT { $$ = 0; } ; data_count: TK_INTEGER { SQL_input* sql = (SQL_input*) info; int val = SQL_getint(sql); if( ( val > 255 ) || ( val < 0 ) ) YYABORT; $$ = val; } ; oneselect: unorderedsel TK_ORDER TK_BY selcollist { SQL_input* sql = (SQL_input*) info; if( !$1 ) YYABORT; if( $4 ) $$ = do_order_by( sql->db, $1, $4 ); else $$ = $1; } | unorderedsel ; unorderedsel: TK_SELECT selcollist from { SQL_input* sql = (SQL_input*) info; if( !$3 ) YYABORT; if( $2 ) { $$ = do_one_select( sql->db, $3, $2 ); if( !$$ ) YYABORT; } else $$ = $3; } | TK_SELECT TK_DISTINCT selcollist from { SQL_input* sql = (SQL_input*) info; MSIVIEW *view = $4; if( !view ) YYABORT; if( $3 ) { view = do_one_select( sql->db, view, $3 ); if( !view ) YYABORT; } DISTINCT_CreateView( sql->db, & $$, view ); } ; selcollist: column { string_list *list; list = HeapAlloc( GetProcessHeap(), 0, sizeof *list ); if( !list ) YYABORT; list->string = $1; list->next = NULL; $$ = list; TRACE("Collist %s\n",debugstr_w($$->string)); } | column TK_COMMA selcollist { string_list *list; list = HeapAlloc( GetProcessHeap(), 0, sizeof *list ); if( !list ) YYABORT; list->string = $1; list->next = $3; $$ = list; TRACE("From table: %s\n",debugstr_w($$->string)); } | TK_STAR { $$ = NULL; } ; from: TK_FROM table { SQL_input* sql = (SQL_input*) info; UINT r; $$ = NULL; TRACE("From table: %s\n",debugstr_w($2)); r = TABLE_CreateView( sql->db, $2, & $$ ); if( r != ERROR_SUCCESS ) YYABORT; } | TK_FROM table TK_WHERE expr { SQL_input* sql = (SQL_input*) info; MSIVIEW *view = NULL; UINT r; $$ = NULL; TRACE("From table: %s\n",debugstr_w($2)); r = TABLE_CreateView( sql->db, $2, &view ); if( r != ERROR_SUCCESS ) YYABORT; r = WHERE_CreateView( sql->db, &view, view, $4 ); if( r != ERROR_SUCCESS ) YYABORT; $$ = view; } ; expr: TK_LP expr TK_RP { $$ = $2; } | column_val TK_EQ column_val { $$ = EXPR_complex( $1, OP_EQ, $3 ); } | expr TK_AND expr { $$ = EXPR_complex( $1, OP_AND, $3 ); } | expr TK_OR expr { $$ = EXPR_complex( $1, OP_OR, $3 ); } | column_val TK_EQ val { $$ = EXPR_complex( $1, OP_EQ, $3 ); } | column_val TK_GT val { $$ = EXPR_complex( $1, OP_GT, $3 ); } | column_val TK_LT val { $$ = EXPR_complex( $1, OP_LT, $3 ); } | column_val TK_LE val { $$ = EXPR_complex( $1, OP_LE, $3 ); } | column_val TK_GE val { $$ = EXPR_complex( $1, OP_GE, $3 ); } | column_val TK_NE val { $$ = EXPR_complex( $1, OP_NE, $3 ); } | column_val TK_IS TK_NULL { $$ = EXPR_complex( $1, OP_ISNULL, NULL ); } | column_val TK_IS TK_NOT TK_NULL { $$ = EXPR_complex( $1, OP_NOTNULL, NULL ); } ; val: column_val | const_val ; constlist: const_val { value_list *vals; vals = HeapAlloc( GetProcessHeap(), 0, sizeof *vals ); if( vals ) { vals->val = $1; vals->next = NULL; } $$ = vals; } | constlist TK_COMMA const_val { value_list *vals; vals = HeapAlloc( GetProcessHeap(), 0, sizeof *vals ); if( vals ) { vals->val = $3; vals->next = NULL; } $1->next = vals; $$ = $1; } ; update_assign_list: column_assignment | column_assignment TK_COMMA update_assign_list { $1.col_list->next = $3.col_list; $1.val_list->next = $3.val_list; $$ = $1; } ; column_assignment: column TK_EQ const_val { $$.col_list = HeapAlloc( GetProcessHeap(), 0, sizeof *$$.col_list ); if( !$$.col_list ) YYABORT; $$.col_list->string = $1; $$.col_list->next = NULL; $$.val_list = HeapAlloc( GetProcessHeap(), 0, sizeof *$$.val_list ); if( !$$.val_list ) YYABORT; $$.val_list->val = $3; $$.val_list->next = 0; } ; const_val: TK_INTEGER { $$ = EXPR_ival( &$1 ); } | TK_STRING { $$ = EXPR_sval( &$1 ); } | TK_WILDCARD { $$ = EXPR_wildcard(); } ; column_val: column { $$ = EXPR_column( $1 ); } ; column: table TK_DOT string_or_id { $$ = $3; /* FIXME */ } | string_or_id { $$ = $1; } ; table: string_or_id { $$ = $1; } ; string_or_id: TK_ID { $$ = SQL_getstring( &$1 ); } | TK_STRING { $$ = SQL_getstring( &$1 ); } ; %% int SQL_lex( void *SQL_lval, SQL_input *sql) { int token; struct sql_str * str = SQL_lval; do { sql->n += sql->len; if( ! sql->command[sql->n] ) return 0; /* end of input */ TRACE("string : %s\n", debugstr_w(&sql->command[sql->n])); sql->len = sqliteGetToken( &sql->command[sql->n], &token ); if( sql->len==0 ) break; str->data = &sql->command[sql->n]; str->len = sql->len; } while( token == TK_SPACE ); TRACE("token : %d (%s)\n", token, debugstr_wn(&sql->command[sql->n], sql->len)); return token; } LPWSTR SQL_getstring( struct sql_str *strdata) { LPCWSTR p = strdata->data; UINT len = strdata->len; LPWSTR str; /* if there's quotes, remove them */ if( ( (p[0]=='`') && (p[len-1]=='`') ) || ( (p[0]=='\'') && (p[len-1]=='\'') ) ) { p++; len -= 2; } str = HeapAlloc( GetProcessHeap(), 0, (len + 1)*sizeof(WCHAR)); if(!str ) return str; memcpy(str, p, len*sizeof(WCHAR) ); str[len]=0; return str; } INT SQL_getint( SQL_input *sql ) { LPCWSTR p = &sql->command[sql->n]; return atoiW( p ); } int SQL_error(const char *str) { return 0; } static MSIVIEW *do_one_select( MSIDATABASE *db, MSIVIEW *in, string_list *columns ) { MSIVIEW *view = NULL; SELECT_CreateView( db, &view, in, columns ); delete_string_list( columns ); if( !view ) ERR("Error creating select query\n"); return view; } static MSIVIEW *do_order_by( MSIDATABASE *db, MSIVIEW *in, string_list *columns ) { MSIVIEW *view = NULL; ORDER_CreateView( db, &view, in ); if( view ) { string_list *x = columns; for( x = columns; x ; x = x->next ) ORDER_AddColumn( view, x->string ); } else ERR("Error creating select query\n"); delete_string_list( columns ); return view; } static struct expr * EXPR_wildcard() { struct expr *e = HeapAlloc( GetProcessHeap(), 0, sizeof *e ); if( e ) { e->type = EXPR_WILDCARD; } return e; } static struct expr * EXPR_complex( struct expr *l, UINT op, struct expr *r ) { struct expr *e = HeapAlloc( GetProcessHeap(), 0, sizeof *e ); if( e ) { e->type = EXPR_COMPLEX; e->u.expr.left = l; e->u.expr.op = op; e->u.expr.right = r; } return e; } static struct expr * EXPR_column( LPWSTR str ) { struct expr *e = HeapAlloc( GetProcessHeap(), 0, sizeof *e ); if( e ) { e->type = EXPR_COLUMN; e->u.sval = str; } return e; } static struct expr * EXPR_ival( struct sql_str *str ) { struct expr *e = HeapAlloc( GetProcessHeap(), 0, sizeof *e ); if( e ) { e->type = EXPR_IVAL; e->u.ival = atoiW( str->data ); } return e; } static struct expr * EXPR_sval( struct sql_str *str ) { struct expr *e = HeapAlloc( GetProcessHeap(), 0, sizeof *e ); if( e ) { e->type = EXPR_SVAL; e->u.sval = SQL_getstring( str ); } return e; } void delete_expr( struct expr *e ) { if( !e ) return; if( e->type == EXPR_COMPLEX ) { delete_expr( e->u.expr.left ); delete_expr( e->u.expr.right ); } else if( e->type == EXPR_UTF8 ) HeapFree( GetProcessHeap(), 0, e->u.utf8 ); else if( e->type == EXPR_SVAL ) HeapFree( GetProcessHeap(), 0, e->u.sval ); HeapFree( GetProcessHeap(), 0, e ); } void delete_string_list( string_list *sl ) { while( sl ) { string_list *t = sl->next; HeapFree( GetProcessHeap(), 0, sl->string ); HeapFree( GetProcessHeap(), 0, sl ); sl = t; } } void delete_value_list( value_list *vl ) { while( vl ) { value_list *t = vl->next; delete_expr( vl->val ); HeapFree( GetProcessHeap(), 0, vl ); vl = t; } } static BOOL SQL_MarkPrimaryKeys( create_col_info *cols, string_list *keys ) { string_list *k; BOOL found = TRUE; for( k = keys; k && found; k = k->next ) { create_col_info *c; found = FALSE; for( c = cols; c && !found; c = c->next ) { if( lstrcmpW( k->string, c->colname ) ) continue; c->type |= MSITYPE_KEY; found = TRUE; } } return found; } UINT MSI_ParseSQL( MSIDATABASE *db, LPCWSTR command, MSIVIEW **phview ) { SQL_input sql; int r; *phview = NULL; sql.db = db; sql.command = command; sql.n = 0; sql.len = 0; sql.view = phview; r = SQL_parse(&sql); TRACE("Parse returned %d\n", r); if( r ) { if( *sql.view ) (*sql.view)->ops->delete( *sql.view ); *sql.view = NULL; return ERROR_BAD_QUERY_SYNTAX; } return ERROR_SUCCESS; }