/* This file is part of the KDE project
   Copyright (C) 2004 Lucijan Busch <lucijan@kde.org>
   Copyright (C) 2004-2018 Jarosław Staniek <staniek@kde.org>

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Library General Public
   License as published by the Free Software Foundation; either
   version 2 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
   Library General Public License for more details.

   You should have received a copy of the GNU Library General Public License
   along with this library; see the file COPYING.LIB.  If not, write to
   the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
   Boston, MA 02110-1301, USA.
*/

// To keep binary compatibility, do not reorder tokens! Add new only at the end.
%token SQL_TYPE
%token AS
%token AS_EMPTY /* used for aliases with skipped AS keyword */
%token ASC
%token AUTO_INCREMENT
%token BIT
%token BITWISE_SHIFT_LEFT
%token BITWISE_SHIFT_RIGHT
%token BY
%token CHARACTER_STRING_LITERAL
%token CONCATENATION /* || */
%token CREATE
%token DESC
%token DISTINCT
%token DOUBLE_QUOTED_STRING
%token FROM
%token JOIN
%token KEY
%token LEFT
%token LESS_OR_EQUAL
%token GREATER_OR_EQUAL
%token SQL_NULL
%token SQL_IS
%token SQL_IS_NULL /*helper */
%token SQL_IS_NOT_NULL /*helper */
%token ORDER
%token PRIMARY
%token SELECT
%token INTEGER_CONST
%token REAL_CONST
%token RIGHT
%token SQL_ON
%token DATE_CONST
%token DATETIME_CONST
%token TIME_CONST
%token TABLE
%token IDENTIFIER
%token IDENTIFIER_DOT_ASTERISK
%token QUERY_PARAMETER
%token VARCHAR
%token WHERE
%token SQL
%token SQL_TRUE
%token SQL_FALSE
%token UNION

%token SCAN_ERROR

