Implement lexer and parser for cypher commands with MATCH prototype. (#880)
This commit introduces the cypher .l and cypher.y file contains n a lexer
and grammar for handling cypher commands, respectively. The new files also
include a prototype function for the MATCH command, which will be further
developed in subsequent commits.
flex -b -Cfe -p -p -o ‘cypher.c’ cypher.l
bison -d cypher.y
make
diff --git a/Makefile b/Makefile
index 2d14f69..0a1b239 100644
--- a/Makefile
+++ b/Makefile
@@ -43,6 +43,8 @@
mainloop.o \
prompt.o \
psqlscanslash.o \
+ cypher.o \
+ cypher.tab.o \
sql_help.o \
startup.o \
stringutils.o \
@@ -53,6 +55,8 @@
all: psql
psql: $(OBJS)
+ flex -b -Cfe -p -p -o'cypher.c' cypher.l
+ bison -d cypher.y
$(CC) $(CFLAGS) $(OBJS) $(LDFLAGS) $(LDFLAGS_EX) $(LIBS) -o agesql
diff --git a/cypher.l b/cypher.l
new file mode 100644
index 0000000..42fe06c
--- /dev/null
+++ b/cypher.l
@@ -0,0 +1,44 @@
+%option noyywrap
+%{
+#include "postgres_fe.h"
+
+#include "psqlscanslash.h"
+#include "common/logging.h"
+#include "fe_utils/conditional.h"
+
+#include "libpq-fe.h"
+#include "cypherscan.h"
+#include "cypher.tab.h"
+%}
+
+%%
+
+"->" { return ARROW; }
+"[" { return LBRACKET; }
+"]" { return RBRACKET; }
+"(" { return LPAREN; }
+")" { return RPAREN; }
+":" { return COLON; }
+"|" { return PIPE; }
+"," { return COMMA; }
+";" { return SEMICOLON; }
+"{" { return LBRACE; }
+"}" { return RBRACE; }
+
+"MATCH" { return MATCH; }
+"WHERE" { return WHERE; }
+"WITH" { return WITH; }
+"ORDER" { return ORDER; }
+"BY" { return BY; }
+"SKIP" { return SKIP; }
+"LIMIT" { return LIMIT; }
+"RETURN" { return RETURN; }
+"AS" { return AS; }
+
+[0-9]+ { yylval.int_val = atoi(yytext); return INTEGER; }
+[a-zA-Z][a-zA-Z0-9_]* { yylval.str_val = strdup(yytext); return IDENTIFIER; }
+"([^\"]|\.)*" { yylval.str_val = strdup(yytext); return STRING; }
+. { return UNKNOWN; }
+
+%%
+
diff --git a/cypher.y b/cypher.y
new file mode 100644
index 0000000..ccfbcfb
--- /dev/null
+++ b/cypher.y
@@ -0,0 +1,193 @@
+%{
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "postgres_fe.h"
+
+#include "psqlscanslash.h"
+#include "common/logging.h"
+#include "fe_utils/conditional.h"
+
+#include "libpq-fe.h"
+#include "cypherscan.h"
+#include "cypher.tab.h"
+
+void yyerror(char const *s);
+
+typedef struct yy_buffer_state *YY_BUFFER_STATE;
+
+typedef struct {
+ char* str_val;
+ int int_val;
+} yyval;
+
+int yylex(void);
+
+int order_clause_direction = 1; // 1 for ascending, -1 for descending
+%}
+
+%union {
+ char* str_val;
+ int int_val;
+}
+
+%token ASC ARROW AS DESC LBRACKET RBRACKET LPAREN RPAREN COLON PIPE COMMA SEMICOLON LBRACE RBRACE MATCH WHERE WITH ORDER BY SKIP LIMIT RETURN
+%token <int_val> INTEGER
+%token <str_val> IDENTIFIER STRING
+%token UNKNOWN
+
+%left PIPE
+%left ARROW
+
+%start statement
+
+%%
+
+statement:
+ { printf("Query parsed successfully.\n"); }
+ ;
+
+query:
+ match_clause
+ //where_clause_opt
+ //with_clause_opt
+ //return_clause
+ ;
+
+match_clause:
+ MATCH path_pattern { printf("Match clause parsed successfully.\n"); }
+ ;
+
+path_pattern:
+ node_pattern
+ | node_pattern ARROW rel_pattern node_pattern { printf("Path pattern parsed successfully.\n"); }
+ ;
+
+node_pattern:
+ LPAREN node_labels_opt node_properties_opt RPAREN { printf("Node pattern parsed successfully.\n"); }
+ ;
+
+node_labels_opt:
+ /* empty */ { printf("No node labels.\n"); }
+ | COLON IDENTIFIER { printf("Node label parsed: %s.\n", $2); }
+ | node_labels_opt COLON IDENTIFIER { printf("Node label parsed: %s.\n", $3); }
+ ;
+
+node_properties_opt:
+ /* empty */ { printf("No node properties.\n"); }
+ | LBRACE map_literal RBRACE { printf("Node properties parsed successfully.\n"); }
+ ;
+
+rel_pattern:
+ rel_type rel_direction rel_type { printf("Rel pattern parsed successfully.\n"); }
+ ;
+
+rel_type:
+ { printf("Rel type parsed: \n"); }
+ ;
+
+//rel_type:
+// COLON str_val { printf("Rel type parsed: %s.\n", $2); }
+// | LBRACKET str_val RBRACKET { printf("Rel type parsed: %s.\n", $2); }
+// ;
+
+
+rel_direction:
+ {printf("Rel direction parsed: ->.\n"); }
+ ;
+
+//rel_direction:
+// ARROW { printf("Rel direction parsed: ->.\n"); }
+// | ARROW rel_type_name ARROW { printf("Rel direction parsed: ->%s->.\n", $2); }
+// ;
+
+map_literal:
+ /* empty */ { printf("Empty map literal.\n"); }
+ | nonempty_map_literal { printf("Nonempty map literal.\n"); }
+ ;
+
+nonempty_map_literal:
+ map_entry { printf("Map literal entry parsed successfully.\n"); }
+ | nonempty_map_literal COMMA map_entry { printf("Map literal entry parsed successfully.\n"); }
+ ;
+
+map_entry:
+ IDENTIFIER COLON expression { printf("Map entry parsed successfully.\n"); }
+ ;
+
+expression:
+ INTEGER { printf("Integer expression parsed: %d.\n", $1); }
+ | STRING { printf("String expression parsed: %s.\n", $1); }
+ | IDENTIFIER { printf("Identifier expression parsed: %s.\n", $1); }
+ ;
+
+where_clause_opt:
+ /* empty */ { printf("No WHERE clause.\n"); }
+ | WHERE expression { printf("WHERE clause parsed successfully.\n"); }
+ ;
+
+with_clause_opt:
+ /* empty */ { printf("No WITH clause.\n"); }
+ | WITH expression_list return_clause { printf("WITH clause parsed successfully.\n"); }
+
+expression_list:
+ expression { printf("Expression parsed successfully.\n"); }
+ | expression_list COMMA expression { printf("Expression parsed successfully.\n"); }
+ ;
+
+return_clause:
+ RETURN return_item_list order_clause_opt skip_clause_opt limit_clause_opt { printf("Return clause parsed successfully.\n"); }
+;
+
+return_item_list:
+ return_item { printf("Return item parsed successfully.\n"); }
+ | return_item_list COMMA return_item { printf("Return item parsed successfully.\n"); }
+ ;
+
+return_item:
+ expression { printf("Return item parsed successfully.\n"); }
+ | expression AS IDENTIFIER { printf("Return item with alias parsed successfully.\n"); }
+ ;
+
+order_clause_opt:
+ /* empty */ { printf("No ORDER BY clause.\n"); }
+ | ORDER BY sort_item_list { printf("ORDER BY clause parsed successfully.\n"); }
+ ;
+
+sort_item_list:
+sort_item { printf("Sort item parsed successfully.\n"); }
+| sort_item_list COMMA sort_item { printf("Sort item parsed successfully.\n"); }
+;
+
+sort_item:
+expression sort_direction_opt { printf("Sort item parsed successfully.\n"); }
+;
+
+sort_direction_opt:
+ /* empty */ { printf("Sort direction not specified; defaulting to ASC.\n"); order_clause_direction = 1; }
+ | ASC { printf("Sort direction specified: ASC.\n"); order_clause_direction = 1; }
+ | DESC { printf("Sort direction specified: DESC.\n"); order_clause_direction = -1; }
+ ;
+
+skip_clause_opt:
+ /* empty */ { printf("No SKIP clause.\n"); }
+ | SKIP INTEGER { printf("SKIP clause parsed: %d.\n", $2); }
+ ;
+
+limit_clause_opt:
+ /* empty */ { printf("No LIMIT clause.\n"); }
+ | LIMIT INTEGER { printf("LIMIT clause parsed: %d.\n", $2); }
+ ;
+
+%%
+
+void yyerror(char const *s)
+{
+ printf("Parser error: %s\n", s);
+}
+
+void
+psql_scan_cypher_command(PsqlScanState state)
+{
+
+}
diff --git a/cypherscan.h b/cypherscan.h
new file mode 100644
index 0000000..80dcf05
--- /dev/null
+++ b/cypherscan.h
@@ -0,0 +1,14 @@
+/*
+ * psql - the PostgreSQL interactive terminal
+ *
+ * Copyright (c) 2000-2022, PostgreSQL Global Development Group
+ *
+ * src/bin/psql/cypherscan.h
+ */
+#ifndef CYPHERSACN_H
+#define CYPHERSCAN_H
+
+#include "fe_utils/psqlscan.h"
+void psql_scan_cypher_command(PsqlScanState state);
+
+#endif /* CYPHERSCAN_H */
diff --git a/mainloop.c b/mainloop.c
index b0c4177..0e1ee2b 100644
--- a/mainloop.c
+++ b/mainloop.c
@@ -15,6 +15,7 @@
#include "mb/pg_wchar.h"
#include "prompt.h"
#include "settings.h"
+#include "cypherscan.h"
/* callback functions for our flex lexer */
const PsqlScanCallbacks psqlscan_callbacks = {
@@ -491,6 +492,7 @@
pg_send_history(history_buf);
line_saved_in_history = true;
}
+ psql_scan_cypher_command(query_buf->data);
/* execute backslash command */
slashCmdStatus = HandleSlashCmds(scan_state,
@@ -498,6 +500,7 @@
query_buf,
previous_buf);
+
success = slashCmdStatus != PSQL_CMD_ERROR;
/*
diff --git a/psqlscanslash.c b/psqlscanslash.c
index aee12dd..515f215 100644
--- a/psqlscanslash.c
+++ b/psqlscanslash.c
@@ -8,7 +8,7 @@
*
* See fe_utils/psqlscan_int.h for additional commentary.
*
- * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
@@ -1151,8 +1151,6 @@
#line 29 "psqlscanslash.l"
#include "fe_utils/psqlscan_int.h"
-#define PQmblenBounded(s, e) strnlen(s, PQmblen(s, e))
-
/*
* We must have a typedef YYSTYPE for yylex's first argument, but this lexer
* doesn't presently make use of that argument, so just declare it as int.
@@ -1195,7 +1193,7 @@
/* LCOV_EXCL_START */
-#line 1199 "psqlscanslash.c"
+#line 1197 "psqlscanslash.c"
/* Except for the prefix, these options should match psqlscan.l */
#define YY_NO_INPUT 1
/*
@@ -1211,7 +1209,7 @@
/*
* Assorted character class definitions that should match psqlscan.l.
*/
-#line 1215 "psqlscanslash.c"
+#line 1213 "psqlscanslash.c"
#define INITIAL 0
#define xslashcmd 1
@@ -1491,11 +1489,11 @@
}
{
-#line 121 "psqlscanslash.l"
+#line 119 "psqlscanslash.l"
-#line 125 "psqlscanslash.l"
+#line 123 "psqlscanslash.l"
/* Declare some local variables inside yylex(), for convenience */
PsqlScanState cur_state = yyextra;
PQExpBuffer output_buf = cur_state->output_buf;
@@ -1515,7 +1513,7 @@
* lexer; but if we are, just spit data to the output_buf until EOF.
*/
-#line 1519 "psqlscanslash.c"
+#line 1517 "psqlscanslash.c"
while ( /*CONSTCOND*/1 ) /* loops until end-of-file is reached */
{
@@ -1548,7 +1546,7 @@
case 1:
/* rule 1 can match eol */
YY_RULE_SETUP
-#line 144 "psqlscanslash.l"
+#line 142 "psqlscanslash.l"
{ ECHO; }
YY_BREAK
/*
@@ -1559,7 +1557,7 @@
case 2:
/* rule 2 can match eol */
YY_RULE_SETUP
-#line 153 "psqlscanslash.l"
+#line 151 "psqlscanslash.l"
{
yyless(0);
cur_state->start_state = YY_START;
@@ -1568,7 +1566,7 @@
YY_BREAK
case 3:
YY_RULE_SETUP
-#line 159 "psqlscanslash.l"
+#line 157 "psqlscanslash.l"
{ ECHO; }
YY_BREAK
@@ -1581,12 +1579,12 @@
case 4:
/* rule 4 can match eol */
YY_RULE_SETUP
-#line 170 "psqlscanslash.l"
+#line 168 "psqlscanslash.l"
{ }
YY_BREAK
case 5:
YY_RULE_SETUP
-#line 172 "psqlscanslash.l"
+#line 170 "psqlscanslash.l"
{
if (option_type == OT_FILEPIPE)
{
@@ -1604,7 +1602,7 @@
YY_BREAK
case 6:
YY_RULE_SETUP
-#line 187 "psqlscanslash.l"
+#line 185 "psqlscanslash.l"
{
yyless(0);
BEGIN(xslasharg);
@@ -1622,7 +1620,7 @@
case 7:
/* rule 7 can match eol */
YY_RULE_SETUP
-#line 203 "psqlscanslash.l"
+#line 201 "psqlscanslash.l"
{
/*
* Unquoted space is end of arg; do not eat. Likewise
@@ -1640,7 +1638,7 @@
YY_BREAK
case 8:
YY_RULE_SETUP
-#line 218 "psqlscanslash.l"
+#line 216 "psqlscanslash.l"
{
*option_quote = '\'';
unquoted_option_chars = 0;
@@ -1649,7 +1647,7 @@
YY_BREAK
case 9:
YY_RULE_SETUP
-#line 224 "psqlscanslash.l"
+#line 222 "psqlscanslash.l"
{
backtick_start_offset = output_buf->len;
*option_quote = '`';
@@ -1659,7 +1657,7 @@
YY_BREAK
case 10:
YY_RULE_SETUP
-#line 231 "psqlscanslash.l"
+#line 229 "psqlscanslash.l"
{
ECHO;
*option_quote = '"';
@@ -1669,7 +1667,7 @@
YY_BREAK
case 11:
YY_RULE_SETUP
-#line 238 "psqlscanslash.l"
+#line 236 "psqlscanslash.l"
{
/* Possible psql variable substitution */
if (cur_state->callbacks->get_variable == NULL)
@@ -1709,7 +1707,7 @@
YY_BREAK
case 12:
YY_RULE_SETUP
-#line 275 "psqlscanslash.l"
+#line 273 "psqlscanslash.l"
{
psqlscan_escape_variable(cur_state, yytext, yyleng,
PQUOTE_SQL_LITERAL);
@@ -1719,7 +1717,7 @@
YY_BREAK
case 13:
YY_RULE_SETUP
-#line 283 "psqlscanslash.l"
+#line 281 "psqlscanslash.l"
{
psqlscan_escape_variable(cur_state, yytext, yyleng,
PQUOTE_SQL_IDENT);
@@ -1729,14 +1727,14 @@
YY_BREAK
case 14:
YY_RULE_SETUP
-#line 290 "psqlscanslash.l"
+#line 288 "psqlscanslash.l"
{
psqlscan_test_variable(cur_state, yytext, yyleng);
}
YY_BREAK
case 15:
YY_RULE_SETUP
-#line 294 "psqlscanslash.l"
+#line 292 "psqlscanslash.l"
{
/* Throw back everything but the colon */
yyless(1);
@@ -1746,7 +1744,7 @@
YY_BREAK
case 16:
YY_RULE_SETUP
-#line 301 "psqlscanslash.l"
+#line 299 "psqlscanslash.l"
{
/* Throw back everything but the colon */
yyless(1);
@@ -1756,7 +1754,7 @@
YY_BREAK
case 17:
YY_RULE_SETUP
-#line 308 "psqlscanslash.l"
+#line 306 "psqlscanslash.l"
{
/* Throw back everything but the colon */
yyless(1);
@@ -1766,7 +1764,7 @@
YY_BREAK
case 18:
YY_RULE_SETUP
-#line 315 "psqlscanslash.l"
+#line 313 "psqlscanslash.l"
{
/* Throw back everything but the colon */
yyless(1);
@@ -1776,7 +1774,7 @@
YY_BREAK
case 19:
YY_RULE_SETUP
-#line 322 "psqlscanslash.l"
+#line 320 "psqlscanslash.l"
{
unquoted_option_chars++;
ECHO;
@@ -1790,42 +1788,42 @@
*/
case 20:
YY_RULE_SETUP
-#line 335 "psqlscanslash.l"
+#line 333 "psqlscanslash.l"
{ BEGIN(xslasharg); }
YY_BREAK
case 21:
YY_RULE_SETUP
-#line 337 "psqlscanslash.l"
+#line 335 "psqlscanslash.l"
{ appendPQExpBufferChar(output_buf, '\''); }
YY_BREAK
case 22:
YY_RULE_SETUP
-#line 339 "psqlscanslash.l"
+#line 337 "psqlscanslash.l"
{ appendPQExpBufferChar(output_buf, '\n'); }
YY_BREAK
case 23:
YY_RULE_SETUP
-#line 340 "psqlscanslash.l"
+#line 338 "psqlscanslash.l"
{ appendPQExpBufferChar(output_buf, '\t'); }
YY_BREAK
case 24:
YY_RULE_SETUP
-#line 341 "psqlscanslash.l"
+#line 339 "psqlscanslash.l"
{ appendPQExpBufferChar(output_buf, '\b'); }
YY_BREAK
case 25:
YY_RULE_SETUP
-#line 342 "psqlscanslash.l"
+#line 340 "psqlscanslash.l"
{ appendPQExpBufferChar(output_buf, '\r'); }
YY_BREAK
case 26:
YY_RULE_SETUP
-#line 343 "psqlscanslash.l"
+#line 341 "psqlscanslash.l"
{ appendPQExpBufferChar(output_buf, '\f'); }
YY_BREAK
case 27:
YY_RULE_SETUP
-#line 345 "psqlscanslash.l"
+#line 343 "psqlscanslash.l"
{
/* octal case */
appendPQExpBufferChar(output_buf,
@@ -1834,7 +1832,7 @@
YY_BREAK
case 28:
YY_RULE_SETUP
-#line 351 "psqlscanslash.l"
+#line 349 "psqlscanslash.l"
{
/* hex case */
appendPQExpBufferChar(output_buf,
@@ -1843,13 +1841,13 @@
YY_BREAK
case 29:
YY_RULE_SETUP
-#line 357 "psqlscanslash.l"
+#line 355 "psqlscanslash.l"
{ psqlscan_emit(cur_state, yytext + 1, 1); }
YY_BREAK
case 30:
/* rule 30 can match eol */
YY_RULE_SETUP
-#line 359 "psqlscanslash.l"
+#line 357 "psqlscanslash.l"
{ ECHO; }
YY_BREAK
@@ -1860,7 +1858,7 @@
*/
case 31:
YY_RULE_SETUP
-#line 369 "psqlscanslash.l"
+#line 367 "psqlscanslash.l"
{
/* In an inactive \if branch, don't evaluate the command */
if (cur_state->cb_passthrough == NULL ||
@@ -1871,7 +1869,7 @@
YY_BREAK
case 32:
YY_RULE_SETUP
-#line 377 "psqlscanslash.l"
+#line 375 "psqlscanslash.l"
{
/* Possible psql variable substitution */
if (cur_state->callbacks->get_variable == NULL)
@@ -1901,7 +1899,7 @@
YY_BREAK
case 33:
YY_RULE_SETUP
-#line 404 "psqlscanslash.l"
+#line 402 "psqlscanslash.l"
{
psqlscan_escape_variable(cur_state, yytext, yyleng,
PQUOTE_SHELL_ARG);
@@ -1909,7 +1907,7 @@
YY_BREAK
case 34:
YY_RULE_SETUP
-#line 409 "psqlscanslash.l"
+#line 407 "psqlscanslash.l"
{
/* Throw back everything but the colon */
yyless(1);
@@ -1919,7 +1917,7 @@
case 35:
/* rule 35 can match eol */
YY_RULE_SETUP
-#line 415 "psqlscanslash.l"
+#line 413 "psqlscanslash.l"
{ ECHO; }
YY_BREAK
@@ -1927,7 +1925,7 @@
/* double-quoted text: copy verbatim, including the double quotes */
case 36:
YY_RULE_SETUP
-#line 422 "psqlscanslash.l"
+#line 420 "psqlscanslash.l"
{
ECHO;
BEGIN(xslasharg);
@@ -1936,7 +1934,7 @@
case 37:
/* rule 37 can match eol */
YY_RULE_SETUP
-#line 427 "psqlscanslash.l"
+#line 425 "psqlscanslash.l"
{ ECHO; }
YY_BREAK
@@ -1946,7 +1944,7 @@
case 38:
/* rule 38 can match eol */
YY_RULE_SETUP
-#line 435 "psqlscanslash.l"
+#line 433 "psqlscanslash.l"
{
if (output_buf->len > 0)
ECHO;
@@ -1954,7 +1952,7 @@
YY_BREAK
case 39:
YY_RULE_SETUP
-#line 440 "psqlscanslash.l"
+#line 438 "psqlscanslash.l"
{ ECHO; }
YY_BREAK
@@ -1962,7 +1960,7 @@
/* at end of command, eat a double backslash, but not anything else */
case 40:
YY_RULE_SETUP
-#line 447 "psqlscanslash.l"
+#line 445 "psqlscanslash.l"
{
cur_state->start_state = YY_START;
return LEXRES_OK;
@@ -1971,7 +1969,7 @@
case 41:
/* rule 41 can match eol */
YY_RULE_SETUP
-#line 452 "psqlscanslash.l"
+#line 450 "psqlscanslash.l"
{
yyless(0);
cur_state->start_state = YY_START;
@@ -1988,7 +1986,7 @@
case YY_STATE_EOF(xslashdquote):
case YY_STATE_EOF(xslashwholeline):
case YY_STATE_EOF(xslashend):
-#line 460 "psqlscanslash.l"
+#line 458 "psqlscanslash.l"
{
if (cur_state->buffer_stack == NULL)
{
@@ -2006,10 +2004,10 @@
YY_BREAK
case 42:
YY_RULE_SETUP
-#line 475 "psqlscanslash.l"
+#line 473 "psqlscanslash.l"
YY_FATAL_ERROR( "flex scanner jammed" );
YY_BREAK
-#line 2013 "psqlscanslash.c"
+#line 2011 "psqlscanslash.c"
case YY_END_OF_BUFFER:
{
@@ -3125,7 +3123,7 @@
#define YYTABLES_NAME "yytables"
-#line 475 "psqlscanslash.l"
+#line 473 "psqlscanslash.l"
/* LCOV_EXCL_STOP */