from __future__ import annotations


from sqlglot import exp, generator, tokens, transforms
from sqlglot.errors import UnsupportedError
from sqlglot.dialects.dialect import (
    DATE_ADD_OR_SUB,
    Dialect,
    NormalizationStrategy,
    groupconcat_sql,
    no_last_day_sql,
    rename_func,
    strposition_sql,
    timestrtotime_sql,
    timestamptrunc_sql,
)
from sqlglot.generator import unsupported_args
from sqlglot.optimizer.scope import build_scope
from sqlglot.parsers.exasol import DATE_UNITS, ExasolParser
from sqlglot.tokens import TokenType


def _sha2_sql(self: Exasol.Generator, expression: exp.SHA2) -> str:
    length = expression.text("length")
    func_name = "HASH_SHA256" if length == "256" else "HASH_SHA512"
    return self.func(func_name, expression.this)


def _date_diff_sql(self: Exasol.Generator, expression: exp.DateDiff | exp.TsOrDsDiff) -> str:
    unit = expression.text("unit").upper() or "DAY"

    if unit not in DATE_UNITS:
        self.unsupported(f"'{unit}' is not supported in Exasol.")
        return self.function_fallback_sql(expression)

    return self.func(f"{unit}S_BETWEEN", expression.this, expression.expression)


# https://docs.exasol.com/db/latest/sql/select.htm#:~:text=If%20you%20have,local.x%3E10
def _add_local_prefix_for_aliases(expression: exp.Expr) -> exp.Expr:
    if isinstance(expression, exp.Select):
        aliases: dict[str, bool] = {
            alias.name: bool(alias.args.get("quoted"))
            for sel in expression.selects
            if isinstance(sel, exp.Alias) and (alias := sel.args.get("alias"))
        }

        table = expression.find(exp.Table)
        table_ident = table.this if table else None

        if (
            table_ident
            and table_ident.name.upper() == "LOCAL"
            and not bool(table_ident.args.get("quoted"))
        ):
            table_ident.replace(exp.to_identifier(table_ident.name.upper(), quoted=True))

        def prefix_local(node, visible_aliases: dict[str, bool]) -> exp.Expr:
            if isinstance(node, exp.Column) and not node.table:
                if node.name in visible_aliases:
                    return exp.Column(
                        this=exp.to_identifier(node.name, quoted=visible_aliases[node.name]),
                        table=exp.to_identifier("LOCAL", quoted=False),
                    )
            return node

        for key in ("where", "group", "having"):
            if arg := expression.args.get(key):
                expression.set(key, arg.transform(lambda node: prefix_local(node, aliases)))

        seen_aliases: dict[str, bool] = {}
        new_selects: list[exp.Expr] = []
        for sel in expression.selects:
            if isinstance(sel, exp.Alias):
                inner = sel.this.transform(lambda node: prefix_local(node, seen_aliases))
                sel.set("this", inner)

                alias_node = sel.args.get("alias")

                seen_aliases[sel.alias] = bool(alias_node and getattr(alias_node, "quoted", False))
                new_selects.append(sel)
            else:
                new_selects.append(sel.transform(lambda node: prefix_local(node, seen_aliases)))
        expression.set("expressions", new_selects)

    return expression


def _trunc_sql(self: Exasol.Generator, kind: str, expression: exp.DateTrunc) -> str:
    unit = expression.text("unit")
    node = expression.this.this if isinstance(expression.this, exp.Cast) else expression.this
    expr_sql = self.sql(node)
    if isinstance(node, exp.Literal) and node.is_string:
        expr_sql = (
            f"{kind} '{node.this.replace('T', ' ')}'"
            if kind == "TIMESTAMP"
            else f"DATE '{node.this}'"
        )
    return f"DATE_TRUNC('{unit}', {expr_sql})"


def _date_trunc_sql(self: Exasol.Generator, expression: exp.DateTrunc) -> str:
    return _trunc_sql(self, "DATE", expression)


def _timestamp_trunc_sql(self: Exasol.Generator, expression: exp.DateTrunc) -> str:
    return _trunc_sql(self, "TIMESTAMP", expression)


def is_case_insensitive(node: exp.Expr) -> bool:
    return isinstance(node, exp.Collate) and node.text("expression").upper() == "UTF8_LCASE"


