/* -------------------------------------------------------------------------
 *  This file is part of the oGRAC project.
 * Copyright (c) 2024 Huawei Technologies Co.,Ltd.
 *
 * oGRAC is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *
 *          http://license.coscl.org.cn/MulanPSL2
 *
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 * -------------------------------------------------------------------------
 *
 * ogsql_delete_parser.c
 *
 *
 * IDENTIFICATION
 * src/ogsql/parser/ogsql_delete_parser.c
 *
 * -------------------------------------------------------------------------
 */

#include "ogsql_delete_parser.h"
#include "srv_instance.h"
#include "table_parser.h"
#include "ogsql_select_parser.h"
#include "ogsql_hint_parser.h"
#include "cond_parser.h"
#include "ogsql_update_parser.h"

#ifdef __cplusplus
extern "C" {
#endif

static status_t sql_init_delete(sql_stmt_t *stmt, sql_delete_t *delete_ctx)
{
    OG_RETURN_IFERR(sql_create_list(stmt, &delete_ctx->objects));
    OG_RETURN_IFERR(sql_create_list(stmt, &delete_ctx->pl_dc_lst));
    OG_RETURN_IFERR(sql_alloc_mem(stmt->context, sizeof(sql_query_t), (void **)&delete_ctx->query));
    OG_RETURN_IFERR(sql_init_query(stmt, NULL, stmt->session->lex->loc, delete_ctx->query));
    OG_RETURN_IFERR(sql_copy_str(stmt->context, "DEL$1", &delete_ctx->query->block_info->origin_name));
    delete_ctx->plan = NULL;
    delete_ctx->ret_columns = NULL;
    delete_ctx->hint_info = NULL;
    return OG_SUCCESS;
}

static status_t sql_parse_del_object(sql_stmt_t *stmt, sql_delete_t *delete_ctx, word_t *word)
{
    del_object_t *curr_obj = NULL;
    del_object_t *prev_obj = NULL;
    lex_t *lex = stmt->session->lex;
    uint32 save_flags = lex->flags;
    key_word_t *save_key_words = lex->key_words;
    uint32 save_key_word_count = lex->key_word_count;
    key_word_t key_words[] = { { (uint32)KEY_WORD_RETURNING, OG_FALSE, { (char *)"returning", 9 } } };

    OG_RETURN_IFERR(lex_expected_fetch_variant(lex, word));
    OG_RETURN_IFERR(cm_galist_new(delete_ctx->objects, sizeof(del_object_t), (void **)&curr_obj));
    OG_RETURN_IFERR(sql_decode_object_name(stmt, word, &curr_obj->user, &curr_obj->name));

    lex->flags = LEX_SINGLE_WORD;
    lex->key_words = key_words;
    lex->key_word_count = ELEMENT_COUNT(key_words);

    if (lex_fetch(lex, word) != OG_SUCCESS) {
        lex->key_words = save_key_words;
        lex->key_word_count = save_key_word_count;
        return OG_ERROR;
    }

    if (sql_try_parse_table_alias(stmt, &curr_obj->alias, word) != OG_SUCCESS) {
        lex->key_words = save_key_words;
        lex->key_word_count = save_key_word_count;
        return OG_ERROR;
    }

    for (uint32 i = 0; i < delete_ctx->objects->count - 1; i++) {
        prev_obj = (del_object_t *)cm_galist_get(delete_ctx->objects, i);
        if (cm_text_equal((text_t *)&prev_obj->user, (text_t *)&curr_obj->user) &&
            cm_text_equal((text_t *)&prev_obj->name, (text_t *)&curr_obj->name)) {
            OG_SRC_THROW_ERROR_EX(word->text.loc, ERR_SQL_SYNTAX_ERROR, "duplicated object %s found", W2S(word));
            lex->key_words = save_key_words;
            lex->key_word_count = save_key_word_count;
            return OG_ERROR;
        }
    }
    lex->flags = save_flags;
    lex->key_words = save_key_words;
    lex->key_word_count = save_key_word_count;

    return OG_SUCCESS;
}

static status_t sql_parse_del_objects(sql_stmt_t *stmt, sql_delete_t *delete_ctx, word_t *word)
{
    for (;;) {
        OG_RETURN_IFERR(sql_parse_del_object(stmt, delete_ctx, word));

        if (!IS_SPEC_CHAR(word, ',')) {
            break;
        }
    }
    return OG_SUCCESS;
}

static status_t sql_parse_convert_del_table(sql_stmt_t *stmt, sql_delete_t *delete_ctx)
{
    sql_table_t *table = NULL;
    del_object_t *del_obj = (del_object_t *)cm_galist_get(delete_ctx->objects, 0);

    OG_RETURN_IFERR(sql_array_new(&delete_ctx->query->tables, sizeof(sql_table_t), (void **)&table));
    table->user = del_obj->user;
    table->name = del_obj->name;

    if (del_obj->alias.len > 0) {
        table->alias = del_obj->alias;
        del_obj->name = del_obj->alias;
    }

    if (sql_regist_table(stmt, table) != OG_SUCCESS) {
        cm_set_error_loc(table->name.loc);
        return OG_ERROR;
    }
    return OG_SUCCESS;
}

static status_t sql_parse_delete_tables(sql_stmt_t *stmt, sql_delete_t *delete_ctx, word_t *word)
{
    bool32 result = OG_FALSE;
    lex_t *lex = stmt->session->lex;

    do {
        OG_RETURN_IFERR(lex_try_fetch(lex, "FROM", &result));

        OG_RETURN_IFERR(sql_parse_del_objects(stmt, delete_ctx, word));

#ifdef OG_RAC_ING
        if (IS_COORDINATOR) {
            if (delete_ctx->objects->count > 1) {
                OG_THROW_ERROR(ERR_CAPABILITY_NOT_SUPPORT, "multi delete");
                return OG_ERROR;
            }
            OG_RETURN_IFERR(sql_parse_convert_del_table(stmt, delete_ctx));
            break;
        }
#endif
        if (result) {
            if (word->id == KEY_WORD_USING) {
                OG_RETURN_IFERR(sql_parse_join_entry(stmt, delete_ctx->query, word));
                break;
            }
            if (delete_ctx->objects->count > 1) {
                OG_SRC_THROW_ERROR_EX(word->text.loc, ERR_SQL_SYNTAX_ERROR, " USING expected but %s found", W2S(word));
                return OG_ERROR;
            }
            OG_RETURN_IFERR(sql_parse_convert_del_table(stmt, delete_ctx));
            break;
        }
        if (word->id == KEY_WORD_FROM) {
            OG_RETURN_IFERR(sql_parse_join_entry(stmt, delete_ctx->query, word));
            break;
        }
        if (delete_ctx->objects->count > 1) {
            OG_SRC_THROW_ERROR_EX(word->text.loc, ERR_SQL_SYNTAX_ERROR, " FROM expected but %s found", W2S(word));
            return OG_ERROR;
        }
        OG_RETURN_IFERR(sql_parse_convert_del_table(stmt, delete_ctx));
    } while (OG_FALSE);

    lex->flags = LEX_WITH_OWNER | LEX_WITH_ARG;
    return OG_SUCCESS;
}

static status_t sql_parse_delete(sql_stmt_t *stmt, sql_delete_t *delete_ctx)
{
    word_t word;
    status_t status = OG_ERROR;

    OG_RETURN_IFERR(sql_init_delete(stmt, delete_ctx));

    OG_RETURN_IFERR(SQL_NODE_PUSH(stmt, delete_ctx->query));
    OG_RETURN_IFERR(SQL_SSA_PUSH(stmt, &delete_ctx->query->ssa));

    do {
        OG_BREAK_IF_ERROR(sql_parse_delete_tables(stmt, delete_ctx, &word));

        if (word.id == KEY_WORD_WHERE) {
            OG_BREAK_IF_ERROR(sql_create_cond_until(stmt, &delete_ctx->query->cond, &word));
        }

        if (word.id == KEY_WORD_ORDER) {
            if (delete_ctx->query->tables.count > 1) {
                OG_SRC_THROW_ERROR(word.text.loc, ERR_SQL_SYNTAX_ERROR, "multi delete do not support order by");
                return OG_ERROR;
            }
            OG_BREAK_IF_ERROR(sql_parse_order_by(stmt, delete_ctx->query, &word));
        }

        if (word.id == KEY_WORD_LIMIT || word.id == KEY_WORD_OFFSET) {
            if (delete_ctx->query->tables.count > 1) {
                OG_SRC_THROW_ERROR(word.text.loc, ERR_SQL_SYNTAX_ERROR, "multi delete do not support limit");
                return OG_ERROR;
            }
            OG_BREAK_IF_ERROR(sql_parse_limit_offset(stmt, &delete_ctx->query->limit, &word));
        }

        if (word.id == KEY_WORD_RETURN || word.id == KEY_WORD_RETURNING) {
            OG_BREAK_IF_ERROR(sql_parse_return_columns(stmt, &delete_ctx->ret_columns, &word));
        }

        if (word.type != WORD_TYPE_EOF) {
            OG_SRC_THROW_ERROR_EX(word.text.loc, ERR_SQL_SYNTAX_ERROR, "text end expected but %s found", W2S(&word));
            break;
        }
        status = OG_SUCCESS;
    } while (0);

    OG_RETURN_IFERR(sql_set_table_qb_name(stmt, delete_ctx->query));

    SQL_SSA_POP(stmt);
    SQL_NODE_POP(stmt);
    return status;
}

status_t sql_create_delete_context(sql_stmt_t *stmt, sql_text_t *sql, sql_delete_t **delete_ctx)
{
    lex_t *lex = stmt->session->lex;

    if (sql_alloc_mem(stmt->context, sizeof(sql_delete_t), (void **)delete_ctx) != OG_SUCCESS) {
        return OG_ERROR;
    }

    if (lex_push(lex, sql) != OG_SUCCESS) {
        return OG_ERROR;
    }

    if (lex_expected_fetch_word(lex, "DELETE") != OG_SUCCESS) {
        lex_pop(lex);
        return OG_ERROR;
    }

    if (sql_parse_delete(stmt, *delete_ctx) != OG_SUCCESS) {
        lex_pop(lex);
        return OG_ERROR;
    }

    lex_pop(lex);
    return OG_SUCCESS;
}

#ifdef __cplusplus
}
#endif