1 /*
2  * Copyright 2015-2018 HuntLabs.cn
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 module hunt.sql.dialect.postgresql.parser.PGSQLStatementParser;
17 
18 import hunt.sql.ast.SQLExpr;
19 import hunt.sql.ast.SQLName;
20 import hunt.sql.ast.SQLStatement;
21 import hunt.sql.ast.expr;
22 import hunt.sql.ast.statement;
23 import hunt.sql.dialect.postgresql.ast.stmt.PGDeleteStatement;
24 import hunt.sql.dialect.postgresql.ast.stmt.PGInsertStatement;
25 import hunt.sql.dialect.postgresql.ast.stmt.PGSelectStatement;
26 import hunt.sql.dialect.postgresql.ast.stmt.PGShowStatement;
27 import hunt.sql.dialect.postgresql.ast.stmt.PGUpdateStatement;
28 import hunt.sql.parser;
29 import hunt.sql.dialect.postgresql.ast.stmt;
30 import hunt.sql.parser.Lexer;
31 import hunt.sql.parser.ParserException;
32 import hunt.sql.parser.SQLStatementParser;
33 import hunt.sql.parser.Token;
34 import hunt.sql.util.FnvHash;
35 import hunt.sql.util.DBType;
36 import hunt.sql.dialect.postgresql.parser.PGExprParser;
37 import hunt.sql.dialect.postgresql.parser.PGSelectParser;
38 import hunt.sql.ast.SQLObjectImpl;
39 import hunt.Boolean;
40 import hunt.String;
41 import hunt.collection;
42 import hunt.text;
43 import std.uni;
44 
45 public class PGSQLStatementParser : SQLStatementParser {
46     enum string TIME_ZONE = "TIME ZONE";
47     enum string TIME = "TIME";
48     enum string LOCAL = "LOCAL";
49 
50     public this(PGExprParser parser) {
51         super(parser);
52     }
53     
54     public this(string sql){
55         super(new PGExprParser(sql));
56     }
57 
58     public this(string sql, SQLParserFeature[] features...){
59         super(new PGExprParser(sql, features));
60     }
61 
62     public this(Lexer lexer){
63         super(new PGExprParser(lexer));
64     }
65 
66     override public PGSelectParser createSQLSelectParser() {
67         return new PGSelectParser(this.exprParser, selectListCache);
68     }
69 
70     override public SQLUpdateStatement parseUpdateStatement() {
71         accept(Token.UPDATE);
72 
73         PGUpdateStatement udpateStatement = new PGUpdateStatement();
74 
75         SQLSelectParser selectParser = this.exprParser.createSelectParser();
76         SQLTableSource tableSource = selectParser.parseTableSource();
77         udpateStatement.setTableSource(tableSource);
78 
79         parseUpdateSet(udpateStatement);
80 
81         if (lexer.token() == Token.FROM) {
82             lexer.nextToken();
83             SQLTableSource from = selectParser.parseTableSource();
84             udpateStatement.setFrom(from);
85         }
86 
87         if (lexer.token() == (Token.WHERE)) {
88             lexer.nextToken();
89             udpateStatement.setWhere(this.exprParser.expr());
90         }
91 
92         if (lexer.token() == Token.RETURNING) {
93             lexer.nextToken();
94 
95             for (;;) {
96                 udpateStatement.getReturning().add(this.exprParser.expr());
97                 if (lexer.token() == Token.COMMA) {
98                     lexer.nextToken();
99                     continue;
100                 }
101                 break;
102             }
103         }
104 
105         return udpateStatement;
106     }
107 
108     override public PGInsertStatement parseInsert() {
109         PGInsertStatement stmt = new PGInsertStatement();
110 
111         if (lexer.token() == Token.INSERT) {
112             lexer.nextToken();
113             accept(Token.INTO);
114 
115             SQLName tableName = this.exprParser.name();
116             stmt.setTableName(tableName);
117 
118             if (lexer.token() == Token.AS) {
119                 lexer.nextToken();
120                 stmt.setAlias(lexer.stringVal());
121                 lexer.nextToken();
122             } else if (lexer.token() == Token.IDENTIFIER) {
123                 stmt.setAlias(lexer.stringVal());
124                 lexer.nextToken();
125             }
126 
127         }
128         
129         if (lexer.token() == Token.DEFAULT) {
130         	lexer.nextToken();
131         	accept(Token.VALUES);
132         	stmt.setDefaultValues(true);
133         }
134 
135         if (lexer.token() == (Token.LPAREN)) {
136             lexer.nextToken();
137             this.exprParser.exprList(stmt.getColumns(), stmt);
138             accept(Token.RPAREN);
139         }
140 
141         if (lexer.token() == (Token.VALUES)) {
142             lexer.nextToken();
143 
144             for (;;) {
145                 accept(Token.LPAREN);
146                 ValuesClause valuesCaluse = new ValuesClause();
147                 this.exprParser.exprList(valuesCaluse.getValues(), valuesCaluse);
148                 stmt.addValueCause(valuesCaluse);
149 
150                 accept(Token.RPAREN);
151                 if (lexer.token() == Token.COMMA) {
152                     lexer.nextToken();
153                     continue;
154                 }
155                 break;
156             }
157         } else if (lexer.token() == (Token.SELECT)) {
158             SQLQueryExpr queryExpr = cast(SQLQueryExpr) this.exprParser.expr();
159             stmt.setQuery(queryExpr.getSubQuery());
160         }
161 
162         if (lexer.token() == Token.ON) {
163             lexer.nextToken();
164             if (lexer.identifierEquals(FnvHash.Constants.CONFLICT)) {
165                 lexer.nextToken();
166 
167                 if (lexer.token() == Token.LPAREN) {
168                     lexer.nextToken();
169                     List!(SQLExpr) onConflictTarget = new ArrayList!(SQLExpr)();
170                     this.exprParser.exprList(onConflictTarget, stmt);
171                     stmt.setOnConflictTarget(onConflictTarget);
172                     accept(Token.RPAREN);
173                 }
174 
175                 if (lexer.token() == Token.ON) {
176                     lexer.nextToken();
177                     accept(Token.CONSTRAINT);
178                     SQLName constraintName = this.exprParser.name();
179                     stmt.setOnConflictConstraint(constraintName);
180                 }
181 
182                 if (lexer.token() == Token.WHERE) {
183                     lexer.nextToken();
184                     SQLExpr where = this.exprParser.expr();
185                     stmt.setOnConflictWhere(where);
186                 }
187 
188                 if (lexer.token() == Token.DO) {
189                     lexer.nextToken();
190 
191                     if (lexer.identifierEquals(FnvHash.Constants.NOTHING)) {
192                         lexer.nextToken();
193                         stmt.setOnConflictDoNothing(true);
194                     } else {
195                         accept(Token.UPDATE);
196                         accept(Token.SET);
197 
198                         for (;;) {
199                             SQLUpdateSetItem item = this.exprParser.parseUpdateSetItem();
200                             stmt.addConflicUpdateItem(item);
201 
202                             if (lexer.token() != Token.COMMA) {
203                                 break;
204                             }
205 
206                             lexer.nextToken();
207                         }
208                     }
209                 }
210             }
211         }
212 
213         if (lexer.token() == Token.RETURNING) {
214             lexer.nextToken();
215             SQLExpr returning = this.exprParser.expr();
216 
217             if (lexer.token() == Token.COMMA) {
218                 lexer.nextToken();
219                 SQLListExpr list = new SQLListExpr();
220                 list.addItem(returning);
221 
222                 this.exprParser.exprList(list.getItems(), list);
223 
224                 returning = list;
225             }
226             stmt.setReturning(returning);
227         }
228         return stmt;
229     }
230 
231     override public PGDeleteStatement parseDeleteStatement() {
232         lexer.nextToken();
233         PGDeleteStatement deleteStatement = new PGDeleteStatement();
234 
235         if (lexer.token() == (Token.FROM)) {
236             lexer.nextToken();
237         }
238         if (lexer.token() == (Token.ONLY)) {
239             lexer.nextToken();
240             deleteStatement.setOnly(true);
241         }
242 
243         SQLName tableName = exprParser.name();
244 
245         deleteStatement.setTableName(tableName);
246         
247         if (lexer.token() == Token.AS) {
248 			accept(Token.AS);
249 		}
250 		if (lexer.token() == Token.IDENTIFIER) {
251 			deleteStatement.setAlias(lexer.stringVal());
252 			lexer.nextToken();
253 		}
254 
255         if (lexer.token() == Token.USING) {
256             lexer.nextToken();
257 
258             SQLTableSource tableSource = createSQLSelectParser().parseTableSource();
259             deleteStatement.setUsing(tableSource);
260         }
261 
262         if (lexer.token() == (Token.WHERE)) {
263             lexer.nextToken();
264 
265             if (lexer.token() == Token.CURRENT) {
266                 lexer.nextToken();
267                 accept(Token.OF);
268                 SQLName cursorName = this.exprParser.name();
269                 SQLExpr where = new SQLCurrentOfCursorExpr(cursorName);
270                 deleteStatement.setWhere(where);
271             } else {
272                 SQLExpr where = this.exprParser.expr();
273                 deleteStatement.setWhere(where);
274             }
275         }
276 
277         if (lexer.token() == Token.RETURNING) {
278             lexer.nextToken();
279             accept(Token.STAR);
280             deleteStatement.setReturning(true);
281         }
282 
283         return deleteStatement;
284     }
285 
286     override public bool parseStatementListDialect(List!(SQLStatement) statementList) {
287         switch (lexer.token()) {
288             case Token.BEGIN:
289             case Token.START: {
290                 PGStartTransactionStatement stmt = parseBegin();
291                 statementList.add(stmt);
292                 return true;
293             }
294 
295             case Token.WITH:
296                 statementList.add(parseWith());
297                 return true;
298             default:
299                 break;
300         }
301 
302         if (lexer.identifierEquals(FnvHash.Constants.CONNECT)) {
303             SQLStatement stmt = parseConnectTo();
304             statementList.add(stmt);
305             return true;
306         }
307 
308         return false;
309     }
310 
311     protected PGStartTransactionStatement parseBegin() {
312         PGStartTransactionStatement stmt = new PGStartTransactionStatement();
313         if (lexer.token() == Token.START) {
314             lexer.nextToken();
315             acceptIdentifier("TRANSACTION");
316         } else {
317             accept(Token.BEGIN);
318         }
319 
320         return stmt;
321     }
322 
323     public SQLStatement parseConnectTo() {
324         acceptIdentifier("CONNECT");
325         accept(Token.TO);
326 
327         PGConnectToStatement stmt = new PGConnectToStatement();
328         SQLName target = this.exprParser.name();
329         stmt.setTarget(target);
330 
331         return stmt;
332     }
333 
334     override public PGSelectStatement parseSelect() {
335         PGSelectParser selectParser = createSQLSelectParser();
336         SQLSelect select = selectParser.select();
337         return new PGSelectStatement(select);
338     }
339 
340     override public SQLStatement parseWith() {
341         SQLWithSubqueryClause with_p = this.parseWithQuery();
342         // PGWithClause with = this.parseWithClause();
343         if (lexer.token() == Token.INSERT) {
344             PGInsertStatement stmt = this.parseInsert();
345             stmt.setWith(with_p);
346             return stmt;
347         }
348 
349         if (lexer.token() == Token.SELECT) {
350             PGSelectStatement stmt = this.parseSelect();
351             stmt.getSelect().setWithSubQuery(with_p);
352             return stmt;
353         }
354 
355         if (lexer.token() == Token.DELETE) {
356             PGDeleteStatement stmt = this.parseDeleteStatement();
357             stmt.setWith(with_p);
358             return stmt;
359         }
360 
361         if (lexer.token() == Token.UPDATE) {
362             PGUpdateStatement stmt = cast(PGUpdateStatement) this.parseUpdateStatement();
363             stmt.setWith(with_p);
364             return stmt;
365         }
366 
367         throw new ParserException("TODO. " ~ lexer.info());
368     }
369 
370     override protected SQLAlterTableAlterColumn parseAlterColumn() {
371         if (lexer.token() == Token.COLUMN) {
372             lexer.nextToken();
373         }
374 
375         SQLColumnDefinition column = this.exprParser.parseColumn();
376 
377         SQLAlterTableAlterColumn alterColumn = new SQLAlterTableAlterColumn();
378         alterColumn.setColumn(column);
379 
380         if (column.getDataType() is null && column.getConstraints().size() == 0) {
381             if (lexer.token() == Token.SET) {
382                 lexer.nextToken();
383                 if (lexer.token() == Token.NOT) {
384                     lexer.nextToken();
385                     accept(Token.NULL);
386                     alterColumn.setSetNotNull(true);
387                 } else {
388                     accept(Token.DEFAULT);
389                     SQLExpr defaultValue = this.exprParser.expr();
390                     alterColumn.setSetDefault(defaultValue);
391                 }
392             } else if (lexer.token() == Token.DROP) {
393                 lexer.nextToken();
394                 if (lexer.token() == Token.NOT) {
395                     lexer.nextToken();
396                     accept(Token.NULL);
397                     alterColumn.setDropNotNull(true);
398                 } else {
399                     accept(Token.DEFAULT);
400                     alterColumn.setDropDefault(true);
401                 }
402             }
403         }
404         return alterColumn;
405     }
406     
407     override public SQLStatement parseShow() {
408         accept(Token.SHOW);
409         PGShowStatement stmt = new PGShowStatement();
410         switch (lexer.token()) {
411         case Token.ALL:
412             stmt.setExpr(new SQLIdentifierExpr(Token.ALL.stringof)); //@gxc
413             lexer.nextToken();
414             break;
415         default:
416             stmt.setExpr(this.exprParser.expr());
417             break;
418         }
419         return stmt;
420     }
421     
422     override
423     public SQLStatement parseCommit() {
424         SQLCommitStatement stmt = new SQLCommitStatement();
425         stmt.setDbType(this.dbType);
426         lexer.nextToken();
427         return stmt;
428     }
429 
430     override
431     public SQLStatement parseSet() {
432         accept(Token.SET);
433         Token token = lexer.token();
434         string range = "";
435 
436         SQLSetStatement.Option option = null;
437         if (token == Token.SESSION) {
438             lexer.nextToken();
439             range = Token.SESSION.stringof;//@gxc
440             option = SQLSetStatement.Option.SESSION;
441         } else if (token == Token.IDENTIFIER && equalsIgnoreCase(LOCAL, lexer.stringVal())) {
442             range = LOCAL;
443             option = SQLSetStatement.Option.LOCAL;
444             lexer.nextToken();
445         }
446         string parameter = lexer.stringVal();
447         SQLExpr paramExpr;
448         List!(SQLExpr) values = new ArrayList!(SQLExpr)();
449         if (equalsIgnoreCase(TIME, parameter)) {
450             lexer.nextToken();
451             acceptIdentifier("ZONE");
452             paramExpr = new SQLIdentifierExpr("TIME ZONE");
453             string value = lexer.stringVal();
454             if (lexer.token() == Token.IDENTIFIER) {
455                 values.add(new SQLIdentifierExpr(toUpper(value)));
456             } else {
457                 values.add(new SQLCharExpr(value));
458             }
459             lexer.nextToken();
460 //            return new PGSetStatement(range, TIME_ZONE, exprs);
461         } else {
462             paramExpr = new SQLIdentifierExpr(parameter);
463             lexer.nextToken();
464 
465             while (!lexer.isEOF()) {
466                 lexer.nextToken();
467                 if (lexer.token() == Token.LITERAL_CHARS) {
468                     values.add(new SQLCharExpr(lexer.stringVal()));
469                 } else if (lexer.token() == Token.LITERAL_INT) {
470                     values.add(new SQLIdentifierExpr(lexer.numberString()));
471                 } else {
472                     values.add(new SQLIdentifierExpr(lexer.stringVal()));
473                 }
474                 // skip comma
475                 lexer.nextToken();
476             }
477         }
478 
479         // value | 'value' | DEFAULT
480 
481 
482 
483         SQLExpr valueExpr;
484         if (values.size() == 1) {
485             valueExpr = values.get(0);
486         } else {
487             SQLListExpr listExpr = new SQLListExpr();
488             foreach(SQLExpr value ; values) {
489                 listExpr.addItem(value);
490             }
491             valueExpr = listExpr;
492         }
493         SQLSetStatement stmt = new SQLSetStatement(paramExpr, valueExpr, dbType);
494         stmt.setOption(option);
495         return stmt;
496     }
497 
498     override public SQLCreateSequenceStatement parseCreateSequence(bool acceptCreate) {
499         if (acceptCreate) {
500             accept(Token.CREATE);
501         }
502 
503         accept(Token.SEQUENCE);
504 
505         SQLCreateSequenceStatement stmt = new SQLCreateSequenceStatement();
506         stmt.setDbType(dbType);
507         stmt.setName(this.exprParser.name());
508 
509         for (;;) {
510             if (lexer.token() == Token.START) {
511                 lexer.nextToken();
512                 accept(Token.WITH);
513                 stmt.setStartWith(this.exprParser.expr());
514                 continue;
515             } else if (lexer.identifierEquals("INCREMENT")) {
516                 lexer.nextToken();
517                 accept(Token.BY);
518                 stmt.setIncrementBy(this.exprParser.expr());
519                 continue;
520             } else if (lexer.token() == Token.CACHE || lexer.identifierEquals(FnvHash.Constants.CACHE)) {
521                 lexer.nextToken();
522                 stmt.setCache(Boolean.TRUE);
523 
524                 if (lexer.token() == Token.LITERAL_INT) {
525                     stmt.setCacheValue(this.exprParser.primary());
526                 }
527                 continue;
528             } else if (lexer.token() == Token.NOCACHE) {
529                 lexer.nextToken();
530                 stmt.setCache(Boolean.FALSE);
531                 continue;
532             } else if (lexer.token() == Token.ORDER) {
533                 lexer.nextToken();
534                 stmt.setOrder(Boolean.TRUE);
535                 continue;
536             } else if (lexer.identifierEquals("NOORDER")) {
537                 lexer.nextToken();
538                 stmt.setOrder(Boolean.FALSE);
539                 continue;
540             } else if (lexer.identifierEquals("CYCLE")) {
541                 lexer.nextToken();
542                 stmt.setCycle(Boolean.TRUE);
543                 continue;
544             } else if (lexer.identifierEquals("NOCYCLE")) {
545                 lexer.nextToken();
546                 stmt.setCycle(Boolean.FALSE);
547                 continue;
548             } else if (lexer.identifierEquals("MINVALUE")) {
549                 lexer.nextToken();
550                 stmt.setMinValue(this.exprParser.expr());
551                 continue;
552             } else if (lexer.identifierEquals("MAXVALUE")) {
553                 lexer.nextToken();
554                 stmt.setMaxValue(this.exprParser.expr());
555                 continue;
556             } else if (lexer.identifierEquals("NOMAXVALUE")) {
557                 lexer.nextToken();
558                 stmt.setNoMaxValue(true);
559                 continue;
560             } else if (lexer.identifierEquals("NOMINVALUE")) {
561                 lexer.nextToken();
562                 stmt.setNoMinValue(true);
563                 continue;
564             }
565             break;
566         }
567 
568         return stmt;
569     }
570 
571     override public SQLStatement parseCreateIndex(bool acceptCreate) {
572         if (acceptCreate) {
573             accept(Token.CREATE);
574         }
575 
576         SQLCreateIndexStatement stmt = new SQLCreateIndexStatement(getDbType());
577         if (lexer.token() == Token.UNIQUE) {
578             lexer.nextToken();
579             if (lexer.identifierEquals("CLUSTERED")) {
580                 lexer.nextToken();
581                 stmt.setType("UNIQUE CLUSTERED");
582             } else {
583                 stmt.setType("UNIQUE");
584             }
585         } else if (lexer.identifierEquals("FULLTEXT")) {
586             stmt.setType("FULLTEXT");
587             lexer.nextToken();
588         } else if (lexer.identifierEquals("NONCLUSTERED")) {
589             stmt.setType("NONCLUSTERED");
590             lexer.nextToken();
591         }
592 
593         accept(Token.INDEX);
594 
595         stmt.setName(this.exprParser.name());
596 
597         accept(Token.ON);
598 
599         stmt.setTable(this.exprParser.name());
600 
601         if (lexer.token() == Token.USING) {
602             lexer.nextToken();
603             string using = lexer.stringVal();
604             accept(Token.IDENTIFIER);
605             stmt.setUsing(using);
606         }
607 
608         accept(Token.LPAREN);
609 
610         for (;;) {
611             SQLSelectOrderByItem item = this.exprParser.parseSelectOrderByItem();
612             item.setParent(stmt);
613             stmt.addItem(item);
614             if (lexer.token() == Token.COMMA) {
615                 lexer.nextToken();
616                 continue;
617             }
618             break;
619         }
620         accept(Token.RPAREN);
621 
622         return stmt;
623     }
624 }