//%token SQL_ABS
//%token ACOS
//%token AMPERSAND
//%token SQL_ABSOLUTE
//%token ADA
//%token ADD
//%token ADD_DAYS
//%token ADD_HOURS
//%token ADD_MINUTES
//%token ADD_MONTHS
//%token ADD_SECONDS
//%token ADD_YEARS
//%token ALL
//%token ALLOCATE
//%token ALTER
%token AND
//%token ANY
//%token ARE
//%token ASIN
//%token ASCII
//%token ASSERTION
//%token ATAN
//%token ATAN2
//%token AUTHORIZATION
//%token AVG
//%token BEFORE
%token BETWEEN
%token NOT_BETWEEN
//%token SQL_BEGIN
//%token BIGINT
//%token BINARY
//%token BIT_LENGTH
//%token BREAK
//%token CASCADE
//%token CASCADED
//%token CASE
//%token CAST
//%token CATALOG
//%token CEILING
//%token CENTER
//%token SQL_CHAR
//%token CHAR_LENGTH
//%token CHECK
//%token CLOSE
//%token COALESCE
//%token COBOL
//%token COLLATE
//%token COLLATION
//%token COLUMN
//%token COMMIT
//%token COMPUTE
//%token CONCAT
//%token CONNECT
//%token CONNECTION
//%token CONSTRAINT
//%token CONSTRAINTS
//%token CONTINUE
//%token CONVERT
//%token CORRESPONDING
//%token COS
//%token COT
//%token COUNT
//%token CURDATE
//%token CURRENT
//%token CURRENT_DATE
//%token CURRENT_TIME
//%token CURRENT_TIMESTAMP
//%token CURTIME
//%token CURSOR
//%token DATABASE
//%token SQL_DATE
//%token DATE_FORMAT
//%token DATE_REMAINDER
//%token DATE_VALUE
//%token DAY
//%token DAYOFMONTH
//%token DAYOFWEEK
//%token DAYOFYEAR
//%token DAYS_BETWEEN
//%token DEALLOCATE
//%token DEC
//%token DECLARE
//%token DEFAULT
//%token DEFERRABLE
//%token DEFERRED
//%token SQL_DELETE
//%token DESCRIBE
//%token DESCRIPTOR
//%token DIAGNOSTICS
//%token DICTIONARY
//%token DIRECTORY
//%token DISCONNECT
//%token DISPLACEMENT
//%token DOMAIN_TOKEN
//%token SQL_DOUBLE
//%token DROP
//%token ELSE
//%token END
//%token END_EXEC
//%token ESCAPE
%token EXCEPT
//%token SQL_EXCEPTION
//%token EXEC
//%token EXECUTE
//%token EXISTS
//%token EXP
//%token EXPONENT
//%token EXTERNAL
//%token EXTRACT
//%token FETCH
//%token FIRST
//%token SQL_FLOAT
//%token FLOOR
//%token FN
//%token FOR
//%token FOREIGN
//%token FORTRAN
//%token FOUND
//%token FOUR_DIGITS
//%token FULL
//%token GET
//%token GLOBAL
//%token GO
//%token GOTO
//%token GRANT
//conflict %token GROUP
//%token HAVING
//%token HOUR
//%token HOURS_BETWEEN
//%token IDENTITY
//%token IFNULL
//%token SQL_IGNORE
//%token IMMEDIATE
//%token INCLUDE
//%token INDEX
//%token INDICATOR
//%token INITIALLY
//%token INNER
//%token SQL_INPUT
%token SQL_IN
//%token INSENSITIVE
//%token INSERT
//%token INTEGER
%token INTERSECT
//%token INTERVAL
//%token INTO
//%token IS
//%token ISOLATION
//%token JUSTIFY
//%token LANGUAGE
//%token LAST
//%token LCASE
//%token LENGTH
//%token LEVEL
%token LIKE
%token ILIKE
%token NOT_LIKE
//%token LINE_WIDTH
//%token LOCAL
//%token LOCATE
//%token LOG
//%token SQL_LONG
//%token LOWER
//%token LTRIM
//%token LTRIP
//%token MATCH
//%token SQL_MAX
//%token MICROSOFT
//%token SQL_MIN
//%token MINUTE
//%token MINUTES_BETWEEN
//%token MOD
//%token MODIFY
//%token MODULE
//%token MONTH
//%token MONTHS_BETWEEN
//%token MUMPS
//%token NAMES
//%token NATIONAL
//%token NCHAR
//%token NEXT
//%token NODUP
//%token NONE
%token NOT
%token NOT_EQUAL
%token NOT_EQUAL2
//%token NOW
//%token NULLIF
//%token NUMERIC
//%token OCTET_LENGTH
//%token ODBC
//%token OF
//%token SQL_OFF
//%token ONLY
//%token OPEN
//%token OPTION
//%token OUTER
//%token OUTPUT
//%token OVERLAPS
//%token PAGE
//%token PARTIAL
//%token SQL_PASCAL
//%token PERSISTENT
//%token CQL_PI
%token OR
//%token PLI
//%token POSITION
//%token PRECISION
//%token PREPARE
//%token PRESERVE
//%token PRIOR
//%token PRIVILEGES
//%token PROCEDURE
//%token PRODUCT
//%token PUBLIC
//%token QUARTER
//%token QUIT
//%token RAND
//%token READ_ONLY
//%token REAL
//%token REFERENCES
//%token REPEAT
//%token REPLACE
//%token RESTRICT
//%token REVOKE
//%token ROLLBACK
//%token ROWS
//%token RPAD
//%token RTRIM
//%token SCHEMA
//%token SCREEN_WIDTH
//%token SCROLL
//%token SECOND
//%token SECONDS_BETWEEN
//%token SEQUENCE
//%token SETOPT
//%token SET
//%token SHOWOPT
//%token SIGN
%token SIMILAR_TO
%token NOT_SIMILAR_TO
//%token SIN
//%token SQL_SIZE
//%token SMALLINT
//%token SOME
//%token SPACE
//%token SQLCA
//%token SQLCODE
//%token SQLERROR
//%token SQLSTATE
//%token SQLWARNING
//%token SQRT
//%token STDEV
//%token SUBSTRING
//%token SUM
//%token SYSDATE
//%token SYSDATE_FORMAT
//%token SYSTEM
//%token TAN
//%token TEMPORARY
//%token THEN
//%token THREE_DIGITS
//%token TIME
//%token TIMESTAMP
//%token TIMEZONE_HOUR
//%token TIMEZONE_MINUTE
//%token TINYINT
//%token TO
//%token TO_CHAR
//%token TO_DATE
//%token TRANSACTION
//%token TRANSLATE
//%token TRANSLATION
//%token TRUNCATE
//%token GENERAL_TITLE
//%token TWO_DIGITS
//%token UCASE
//%token UNIQUE
//%token SQL_UNKNOWN
//%token UNSIGNED_INTEGER
//%token UPDATE
//%token UPPER
//%token USAGE
//%token USER
//%token ERROR_DIGIT_BEFORE_IDENTIFIER
//%token USING
//%token VALUE
//%token VALUES
//%token VARBINARY
//%token VARYING
//%token VENDOR
//%token VIEW
//%token WEEK
//%token WHEN
//%token WHENEVER
//%token WHERE_CURRENT_OF
//%token WITH
//%token WORD_WRAPPED
//%token WORK
//%token WRAPPED
%token XOR
//%token YEAR
//%token YEARS_BETWEEN
%token UMINUS
%token TABS_OR_SPACES    // e.g. inside of date or time constants
%token DATE_TIME_INTEGER // inside of date or time constants
%token TIME_AM
%token TIME_PM

%type <stringValue> IDENTIFIER
%type <stringValue> IDENTIFIER_DOT_ASTERISK
%type <stringValue> QUERY_PARAMETER
%type <stringValue> CHARACTER_STRING_LITERAL
%type <stringValue> DOUBLE_QUOTED_STRING

/*
%type <field> ColExpression
%type <field> ColView
*/
%type <expr> ColExpression
%type <expr> ColWildCard
//%type <expr> ColView
%type <expr> ColItem
%type <exprList> ColViews
%type <expr> aExpr
%type <expr> aExpr2
%type <expr> aExpr3
%type <expr> aExpr4
%type <expr> aExpr5
%type <expr> aExpr6
%type <expr> aExpr7
%type <expr> aExpr8
%type <expr> aExpr9
%type <expr> aExpr10
%type <dateValue> DateConst
%type <dateValue> DateValue
%type <yearValue> YearConst
%type <timeValue> TimeConst
%type <timeValue> TimeValue
%type <dateTimeValue> DateTimeConst
%type <binaryValue> TimeMs
%type <timePeriodValue> TimePeriod
%type <exprList> aExprList
%type <exprList> aExprList2
%type <expr> WhereClause
%type <orderByColumns> OrderByClause
%type <sortOrderValue> OrderByOption
%type <variantValue> OrderByColumnId
%type <selectOptions> SelectOptions
%type <expr> FlatTable
%type <exprList> Tables
%type <exprList> FlatTableList
%type <querySchema> SelectStatement
%type <querySchema> Select
/*todo : list*/
%type <querySchema> StatementList
/*todo: not only select*/
%type <querySchema> Statement