def _substring_index_sql(self: Exasol.Generator, expression: exp.SubstringIndex) -> str:
    this = expression.this
    delimiter = expression.args["delimiter"]
    count_node = expression.args["count"]
    count_sql = self.sql(expression, "count")
    num = count_node.to_py() if count_node.is_number else 0

    haystack_sql = self.sql(this)
    if num == 0:
        return self.func("SUBSTR", haystack_sql, "1", "0")

    from_right = num < 0
    direction = "-1" if from_right else "1"
    occur = self.func("ABS", count_sql) if from_right else count_sql

    delimiter_sql = self.sql(delimiter)

    position = self.func(
        "INSTR",
        self.func("LOWER", haystack_sql) if is_case_insensitive(this) else haystack_sql,
        self.func("LOWER", delimiter_sql) if is_case_insensitive(delimiter) else delimiter_sql,
        direction,
        occur,
    )
    nullable_pos = self.func("NULLIF", position, "0")

    if from_right:
        start = self.func(
            "NVL", f"{nullable_pos} + {self.func('LENGTH', delimiter_sql)}", direction
        )
        return self.func("SUBSTR", haystack_sql, start)

    length = self.func("NVL", f"{nullable_pos} - 1", self.func("LENGTH", haystack_sql))
    return self.func("SUBSTR", haystack_sql, direction, length)


# https://docs.exasol.com/db/latest/sql/select.htm#:~:text=The%20select_list%20defines%20the%20columns%20of%20the%20result%20table.%20If%20*%20is%20used%2C%20all%20columns%20are%20listed.%20You%20can%20use%20an%20expression%20like%20t.*%20to%20list%20all%20columns%20of%20the%20table%20t%2C%20the%20view%20t%2C%20or%20the%20object%20with%20the%20table%20alias%20t.
def _qualify_unscoped_star(expression: exp.Expr) -> exp.Expr:
    """
    Exasol doesn't support a bare * alongside other select items, so we rewrite it
    Rewrite: SELECT *, <other> FROM <Table>
    Into: SELECT T.*, <other> FROM <Table> AS T
    """

    if not isinstance(expression, exp.Select):
        return expression

    select_expressions = expression.expressions or []

    def is_bare_star(expr: exp.Expr) -> bool:
        return isinstance(expr, exp.Star) and expr.this is None

    has_other_expression = False
    bare_star_expr: exp.Expr | None = None
    for expr in select_expressions:
        has_bare_star = is_bare_star(expr)
        if has_bare_star and bare_star_expr is None:
            bare_star_expr = expr
        elif not has_bare_star:
            has_other_expression = True
        if bare_star_expr and has_other_expression:
            break

    if not (bare_star_expr and has_other_expression):
        return expression

    scope = build_scope(expression)

    if not scope or not scope.selected_sources:
        return expression

    table_identifiers: list[exp.Identifier] = []

    for source_name, (source_expr, _) in scope.selected_sources.items():
        ident = (
            source_expr.this.copy()
            if isinstance(source_expr, exp.Table) and isinstance(source_expr.this, exp.Identifier)
            else exp.to_identifier(source_name)
        )
        table_identifiers.append(ident)

    qualified_star_columns = [
        exp.Column(this=bare_star_expr.copy(), table=ident) for ident in table_identifiers
    ]

    new_select_expressions: list[exp.Expr] = []

    for select_expr in select_expressions:
        new_select_expressions.extend(qualified_star_columns) if is_bare_star(
            select_expr
        ) else new_select_expressions.append(select_expr)

    expression.set("expressions", new_select_expressions)
    return expression


def _add_date_sql(self: Exasol.Generator, expression: DATE_ADD_OR_SUB) -> str:
    interval = expression.expression if isinstance(expression.expression, exp.Interval) else None

    unit = (
        (interval.text("unit") or "DAY").upper()
        if interval is not None
        else (expression.text("unit") or "DAY").upper()
    )

    if unit not in DATE_UNITS:
        self.unsupported(f"'{unit}' is not supported in Exasol.")
        return self.function_fallback_sql(expression)

    offset_expr: exp.Expr = expression.expression
    if interval is not None:
        offset_expr = interval.this

    if isinstance(expression, exp.DateSub):
        offset_expr = exp.Neg(this=offset_expr)

    return self.func(f"ADD_{unit}S", expression.this, offset_expr)


