wine/dlls/msi/sql.y
2004-12-16 14:33:56 +00:00

833 lines
19 KiB
Plaintext

%{
/*
* 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 <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#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 *, int sign);
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 <str> TK_ID
%token TK_INSERT TK_INSTEAD TK_INT
%token <str> 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 <str> 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 <string> column table string_or_id
%type <column_list> selcollist
%type <query> from unorderedsel oneselect onequery onecreate oneinsert oneupdate
%type <expr> expr val column_val const_val
%type <column_type> column_type data_type data_type_l data_count
%type <column_info> column_def table_def
%type <val_list> constlist
%type <update_col_info> 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, 1 );
}
| TK_MINUS TK_INTEGER
{
$$ = EXPR_ival( &$2, -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 , int sign)
{
struct expr *e = HeapAlloc( GetProcessHeap(), 0, sizeof *e );
if( e )
{
e->type = EXPR_IVAL;
e->u.ival = atoiW( str->data ) * sign;
}
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;
}