%type <colType> SQL_TYPE
%type <integerValue> INTEGER_CONST
%type <binaryValue> REAL_CONST
%type <binaryValue> DATE_TIME_INTEGER
/*%type <integerValue> SIGNED_INTEGER */

%{
#include <stdio.h>
#include <string.h>
#include <string>
#include <iostream>
#include <limits.h>
//! @todo OK?
#ifdef Q_OS_WIN
//workaround for bug on msvc
# undef LLONG_MIN
#endif
#ifndef LLONG_MAX
# define LLONG_MAX     0x7fffffffffffffffLL
#endif
#ifndef LLONG_MIN
# define LLONG_MIN     0x8000000000000000LL
#endif
#ifndef LLONG_MAX
# define ULLONG_MAX    0xffffffffffffffffLL
#endif

#ifdef _WIN32
# include <malloc.h>
#endif

#include <QObject>
#include <QVariant>
#include <QPoint>

#include "KDbConnection.h"
#include "KDbDateTime.h"
#include "KDbExpression.h"
#include "KDbField.h"
#include "KDbOrderByColumn.h"
#include "KDbParser.h"
#include "KDbParser_p.h"
#include "KDbQuerySchema.h"
#include "KDbQuerySchema_p.h"
#include "KDbSqlTypes.h"
#include "KDbTableSchema.h"
#include "kdb_debug.h"

struct OrderByColumnInternal;

#ifdef Q_OS_SOLARIS
#include <alloca.h>
#endif

QDebug operator<<(QDebug dbg, const KDbExpressionPtr& expr)
{
    dbg.nospace() << expr.e;
    return dbg.space();
}

int yylex();

#define YY_NO_UNPUT
#define YYSTACK_USE_ALLOCA 1
#define YYMAXDEPTH 255

    extern "C"
    {
        int yywrap()
        {
            return 1;
        }
    }

%}

%union {
    QString* stringValue;
    QByteArray* binaryValue;
    qint64 integerValue;
    bool booleanValue;
    KDbDate* dateValue;
    KDbYear* yearValue;
    KDbTime* timeValue;
    KDbTime::Period timePeriodValue;
    KDbDateTime* dateTimeValue;
    KDbOrderByColumn::SortOrder sortOrderValue;
    KDbField::Type colType;
    KDbField *field;
    KDbExpression *expr;
    KDbNArgExpression *exprList;
    KDbConstExpression *constExpression;
    KDbQuerySchema *querySchema;
    SelectOptionsInternal *selectOptions;
    QList<OrderByColumnInternal> *orderByColumns;
    QVariant *variantValue;
}

/* precedence: lowest to highest */
//%nonassoc    SIMILAR
//%nonassoc    ESCAPE
//%nonassoc    OVERLAPS
//%nonassoc    IN_P
//%left        POSTFIXOP        // dummy for postfix Op rules
//%left        Op OPERATOR        // multi-character ops and user-defined operators
//%nonassoc    NOTNULL
//%nonassoc    ISNULL
//%nonassoc    IS               // sets precedence for IS NULL, etc
//%nonassoc    NULL_P
//%nonassoc    TRUE_P
//%nonassoc    FALSE_P

// <-- To keep binary compatibility insert new tokens here.

/*
 * These might seem to be low-precedence, but actually they are not part
 * of the arithmetic hierarchy at all in their use as JOIN operators.
 * We make them high-precedence to support their use as function names.
 * They wouldn't be given a precedence at all, were it not that we need
 * left-associativity among the JOIN rules themselves.
 */
/*
%left JOIN
%left UNIONJOIN
%left CROSS
%left LEFT
%left FULL
%left RIGHT
%left INNER_P
%left NATURAL
*/

%%

TopLevelStatement :
StatementList
{
//todo: multiple statements
//todo: not only "select" statements
    KDbParserPrivate::get(globalParser)->setStatementType(KDbParser::Select);
    KDbParserPrivate::get(globalParser)->setQuerySchema($1);
}
;

StatementList:
Statement ';' StatementList
{
//todo: multiple statements
}
| Statement
| Statement ';'
{
    $$ = $1;
}
;

/*        Statement CreateTableStatement         { YYACCEPT; }
    | Statement SelectStatement         {  }
*/
Statement :
/*CreateTableStatement
{
YYACCEPT;
}
| */
SelectStatement
{
    $$ = $1;
}
;