def _group_by_all(expression: exp.Expr) -> exp.Expr:
    if not isinstance(expression, exp.Select):
        return expression

    group = expression.args.get("group")
    if not group or not group.args.get("all"):
        return expression

    if expression.is_star:
        if any(proj.find(exp.AggFunc) for proj in expression.expressions):
            raise UnsupportedError(
                "GROUP BY ALL with star projection and aggregates is not supported by Exasol"
            )
        expression.set("distinct", exp.Distinct())
        expression.set("group", None)
        return expression

    group_positions = [
        exp.Literal.number(i)
        for i, proj in enumerate(expression.expressions, start=1)
        if not proj.find(exp.AggFunc)
    ]

    if not group_positions:
        expression.set("group", None)
        return expression

    group.set("expressions", group_positions)
    group.set("all", None)

    return expression


class Exasol(Dialect):
    # https://docs.exasol.com/db/latest/sql_references/basiclanguageelements.htm#SQLidentifier
    NORMALIZATION_STRATEGY = NormalizationStrategy.UPPERCASE
    # https://docs.exasol.com/db/latest/sql_references/data_types/datatypesoverview.htm
    SUPPORTS_USER_DEFINED_TYPES = False
    # https://docs.exasol.com/db/latest/sql/select.htm
    SUPPORTS_COLUMN_JOIN_MARKS = True
    NULL_ORDERING = "nulls_are_last"
    # https://docs.exasol.com/db/latest/sql_references/literals.htm#StringLiterals
    CONCAT_COALESCE = True

    TIME_MAPPING = {
        "yyyy": "%Y",
        "YYYY": "%Y",
        "yy": "%y",
        "YY": "%y",
        "mm": "%m",
        "MM": "%m",
        "MONTH": "%B",
        "MON": "%b",
        "dd": "%d",
        "DD": "%d",
        "DAY": "%A",
        "DY": "%a",
        "H12": "%I",
        "H24": "%H",
        "HH": "%H",
        "ID": "%u",
        "vW": "%V",
        "IW": "%V",
        "vYYY": "%G",
        "IYYY": "%G",
        "MI": "%M",
        "SS": "%S",
        "uW": "%W",
        "UW": "%U",
        "Z": "%z",
    }

    class Tokenizer(tokens.Tokenizer):
        IDENTIFIERS = ['"', ("[", "]")]
        KEYWORDS = {
            **tokens.Tokenizer.KEYWORDS,
            "USER": TokenType.CURRENT_USER,
            # https://docs.exasol.com/db/latest/sql_references/functions/alphabeticallistfunctions/if.htm
            "ENDIF": TokenType.END,
            "LONG VARCHAR": TokenType.TEXT,
            "REGEXP_LIKE": TokenType.RLIKE,
            "SEPARATOR": TokenType.SEPARATOR,
            "SYSTIMESTAMP": TokenType.SYSTIMESTAMP,
        }
        KEYWORDS.pop("DIV")

    Parser = ExasolParser

    class Generator(generator.Generator):
        # https://docs.exasol.com/db/latest/sql_references/data_types/datatypedetails.htm#StringDataType
        STRING_TYPE_MAPPING = {
            exp.DType.BLOB: "VARCHAR",
            exp.DType.LONGBLOB: "VARCHAR",
            exp.DType.LONGTEXT: "VARCHAR",
            exp.DType.MEDIUMBLOB: "VARCHAR",
            exp.DType.MEDIUMTEXT: "VARCHAR",
            exp.DType.TINYBLOB: "VARCHAR",
            exp.DType.TINYTEXT: "VARCHAR",
            # https://docs.exasol.com/db/latest/sql_references/data_types/datatypealiases.htm
            exp.DType.TEXT: "LONG VARCHAR",
            exp.DType.VARBINARY: "VARCHAR",
        }

        # https://docs.exasol.com/db/latest/sql_references/data_types/datatypealiases.htm
        TYPE_MAPPING = {
            **generator.Generator.TYPE_MAPPING,
            **STRING_TYPE_MAPPING,
            exp.DType.TINYINT: "SMALLINT",
            exp.DType.MEDIUMINT: "INT",
            exp.DType.DECIMAL32: "DECIMAL",
            exp.DType.DECIMAL64: "DECIMAL",
            exp.DType.DECIMAL128: "DECIMAL",
            exp.DType.DECIMAL256: "DECIMAL",
            exp.DType.DATETIME: "TIMESTAMP",
            exp.DType.TIMESTAMPTZ: "TIMESTAMP",
            exp.DType.TIMESTAMPLTZ: "TIMESTAMP",
            exp.DType.TIMESTAMPNTZ: "TIMESTAMP",
        }

        def datatype_sql(self, expression: exp.DataType) -> str:
            # Exasol supports a fixed default precision of 3 for TIMESTAMP WITH LOCAL TIME ZONE
            # and does not allow specifying a different custom precision
            if expression.is_type(exp.DType.TIMESTAMPLTZ):
                return "TIMESTAMP WITH LOCAL TIME ZONE"

            return super().datatype_sql(expression)

        TRANSFORMS = {
            **generator.Generator.TRANSFORMS,
            # https://docs.exasol.com/db/latest/sql_references/functions/alphabeticallistfunctions/every.htm
            exp.All: rename_func("EVERY"),
            # https://docs.exasol.com/db/latest/sql_references/functions/alphabeticallistfunctions/bit_and.htm
            exp.BitwiseAnd: rename_func("BIT_AND"),
            # https://docs.exasol.com/db/latest/sql_references/functions/alphabeticallistfunctions/bit_or.htm
            exp.BitwiseOr: rename_func("BIT_OR"),
            # https://docs.exasol.com/db/latest/sql_references/functions/alphabeticallistfunctions/bit_not.htm
            exp.BitwiseNot: rename_func("BIT_NOT"),
            # https://docs.exasol.com/db/latest/sql_references/functions/alphabeticallistfunctions/bit_lshift.htm
            exp.BitwiseLeftShift: rename_func("BIT_LSHIFT"),
            # https://docs.exasol.com/db/latest/sql_references/functions/alphabeticallistfunctions/bit_rshift.htm
            exp.BitwiseRightShift: rename_func("BIT_RSHIFT"),
            # https://docs.exasol.com/db/latest/sql_references/functions/alphabeticallistfunctions/bit_xor.htm
            exp.BitwiseXor: rename_func("BIT_XOR"),
            exp.CurrentSchema: lambda *_: "CURRENT_SCHEMA",
            exp.DateDiff: _date_diff_sql,
            exp.DateAdd: _add_date_sql,
            exp.TsOrDsAdd: _add_date_sql,
            exp.DateSub: _add_date_sql,
            # https://docs.exasol.com/db/latest/sql_references/functions/alphabeticallistfunctions/div.htm#DIV
            exp.IntDiv: rename_func("DIV"),
            exp.TsOrDsDiff: _date_diff_sql,
            exp.DateTrunc: _date_trunc_sql,
            exp.DayOfWeek: lambda self, e: f"CAST(TO_CHAR({self.sql(e, 'this')}, 'D') AS INTEGER)",
            exp.DatetimeTrunc: timestamptrunc_sql(),
            exp.GroupConcat: lambda self, e: groupconcat_sql(
                self, e, func_name="LISTAGG", within_group=True
            ),
            # https://docs.exasol.com/db/latest/sql_references/functions/alphabeticallistfunctions/edit_distance.htm#EDIT_DISTANCE
            exp.Levenshtein: unsupported_args("ins_cost", "del_cost", "sub_cost", "max_dist")(
                rename_func("EDIT_DISTANCE")
            ),
            # https://docs.exasol.com/db/latest/sql_references/functions/alphabeticallistfunctions/mod.htm
            exp.Mod: rename_func("MOD"),
            # https://docs.exasol.com/db/latest/sql_references/functions/alphabeticallistfunctions/from_posix_time.htm
            exp.UnixToTime: lambda self, e: self.func("FROM_POSIX_TIME", e.this),
            # https://docs.exasol.com/db/latest/sql_references/functions/alphabeticallistfunctions/rank.htm
            exp.Rank: unsupported_args("expressions")(lambda *_: "RANK()"),
            # https://docs.exasol.com/db/latest/sql_references/functions/alphabeticallistfunctions/dense_rank.htm
            exp.DenseRank: unsupported_args("expressions")(lambda *_: "DENSE_RANK()"),
            # https://docs.exasol.com/db/latest/sql_references/functions/alphabeticallistfunctions/regexp_substr.htm
            exp.RegexpExtract: unsupported_args("parameters", "group")(
                rename_func("REGEXP_SUBSTR")
            ),
            # https://docs.exasol.com/db/latest/sql_references/functions/alphabeticallistfunctions/regexp_replace.htm
            exp.RegexpReplace: unsupported_args("modifiers")(rename_func("REGEXP_REPLACE")),
            # https://docs.exasol.com/db/latest/sql_references/functions/alphabeticallistfunctions/var_pop.htm
            exp.VariancePop: rename_func("VAR_POP"),
            # https://docs.exasol.com/db/latest/sql_references/functions/alphabeticallistfunctions/approximate_count_distinct.htm
            exp.ApproxDistinct: unsupported_args("accuracy")(
                rename_func("APPROXIMATE_COUNT_DISTINCT")
            ),
            # https://docs.exasol.com/db/latest/sql_references/functions/alphabeticallistfunctions/to_char%20(datetime).htm
            exp.TimeToStr: lambda self, e: self.func("TO_CHAR", e.this, self.format_time(e)),
            exp.ToChar: lambda self, e: self.func("TO_CHAR", e.this, self.format_time(e)),
            # https://docs.exasol.com/db/latest/sql_references/functions/alphabeticallistfunctions/to_date.htm
            exp.TsOrDsToDate: lambda self, e: self.func("TO_DATE", e.this, self.format_time(e)),
            exp.TimeStrToTime: timestrtotime_sql,
            exp.TimestampTrunc: _timestamp_trunc_sql,
            exp.StrToTime: lambda self, e: self.func("TO_DATE", e.this, self.format_time(e)),
            exp.CurrentUser: lambda *_: "CURRENT_USER",
            exp.AtTimeZone: lambda self, e: self.func(
                "CONVERT_TZ",
                e.this,
                "'UTC'",
                e.args.get("zone"),
            ),
            # https://docs.exasol.com/db/latest/sql_references/functions/alphabeticallistfunctions/instr.htm
            exp.StrPosition: lambda self, e: strposition_sql(
                self, e, func_name="INSTR", supports_position=True, supports_occurrence=True
            ),
            # https://docs.exasol.com/db/latest/sql_references/functions/alphabeticallistfunctions/hash_sha%5B1%5D.htm#HASH_SHA%5B1%5D
            exp.SHA: rename_func("HASH_SHA"),
            # https://docs.exasol.com/db/latest/sql_references/functions/alphabeticallistfunctions/hash_sha256.htm
            # https://docs.exasol.com/db/latest/sql_references/functions/alphabeticallistfunctions/hash_sha512.htm
            exp.SHA2: _sha2_sql,
            exp.MD5: rename_func("HASH_MD5"),
            # https://docs.exasol.com/db/latest/sql_references/functions/alphabeticallistfunctions/hashtype_md5.htm
            exp.MD5Digest: rename_func("HASHTYPE_MD5"),
            # https://docs.exasol.com/db/latest/sql/create_view.htm
            exp.CommentColumnConstraint: lambda self, e: f"COMMENT IS {self.sql(e, 'this')}",
            exp.Select: transforms.preprocess(
                [
                    _qualify_unscoped_star,
                    _add_local_prefix_for_aliases,
                    _group_by_all,
                ]
            ),
            exp.SubstringIndex: _substring_index_sql,
            exp.WeekOfYear: rename_func("WEEK"),
            # https://docs.exasol.com/db/latest/sql_references/functions/alphabeticallistfunctions/to_date.htm
            exp.Date: rename_func("TO_DATE"),
            # https://docs.exasol.com/db/latest/sql_references/functions/alphabeticallistfunctions/to_timestamp.htm
            exp.Timestamp: rename_func("TO_TIMESTAMP"),
            exp.Quarter: lambda self, e: f"CEIL(MONTH(TO_DATE({self.sql(e, 'this')}))/3)",
            exp.LastDay: no_last_day_sql,
        }

        # https://docs.exasol.com/db/7.1/sql_references/system_tables/metadata/exa_sql_keywords.htm
        RESERVED_KEYWORDS = {
            "absolute",
            "action",
            "add",
            "after",
            "all",
            "allocate",
            "alter",
            "and",
            "any",
            "append",
            "are",
            "array",
            "as",
            "asc",
            "asensitive",
            "assertion",
            "at",
            "attribute",
            "authid",
            "authorization",
            "before",
            "begin",
            "between",
            "bigint",
            "binary",
            "bit",
            "blob",
            "blocked",
            "bool",
            "boolean",
            "both",
            "by",
            "byte",
            "call",
            "called",
            "cardinality",
            "cascade",
            "cascaded",
            "case",
            "casespecific",
            "cast",
            "catalog",
            "chain",
            "char",
            "character",
            "character_set_catalog",
            "character_set_name",
            "character_set_schema",
            "characteristics",
            "check",
            "checked",
            "clob",
            "close",
            "coalesce",
            "collate",
            "collation",
            "collation_catalog",
            "collation_name",
            "collation_schema",
            "column",
            "commit",
            "condition",
            "connect_by_iscycle",
            "connect_by_isleaf",
            "connect_by_root",
            "connection",
            "constant",
            "constraint",
            "constraint_state_default",
            "constraints",
            "constructor",
            "contains",
            "continue",
            "control",
            "convert",
            "corresponding",
            "create",
            "cs",
            "csv",
            "cube",
            "current",
            "current_cluster",
            "current_cluster_uid",
            "current_date",
            "current_path",
            "current_role",
            "current_schema",
            "current_session",
            "current_statement",
            "current_time",
            "current_timestamp",
            "current_user",
            "cursor",
            "cycle",
            "data",
            "datalink",
            "datetime_interval_code",
            "datetime_interval_precision",
            "day",
            "dbtimezone",
            "deallocate",
            "dec",
            "decimal",
            "declare",
            "default",
            "default_like_escape_character",
            "deferrable",
            "deferred",
            "defined",
            "definer",
            "delete",
            "deref",
            "derived",
            "desc",
            "describe",
            "descriptor",
            "deterministic",
            "disable",
            "disabled",
            "disconnect",
            "dispatch",
            "distinct",
            "dlurlcomplete",
            "dlurlpath",
            "dlurlpathonly",
            "dlurlscheme",
            "dlurlserver",
            "dlvalue",
            "do",
            "domain",
            "double",
            "drop",
            "dynamic",
            "dynamic_function",
            "dynamic_function_code",
            "each",
            "else",
            "elseif",
            "elsif",
            "emits",
            "enable",
            "enabled",
            "end",
            "end-exec",
            "endif",
            "enforce",
            "equals",
            "errors",
            "escape",
            "except",
            "exception",
            "exec",
            "execute",
            "exists",
            "exit",
            "export",
            "external",
            "extract",
            "false",
            "fbv",
            "fetch",
            "file",
            "final",
            "first",
            "float",
            "following",
            "for",
            "forall",
            "force",
            "format",
            "found",
            "free",
            "from",
            "fs",
            "full",
            "function",
            "general",
            "generated",
            "geometry",
            "get",
            "global",
            "go",
            "goto",
            "grant",
            "granted",
            "group",
            "group_concat",
            "grouping",
            "groups",
            "hashtype",
            "hashtype_format",
            "having",
            "high",
            "hold",
            "hour",
            "identity",
            "if",
            "ifnull",
            "immediate",
            "impersonate",
            "implementation",
            "import",
            "in",
            "index",
            "indicator",
            "inner",
            "inout",
            "input",
            "insensitive",
            "insert",
            "instance",
            "instantiable",
            "int",
            "integer",
            "integrity",
            "intersect",
            "interval",
            "into",
            "inverse",
            "invoker",
            "is",
            "iterate",
            "join",
            "key_member",
            "key_type",
            "large",
            "last",
            "lateral",
            "ldap",
            "leading",
            "leave",
            "left",
            "level",
            "like",
            "limit",
            "listagg",
            "localtime",
            "localtimestamp",
            "locator",
            "log",
            "longvarchar",
            "loop",
            "low",
            "map",
            "match",
            "matched",
            "merge",
            "method",
            "minus",
            "minute",
            "mod",
            "modifies",
            "modify",
            "module",
            "month",
            "names",
            "national",
            "natural",
            "nchar",
            "nclob",
            "new",
            "next",
            "nls_date_format",
            "nls_date_language",
            "nls_first_day_of_week",
            "nls_numeric_characters",
            "nls_timestamp_format",
            "no",
            "nocycle",
            "nologging",
            "none",
            "not",
            "null",
            "nullif",
            "number",
            "numeric",
            "nvarchar",
            "nvarchar2",
            "object",
            "of",
            "off",
            "old",
            "on",
            "only",
            "open",
            "option",
            "options",
            "or",
            "order",
            "ordering",
            "ordinality",
            "others",
            "out",
            "outer",
            "output",
            "over",
            "overlaps",
            "overlay",
            "overriding",
            "pad",
            "parallel_enable",
            "parameter",
            "parameter_specific_catalog",
            "parameter_specific_name",
            "parameter_specific_schema",
            "parquet",
            "partial",
            "path",
            "permission",
            "placing",
            "plus",
            "preceding",
            "preferring",
            "prepare",
            "preserve",
            "prior",
            "privileges",
            "procedure",
            "profile",
            "qualify",
            "random",
            "range",
            "read",
            "reads",
            "real",
            "recovery",
            "recursive",
            "ref",
            "references",
            "referencing",
            "refresh",
            "regexp_like",
            "relative",
            "release",
            "rename",
            "repeat",
            "replace",
            "restore",
            "restrict",
            "result",
            "return",
            "returned_length",
            "returned_octet_length",
            "returns",
            "revoke",
            "right",
            "rollback",
            "rollup",
            "routine",
            "row",
            "rows",
            "rowtype",
            "savepoint",
            "schema",
            "scope",
            "scope_user",
            "script",
            "scroll",
            "search",
            "second",
            "section",
            "security",
            "select",
            "selective",
            "self",
            "sensitive",
            "separator",
            "sequence",
            "session",
            "session_user",
            "sessiontimezone",
            "set",
            "sets",
            "shortint",
            "similar",
            "smallint",
            "some",
            "source",
            "space",
            "specific",
            "specifictype",
            "sql",
            "sql_bigint",
            "sql_bit",
            "sql_char",
            "sql_date",
            "sql_decimal",
            "sql_double",
            "sql_float",
            "sql_integer",
            "sql_longvarchar",
            "sql_numeric",
            "sql_preprocessor_script",
            "sql_real",
            "sql_smallint",
            "sql_timestamp",
            "sql_tinyint",
            "sql_type_date",
            "sql_type_timestamp",
            "sql_varchar",
            "sqlexception",
            "sqlstate",
            "sqlwarning",
            "start",
            "state",
            "statement",
            "static",
            "structure",
            "style",
            "substring",
            "subtype",
            "sysdate",
            "system",
            "system_user",
            "systimestamp",
            "table",
            "temporary",
            "text",
            "then",
            "time",
            "timestamp",
            "timezone_hour",
            "timezone_minute",
            "tinyint",
            "to",
            "trailing",
            "transaction",
            "transform",
            "transforms",
            "translation",
            "treat",
            "trigger",
            "trim",
            "true",
            "truncate",
            "under",
            "union",
            "unique",
            "unknown",
            "unlink",
            "unnest",
            "until",
            "update",
            "usage",
            "user",
            "using",
            "value",
            "values",
            "varchar",
            "varchar2",
            "varray",
            "verify",
            "view",
            "when",
            "whenever",
            "where",
            "while",
            "window",
            "with",
            "within",
            "without",
            "work",
            "year",
            "yes",
            "zone",
        }

        def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str:
            from_tz = expression.args.get("source_tz")
            to_tz = expression.args.get("target_tz")
            datetime = expression.args.get("timestamp")
            options = expression.args.get("options")

            return self.func("CONVERT_TZ", datetime, from_tz, to_tz, options)

        def if_sql(self, expression: exp.If) -> str:
            this = self.sql(expression, "this")
            true = self.sql(expression, "true")
            false = self.sql(expression, "false")
            return f"IF {this} THEN {true} ELSE {false} ENDIF"

        def collate_sql(self, expression: exp.Collate) -> str:
            return self.sql(expression.this)

        def jsonextract_sql(self, expression: exp.JSONExtract) -> str:
            sql = self.func(
                "JSON_EXTRACT", expression.this, expression.expression, *expression.expressions
            )

            emits = self.sql(expression, "emits")
            if emits:
                sql = f"{sql} EMITS {emits}"

            return sql

        @unsupported_args("flag")
        def regexplike_sql(self, expression: exp.RegexpLike) -> str:
            if not expression.args.get("full_match"):
                pattern = expression.expression
                if pattern.is_string:
                    expression.set("expression", exp.Literal.string(f".*{pattern.name}.*"))
                else:
                    expression.set(
                        "expression",
                        exp.Paren(
                            this=exp.Concat(
                                expressions=[
                                    exp.Literal.string(".*"),
                                    pattern,
                                    exp.Literal.string(".*"),
                                ]
                            )
                        ),
                    )
            return self.binary(expression, "REGEXP_LIKE")
