msi: Initialize the size parameter.
[wine] / dlls / msi / sql.y
1 %{
2
3 /*
4  * Implementation of the Microsoft Installer (msi.dll)
5  *
6  * Copyright 2002-2004 Mike McCormack for CodeWeavers
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public
10  * License as published by the Free Software Foundation; either
11  * version 2.1 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
21  */
22
23
24 #include "config.h"
25
26 #include <stdarg.h>
27 #include <stdio.h>
28 #include <stdlib.h>
29
30 #include "windef.h"
31 #include "winbase.h"
32 #include "query.h"
33 #include "wine/list.h"
34 #include "wine/debug.h"
35
36 #define YYLEX_PARAM info
37 #define YYPARSE_PARAM info
38
39 static int sql_error(const char *str);
40
41 WINE_DEFAULT_DEBUG_CHANNEL(msi);
42
43 typedef struct tag_SQL_input
44 {
45     MSIDATABASE *db;
46     LPCWSTR command;
47     DWORD n, len;
48     MSIVIEW **view;  /* view structure for the resulting query */
49     struct list *mem;
50 } SQL_input;
51
52 static LPWSTR SQL_getstring( void *info, const struct sql_str *str );
53 static INT SQL_getint( void *info );
54 static int sql_lex( void *SQL_lval, SQL_input *info );
55
56 static LPWSTR parser_add_table( LPWSTR list, LPWSTR table );
57 static void *parser_alloc( void *info, unsigned int sz );
58 static column_info *parser_alloc_column( void *info, LPCWSTR table, LPCWSTR column );
59
60 static BOOL SQL_MarkPrimaryKeys( column_info *cols, column_info *keys);
61
62 static struct expr * EXPR_complex( void *info, struct expr *l, UINT op, struct expr *r );
63 static struct expr * EXPR_unary( void *info, struct expr *l, UINT op );
64 static struct expr * EXPR_column( void *info, const column_info *column );
65 static struct expr * EXPR_ival( void *info, int val );
66 static struct expr * EXPR_sval( void *info, const struct sql_str *str );
67 static struct expr * EXPR_wildcard( void *info );
68
69 %}
70
71 %pure-parser
72
73 %union
74 {
75     struct sql_str str;
76     LPWSTR string;
77     column_info *column_list;
78     MSIVIEW *query;
79     struct expr *expr;
80     USHORT column_type;
81     int integer;
82 }
83
84 %token TK_ALTER TK_AND TK_BY TK_CHAR TK_COMMA TK_CREATE TK_DELETE
85 %token TK_DISTINCT TK_DOT TK_EQ TK_FREE TK_FROM TK_GE TK_GT TK_HOLD TK_ADD
86 %token <str> TK_ID
87 %token TK_ILLEGAL TK_INSERT TK_INT
88 %token <str> TK_INTEGER
89 %token TK_INTO TK_IS TK_KEY TK_LE TK_LONG TK_LONGCHAR TK_LP TK_LT
90 %token TK_LOCALIZABLE TK_MINUS TK_NE TK_NOT TK_NULL
91 %token TK_OBJECT TK_OR TK_ORDER TK_PRIMARY TK_RP
92 %token TK_SELECT TK_SET TK_SHORT TK_SPACE TK_STAR
93 %token <str> TK_STRING
94 %token TK_TABLE TK_TEMPORARY TK_UPDATE TK_VALUES TK_WHERE TK_WILDCARD
95
96 /*
97  * These are extra tokens used by the lexer but never seen by the
98  * parser.  We put them in a rule so that the parser generator will
99  * add them to the parse.h output file.
100  *
101  */
102 %nonassoc END_OF_FILE ILLEGAL SPACE UNCLOSED_STRING COMMENT FUNCTION
103           COLUMN AGG_FUNCTION.
104
105 %type <string> table tablelist id
106 %type <column_list> selcollist column column_and_type column_def table_def
107 %type <column_list> column_assignment update_assign_list constlist
108 %type <query> query from fromtable selectfrom unorderedsel
109 %type <query> oneupdate onedelete oneselect onequery onecreate oneinsert onealter
110 %type <expr> expr val column_val const_val
111 %type <column_type> column_type data_type data_type_l data_count
112 %type <integer> number alterop
113
114 /* Reference: http://mates.ms.mff.cuni.cz/oracle/doc/ora815nt/server.815/a67779/operator.htm */
115 %left TK_OR
116 %left TK_AND
117 %left TK_NOT
118 %left TK_EQ TK_NE TK_LT TK_GT TK_LE TK_GE TK_LIKE
119 %right TK_NEGATION
120
121 %%
122
123 query:
124     onequery
125     {
126         SQL_input* sql = (SQL_input*) info;
127         *sql->view = $1;
128     }
129     ;
130
131 onequery:
132     oneselect
133   | onecreate
134   | oneinsert
135   | oneupdate
136   | onedelete
137   | onealter
138     ;
139
140 oneinsert:
141     TK_INSERT TK_INTO table TK_LP selcollist TK_RP TK_VALUES TK_LP constlist TK_RP
142         {
143             SQL_input *sql = (SQL_input*) info;
144             MSIVIEW *insert = NULL;
145             UINT r;
146
147             r = INSERT_CreateView( sql->db, &insert, $3, $5, $9, FALSE );
148             if( !insert )
149                 YYABORT;
150             $$ = insert;
151         }
152   | TK_INSERT TK_INTO table TK_LP selcollist TK_RP TK_VALUES TK_LP constlist TK_RP TK_TEMPORARY
153         {
154             SQL_input *sql = (SQL_input*) info;
155             MSIVIEW *insert = NULL;
156
157             INSERT_CreateView( sql->db, &insert, $3, $5, $9, TRUE );
158             if( !insert )
159                 YYABORT;
160             $$ = insert;
161         }
162     ;
163
164 onecreate:
165     TK_CREATE TK_TABLE table TK_LP table_def TK_RP
166         {
167             SQL_input* sql = (SQL_input*) info;
168             MSIVIEW *create = NULL;
169
170             if( !$5 )
171                 YYABORT;
172             CREATE_CreateView( sql->db, &create, $3, $5, FALSE );
173             if( !create )
174                 YYABORT;
175             $$ = create;
176         }
177   | TK_CREATE TK_TABLE table TK_LP table_def TK_RP TK_HOLD
178         {
179             SQL_input* sql = (SQL_input*) info;
180             MSIVIEW *create = NULL;
181
182             if( !$5 )
183                 YYABORT;
184             CREATE_CreateView( sql->db, &create, $3, $5, TRUE );
185             if( !create )
186                 YYABORT;
187             $$ = create;
188         }
189     ;
190
191 oneupdate:
192     TK_UPDATE table TK_SET update_assign_list TK_WHERE expr
193         {
194             SQL_input* sql = (SQL_input*) info;
195             MSIVIEW *update = NULL;
196
197             UPDATE_CreateView( sql->db, &update, $2, $4, $6 );
198             if( !update )
199                 YYABORT;
200             $$ = update;
201         }
202   | TK_UPDATE table TK_SET update_assign_list
203         {
204             SQL_input* sql = (SQL_input*) info;
205             MSIVIEW *update = NULL;
206
207             UPDATE_CreateView( sql->db, &update, $2, $4, NULL );
208             if( !update )
209                 YYABORT;
210             $$ = update;
211         }
212     ;
213
214 onedelete:
215     TK_DELETE from
216         {
217             SQL_input* sql = (SQL_input*) info;
218             MSIVIEW *delete = NULL;
219
220             DELETE_CreateView( sql->db, &delete, $2 );
221             if( !delete )
222                 YYABORT;
223             $$ = delete;
224         }
225     ;
226
227 onealter:
228     TK_ALTER TK_TABLE table alterop
229         {
230             SQL_input* sql = (SQL_input*) info;
231             MSIVIEW *alter = NULL;
232
233             ALTER_CreateView( sql->db, &alter, $3, NULL, $4 );
234             if( !alter )
235                 YYABORT;
236             $$ = alter;
237         }
238   | TK_ALTER TK_TABLE table TK_ADD column_and_type
239         {
240             SQL_input *sql = (SQL_input *)info;
241             MSIVIEW *alter = NULL;
242
243             ALTER_CreateView( sql->db, &alter, $3, $5, 0 );
244             if (!alter)
245                 YYABORT;
246             $$ = alter;
247         }
248   | TK_ALTER TK_TABLE table TK_ADD column_and_type TK_HOLD
249         {
250             SQL_input *sql = (SQL_input *)info;
251             MSIVIEW *alter = NULL;
252
253             ALTER_CreateView( sql->db, &alter, $3, $5, 1 );
254             if (!alter)
255                 YYABORT;
256             $$ = alter;
257         }
258     ;
259
260 alterop:
261     TK_HOLD
262         {
263             $$ = 1;
264         }
265   | TK_FREE
266         {
267             $$ = -1;
268         }
269   ;
270
271 table_def:
272     column_def TK_PRIMARY TK_KEY selcollist
273         {
274             if( SQL_MarkPrimaryKeys( $1, $4 ) )
275                 $$ = $1;
276             else
277                 $$ = NULL;
278         }
279     ;
280
281 column_def:
282     column_def TK_COMMA column_and_type
283         {
284             column_info *ci;
285
286             for( ci = $1; ci->next; ci = ci->next )
287                 ;
288
289             ci->next = $3;
290             $$ = $1;
291         }
292   | column_and_type
293         {
294             $$ = $1;
295         }
296     ;
297
298 column_and_type:
299     column column_type
300         {
301             $$ = $1;
302             $$->type = ($2 | MSITYPE_VALID);
303             $$->temporary = $2 & MSITYPE_TEMPORARY ? TRUE : FALSE;
304         }
305     ;
306
307 column_type:
308     data_type_l
309         {
310             $$ = $1;
311         }
312   | data_type_l TK_LOCALIZABLE
313         {
314             $$ = $1 | MSITYPE_LOCALIZABLE;
315         }
316   | data_type_l TK_TEMPORARY
317         {
318             $$ = $1 | MSITYPE_TEMPORARY;
319         }
320     ;
321
322 data_type_l:
323     data_type
324         {
325             $$ |= MSITYPE_NULLABLE;
326         }
327   | data_type TK_NOT TK_NULL
328         {
329             $$ = $1;
330         }
331     ;
332
333 data_type:
334     TK_CHAR
335         {
336             $$ = MSITYPE_STRING | 1;
337         }
338   | TK_CHAR TK_LP data_count TK_RP
339         {
340             $$ = MSITYPE_STRING | 0x400 | $3;
341         }
342   | TK_LONGCHAR
343         {
344             $$ = 2;
345         }
346   | TK_SHORT
347         {
348             $$ = 2;
349         }
350   | TK_INT
351         {
352             $$ = 2;
353         }
354   | TK_LONG
355         {
356             $$ = 4;
357         }
358   | TK_OBJECT
359         {
360             $$ = MSITYPE_STRING | MSITYPE_VALID;
361         }
362     ;
363
364 data_count:
365     number
366         {
367             if( ( $1 > 255 ) || ( $1 < 0 ) )
368                 YYABORT;
369             $$ = $1;
370         }
371     ;
372
373 oneselect:
374     unorderedsel TK_ORDER TK_BY selcollist
375         {
376             UINT r;
377
378             if( $4 )
379             {
380                 r = $1->ops->sort( $1, $4 );
381                 if ( r != ERROR_SUCCESS)
382                     YYABORT;
383             }
384
385             $$ = $1;
386         }
387   | unorderedsel
388     ;
389
390 unorderedsel:
391     TK_SELECT selectfrom
392         {
393             $$ = $2;
394         }
395   | TK_SELECT TK_DISTINCT selectfrom
396         {
397             SQL_input* sql = (SQL_input*) info;
398             UINT r;
399
400             $$ = NULL;
401             r = DISTINCT_CreateView( sql->db, &$$, $3 );
402             if (r != ERROR_SUCCESS)
403             {
404                 $3->ops->delete($3);
405                 YYABORT;
406             }
407         }
408     ;
409
410 selectfrom:
411     selcollist from
412         {
413             SQL_input* sql = (SQL_input*) info;
414             UINT r;
415
416             $$ = NULL;
417             if( $1 )
418             {
419                 r = SELECT_CreateView( sql->db, &$$, $2, $1 );
420                 if (r != ERROR_SUCCESS)
421                 {
422                     $2->ops->delete($2);
423                     YYABORT;
424                 }
425             }
426             else
427                 $$ = $2;
428         }
429     ;
430
431 selcollist:
432     column
433   | column TK_COMMA selcollist
434         {
435             $1->next = $3;
436         }
437   | TK_STAR
438         {
439             $$ = NULL;
440         }
441     ;
442
443 from:
444     fromtable
445   | fromtable TK_WHERE expr
446         {
447             SQL_input* sql = (SQL_input*) info;
448             UINT r;
449
450             $$ = NULL;
451             r = WHERE_CreateView( sql->db, &$$, $1, $3 );
452             if( r != ERROR_SUCCESS )
453             {
454                 $1->ops->delete( $1 );
455                 YYABORT;
456             }
457         }
458     ;
459
460 fromtable:
461     TK_FROM table
462         {
463             SQL_input* sql = (SQL_input*) info;
464             UINT r;
465
466             $$ = NULL;
467             r = TABLE_CreateView( sql->db, $2, &$$ );
468             if( r != ERROR_SUCCESS || !$$ )
469                 YYABORT;
470         }
471   | TK_FROM tablelist
472         {
473             SQL_input* sql = (SQL_input*) info;
474             UINT r;
475
476             r = JOIN_CreateView( sql->db, &$$, $2 );
477             msi_free( $2 );
478             if( r != ERROR_SUCCESS )
479                 YYABORT;
480         }
481     ;
482
483 tablelist:
484     table
485         {
486             $$ = strdupW($1);
487         }
488   |
489     table TK_COMMA tablelist
490         {
491             $$ = parser_add_table($3, $1);
492             if (!$$)
493                 YYABORT;
494         }
495     ;
496
497 expr:
498     TK_LP expr TK_RP
499         {
500             $$ = $2;
501             if( !$$ )
502                 YYABORT;
503         }
504   | expr TK_AND expr
505         {
506             $$ = EXPR_complex( info, $1, OP_AND, $3 );
507             if( !$$ )
508                 YYABORT;
509         }
510   | expr TK_OR expr
511         {
512             $$ = EXPR_complex( info, $1, OP_OR, $3 );
513             if( !$$ )
514                 YYABORT;
515         }
516   | column_val TK_EQ val
517         {
518             $$ = EXPR_complex( info, $1, OP_EQ, $3 );
519             if( !$$ )
520                 YYABORT;
521         }
522   | column_val TK_GT val
523         {
524             $$ = EXPR_complex( info, $1, OP_GT, $3 );
525             if( !$$ )
526                 YYABORT;
527         }
528   | column_val TK_LT val
529         {
530             $$ = EXPR_complex( info, $1, OP_LT, $3 );
531             if( !$$ )
532                 YYABORT;
533         }
534   | column_val TK_LE val
535         {
536             $$ = EXPR_complex( info, $1, OP_LE, $3 );
537             if( !$$ )
538                 YYABORT;
539         }
540   | column_val TK_GE val
541         {
542             $$ = EXPR_complex( info, $1, OP_GE, $3 );
543             if( !$$ )
544                 YYABORT;
545         }
546   | column_val TK_NE val
547         {
548             $$ = EXPR_complex( info, $1, OP_NE, $3 );
549             if( !$$ )
550                 YYABORT;
551         }
552   | column_val TK_IS TK_NULL
553         {
554             $$ = EXPR_unary( info, $1, OP_ISNULL );
555             if( !$$ )
556                 YYABORT;
557         }
558   | column_val TK_IS TK_NOT TK_NULL
559         {
560             $$ = EXPR_unary( info, $1, OP_NOTNULL );
561             if( !$$ )
562                 YYABORT;
563         }
564     ;
565
566 val:
567     column_val
568   | const_val
569     ;
570
571 constlist:
572     const_val
573         {
574             $$ = parser_alloc_column( info, NULL, NULL );
575             if( !$$ )
576                 YYABORT;
577             $$->val = $1;
578         }
579   | const_val TK_COMMA constlist
580         {
581             $$ = parser_alloc_column( info, NULL, NULL );
582             if( !$$ )
583                 YYABORT;
584             $$->val = $1;
585             $$->next = $3;
586         }
587     ;
588
589 update_assign_list:
590     column_assignment
591   | column_assignment TK_COMMA update_assign_list
592         {
593             $$ = $1;
594             $$->next = $3;
595         }
596     ;
597
598 column_assignment:
599     column TK_EQ const_val
600         {
601             $$ = $1;
602             $$->val = $3;
603         }
604     ;
605
606 const_val:
607     number
608         {
609             $$ = EXPR_ival( info, $1 );
610             if( !$$ )
611                 YYABORT;
612         }
613   | TK_MINUS number %prec TK_NEGATION
614         {
615             $$ = EXPR_ival( info, -$2 );
616             if( !$$ )
617                 YYABORT;
618         }
619   | TK_STRING
620         {
621             $$ = EXPR_sval( info, &$1 );
622             if( !$$ )
623                 YYABORT;
624         }
625   | TK_WILDCARD
626         {
627             $$ = EXPR_wildcard( info );
628             if( !$$ )
629                 YYABORT;
630         }
631     ;
632
633 column_val:
634     column
635         {
636             $$ = EXPR_column( info, $1 );
637             if( !$$ )
638                 YYABORT;
639         }
640     ;
641
642 column:
643     table TK_DOT id
644         {
645             $$ = parser_alloc_column( info, $1, $3 );
646             if( !$$ )
647                 YYABORT;
648         }
649   | id
650         {
651             $$ = parser_alloc_column( info, NULL, $1 );
652             if( !$$ )
653                 YYABORT;
654         }
655     ;
656
657 table:
658     id
659         {
660             $$ = $1;
661         }
662     ;
663
664 id:
665     TK_ID
666         {
667             $$ = SQL_getstring( info, &$1 );
668             if( !$$ )
669                 YYABORT;
670         }
671     ;
672
673 number:
674     TK_INTEGER
675         {
676             $$ = SQL_getint( info );
677         }
678     ;
679
680 %%
681
682 static LPWSTR parser_add_table(LPWSTR list, LPWSTR table)
683 {
684     DWORD size = lstrlenW(list) + lstrlenW(table) + 2;
685     static const WCHAR space[] = {' ',0};
686
687     list = msi_realloc(list, size * sizeof(WCHAR));
688     if (!list) return NULL;
689
690     lstrcatW(list, space);
691     lstrcatW(list, table);
692     return list;
693 }
694
695 static void *parser_alloc( void *info, unsigned int sz )
696 {
697     SQL_input* sql = (SQL_input*) info;
698     struct list *mem;
699
700     mem = msi_alloc( sizeof (struct list) + sz );
701     list_add_tail( sql->mem, mem );
702     return &mem[1];
703 }
704
705 static column_info *parser_alloc_column( void *info, LPCWSTR table, LPCWSTR column )
706 {
707     column_info *col;
708
709     col = parser_alloc( info, sizeof (*col) );
710     if( col )
711     {
712         col->table = table;
713         col->column = column;
714         col->val = NULL;
715         col->type = 0;
716         col->next = NULL;
717     }
718
719     return col;
720 }
721
722 static int sql_lex( void *SQL_lval, SQL_input *sql )
723 {
724     int token;
725     struct sql_str * str = SQL_lval;
726
727     do
728     {
729         sql->n += sql->len;
730         if( ! sql->command[sql->n] )
731             return 0;  /* end of input */
732
733         /* TRACE("string : %s\n", debugstr_w(&sql->command[sql->n])); */
734         sql->len = sqliteGetToken( &sql->command[sql->n], &token );
735         if( sql->len==0 )
736             break;
737         str->data = &sql->command[sql->n];
738         str->len = sql->len;
739     }
740     while( token == TK_SPACE );
741
742     /* TRACE("token : %d (%s)\n", token, debugstr_wn(&sql->command[sql->n], sql->len)); */
743
744     return token;
745 }
746
747 LPWSTR SQL_getstring( void *info, const struct sql_str *strdata )
748 {
749     LPCWSTR p = strdata->data;
750     UINT len = strdata->len;
751     LPWSTR str;
752
753     /* if there's quotes, remove them */
754     if( ( (p[0]=='`') && (p[len-1]=='`') ) ||
755         ( (p[0]=='\'') && (p[len-1]=='\'') ) )
756     {
757         p++;
758         len -= 2;
759     }
760     str = parser_alloc( info, (len + 1)*sizeof(WCHAR) );
761     if( !str )
762         return str;
763     memcpy( str, p, len*sizeof(WCHAR) );
764     str[len]=0;
765
766     return str;
767 }
768
769 INT SQL_getint( void *info )
770 {
771     SQL_input* sql = (SQL_input*) info;
772     LPCWSTR p = &sql->command[sql->n];
773     INT i, r = 0;
774
775     for( i=0; i<sql->len; i++ )
776     {
777         if( '0' > p[i] || '9' < p[i] )
778         {
779             ERR("should only be numbers here!\n");
780             break;
781         }
782         r = (p[i]-'0') + r*10;
783     }
784
785     return r;
786 }
787
788 static int sql_error( const char *str )
789 {
790     return 0;
791 }
792
793 static struct expr * EXPR_wildcard( void *info )
794 {
795     struct expr *e = parser_alloc( info, sizeof *e );
796     if( e )
797     {
798         e->type = EXPR_WILDCARD;
799     }
800     return e;
801 }
802
803 static struct expr * EXPR_complex( void *info, struct expr *l, UINT op, struct expr *r )
804 {
805     struct expr *e = parser_alloc( info, sizeof *e );
806     if( e )
807     {
808         e->type = EXPR_COMPLEX;
809         e->u.expr.left = l;
810         e->u.expr.op = op;
811         e->u.expr.right = r;
812     }
813     return e;
814 }
815
816 static struct expr * EXPR_unary( void *info, struct expr *l, UINT op )
817 {
818     struct expr *e = parser_alloc( info, sizeof *e );
819     if( e )
820     {
821         e->type = EXPR_UNARY;
822         e->u.expr.left = l;
823         e->u.expr.op = op;
824         e->u.expr.right = NULL;
825     }
826     return e;
827 }
828
829 static struct expr * EXPR_column( void *info, const column_info *column )
830 {
831     struct expr *e = parser_alloc( info, sizeof *e );
832     if( e )
833     {
834         e->type = EXPR_COLUMN;
835         e->u.sval = column->column;
836     }
837     return e;
838 }
839
840 static struct expr * EXPR_ival( void *info, int val )
841 {
842     struct expr *e = parser_alloc( info, sizeof *e );
843     if( e )
844     {
845         e->type = EXPR_IVAL;
846         e->u.ival = val;
847     }
848     return e;
849 }
850
851 static struct expr * EXPR_sval( void *info, const struct sql_str *str )
852 {
853     struct expr *e = parser_alloc( info, sizeof *e );
854     if( e )
855     {
856         e->type = EXPR_SVAL;
857         e->u.sval = SQL_getstring( info, str );
858     }
859     return e;
860 }
861
862 static BOOL SQL_MarkPrimaryKeys( column_info *cols,
863                                  column_info *keys )
864 {
865     column_info *k;
866     BOOL found = TRUE;
867
868     for( k = keys; k && found; k = k->next )
869     {
870         column_info *c;
871
872         found = FALSE;
873         for( c = cols; c && !found; c = c->next )
874         {
875              if( lstrcmpW( k->column, c->column ) )
876                  continue;
877              c->type |= MSITYPE_KEY;
878              found = TRUE;
879         }
880     }
881
882     return found;
883 }
884
885 UINT MSI_ParseSQL( MSIDATABASE *db, LPCWSTR command, MSIVIEW **phview,
886                    struct list *mem )
887 {
888     SQL_input sql;
889     int r;
890
891     *phview = NULL;
892
893     sql.db = db;
894     sql.command = command;
895     sql.n = 0;
896     sql.len = 0;
897     sql.view = phview;
898     sql.mem = mem;
899
900     r = sql_parse(&sql);
901
902     TRACE("Parse returned %d\n", r);
903     if( r )
904     {
905         *sql.view = NULL;
906         return ERROR_BAD_QUERY_SYNTAX;
907     }
908
909     return ERROR_SUCCESS;
910 }