/*CreateTableStatement :
CREATE TABLE IDENTIFIER
{
    globalParser->setStatementType(KDbParser::CreateTable);
    globalParser->createTable($3->toLatin1());
    delete $3;
}
'(' ColDefs ')'
;

ColDefs:
ColDefs ',' ColDef|ColDef
{
}
;

ColDef:
IDENTIFIER ColType
{
    sqlParserDebug() << "adding field " << *$1;
    globalField->setName(*$1);
    globalParser->table()->addField(globalField);
    globalField = nullptr;
    delete $1;
}
| IDENTIFIER ColType ColKeys
{
    sqlParserDebug() << "adding field " << *$1;
    globalField->setName(*$1);
    delete $1;
    globalParser->table()->addField(globalField);

//    if(globalField->isPrimaryKey())
//        globalParser->table()->addPrimaryKey(globalField->name());

//    delete globalField;
//    globalField = nullptr;
}
;

ColKeys:
ColKeys ColKey|ColKey
{
}
;

ColKey:
PRIMARY KEY
{
    globalField->setPrimaryKey(true);
    sqlParserDebug() << "primary";
}
| NOT SQL_NULL
{
    globalField->setNotNull(true);
    sqlParserDebug() << "not_null";
}
| AUTO_INCREMENT
{
    globalField->setAutoIncrement(true);
    sqlParserDebug() << "ainc";
}
;

ColType:
SQL_TYPE
{
    globalField = new KDbField();
    globalField->setType($1);
}
| SQL_TYPE '(' INTEGER_CONST ')'
{
    sqlParserDebug() << "sql + length";
    globalField = new KDbField();
    globalField->setPrecision($3);
    globalField->setType($1);
}
| VARCHAR '(' INTEGER_CONST ')'
{
    globalField = new KDbField();
    globalField->setPrecision($3);
    globalField->setType(KDbField::Text);
}
|
%empty
{
    // SQLITE compatibillity
    globalField = new KDbField();
    globalField->setType(KDbField::InvalidType);
}
;*/

SelectStatement:
Select
{
    sqlParserDebug() << "Select";
    if (!($$ = buildSelectQuery( $1, nullptr )))
        YYABORT;
}
| Select ColViews
{
    sqlParserDebug() << "Select ColViews=" << *$2;

    if (!($$ = buildSelectQuery( $1, $2 )))
        YYABORT;
}
| Select ColViews Tables
{
    if (!($$ = buildSelectQuery( $1, $2, $3 )))
        YYABORT;
}
| Select Tables
{
    sqlParserDebug() << "Select ColViews Tables";
    if (!($$ = buildSelectQuery( $1, nullptr, $2 )))
        YYABORT;
}
| Select ColViews SelectOptions
{
    sqlParserDebug() << "Select ColViews Conditions";
    if (!($$ = buildSelectQuery( $1, $2, nullptr, $3 )))
        YYABORT;
}
| Select Tables SelectOptions
{
    sqlParserDebug() << "Select Tables SelectOptions";
    if (!($$ = buildSelectQuery( $1, nullptr, $2, $3 )))
        YYABORT;
}
| Select ColViews Tables SelectOptions
{
    sqlParserDebug() << "Select ColViews Tables SelectOptions";
    if (!($$ = buildSelectQuery( $1, $2, $3, $4 )))
        YYABORT;
}
;

Select:
SELECT
{
    sqlParserDebug() << "SELECT";
    $$ = KDbParserPrivate::get(globalParser)->createQuery();
}
;

SelectOptions: /* todo: more options (having, group by, limit...) */
WhereClause
{
    sqlParserDebug() << "WhereClause";
    $$ = new SelectOptionsInternal;
    $$->whereExpr = *$1;
    delete $1;
}
| ORDER BY OrderByClause
{
    sqlParserDebug() << "OrderByClause";
    $$ = new SelectOptionsInternal;
    $$->orderByColumns = $3;
}
| WhereClause ORDER BY OrderByClause
{
    sqlParserDebug() << "WhereClause ORDER BY OrderByClause";
    $$ = new SelectOptionsInternal;
    $$->whereExpr = *$1;
    delete $1;
    $$->orderByColumns = $4;
}
| ORDER BY OrderByClause WhereClause
{
    sqlParserDebug() << "OrderByClause WhereClause";
    $$ = new SelectOptionsInternal;
    $$->whereExpr = *$4;
    delete $4;
    $$->orderByColumns = $3;
}
;

WhereClause:
WHERE aExpr
{
    $$ = $2;
}
;

/* todo: support "ORDER BY NULL" as described here https://dev.mysql.com/doc/refman/5.1/en/select.html */
/* todo: accept expr and position as well */
OrderByClause:
OrderByColumnId
{
    sqlParserDebug() << "ORDER BY IDENTIFIER";
    $$ = new QList<OrderByColumnInternal>;
    OrderByColumnInternal orderByColumn;
    orderByColumn.setColumnByNameOrNumber( *$1 );
    $$->append( orderByColumn );
    delete $1;
}
| OrderByColumnId OrderByOption
{
    sqlParserDebug() << "ORDER BY IDENTIFIER OrderByOption";
    $$ = new QList<OrderByColumnInternal>;
    OrderByColumnInternal orderByColumn;
    orderByColumn.setColumnByNameOrNumber( *$1 );
    orderByColumn.order = $2;
    $$->append( orderByColumn );
    delete $1;
}
| OrderByColumnId ',' OrderByClause
{
    $$ = $3;
    OrderByColumnInternal orderByColumn;
    orderByColumn.setColumnByNameOrNumber( *$1 );
    $$->append( orderByColumn );
    delete $1;
}
| OrderByColumnId OrderByOption ',' OrderByClause
{
    $$ = $4;
    OrderByColumnInternal orderByColumn;
    orderByColumn.setColumnByNameOrNumber( *$1 );
    orderByColumn.order = $2;
    $$->append( orderByColumn );
    delete $1;
}
;

OrderByColumnId:
IDENTIFIER
{
    $$ = new QVariant( *$1 );
    sqlParserDebug() << "OrderByColumnId: " << *$$;
    delete $1;
}
| IDENTIFIER '.' IDENTIFIER
{
    $$ = new QVariant( *$1 + QLatin1Char('.') + *$3 );
    sqlParserDebug() << "OrderByColumnId: " << *$$;
    delete $1;
    delete $3;
}
| INTEGER_CONST
{
    $$ = new QVariant($1);
    sqlParserDebug() << "OrderByColumnId: " << *$$;
}

OrderByOption:
ASC
{
    $$ = KDbOrderByColumn::SortOrder::Ascending;
}
| DESC
{
    $$ = KDbOrderByColumn::SortOrder::Descending;
}
;

aExpr:
aExpr2
;

/* --- binary logical --- */
aExpr2:
aExpr3 AND aExpr2
{
//    sqlParserDebug() << "AND " << $3.debugString();
    $$ = new KDbBinaryExpression(*$1, KDbToken::AND, *$3);
    delete $1;
    delete $3;
}
| aExpr3 OR aExpr2
{
    $$ = new KDbBinaryExpression(*$1, KDbToken::OR, *$3);
    delete $1;
    delete $3;
}
| aExpr3 XOR aExpr2
{
    $$ = new KDbBinaryExpression(*$1, KDbToken::XOR, *$3);
    delete $1;
    delete $3;
}
|
aExpr3
;

/* relational op precedence */
aExpr3:
aExpr4 '>' %prec GREATER_OR_EQUAL aExpr3
{
    $$ = new KDbBinaryExpression(*$1, '>', *$3);
    delete $1;
    delete $3;
}
| aExpr4 GREATER_OR_EQUAL aExpr3
{
    $$ = new KDbBinaryExpression(*$1, KDbToken::GREATER_OR_EQUAL, *$3);
    delete $1;
    delete $3;
}
| aExpr4 '<' %prec LESS_OR_EQUAL aExpr3
{
    $$ = new KDbBinaryExpression(*$1, '<', *$3);
    delete $1;
    delete $3;
}
| aExpr4 LESS_OR_EQUAL aExpr3
{
    $$ = new KDbBinaryExpression(*$1, KDbToken::LESS_OR_EQUAL, *$3);
    delete $1;
    delete $3;
}
| aExpr4 '=' aExpr3
{
    $$ = new KDbBinaryExpression(*$1, '=', *$3);
    delete $1;
    delete $3;
}
|
aExpr4
;

/* relational (equality) op precedence */
aExpr4:
aExpr5 NOT_EQUAL aExpr4
{
    $$ = new KDbBinaryExpression(*$1, KDbToken::NOT_EQUAL, *$3);
    delete $1;
    delete $3;
}
| aExpr5 NOT_EQUAL2 aExpr4
{
    $$ = new KDbBinaryExpression(*$1, KDbToken::NOT_EQUAL2, *$3);
    delete $1;
    delete $3;
}
| aExpr5 LIKE aExpr4
{
    $$ = new KDbBinaryExpression(*$1, KDbToken::LIKE, *$3);
    delete $1;
    delete $3;
}
| aExpr5 NOT_LIKE aExpr4
{
    $$ = new KDbBinaryExpression(*$1, KDbToken::NOT_LIKE, *$3);
    delete $1;
    delete $3;
}
| aExpr5 SQL_IN aExpr4
{
    $$ = new KDbBinaryExpression(*$1, KDbToken::SQL_IN, *$3);
    delete $1;
    delete $3;
}
| aExpr5 SIMILAR_TO aExpr4
{
    $$ = new KDbBinaryExpression(*$1, KDbToken::SIMILAR_TO, *$3);
    delete $1;
    delete $3;
}
| aExpr5 NOT_SIMILAR_TO aExpr4
{
    $$ = new KDbBinaryExpression(*$1, KDbToken::NOT_SIMILAR_TO, *$3);
    delete $1;
    delete $3;
}
| aExpr5 BETWEEN aExpr4 AND aExpr4
{
    $$ = new KDbNArgExpression(KDb::RelationalExpression, KDbToken::BETWEEN_AND);
    $$->toNArg().append( *$1 );
    $$->toNArg().append( *$3 );
    $$->toNArg().append( *$5 );
    delete $1;
    delete $3;
    delete $5;
}
| aExpr5 NOT_BETWEEN aExpr4 AND aExpr4
{
    $$ = new KDbNArgExpression(KDb::RelationalExpression, KDbToken::NOT_BETWEEN_AND);
    $$->toNArg().append( *$1 );
    $$->toNArg().append( *$3 );
    $$->toNArg().append( *$5 );
    delete $1;
    delete $3;
    delete $5;
}
|
aExpr5
;

/* --- unary logical right --- */
aExpr5:
aExpr5 SQL_IS_NULL
{
    $$ = new KDbUnaryExpression( KDbToken::SQL_IS_NULL, *$1 );
    delete $1;
}
| aExpr5 SQL_IS_NOT_NULL
{
    $$ = new KDbUnaryExpression( KDbToken::SQL_IS_NOT_NULL, *$1 );
    delete $1;
}
|
aExpr6
;

/* arithm. lowest precedence */
aExpr6:
aExpr7 BITWISE_SHIFT_LEFT aExpr6
{
    $$ = new KDbBinaryExpression(*$1, KDbToken::BITWISE_SHIFT_LEFT, *$3);
    delete $1;
    delete $3;
}
| aExpr7 BITWISE_SHIFT_RIGHT aExpr6
{
    $$ = new KDbBinaryExpression(*$1, KDbToken::BITWISE_SHIFT_RIGHT, *$3);
    delete $1;
    delete $3;
}
|
aExpr7
;

/* arithm. lower precedence */
aExpr7:
aExpr8 '+' aExpr7
{
    $$ = new KDbBinaryExpression(*$1, '+', *$3);
    delete $1;
    delete $3;
}
| aExpr8 CONCATENATION aExpr7
{
    $$ = new KDbBinaryExpression(*$1, KDbToken::CONCATENATION, *$3);
    delete $1;
    delete $3;
}
| aExpr8 '-' %prec UMINUS aExpr7
{
    $$ = new KDbBinaryExpression(*$1, '-', *$3);
    delete $1;
    delete $3;
}
| aExpr8 '&' aExpr7
{
    $$ = new KDbBinaryExpression(*$1, '&', *$3);
    delete $1;
    delete $3;
}
| aExpr8 '|' aExpr7
{
    $$ = new KDbBinaryExpression(*$1, '|', *$3);
    delete $1;
    delete $3;
}
|
aExpr8
;

/* arithm. higher precedence */
aExpr8:
aExpr9 '/' aExpr8
{
    $$ = new KDbBinaryExpression(*$1, '/', *$3);
    delete $1;
    delete $3;
}
| aExpr9 '*' aExpr8
{
    $$ = new KDbBinaryExpression(*$1, '*', *$3);
    delete $1;
    delete $3;
}
| aExpr9 '%' aExpr8
{
    $$ = new KDbBinaryExpression(*$1, '%', *$3);
    delete $1;
    delete $3;
}
|
aExpr9
;

/* parenthesis, unary operators, and terminals precedence */
aExpr9:
/* --- unary logical left --- */
'-' aExpr9
{
    $$ = new KDbUnaryExpression( '-', *$2 );
    delete $2;
}
| '+' aExpr9
{
    $$ = new KDbUnaryExpression( '+', *$2 );
    delete $2;
}
| '~' aExpr9
{
    $$ = new KDbUnaryExpression( '~', *$2 );
    delete $2;
}
| NOT aExpr9
{
    $$ = new KDbUnaryExpression( KDbToken::NOT, *$2 );
    delete $2;
}
| IDENTIFIER
{
    $$ = new KDbVariableExpression( *$1 );

    //! @todo simplify this later if that's 'only one field name' expression
    sqlParserDebug() << "  + identifier: " << *$1;
    delete $1;
}
| QUERY_PARAMETER
{
    $$ = new KDbQueryParameterExpression( *$1 );
    sqlParserDebug() << "  + query parameter:" << *$$;
    delete $1;
}
| IDENTIFIER aExprList
{
    sqlParserDebug() << "  + function:" << *$1 << "(" << *$2 << ")";
    $$ = new KDbFunctionExpression(*$1, *$2);
    delete $1;
    delete $2;
}
/*! @todo shall we also support db name? */
| IDENTIFIER '.' IDENTIFIER
{
    $$ = new KDbVariableExpression( *$1 + QLatin1Char('.') + *$3 );
    sqlParserDebug() << "  + identifier.identifier:" << *$1 << "." << *$3;
    delete $1;
    delete $3;
}
| SQL_NULL
{
    $$ = new KDbConstExpression( KDbToken::SQL_NULL, QVariant() );
    sqlParserDebug() << "  + NULL";
//    $$ = new KDbField();
    //$$->setName(QString::null);
}
| SQL_TRUE
{
    $$ = new KDbConstExpression( KDbToken::SQL_TRUE, true );
}
| SQL_FALSE
{
    $$ = new KDbConstExpression( KDbToken::SQL_FALSE, false );
}
| CHARACTER_STRING_LITERAL
{
    $$ = new KDbConstExpression( KDbToken::CHARACTER_STRING_LITERAL, *$1 );
    sqlParserDebug() << "  + constant " << $1;
    delete $1;
}
| INTEGER_CONST
{
    QVariant val;
    if ($1 <= INT_MAX && $1 >= INT_MIN)
        val = (int)$1;
    else if ($1 <= UINT_MAX && $1 >= 0)
        val = (uint)$1;
    else if ($1 <= LLONG_MAX && $1 >= LLONG_MIN)
        val = (qint64)$1;

//    if ($1 < ULLONG_MAX)
//        val = (quint64)$1;
//! @todo ok?

    $$ = new KDbConstExpression( KDbToken::INTEGER_CONST, val );
    sqlParserDebug() << "  + int constant: " << val.toString();
}
| REAL_CONST
{
    $$ = new KDbConstExpression( KDbToken::REAL_CONST, *$1 );
    sqlParserDebug() << "  + real constant: " << *$1;
    delete $1;
}
| DateConst
{
    $$ = new KDbConstExpression(KDbToken::DATE_CONST, QVariant::fromValue(*$1));
    sqlParserDebug() << "  + date constant:" << *$1;
    delete $1;
}
| TimeConst
{
    $$ = new KDbConstExpression(KDbToken::TIME_CONST, QVariant::fromValue(*$1));
    sqlParserDebug() << "  + time constant:" << *$1;
    delete $1;
}
| DateTimeConst
{
    $$ = new KDbConstExpression(KDbToken::DATETIME_CONST, QVariant::fromValue(*$1));
    sqlParserDebug() << "  + datetime constant:" << *$1;
    delete $1;
}
|
aExpr10
;

DateConst:
'#' DateValue '#'
{
    $$ = $2;
    sqlParserDebug() << "DateConst:" << *$$;
}
;

DateValue:
YearConst '-' DATE_TIME_INTEGER '-' DATE_TIME_INTEGER
{
    $$ = new KDbDate(*$1, *$3, *$5);
    sqlParserDebug() << "DateValue:" << *$$;
    delete $1;
    delete $3;
    delete $5;
}
| DATE_TIME_INTEGER '/' DATE_TIME_INTEGER '/' YearConst /* M/D/Y */
{
    $$ = new KDbDate(*$5, *$1, *$3);
    sqlParserDebug() << "DateValue:" << *$$;
    delete $1;
    delete $3;
    delete $5;
}
;

YearConst:
DATE_TIME_INTEGER
{
    $$ = new KDbYear(KDbYear::Sign::None, *$1);
    sqlParserDebug() << "YearConst:" << *$$;
    delete $1;
}
| '+' DATE_TIME_INTEGER
{
    $$ = new KDbYear(KDbYear::Sign::Plus, *$2);
    sqlParserDebug() << "YearConst:" << *$$;
    delete $2;
}
| '-' DATE_TIME_INTEGER
{
    $$ = new KDbYear(KDbYear::Sign::Minus, *$2);
    sqlParserDebug() << "YearConst:" << *$$;
    delete $2;
}
;

TimeConst:
'#' TimeValue '#'
{
    $$ = $2;
    sqlParserDebug() << "TimeConst:" << *$$;
}
;

TimeValue:
DATE_TIME_INTEGER ':' DATE_TIME_INTEGER TimeMs TimePeriod
{
    $$ = new KDbTime(*$1, *$3, {}, *$4, $5);
    sqlParserDebug() << "TimeValue:" << *$$;
    delete $1;
    delete $3;
    delete $4;
}
| DATE_TIME_INTEGER ':' DATE_TIME_INTEGER ':' DATE_TIME_INTEGER TimeMs TimePeriod
{
    $$ = new KDbTime(*$1, *$3, *$5, *$6, $7);
    sqlParserDebug() << "TimeValue:" << *$$;
    delete $1;
    delete $3;
    delete $5;
    delete $6;
}
;

TimeMs:
'.' DATE_TIME_INTEGER
{
    $$ = $2;
}
| %empty
{
    $$ = new QByteArray;
}
;

TimePeriod:
TIME_AM
{
    $$ = KDbTime::Period::Am;
}
| TIME_PM
{
    $$ = KDbTime::Period::Pm;
}
| %empty
{
    $$ = KDbTime::Period::None;
}
;

DateTimeConst:
'#' DateValue TABS_OR_SPACES TimeValue '#'
{
    $$ = new KDbDateTime(*$2, *$4);
    sqlParserDebug() << "DateTimeConst:" << *$$;
    delete $2;
    delete $4;
}
;

aExpr10:
'(' aExpr ')'
{
    sqlParserDebug() << "(expr)";
    $$ = new KDbUnaryExpression('(', *$2);
    delete $2;
}
;

aExprList:
'(' aExprList2 ')'
{
    $$ = $2;
}
| '(' ')'
{
    $$ = new KDbNArgExpression(KDb::ArgumentListExpression, ',');
}
;

aExprList2:
aExpr ',' aExprList2
{
    $$ = $3;
    $$->prepend( *$1 );
    delete $1;
}
| aExpr
{
    $$ = new KDbNArgExpression(KDb::ArgumentListExpression, ',');
    $$->append( *$1 );
    delete $1;
}
;

Tables:
FROM FlatTableList
{
    $$ = $2;
}
/*
| Tables LEFT JOIN IDENTIFIER SQL_ON ColExpression
{
    sqlParserDebug() << "LEFT JOIN: '" << *$4 << "' ON " << $6;
    addTable($4->toQString());
    delete $4;
}
| Tables LEFT OUTER JOIN IDENTIFIER SQL_ON ColExpression
{
    sqlParserDebug() << "LEFT OUTER JOIN: '" << $5 << "' ON " << $7;
    addTable($5);
}
| Tables INNER JOIN IDENTIFIER SQL_ON ColExpression
{
    sqlParserDebug() << "INNER JOIN: '" << *$4 << "' ON " << $6;
    addTable($4->toQString());
    delete $4;
}
| Tables RIGHT JOIN IDENTIFIER SQL_ON ColExpression
{
    sqlParserDebug() << "RIGHT JOIN: '" << *$4 << "' ON " << $6;
    addTable(*$4);
    delete $4;
}
| Tables RIGHT OUTER JOIN IDENTIFIER SQL_ON ColExpression
{
    sqlParserDebug() << "RIGHT OUTER JOIN: '" << *$5 << "' ON " << $7;
    addTable($5->toQString());
    delete $5;
}*/
;

/*
FlatTableList:
aFlatTableList
{
    $$
}
;*/

FlatTableList:
FlatTableList ',' FlatTable
{
    $$ = $1;
    $$->append(*$3);
    delete $3;
}
|FlatTable
{
    $$ = new KDbNArgExpression(KDb::TableListExpression, KDbToken::IDENTIFIER); //ok?
    $$->append(*$1);
    delete $1;
}
;

FlatTable:
IDENTIFIER
{
    sqlParserDebug() << "FROM: '" << *$1 << "'";
    $$ = new KDbVariableExpression(*$1);

    //! @todo this isn't ok for more tables:
    /*
    KDbField::ListIterator it = globalParser->query()->fieldsIterator();
    for(KDbField *item; (item = it.current()); ++it)
    {
        if(item->table() == dummy)
        {
            item->setTable(schema);
        }

        if(item->table() && !item->isQueryAsterisk())
        {
            KDbField *f = item->table()->field(item->name());
            if(!f)
            {
                KDbParserError err(KDbParser::tr("Field List Error"), KDbParser::tr("Unknown column '%1' in table '%2'",item->name(),schema->name()), ctoken, current);
                globalParser->setError(err);
                yyerror("fieldlisterror");
            }
        }
    }*/
    delete $1;
}
| IDENTIFIER IDENTIFIER
{
    //table + alias
    $$ = new KDbBinaryExpression(
        KDbVariableExpression(*$1), KDbToken::AS_EMPTY,
        KDbVariableExpression(*$2)
    );
    delete $1;
    delete $2;
}
| IDENTIFIER AS IDENTIFIER
{
    //table + alias
    $$ = new KDbBinaryExpression(
        KDbVariableExpression(*$1), KDbToken::AS,
        KDbVariableExpression(*$3)
    );
    delete $1;
    delete $3;
}
;



ColViews:
ColViews ',' ColItem
{
    $$ = $1;
    $$->append(*$3);
    delete $3;
    sqlParserDebug() << "ColViews: ColViews , ColItem";
}
|ColItem
{
    $$ = new KDbNArgExpression(KDb::FieldListExpression, KDbToken());
    $$->append(*$1);
    delete $1;
    sqlParserDebug() << "ColViews: ColItem";
}
;

ColItem:
ColExpression
{
//    $$ = new KDbField();
//    dummy->addField($$);
//    $$->setExpression( $1 );
//    globalParser->query()->addField($$);
    $$ = $1;
    sqlParserDebug() << " added column expr:" << *$1;
}
| ColWildCard
{
    $$ = $1;
    sqlParserDebug() << " added column wildcard:" << *$1;
}
| ColExpression AS IDENTIFIER
{
    $$ = new KDbBinaryExpression(
        *$1, KDbToken::AS,
        KDbVariableExpression(*$3)
    );
    sqlParserDebug() << " added column expr:" << *$$;
    delete $1;
    delete $3;
}
| ColExpression IDENTIFIER
{
    $$ = new KDbBinaryExpression(
        *$1, KDbToken::AS_EMPTY,
        KDbVariableExpression(*$2)
    );
    sqlParserDebug() << " added column expr:" << *$$;
    delete $1;
    delete $2;
}
;

ColExpression:
aExpr
{
    $$ = $1;
}
/* HANDLED BY 'IDENTIFIER aExprList'
| IDENTIFIER '(' ColViews ')'
{
    $$ = new KDbFunctionExpression( $1, $3 );
}*/
//! @todo
/*
| SUM '(' ColExpression ')'
{
    KDbFunctionExpression(
//    $$ = new AggregationExpression( SUM,  );
//    $$->setName("SUM(" + $3->name() + ")");
//wait    $$->containsGroupingAggregate(true);
//wait    globalParser->query()->grouped(true);
}*/
//! @todo
/*
| SQL_MIN '(' ColExpression ')'
{
    $$ = $3;
//    $$->setName("MIN(" + $3->name() + ")");
//wait    $$->containsGroupingAggregate(true);
//wait    globalParser->query()->grouped(true);
}*/
//! @todo
/*
| SQL_MAX '(' ColExpression ')'
{
    $$ = $3;
//    $$->setName("MAX(" + $3->name() + ")");
//wait    $$->containsGroupingAggregate(true);
//wait    globalParser->query()->grouped(true);
}*/
//! @todo
/*
| AVG '(' ColExpression ')'
{
    $$ = $3;
//    $$->setName("AVG(" + $3->name() + ")");
//wait    $$->containsGroupingAggregate(true);
//wait    globalParser->query()->grouped(true);
}*/
| DISTINCT '(' ColExpression ')'
{
    $$ = $3;
//! @todo DISTINCT '(' ColExpression ')'
//    $$->setName("DISTINCT(" + $3->name() + ")");
}
;

ColWildCard:
'*'
{
    $$ = new KDbVariableExpression(QLatin1String("*"));
    sqlParserDebug() << "all columns";

//    KDbQueryAsterisk *ast = new KDbQueryAsterisk(globalParser->query(), dummy);
//    globalParser->query()->addAsterisk(ast);
//    requiresTable = true;
}
| IDENTIFIER '.' '*'
{
    QString s( *$1 );
    s += QLatin1String(".*");
    $$ = new KDbVariableExpression(s);
    sqlParserDebug() << "  + all columns from " << s;
    delete $1;
}
/*| ERROR_DIGIT_BEFORE_IDENTIFIER
{
    $$ = new KDbVariableExpression($1);
    sqlParserDebug() << "  Invalid identifier! " << $1;
    setError(KDbParser::tr("Invalid identifier \"%1\"",$1));
}*/
;

%%

