Newfoundland Missing Persons App: A centralized platform to track, share, and raise awareness about missing individuals in Newfoundland. Designed to aid search efforts, foster community involvement, and provide timely updates. Open for contributions.
#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os
import sys
import dotenv

dotenv.load_dotenv(
    os.path.join(os.path.dirname(os.path.dirname(__file__)), '.env')
)

def main():
    """Run administrative tasks."""
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "server.settings")
    try:
        from django.core.management import execute_from_command_line
    except ImportError as exc:
        raise ImportError(
            "Couldn't import Django. Are you sure it's installed and "
            "available on your PYTHONPATH environment variable? Did you "
            "forget to activate a virtual environment?"
        ) from exc
    execute_from_command_line(sys.argv)


if __name__ == "__main__":
    main()

server/manage.py
PrinceDisant/LostInNL
from pathlib import Path
from decouple import config
import os

# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent


# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = "django-insecure-20gpx9qb8oi_u4f#d+$963y5&%f9ad53n99*0kag=hgm8+_2t("

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True

ALLOWED_HOSTS = []


# Application definition

INSTALLED_APPS = [
    "django.contrib.admin",
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "django.contrib.sessions",
    "django.contrib.messages",
    "django.contrib.staticfiles",
    "website",
]

MIDDLEWARE = [
    "django.middleware.security.SecurityMiddleware",
    "django.contrib.sessions.middleware.SessionMiddleware",
    "django.middleware.common.CommonMiddleware",
    "django.middleware.csrf.CsrfViewMiddleware",
    "django.contrib.auth.middleware.AuthenticationMiddleware",
    "django.contrib.messages.middleware.MessageMiddleware",
    "django.middleware.clickjacking.XFrameOptionsMiddleware",
]

ROOT_URLCONF = "server.urls"

TEMPLATES = [
    {
        "BACKEND": "django.template.backends.django.DjangoTemplates",
        "DIRS": [],
        "APP_DIRS": True,
        "OPTIONS": {
            "context_processors": [
                "django.template.context_processors.debug",
                "django.template.context_processors.request",
                "django.contrib.auth.context_processors.auth",
                "django.contrib.messages.context_processors.messages",
            ],
        },
    },
]

WSGI_APPLICATION = "server.wsgi.application"


# Database

if os.environ.get('GITHUB_ACTIONS'):  # Check if it's running in GitHub Actions
    DATABASES = {
        'default': {
            'ENGINE': 'django.db.backends.mysql',
            'NAME': 'test_LostInNL',
            'USER': 'developer',
            'PASSWORD': 'strongpassword',
            'HOST': '127.0.0.1',
            'PORT': '3306',
        }
    }
else:
    DATABASES = {
        'default': {
            'ENGINE': 'django.db.backends.mysql',
            'NAME': config('DATABASE_NAME'),
            'USER': config('DATABASE_USER'),
            'PASSWORD': config('DATABASE_PASSWORD'),
            'HOST': config('DATABASE_HOST'),
            'PORT': config('DATABASE_PORT', default='3306'),
        }
    }


# Password validation
# https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators

AUTH_PASSWORD_VALIDATORS = [
    {
        "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
    },
    {
        "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
    },
    {
        "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
    },
    {
        "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
    },
]


# Internationalization
# https://docs.djangoproject.com/en/4.2/topics/i18n/

LANGUAGE_CODE = "en-us"

TIME_ZONE = "UTC"

USE_I18N = True

USE_TZ = True


# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/4.2/howto/static-files/

STATIC_URL = "static/"

# Default primary key field type
# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field

DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"

server/server/settings.py
PrinceDisant/LostInNL
from django.db import models
from django.contrib.auth.models import User
from datetime import datetime

# Create your models here.
STATUS_CHOICES = [('missing', 'Missing'), ('found', 'Found')]
ACTION_CHOICES = [('insert', 'Insert'), ('update', 'Update'), ('delete', 'Delete')]


# user's pk - kwargs={'pk' : self_id}

class MissingPerson(models.Model):
    full_name = models.CharField(max_length=255)
    last_seen_date = models.DateField()
    last_seen_location = models.CharField(max_length=255)
    status = models.CharField(max_length=7, choices=STATUS_CHOICES, default=STATUS_CHOICES[0][0])
    picture_url = models.TextField()
    contact_info = models.TextField()
    age = models.IntegerField()
    height = models.FloatField()
    weight = models.FloatField()
    eye_color = models.CharField(max_length=50)
    case_number = models.CharField(max_length=255)
    #user FK
    user = models.ForeignKey(User, on_delete=models.CASCADE)

    def __str__(self):
        return f"{self.full_name} ({self.id})"


class AuditTrail(models.Model):
    #user FK
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    table_name = models.CharField(max_length=255)
    action = models.CharField(max_length=6, choices=ACTION_CHOICES)
    record_id = models.IntegerField()
    #user FK
    modified_by = models.ForeignKey(User, on_delete=models.CASCADE)
    modified_at = models.DateTimeField(auto_now_add=True)

class Forum(models.Model):
    #missing person FK
    missing_person = models.ForeignKey(MissingPerson, on_delete=models.CASCADE)
    title = models.TextField()
    #user FK
    user = models.ForeignKey(User, on_delete=models.CASCADE)

class Comment(models.Model):
    #forum FK
    forum = models.ForeignKey(Forum, on_delete=models.CASCADE)
    content = models.TextField()
    #user FK
    user = models.ForeignKey(User, on_delete=models.CASCADE)

    def __str__(self):
        return f"{self.user.first_name} {self.user.last_name} {self.content}"

class Media(models.Model):
    #missing person FK
    missing_person = models.ForeignKey(MissingPerson, on_delete=models.CASCADE)
    media_url = models.TextField()
    #user FK
    user = models.ForeignKey(User, on_delete=models.CASCADE)

    def __str__(self):
        return f"{self.missing_person_id} @{self.media_url}"

server/website/models.py
PrinceDisant/LostInNL
from django.test import TestCase

class HelloWorldTest(TestCase):
    def test_hello_world(self):
        self.assertTrue(True)

server/website/tests.py
PrinceDisant/LostInNL
from django.urls import path
from . import views

urlpatterns = [
    path('', views.index, name='index'),
    path('index.html', views.index, name='index'),
    path('about.html', views.about, name='about'),
    path('contact.html', views.contact, name='contact'),
    path('listings.html', views.listings, name='listings'),
    path('resources.html', views.resources, name='resources'),
    path('submit.html', views.submit, name='submit'),
]


server/website/urls.py
PrinceDisant/LostInNL
from django.shortcuts import render

# Create your views here.

def index(request):
    return render(request, 'website/index.html', {})

def about(request): 
    return render(request, 'website/about.html',)    

def contact(request):
    return render(request, 'website/contact.html',)

def listings(request):
    return render(request, 'website/listings.html',)

def resources(request):
    return render(request, 'website/resources.html',)

def submit(request):
    return render(request, 'website/submit.html',)

server/website/views.py
PrinceDisant/LostInNL
"""MySQL FLAG Constants

These flags are used along with the FIELD_TYPE to indicate various
properties of columns in a result set.

"""

NOT_NULL = 1
PRI_KEY = 2
UNIQUE_KEY = 4
MULTIPLE_KEY = 8
BLOB = 16
UNSIGNED = 32
ZEROFILL = 64
BINARY = 128
ENUM = 256
AUTO_INCREMENT = 512
TIMESTAMP = 1024
SET = 2048
NUM = 32768
PART_KEY = 16384
GROUP = 32768
UNIQUE = 65536

virt/lib/python3.9/site-packages/MySQLdb/constants/FLAG.py
PrinceDisant/LostInNL
"""
Settings and configuration for Django.

Read values from the module specified by the DJANGO_SETTINGS_MODULE environment
variable, and then from django.conf.global_settings; see the global_settings.py
for a list of all possible variables.
"""

import importlib
import os
import time
import traceback
import warnings
from pathlib import Path

import django
from django.conf import global_settings
from django.core.exceptions import ImproperlyConfigured
from django.utils.deprecation import RemovedInDjango50Warning, RemovedInDjango51Warning
from django.utils.functional import LazyObject, empty

ENVIRONMENT_VARIABLE = "DJANGO_SETTINGS_MODULE"
DEFAULT_STORAGE_ALIAS = "default"
STATICFILES_STORAGE_ALIAS = "staticfiles"

# RemovedInDjango50Warning
USE_DEPRECATED_PYTZ_DEPRECATED_MSG = (
    "The USE_DEPRECATED_PYTZ setting, and support for pytz timezones is "
    "deprecated in favor of the stdlib zoneinfo module. Please update your "
    "code to use zoneinfo and remove the USE_DEPRECATED_PYTZ setting."
)

USE_L10N_DEPRECATED_MSG = (
    "The USE_L10N setting is deprecated. Starting with Django 5.0, localized "
    "formatting of data will always be enabled. For example Django will "
    "display numbers and dates using the format of the current locale."
)

CSRF_COOKIE_MASKED_DEPRECATED_MSG = (
    "The CSRF_COOKIE_MASKED transitional setting is deprecated. Support for "
    "it will be removed in Django 5.0."
)

DEFAULT_FILE_STORAGE_DEPRECATED_MSG = (
    "The DEFAULT_FILE_STORAGE setting is deprecated. Use STORAGES instead."
)

STATICFILES_STORAGE_DEPRECATED_MSG = (
    "The STATICFILES_STORAGE setting is deprecated. Use STORAGES instead."
)


class SettingsReference(str):
    """
    String subclass which references a current settings value. It's treated as
    the value in memory but serializes to a settings.NAME attribute reference.
    """

    def __new__(self, value, setting_name):
        return str.__new__(self, value)

    def __init__(self, value, setting_name):
        self.setting_name = setting_name


class LazySettings(LazyObject):
    """
    A lazy proxy for either global Django settings or a custom settings object.
    The user can manually configure settings prior to using them. Otherwise,
    Django uses the settings module pointed to by DJANGO_SETTINGS_MODULE.
    """

    def _setup(self, name=None):
        """
        Load the settings module pointed to by the environment variable. This
        is used the first time settings are needed, if the user hasn't
        configured settings manually.
        """
        settings_module = os.environ.get(ENVIRONMENT_VARIABLE)
        if not settings_module:
            desc = ("setting %s" % name) if name else "settings"
            raise ImproperlyConfigured(
                "Requested %s, but settings are not configured. "
                "You must either define the environment variable %s "
                "or call settings.configure() before accessing settings."
                % (desc, ENVIRONMENT_VARIABLE)
            )

        self._wrapped = Settings(settings_module)

    def __repr__(self):
        # Hardcode the class name as otherwise it yields 'Settings'.
        if self._wrapped is empty:
            return "<LazySettings [Unevaluated]>"
        return '<LazySettings "%(settings_module)s">' % {
            "settings_module": self._wrapped.SETTINGS_MODULE,
        }

    def __getattr__(self, name):
        """Return the value of a setting and cache it in self.__dict__."""
        if (_wrapped := self._wrapped) is empty:
            self._setup(name)
            _wrapped = self._wrapped
        val = getattr(_wrapped, name)

        # Special case some settings which require further modification.
        # This is done here for performance reasons so the modified value is cached.
        if name in {"MEDIA_URL", "STATIC_URL"} and val is not None:
            val = self._add_script_prefix(val)
        elif name == "SECRET_KEY" and not val:
            raise ImproperlyConfigured("The SECRET_KEY setting must not be empty.")

        self.__dict__[name] = val
        return val

    def __setattr__(self, name, value):
        """
        Set the value of setting. Clear all cached values if _wrapped changes
        (@override_settings does this) or clear single values when set.
        """
        if name == "_wrapped":
            self.__dict__.clear()
        else:
            self.__dict__.pop(name, None)
        super().__setattr__(name, value)

    def __delattr__(self, name):
        """Delete a setting and clear it from cache if needed."""
        super().__delattr__(name)
        self.__dict__.pop(name, None)

    def configure(self, default_settings=global_settings, **options):
        """
        Called to manually configure the settings. The 'default_settings'
        parameter sets where to retrieve any unspecified values from (its
        argument must support attribute access (__getattr__)).
        """
        if self._wrapped is not empty:
            raise RuntimeError("Settings already configured.")
        holder = UserSettingsHolder(default_settings)
        for name, value in options.items():
            if not name.isupper():
                raise TypeError("Setting %r must be uppercase." % name)
            setattr(holder, name, value)
        self._wrapped = holder

    @staticmethod
    def _add_script_prefix(value):
        """
        Add SCRIPT_NAME prefix to relative paths.

        Useful when the app is being served at a subpath and manually prefixing
        subpath to STATIC_URL and MEDIA_URL in settings is inconvenient.
        """
        # Don't apply prefix to absolute paths and URLs.
        if value.startswith(("http://", "https://", "/")):
            return value
        from django.urls import get_script_prefix

        return "%s%s" % (get_script_prefix(), value)

    @property
    def configured(self):
        """Return True if the settings have already been configured."""
        return self._wrapped is not empty

    def _show_deprecation_warning(self, message, category):
        stack = traceback.extract_stack()
        # Show a warning if the setting is used outside of Django.
        # Stack index: -1 this line, -2 the property, -3 the
        # LazyObject __getattribute__(), -4 the caller.
        filename, _, _, _ = stack[-4]
        if not filename.startswith(os.path.dirname(django.__file__)):
            warnings.warn(message, category, stacklevel=2)

    @property
    def USE_L10N(self):
        self._show_deprecation_warning(
            USE_L10N_DEPRECATED_MSG, RemovedInDjango50Warning
        )
        return self.__getattr__("USE_L10N")

    # RemovedInDjango50Warning.
    @property
    def _USE_L10N_INTERNAL(self):
        # Special hook to avoid checking a traceback in internal use on hot
        # paths.
        return self.__getattr__("USE_L10N")

    # RemovedInDjango51Warning.
    @property
    def DEFAULT_FILE_STORAGE(self):
        self._show_deprecation_warning(
            DEFAULT_FILE_STORAGE_DEPRECATED_MSG, RemovedInDjango51Warning
        )
        return self.__getattr__("DEFAULT_FILE_STORAGE")

    # RemovedInDjango51Warning.
    @property
    def STATICFILES_STORAGE(self):
        self._show_deprecation_warning(
            STATICFILES_STORAGE_DEPRECATED_MSG, RemovedInDjango51Warning
        )
        return self.__getattr__("STATICFILES_STORAGE")


class Settings:
    def __init__(self, settings_module):
        # update this dict from global settings (but only for ALL_CAPS settings)
        for setting in dir(global_settings):
            if setting.isupper():
                setattr(self, setting, getattr(global_settings, setting))

        # store the settings module in case someone later cares
        self.SETTINGS_MODULE = settings_module

        mod = importlib.import_module(self.SETTINGS_MODULE)

        tuple_settings = (
            "ALLOWED_HOSTS",
            "INSTALLED_APPS",
            "TEMPLATE_DIRS",
            "LOCALE_PATHS",
            "SECRET_KEY_FALLBACKS",
        )
        self._explicit_settings = set()
        for setting in dir(mod):
            if setting.isupper():
                setting_value = getattr(mod, setting)

                if setting in tuple_settings and not isinstance(
                    setting_value, (list, tuple)
                ):
                    raise ImproperlyConfigured(
                        "The %s setting must be a list or a tuple." % setting
                    )
                setattr(self, setting, setting_value)
                self._explicit_settings.add(setting)

        if self.USE_TZ is False and not self.is_overridden("USE_TZ"):
            warnings.warn(
                "The default value of USE_TZ will change from False to True "
                "in Django 5.0. Set USE_TZ to False in your project settings "
                "if you want to keep the current default behavior.",
                category=RemovedInDjango50Warning,
            )

        if self.is_overridden("USE_DEPRECATED_PYTZ"):
            warnings.warn(USE_DEPRECATED_PYTZ_DEPRECATED_MSG, RemovedInDjango50Warning)

        if self.is_overridden("CSRF_COOKIE_MASKED"):
            warnings.warn(CSRF_COOKIE_MASKED_DEPRECATED_MSG, RemovedInDjango50Warning)

        if hasattr(time, "tzset") and self.TIME_ZONE:
            # When we can, attempt to validate the timezone. If we can't find
            # this file, no check happens and it's harmless.
            zoneinfo_root = Path("/usr/share/zoneinfo")
            zone_info_file = zoneinfo_root.joinpath(*self.TIME_ZONE.split("/"))
            if zoneinfo_root.exists() and not zone_info_file.exists():
                raise ValueError("Incorrect timezone setting: %s" % self.TIME_ZONE)
            # Move the time zone info into os.environ. See ticket #2315 for why
            # we don't do this unconditionally (breaks Windows).
            os.environ["TZ"] = self.TIME_ZONE
            time.tzset()

        if self.is_overridden("USE_L10N"):
            warnings.warn(USE_L10N_DEPRECATED_MSG, RemovedInDjango50Warning)

        if self.is_overridden("DEFAULT_FILE_STORAGE"):
            if self.is_overridden("STORAGES"):
                raise ImproperlyConfigured(
                    "DEFAULT_FILE_STORAGE/STORAGES are mutually exclusive."
                )
            self.STORAGES[DEFAULT_STORAGE_ALIAS] = {
                "BACKEND": self.DEFAULT_FILE_STORAGE
            }
            warnings.warn(DEFAULT_FILE_STORAGE_DEPRECATED_MSG, RemovedInDjango51Warning)

        if self.is_overridden("STATICFILES_STORAGE"):
            if self.is_overridden("STORAGES"):
                raise ImproperlyConfigured(
                    "STATICFILES_STORAGE/STORAGES are mutually exclusive."
                )
            self.STORAGES[STATICFILES_STORAGE_ALIAS] = {
                "BACKEND": self.STATICFILES_STORAGE
            }
            warnings.warn(STATICFILES_STORAGE_DEPRECATED_MSG, RemovedInDjango51Warning)
        # RemovedInDjango51Warning.
        if self.is_overridden("STORAGES"):
            setattr(
                self,
                "DEFAULT_FILE_STORAGE",
                self.STORAGES.get(DEFAULT_STORAGE_ALIAS, {}).get("BACKEND"),
            )
            setattr(
                self,
                "STATICFILES_STORAGE",
                self.STORAGES.get(STATICFILES_STORAGE_ALIAS, {}).get("BACKEND"),
            )

    def is_overridden(self, setting):
        return setting in self._explicit_settings

    def __repr__(self):
        return '<%(cls)s "%(settings_module)s">' % {
            "cls": self.__class__.__name__,
            "settings_module": self.SETTINGS_MODULE,
        }


class UserSettingsHolder:
    """Holder for user configured settings."""

    # SETTINGS_MODULE doesn't make much sense in the manually configured
    # (standalone) case.
    SETTINGS_MODULE = None

    def __init__(self, default_settings):
        """
        Requests for configuration variables not in this class are satisfied
        from the module specified in default_settings (if possible).
        """
        self.__dict__["_deleted"] = set()
        self.default_settings = default_settings

    def __getattr__(self, name):
        if not name.isupper() or name in self._deleted:
            raise AttributeError
        return getattr(self.default_settings, name)

    def __setattr__(self, name, value):
        self._deleted.discard(name)
        if name == "USE_L10N":
            warnings.warn(USE_L10N_DEPRECATED_MSG, RemovedInDjango50Warning)
        if name == "CSRF_COOKIE_MASKED":
            warnings.warn(CSRF_COOKIE_MASKED_DEPRECATED_MSG, RemovedInDjango50Warning)
        if name == "DEFAULT_FILE_STORAGE":
            self.STORAGES[DEFAULT_STORAGE_ALIAS] = {
                "BACKEND": self.DEFAULT_FILE_STORAGE
            }
            warnings.warn(DEFAULT_FILE_STORAGE_DEPRECATED_MSG, RemovedInDjango51Warning)
        if name == "STATICFILES_STORAGE":
            self.STORAGES[STATICFILES_STORAGE_ALIAS] = {
                "BACKEND": self.STATICFILES_STORAGE
            }
            warnings.warn(STATICFILES_STORAGE_DEPRECATED_MSG, RemovedInDjango51Warning)
        super().__setattr__(name, value)
        if name == "USE_DEPRECATED_PYTZ":
            warnings.warn(USE_DEPRECATED_PYTZ_DEPRECATED_MSG, RemovedInDjango50Warning)
        # RemovedInDjango51Warning.
        if name == "STORAGES":
            if default_file_storage := self.STORAGES.get(DEFAULT_STORAGE_ALIAS):
                super().__setattr__(
                    "DEFAULT_FILE_STORAGE", default_file_storage.get("BACKEND")
                )
            else:
                self.STORAGES.setdefault(
                    DEFAULT_STORAGE_ALIAS,
                    {"BACKEND": "django.core.files.storage.FileSystemStorage"},
                )
            if staticfiles_storage := self.STORAGES.get(STATICFILES_STORAGE_ALIAS):
                super().__setattr__(
                    "STATICFILES_STORAGE", staticfiles_storage.get("BACKEND")
                )
            else:
                self.STORAGES.setdefault(
                    STATICFILES_STORAGE_ALIAS,
                    {
                        "BACKEND": (
                            "django.contrib.staticfiles.storage.StaticFilesStorage"
                        ),
                    },
                )

    def __delattr__(self, name):
        self._deleted.add(name)
        if hasattr(self, name):
            super().__delattr__(name)

    def __dir__(self):
        return sorted(
            s
            for s in [*self.__dict__, *dir(self.default_settings)]
            if s not in self._deleted
        )

    def is_overridden(self, setting):
        deleted = setting in self._deleted
        set_locally = setting in self.__dict__
        set_on_default = getattr(
            self.default_settings, "is_overridden", lambda s: False
        )(setting)
        return deleted or set_locally or set_on_default

    def __repr__(self):
        return "<%(cls)s>" % {
            "cls": self.__class__.__name__,
        }


settings = LazySettings()

virt/lib/python3.9/site-packages/django/conf/__init__ 2.py
PrinceDisant/LostInNL
"""
Settings and configuration for Django.

Read values from the module specified by the DJANGO_SETTINGS_MODULE environment
variable, and then from django.conf.global_settings; see the global_settings.py
for a list of all possible variables.
"""

import importlib
import os
import time
import traceback
import warnings
from pathlib import Path

import django
from django.conf import global_settings
from django.core.exceptions import ImproperlyConfigured
from django.utils.deprecation import RemovedInDjango50Warning, RemovedInDjango51Warning
from django.utils.functional import LazyObject, empty

ENVIRONMENT_VARIABLE = "DJANGO_SETTINGS_MODULE"
DEFAULT_STORAGE_ALIAS = "default"
STATICFILES_STORAGE_ALIAS = "staticfiles"

# RemovedInDjango50Warning
USE_DEPRECATED_PYTZ_DEPRECATED_MSG = (
    "The USE_DEPRECATED_PYTZ setting, and support for pytz timezones is "
    "deprecated in favor of the stdlib zoneinfo module. Please update your "
    "code to use zoneinfo and remove the USE_DEPRECATED_PYTZ setting."
)

USE_L10N_DEPRECATED_MSG = (
    "The USE_L10N setting is deprecated. Starting with Django 5.0, localized "
    "formatting of data will always be enabled. For example Django will "
    "display numbers and dates using the format of the current locale."
)

CSRF_COOKIE_MASKED_DEPRECATED_MSG = (
    "The CSRF_COOKIE_MASKED transitional setting is deprecated. Support for "
    "it will be removed in Django 5.0."
)

DEFAULT_FILE_STORAGE_DEPRECATED_MSG = (
    "The DEFAULT_FILE_STORAGE setting is deprecated. Use STORAGES instead."
)

STATICFILES_STORAGE_DEPRECATED_MSG = (
    "The STATICFILES_STORAGE setting is deprecated. Use STORAGES instead."
)


class SettingsReference(str):
    """
    String subclass which references a current settings value. It's treated as
    the value in memory but serializes to a settings.NAME attribute reference.
    """

    def __new__(self, value, setting_name):
        return str.__new__(self, value)

    def __init__(self, value, setting_name):
        self.setting_name = setting_name


class LazySettings(LazyObject):
    """
    A lazy proxy for either global Django settings or a custom settings object.
    The user can manually configure settings prior to using them. Otherwise,
    Django uses the settings module pointed to by DJANGO_SETTINGS_MODULE.
    """

    def _setup(self, name=None):
        """
        Load the settings module pointed to by the environment variable. This
        is used the first time settings are needed, if the user hasn't
        configured settings manually.
        """
        settings_module = os.environ.get(ENVIRONMENT_VARIABLE)
        if not settings_module:
            desc = ("setting %s" % name) if name else "settings"
            raise ImproperlyConfigured(
                "Requested %s, but settings are not configured. "
                "You must either define the environment variable %s "
                "or call settings.configure() before accessing settings."
                % (desc, ENVIRONMENT_VARIABLE)
            )

        self._wrapped = Settings(settings_module)

    def __repr__(self):
        # Hardcode the class name as otherwise it yields 'Settings'.
        if self._wrapped is empty:
            return "<LazySettings [Unevaluated]>"
        return '<LazySettings "%(settings_module)s">' % {
            "settings_module": self._wrapped.SETTINGS_MODULE,
        }

    def __getattr__(self, name):
        """Return the value of a setting and cache it in self.__dict__."""
        if (_wrapped := self._wrapped) is empty:
            self._setup(name)
            _wrapped = self._wrapped
        val = getattr(_wrapped, name)

        # Special case some settings which require further modification.
        # This is done here for performance reasons so the modified value is cached.
        if name in {"MEDIA_URL", "STATIC_URL"} and val is not None:
            val = self._add_script_prefix(val)
        elif name == "SECRET_KEY" and not val:
            raise ImproperlyConfigured("The SECRET_KEY setting must not be empty.")

        self.__dict__[name] = val
        return val

    def __setattr__(self, name, value):
        """
        Set the value of setting. Clear all cached values if _wrapped changes
        (@override_settings does this) or clear single values when set.
        """
        if name == "_wrapped":
            self.__dict__.clear()
        else:
            self.__dict__.pop(name, None)
        super().__setattr__(name, value)

    def __delattr__(self, name):
        """Delete a setting and clear it from cache if needed."""
        super().__delattr__(name)
        self.__dict__.pop(name, None)

    def configure(self, default_settings=global_settings, **options):
        """
        Called to manually configure the settings. The 'default_settings'
        parameter sets where to retrieve any unspecified values from (its
        argument must support attribute access (__getattr__)).
        """
        if self._wrapped is not empty:
            raise RuntimeError("Settings already configured.")
        holder = UserSettingsHolder(default_settings)
        for name, value in options.items():
            if not name.isupper():
                raise TypeError("Setting %r must be uppercase." % name)
            setattr(holder, name, value)
        self._wrapped = holder

    @staticmethod
    def _add_script_prefix(value):
        """
        Add SCRIPT_NAME prefix to relative paths.

        Useful when the app is being served at a subpath and manually prefixing
        subpath to STATIC_URL and MEDIA_URL in settings is inconvenient.
        """
        # Don't apply prefix to absolute paths and URLs.
        if value.startswith(("http://", "https://", "/")):
            return value
        from django.urls import get_script_prefix

        return "%s%s" % (get_script_prefix(), value)

    @property
    def configured(self):
        """Return True if the settings have already been configured."""
        return self._wrapped is not empty

    def _show_deprecation_warning(self, message, category):
        stack = traceback.extract_stack()
        # Show a warning if the setting is used outside of Django.
        # Stack index: -1 this line, -2 the property, -3 the
        # LazyObject __getattribute__(), -4 the caller.
        filename, _, _, _ = stack[-4]
        if not filename.startswith(os.path.dirname(django.__file__)):
            warnings.warn(message, category, stacklevel=2)

    @property
    def USE_L10N(self):
        self._show_deprecation_warning(
            USE_L10N_DEPRECATED_MSG, RemovedInDjango50Warning
        )
        return self.__getattr__("USE_L10N")

    # RemovedInDjango50Warning.
    @property
    def _USE_L10N_INTERNAL(self):
        # Special hook to avoid checking a traceback in internal use on hot
        # paths.
        return self.__getattr__("USE_L10N")

    # RemovedInDjango51Warning.
    @property
    def DEFAULT_FILE_STORAGE(self):
        self._show_deprecation_warning(
            DEFAULT_FILE_STORAGE_DEPRECATED_MSG, RemovedInDjango51Warning
        )
        return self.__getattr__("DEFAULT_FILE_STORAGE")

    # RemovedInDjango51Warning.
    @property
    def STATICFILES_STORAGE(self):
        self._show_deprecation_warning(
            STATICFILES_STORAGE_DEPRECATED_MSG, RemovedInDjango51Warning
        )
        return self.__getattr__("STATICFILES_STORAGE")


class Settings:
    def __init__(self, settings_module):
        # update this dict from global settings (but only for ALL_CAPS settings)
        for setting in dir(global_settings):
            if setting.isupper():
                setattr(self, setting, getattr(global_settings, setting))

        # store the settings module in case someone later cares
        self.SETTINGS_MODULE = settings_module

        mod = importlib.import_module(self.SETTINGS_MODULE)

        tuple_settings = (
            "ALLOWED_HOSTS",
            "INSTALLED_APPS",
            "TEMPLATE_DIRS",
            "LOCALE_PATHS",
            "SECRET_KEY_FALLBACKS",
        )
        self._explicit_settings = set()
        for setting in dir(mod):
            if setting.isupper():
                setting_value = getattr(mod, setting)

                if setting in tuple_settings and not isinstance(
                    setting_value, (list, tuple)
                ):
                    raise ImproperlyConfigured(
                        "The %s setting must be a list or a tuple." % setting
                    )
                setattr(self, setting, setting_value)
                self._explicit_settings.add(setting)

        if self.USE_TZ is False and not self.is_overridden("USE_TZ"):
            warnings.warn(
                "The default value of USE_TZ will change from False to True "
                "in Django 5.0. Set USE_TZ to False in your project settings "
                "if you want to keep the current default behavior.",
                category=RemovedInDjango50Warning,
            )

        if self.is_overridden("USE_DEPRECATED_PYTZ"):
            warnings.warn(USE_DEPRECATED_PYTZ_DEPRECATED_MSG, RemovedInDjango50Warning)

        if self.is_overridden("CSRF_COOKIE_MASKED"):
            warnings.warn(CSRF_COOKIE_MASKED_DEPRECATED_MSG, RemovedInDjango50Warning)

        if hasattr(time, "tzset") and self.TIME_ZONE:
            # When we can, attempt to validate the timezone. If we can't find
            # this file, no check happens and it's harmless.
            zoneinfo_root = Path("/usr/share/zoneinfo")
            zone_info_file = zoneinfo_root.joinpath(*self.TIME_ZONE.split("/"))
            if zoneinfo_root.exists() and not zone_info_file.exists():
                raise ValueError("Incorrect timezone setting: %s" % self.TIME_ZONE)
            # Move the time zone info into os.environ. See ticket #2315 for why
            # we don't do this unconditionally (breaks Windows).
            os.environ["TZ"] = self.TIME_ZONE
            time.tzset()

        if self.is_overridden("USE_L10N"):
            warnings.warn(USE_L10N_DEPRECATED_MSG, RemovedInDjango50Warning)

        if self.is_overridden("DEFAULT_FILE_STORAGE"):
            if self.is_overridden("STORAGES"):
                raise ImproperlyConfigured(
                    "DEFAULT_FILE_STORAGE/STORAGES are mutually exclusive."
                )
            self.STORAGES[DEFAULT_STORAGE_ALIAS] = {
                "BACKEND": self.DEFAULT_FILE_STORAGE
            }
            warnings.warn(DEFAULT_FILE_STORAGE_DEPRECATED_MSG, RemovedInDjango51Warning)

        if self.is_overridden("STATICFILES_STORAGE"):
            if self.is_overridden("STORAGES"):
                raise ImproperlyConfigured(
                    "STATICFILES_STORAGE/STORAGES are mutually exclusive."
                )
            self.STORAGES[STATICFILES_STORAGE_ALIAS] = {
                "BACKEND": self.STATICFILES_STORAGE
            }
            warnings.warn(STATICFILES_STORAGE_DEPRECATED_MSG, RemovedInDjango51Warning)
        # RemovedInDjango51Warning.
        if self.is_overridden("STORAGES"):
            setattr(
                self,
                "DEFAULT_FILE_STORAGE",
                self.STORAGES.get(DEFAULT_STORAGE_ALIAS, {}).get("BACKEND"),
            )
            setattr(
                self,
                "STATICFILES_STORAGE",
                self.STORAGES.get(STATICFILES_STORAGE_ALIAS, {}).get("BACKEND"),
            )

    def is_overridden(self, setting):
        return setting in self._explicit_settings

    def __repr__(self):
        return '<%(cls)s "%(settings_module)s">' % {
            "cls": self.__class__.__name__,
            "settings_module": self.SETTINGS_MODULE,
        }


class UserSettingsHolder:
    """Holder for user configured settings."""

    # SETTINGS_MODULE doesn't make much sense in the manually configured
    # (standalone) case.
    SETTINGS_MODULE = None

    def __init__(self, default_settings):
        """
        Requests for configuration variables not in this class are satisfied
        from the module specified in default_settings (if possible).
        """
        self.__dict__["_deleted"] = set()
        self.default_settings = default_settings

    def __getattr__(self, name):
        if not name.isupper() or name in self._deleted:
            raise AttributeError
        return getattr(self.default_settings, name)

    def __setattr__(self, name, value):
        self._deleted.discard(name)
        if name == "USE_L10N":
            warnings.warn(USE_L10N_DEPRECATED_MSG, RemovedInDjango50Warning)
        if name == "CSRF_COOKIE_MASKED":
            warnings.warn(CSRF_COOKIE_MASKED_DEPRECATED_MSG, RemovedInDjango50Warning)
        if name == "DEFAULT_FILE_STORAGE":
            self.STORAGES[DEFAULT_STORAGE_ALIAS] = {
                "BACKEND": self.DEFAULT_FILE_STORAGE
            }
            warnings.warn(DEFAULT_FILE_STORAGE_DEPRECATED_MSG, RemovedInDjango51Warning)
        if name == "STATICFILES_STORAGE":
            self.STORAGES[STATICFILES_STORAGE_ALIAS] = {
                "BACKEND": self.STATICFILES_STORAGE
            }
            warnings.warn(STATICFILES_STORAGE_DEPRECATED_MSG, RemovedInDjango51Warning)
        super().__setattr__(name, value)
        if name == "USE_DEPRECATED_PYTZ":
            warnings.warn(USE_DEPRECATED_PYTZ_DEPRECATED_MSG, RemovedInDjango50Warning)
        # RemovedInDjango51Warning.
        if name == "STORAGES":
            if default_file_storage := self.STORAGES.get(DEFAULT_STORAGE_ALIAS):
                super().__setattr__(
                    "DEFAULT_FILE_STORAGE", default_file_storage.get("BACKEND")
                )
            else:
                self.STORAGES.setdefault(
                    DEFAULT_STORAGE_ALIAS,
                    {"BACKEND": "django.core.files.storage.FileSystemStorage"},
                )
            if staticfiles_storage := self.STORAGES.get(STATICFILES_STORAGE_ALIAS):
                super().__setattr__(
                    "STATICFILES_STORAGE", staticfiles_storage.get("BACKEND")
                )
            else:
                self.STORAGES.setdefault(
                    STATICFILES_STORAGE_ALIAS,
                    {
                        "BACKEND": (
                            "django.contrib.staticfiles.storage.StaticFilesStorage"
                        ),
                    },
                )

    def __delattr__(self, name):
        self._deleted.add(name)
        if hasattr(self, name):
            super().__delattr__(name)

    def __dir__(self):
        return sorted(
            s
            for s in [*self.__dict__, *dir(self.default_settings)]
            if s not in self._deleted
        )

    def is_overridden(self, setting):
        deleted = setting in self._deleted
        set_locally = setting in self.__dict__
        set_on_default = getattr(
            self.default_settings, "is_overridden", lambda s: False
        )(setting)
        return deleted or set_locally or set_on_default

    def __repr__(self):
        return "<%(cls)s>" % {
            "cls": self.__class__.__name__,
        }


settings = LazySettings()

virt/lib/python3.9/site-packages/django/conf/__init__ 3.py
PrinceDisant/LostInNL
"""
Settings and configuration for Django.

Read values from the module specified by the DJANGO_SETTINGS_MODULE environment
variable, and then from django.conf.global_settings; see the global_settings.py
for a list of all possible variables.
"""

import importlib
import os
import time
import traceback
import warnings
from pathlib import Path

import django
from django.conf import global_settings
from django.core.exceptions import ImproperlyConfigured
from django.utils.deprecation import RemovedInDjango50Warning, RemovedInDjango51Warning
from django.utils.functional import LazyObject, empty

ENVIRONMENT_VARIABLE = "DJANGO_SETTINGS_MODULE"
DEFAULT_STORAGE_ALIAS = "default"
STATICFILES_STORAGE_ALIAS = "staticfiles"

# RemovedInDjango50Warning
USE_DEPRECATED_PYTZ_DEPRECATED_MSG = (
    "The USE_DEPRECATED_PYTZ setting, and support for pytz timezones is "
    "deprecated in favor of the stdlib zoneinfo module. Please update your "
    "code to use zoneinfo and remove the USE_DEPRECATED_PYTZ setting."
)

USE_L10N_DEPRECATED_MSG = (
    "The USE_L10N setting is deprecated. Starting with Django 5.0, localized "
    "formatting of data will always be enabled. For example Django will "
    "display numbers and dates using the format of the current locale."
)

CSRF_COOKIE_MASKED_DEPRECATED_MSG = (
    "The CSRF_COOKIE_MASKED transitional setting is deprecated. Support for "
    "it will be removed in Django 5.0."
)

DEFAULT_FILE_STORAGE_DEPRECATED_MSG = (
    "The DEFAULT_FILE_STORAGE setting is deprecated. Use STORAGES instead."
)

STATICFILES_STORAGE_DEPRECATED_MSG = (
    "The STATICFILES_STORAGE setting is deprecated. Use STORAGES instead."
)


class SettingsReference(str):
    """
    String subclass which references a current settings value. It's treated as
    the value in memory but serializes to a settings.NAME attribute reference.
    """

    def __new__(self, value, setting_name):
        return str.__new__(self, value)

    def __init__(self, value, setting_name):
        self.setting_name = setting_name


class LazySettings(LazyObject):
    """
    A lazy proxy for either global Django settings or a custom settings object.
    The user can manually configure settings prior to using them. Otherwise,
    Django uses the settings module pointed to by DJANGO_SETTINGS_MODULE.
    """

    def _setup(self, name=None):
        """
        Load the settings module pointed to by the environment variable. This
        is used the first time settings are needed, if the user hasn't
        configured settings manually.
        """
        settings_module = os.environ.get(ENVIRONMENT_VARIABLE)
        if not settings_module:
            desc = ("setting %s" % name) if name else "settings"
            raise ImproperlyConfigured(
                "Requested %s, but settings are not configured. "
                "You must either define the environment variable %s "
                "or call settings.configure() before accessing settings."
                % (desc, ENVIRONMENT_VARIABLE)
            )

        self._wrapped = Settings(settings_module)

    def __repr__(self):
        # Hardcode the class name as otherwise it yields 'Settings'.
        if self._wrapped is empty:
            return "<LazySettings [Unevaluated]>"
        return '<LazySettings "%(settings_module)s">' % {
            "settings_module": self._wrapped.SETTINGS_MODULE,
        }

    def __getattr__(self, name):
        """Return the value of a setting and cache it in self.__dict__."""
        if (_wrapped := self._wrapped) is empty:
            self._setup(name)
            _wrapped = self._wrapped
        val = getattr(_wrapped, name)

        # Special case some settings which require further modification.
        # This is done here for performance reasons so the modified value is cached.
        if name in {"MEDIA_URL", "STATIC_URL"} and val is not None:
            val = self._add_script_prefix(val)
        elif name == "SECRET_KEY" and not val:
            raise ImproperlyConfigured("The SECRET_KEY setting must not be empty.")

        self.__dict__[name] = val
        return val

    def __setattr__(self, name, value):
        """
        Set the value of setting. Clear all cached values if _wrapped changes
        (@override_settings does this) or clear single values when set.
        """
        if name == "_wrapped":
            self.__dict__.clear()
        else:
            self.__dict__.pop(name, None)
        super().__setattr__(name, value)

    def __delattr__(self, name):
        """Delete a setting and clear it from cache if needed."""
        super().__delattr__(name)
        self.__dict__.pop(name, None)

    def configure(self, default_settings=global_settings, **options):
        """
        Called to manually configure the settings. The 'default_settings'
        parameter sets where to retrieve any unspecified values from (its
        argument must support attribute access (__getattr__)).
        """
        if self._wrapped is not empty:
            raise RuntimeError("Settings already configured.")
        holder = UserSettingsHolder(default_settings)
        for name, value in options.items():
            if not name.isupper():
                raise TypeError("Setting %r must be uppercase." % name)
            setattr(holder, name, value)
        self._wrapped = holder

    @staticmethod
    def _add_script_prefix(value):
        """
        Add SCRIPT_NAME prefix to relative paths.

        Useful when the app is being served at a subpath and manually prefixing
        subpath to STATIC_URL and MEDIA_URL in settings is inconvenient.
        """
        # Don't apply prefix to absolute paths and URLs.
        if value.startswith(("http://", "https://", "/")):
            return value
        from django.urls import get_script_prefix

        return "%s%s" % (get_script_prefix(), value)

    @property
    def configured(self):
        """Return True if the settings have already been configured."""
        return self._wrapped is not empty

    def _show_deprecation_warning(self, message, category):
        stack = traceback.extract_stack()
        # Show a warning if the setting is used outside of Django.
        # Stack index: -1 this line, -2 the property, -3 the
        # LazyObject __getattribute__(), -4 the caller.
        filename, _, _, _ = stack[-4]
        if not filename.startswith(os.path.dirname(django.__file__)):
            warnings.warn(message, category, stacklevel=2)

    @property
    def USE_L10N(self):
        self._show_deprecation_warning(
            USE_L10N_DEPRECATED_MSG, RemovedInDjango50Warning
        )
        return self.__getattr__("USE_L10N")

    # RemovedInDjango50Warning.
    @property
    def _USE_L10N_INTERNAL(self):
        # Special hook to avoid checking a traceback in internal use on hot
        # paths.
        return self.__getattr__("USE_L10N")

    # RemovedInDjango51Warning.
    @property
    def DEFAULT_FILE_STORAGE(self):
        self._show_deprecation_warning(
            DEFAULT_FILE_STORAGE_DEPRECATED_MSG, RemovedInDjango51Warning
        )
        return self.__getattr__("DEFAULT_FILE_STORAGE")

    # RemovedInDjango51Warning.
    @property
    def STATICFILES_STORAGE(self):
        self._show_deprecation_warning(
            STATICFILES_STORAGE_DEPRECATED_MSG, RemovedInDjango51Warning
        )
        return self.__getattr__("STATICFILES_STORAGE")


class Settings:
    def __init__(self, settings_module):
        # update this dict from global settings (but only for ALL_CAPS settings)
        for setting in dir(global_settings):
            if setting.isupper():
                setattr(self, setting, getattr(global_settings, setting))

        # store the settings module in case someone later cares
        self.SETTINGS_MODULE = settings_module

        mod = importlib.import_module(self.SETTINGS_MODULE)

        tuple_settings = (
            "ALLOWED_HOSTS",
            "INSTALLED_APPS",
            "TEMPLATE_DIRS",
            "LOCALE_PATHS",
            "SECRET_KEY_FALLBACKS",
        )
        self._explicit_settings = set()
        for setting in dir(mod):
            if setting.isupper():
                setting_value = getattr(mod, setting)

                if setting in tuple_settings and not isinstance(
                    setting_value, (list, tuple)
                ):
                    raise ImproperlyConfigured(
                        "The %s setting must be a list or a tuple." % setting
                    )
                setattr(self, setting, setting_value)
                self._explicit_settings.add(setting)

        if self.USE_TZ is False and not self.is_overridden("USE_TZ"):
            warnings.warn(
                "The default value of USE_TZ will change from False to True "
                "in Django 5.0. Set USE_TZ to False in your project settings "
                "if you want to keep the current default behavior.",
                category=RemovedInDjango50Warning,
            )

        if self.is_overridden("USE_DEPRECATED_PYTZ"):
            warnings.warn(USE_DEPRECATED_PYTZ_DEPRECATED_MSG, RemovedInDjango50Warning)

        if self.is_overridden("CSRF_COOKIE_MASKED"):
            warnings.warn(CSRF_COOKIE_MASKED_DEPRECATED_MSG, RemovedInDjango50Warning)

        if hasattr(time, "tzset") and self.TIME_ZONE:
            # When we can, attempt to validate the timezone. If we can't find
            # this file, no check happens and it's harmless.
            zoneinfo_root = Path("/usr/share/zoneinfo")
            zone_info_file = zoneinfo_root.joinpath(*self.TIME_ZONE.split("/"))
            if zoneinfo_root.exists() and not zone_info_file.exists():
                raise ValueError("Incorrect timezone setting: %s" % self.TIME_ZONE)
            # Move the time zone info into os.environ. See ticket #2315 for why
            # we don't do this unconditionally (breaks Windows).
            os.environ["TZ"] = self.TIME_ZONE
            time.tzset()

        if self.is_overridden("USE_L10N"):
            warnings.warn(USE_L10N_DEPRECATED_MSG, RemovedInDjango50Warning)

        if self.is_overridden("DEFAULT_FILE_STORAGE"):
            if self.is_overridden("STORAGES"):
                raise ImproperlyConfigured(
                    "DEFAULT_FILE_STORAGE/STORAGES are mutually exclusive."
                )
            self.STORAGES[DEFAULT_STORAGE_ALIAS] = {
                "BACKEND": self.DEFAULT_FILE_STORAGE
            }
            warnings.warn(DEFAULT_FILE_STORAGE_DEPRECATED_MSG, RemovedInDjango51Warning)

        if self.is_overridden("STATICFILES_STORAGE"):
            if self.is_overridden("STORAGES"):
                raise ImproperlyConfigured(
                    "STATICFILES_STORAGE/STORAGES are mutually exclusive."
                )
            self.STORAGES[STATICFILES_STORAGE_ALIAS] = {
                "BACKEND": self.STATICFILES_STORAGE
            }
            warnings.warn(STATICFILES_STORAGE_DEPRECATED_MSG, RemovedInDjango51Warning)
        # RemovedInDjango51Warning.
        if self.is_overridden("STORAGES"):
            setattr(
                self,
                "DEFAULT_FILE_STORAGE",
                self.STORAGES.get(DEFAULT_STORAGE_ALIAS, {}).get("BACKEND"),
            )
            setattr(
                self,
                "STATICFILES_STORAGE",
                self.STORAGES.get(STATICFILES_STORAGE_ALIAS, {}).get("BACKEND"),
            )

    def is_overridden(self, setting):
        return setting in self._explicit_settings

    def __repr__(self):
        return '<%(cls)s "%(settings_module)s">' % {
            "cls": self.__class__.__name__,
            "settings_module": self.SETTINGS_MODULE,
        }


class UserSettingsHolder:
    """Holder for user configured settings."""

    # SETTINGS_MODULE doesn't make much sense in the manually configured
    # (standalone) case.
    SETTINGS_MODULE = None

    def __init__(self, default_settings):
        """
        Requests for configuration variables not in this class are satisfied
        from the module specified in default_settings (if possible).
        """
        self.__dict__["_deleted"] = set()
        self.default_settings = default_settings

    def __getattr__(self, name):
        if not name.isupper() or name in self._deleted:
            raise AttributeError
        return getattr(self.default_settings, name)

    def __setattr__(self, name, value):
        self._deleted.discard(name)
        if name == "USE_L10N":
            warnings.warn(USE_L10N_DEPRECATED_MSG, RemovedInDjango50Warning)
        if name == "CSRF_COOKIE_MASKED":
            warnings.warn(CSRF_COOKIE_MASKED_DEPRECATED_MSG, RemovedInDjango50Warning)
        if name == "DEFAULT_FILE_STORAGE":
            self.STORAGES[DEFAULT_STORAGE_ALIAS] = {
                "BACKEND": self.DEFAULT_FILE_STORAGE
            }
            warnings.warn(DEFAULT_FILE_STORAGE_DEPRECATED_MSG, RemovedInDjango51Warning)
        if name == "STATICFILES_STORAGE":
            self.STORAGES[STATICFILES_STORAGE_ALIAS] = {
                "BACKEND": self.STATICFILES_STORAGE
            }
            warnings.warn(STATICFILES_STORAGE_DEPRECATED_MSG, RemovedInDjango51Warning)
        super().__setattr__(name, value)
        if name == "USE_DEPRECATED_PYTZ":
            warnings.warn(USE_DEPRECATED_PYTZ_DEPRECATED_MSG, RemovedInDjango50Warning)
        # RemovedInDjango51Warning.
        if name == "STORAGES":
            if default_file_storage := self.STORAGES.get(DEFAULT_STORAGE_ALIAS):
                super().__setattr__(
                    "DEFAULT_FILE_STORAGE", default_file_storage.get("BACKEND")
                )
            else:
                self.STORAGES.setdefault(
                    DEFAULT_STORAGE_ALIAS,
                    {"BACKEND": "django.core.files.storage.FileSystemStorage"},
                )
            if staticfiles_storage := self.STORAGES.get(STATICFILES_STORAGE_ALIAS):
                super().__setattr__(
                    "STATICFILES_STORAGE", staticfiles_storage.get("BACKEND")
                )
            else:
                self.STORAGES.setdefault(
                    STATICFILES_STORAGE_ALIAS,
                    {
                        "BACKEND": (
                            "django.contrib.staticfiles.storage.StaticFilesStorage"
                        ),
                    },
                )

    def __delattr__(self, name):
        self._deleted.add(name)
        if hasattr(self, name):
            super().__delattr__(name)

    def __dir__(self):
        return sorted(
            s
            for s in [*self.__dict__, *dir(self.default_settings)]
            if s not in self._deleted
        )

    def is_overridden(self, setting):
        deleted = setting in self._deleted
        set_locally = setting in self.__dict__
        set_on_default = getattr(
            self.default_settings, "is_overridden", lambda s: False
        )(setting)
        return deleted or set_locally or set_on_default

    def __repr__(self):
        return "<%(cls)s>" % {
            "cls": self.__class__.__name__,
        }


settings = LazySettings()

virt/lib/python3.9/site-packages/django/conf/__init__.py
PrinceDisant/LostInNL
import logging
import sys
from functools import wraps

from asgiref.sync import iscoroutinefunction, sync_to_async

from django.conf import settings
from django.core import signals
from django.core.exceptions import (
    BadRequest,
    PermissionDenied,
    RequestDataTooBig,
    SuspiciousOperation,
    TooManyFieldsSent,
    TooManyFilesSent,
)
from django.http import Http404
from django.http.multipartparser import MultiPartParserError
from django.urls import get_resolver, get_urlconf
from django.utils.log import log_response
from django.views import debug


def convert_exception_to_response(get_response):
    """
    Wrap the given get_response callable in exception-to-response conversion.

    All exceptions will be converted. All known 4xx exceptions (Http404,
    PermissionDenied, MultiPartParserError, SuspiciousOperation) will be
    converted to the appropriate response, and all other exceptions will be
    converted to 500 responses.

    This decorator is automatically applied to all middleware to ensure that
    no middleware leaks an exception and that the next middleware in the stack
    can rely on getting a response instead of an exception.
    """
    if iscoroutinefunction(get_response):

        @wraps(get_response)
        async def inner(request):
            try:
                response = await get_response(request)
            except Exception as exc:
                response = await sync_to_async(
                    response_for_exception, thread_sensitive=False
                )(request, exc)
            return response

        return inner
    else:

        @wraps(get_response)
        def inner(request):
            try:
                response = get_response(request)
            except Exception as exc:
                response = response_for_exception(request, exc)
            return response

        return inner


def response_for_exception(request, exc):
    if isinstance(exc, Http404):
        if settings.DEBUG:
            response = debug.technical_404_response(request, exc)
        else:
            response = get_exception_response(
                request, get_resolver(get_urlconf()), 404, exc
            )

    elif isinstance(exc, PermissionDenied):
        response = get_exception_response(
            request, get_resolver(get_urlconf()), 403, exc
        )
        log_response(
            "Forbidden (Permission denied): %s",
            request.path,
            response=response,
            request=request,
            exception=exc,
        )

    elif isinstance(exc, MultiPartParserError):
        response = get_exception_response(
            request, get_resolver(get_urlconf()), 400, exc
        )
        log_response(
            "Bad request (Unable to parse request body): %s",
            request.path,
            response=response,
            request=request,
            exception=exc,
        )

    elif isinstance(exc, BadRequest):
        if settings.DEBUG:
            response = debug.technical_500_response(
                request, *sys.exc_info(), status_code=400
            )
        else:
            response = get_exception_response(
                request, get_resolver(get_urlconf()), 400, exc
            )
        log_response(
            "%s: %s",
            str(exc),
            request.path,
            response=response,
            request=request,
            exception=exc,
        )
    elif isinstance(exc, SuspiciousOperation):
        if isinstance(exc, (RequestDataTooBig, TooManyFieldsSent, TooManyFilesSent)):
            # POST data can't be accessed again, otherwise the original
            # exception would be raised.
            request._mark_post_parse_error()

        # The request logger receives events for any problematic request
        # The security logger receives events for all SuspiciousOperations
        security_logger = logging.getLogger(
            "django.security.%s" % exc.__class__.__name__
        )
        security_logger.error(
            str(exc),
            exc_info=exc,
            extra={"status_code": 400, "request": request},
        )
        if settings.DEBUG:
            response = debug.technical_500_response(
                request, *sys.exc_info(), status_code=400
            )
        else:
            response = get_exception_response(
                request, get_resolver(get_urlconf()), 400, exc
            )

    else:
        signals.got_request_exception.send(sender=None, request=request)
        response = handle_uncaught_exception(
            request, get_resolver(get_urlconf()), sys.exc_info()
        )
        log_response(
            "%s: %s",
            response.reason_phrase,
            request.path,
            response=response,
            request=request,
            exception=exc,
        )

    # Force a TemplateResponse to be rendered.
    if not getattr(response, "is_rendered", True) and callable(
        getattr(response, "render", None)
    ):
        response = response.render()

    return response


def get_exception_response(request, resolver, status_code, exception):
    try:
        callback = resolver.resolve_error_handler(status_code)
        response = callback(request, exception=exception)
    except Exception:
        signals.got_request_exception.send(sender=None, request=request)
        response = handle_uncaught_exception(request, resolver, sys.exc_info())

    return response


def handle_uncaught_exception(request, resolver, exc_info):
    """
    Processing for any otherwise uncaught exceptions (those that will
    generate HTTP 500 responses).
    """
    if settings.DEBUG_PROPAGATE_EXCEPTIONS:
        raise

    if settings.DEBUG:
        return debug.technical_500_response(request, *exc_info)

    # Return an HttpResponse that displays a friendly error message.
    callback = resolver.resolve_error_handler(500)
    return callback(request)

virt/lib/python3.9/site-packages/django/core/handlers/exception.py
PrinceDisant/LostInNL
import keyword
import re

from django.core.management.base import BaseCommand, CommandError
from django.db import DEFAULT_DB_ALIAS, connections
from django.db.models.constants import LOOKUP_SEP


class Command(BaseCommand):
    help = (
        "Introspects the database tables in the given database and outputs a Django "
        "model module."
    )
    requires_system_checks = []
    stealth_options = ("table_name_filter",)
    db_module = "django.db"

    def add_arguments(self, parser):
        parser.add_argument(
            "table",
            nargs="*",
            type=str,
            help="Selects what tables or views should be introspected.",
        )
        parser.add_argument(
            "--database",
            default=DEFAULT_DB_ALIAS,
            help=(
                'Nominates a database to introspect. Defaults to using the "default" '
                "database."
            ),
        )
        parser.add_argument(
            "--include-partitions",
            action="store_true",
            help="Also output models for partition tables.",
        )
        parser.add_argument(
            "--include-views",
            action="store_true",
            help="Also output models for database views.",
        )

    def handle(self, **options):
        try:
            for line in self.handle_inspection(options):
                self.stdout.write(line)
        except NotImplementedError:
            raise CommandError(
                "Database inspection isn't supported for the currently selected "
                "database backend."
            )

    def handle_inspection(self, options):
        connection = connections[options["database"]]
        # 'table_name_filter' is a stealth option
        table_name_filter = options.get("table_name_filter")

        def table2model(table_name):
            return re.sub(r"[^a-zA-Z0-9]", "", table_name.title())

        with connection.cursor() as cursor:
            yield "# This is an auto-generated Django model module."
            yield "# You'll have to do the following manually to clean this up:"
            yield "#   * Rearrange models' order"
            yield "#   * Make sure each model has one field with primary_key=True"
            yield (
                "#   * Make sure each ForeignKey and OneToOneField has `on_delete` set "
                "to the desired behavior"
            )
            yield (
                "#   * Remove `managed = False` lines if you wish to allow "
                "Django to create, modify, and delete the table"
            )
            yield (
                "# Feel free to rename the models, but don't rename db_table values or "
                "field names."
            )
            yield "from %s import models" % self.db_module
            known_models = []
            # Determine types of tables and/or views to be introspected.
            types = {"t"}
            if options["include_partitions"]:
                types.add("p")
            if options["include_views"]:
                types.add("v")
            table_info = connection.introspection.get_table_list(cursor)
            table_info = {info.name: info for info in table_info if info.type in types}

            for table_name in options["table"] or sorted(name for name in table_info):
                if table_name_filter is not None and callable(table_name_filter):
                    if not table_name_filter(table_name):
                        continue
                try:
                    try:
                        relations = connection.introspection.get_relations(
                            cursor, table_name
                        )
                    except NotImplementedError:
                        relations = {}
                    try:
                        constraints = connection.introspection.get_constraints(
                            cursor, table_name
                        )
                    except NotImplementedError:
                        constraints = {}
                    primary_key_columns = (
                        connection.introspection.get_primary_key_columns(
                            cursor, table_name
                        )
                    )
                    primary_key_column = (
                        primary_key_columns[0] if primary_key_columns else None
                    )
                    unique_columns = [
                        c["columns"][0]
                        for c in constraints.values()
                        if c["unique"] and len(c["columns"]) == 1
                    ]
                    table_description = connection.introspection.get_table_description(
                        cursor, table_name
                    )
                except Exception as e:
                    yield "# Unable to inspect table '%s'" % table_name
                    yield "# The error was: %s" % e
                    continue

                model_name = table2model(table_name)
                yield ""
                yield ""
                yield "class %s(models.Model):" % model_name
                known_models.append(model_name)
                used_column_names = []  # Holds column names used in the table so far
                column_to_field_name = {}  # Maps column names to names of model fields
                used_relations = set()  # Holds foreign relations used in the table.
                for row in table_description:
                    comment_notes = (
                        []
                    )  # Holds Field notes, to be displayed in a Python comment.
                    extra_params = {}  # Holds Field parameters such as 'db_column'.
                    column_name = row.name
                    is_relation = column_name in relations

                    att_name, params, notes = self.normalize_col_name(
                        column_name, used_column_names, is_relation
                    )
                    extra_params.update(params)
                    comment_notes.extend(notes)

                    used_column_names.append(att_name)
                    column_to_field_name[column_name] = att_name

                    # Add primary_key and unique, if necessary.
                    if column_name == primary_key_column:
                        extra_params["primary_key"] = True
                        if len(primary_key_columns) > 1:
                            comment_notes.append(
                                "The composite primary key (%s) found, that is not "
                                "supported. The first column is selected."
                                % ", ".join(primary_key_columns)
                            )
                    elif column_name in unique_columns:
                        extra_params["unique"] = True

                    if is_relation:
                        ref_db_column, ref_db_table = relations[column_name]
                        if extra_params.pop("unique", False) or extra_params.get(
                            "primary_key"
                        ):
                            rel_type = "OneToOneField"
                        else:
                            rel_type = "ForeignKey"
                            ref_pk_column = (
                                connection.introspection.get_primary_key_column(
                                    cursor, ref_db_table
                                )
                            )
                            if ref_pk_column and ref_pk_column != ref_db_column:
                                extra_params["to_field"] = ref_db_column
                        rel_to = (
                            "self"
                            if ref_db_table == table_name
                            else table2model(ref_db_table)
                        )
                        if rel_to in known_models:
                            field_type = "%s(%s" % (rel_type, rel_to)
                        else:
                            field_type = "%s('%s'" % (rel_type, rel_to)
                        if rel_to in used_relations:
                            extra_params["related_name"] = "%s_%s_set" % (
                                model_name.lower(),
                                att_name,
                            )
                        used_relations.add(rel_to)
                    else:
                        # Calling `get_field_type` to get the field type string and any
                        # additional parameters and notes.
                        field_type, field_params, field_notes = self.get_field_type(
                            connection, table_name, row
                        )
                        extra_params.update(field_params)
                        comment_notes.extend(field_notes)

                        field_type += "("

                    # Don't output 'id = meta.AutoField(primary_key=True)', because
                    # that's assumed if it doesn't exist.
                    if att_name == "id" and extra_params == {"primary_key": True}:
                        if field_type == "AutoField(":
                            continue
                        elif (
                            field_type
                            == connection.features.introspected_field_types["AutoField"]
                            + "("
                        ):
                            comment_notes.append("AutoField?")

                    # Add 'null' and 'blank', if the 'null_ok' flag was present in the
                    # table description.
                    if row.null_ok:  # If it's NULL...
                        extra_params["blank"] = True
                        extra_params["null"] = True

                    field_desc = "%s = %s%s" % (
                        att_name,
                        # Custom fields will have a dotted path
                        "" if "." in field_type else "models.",
                        field_type,
                    )
                    if field_type.startswith(("ForeignKey(", "OneToOneField(")):
                        field_desc += ", models.DO_NOTHING"

                    # Add comment.
                    if connection.features.supports_comments and row.comment:
                        extra_params["db_comment"] = row.comment

                    if extra_params:
                        if not field_desc.endswith("("):
                            field_desc += ", "
                        field_desc += ", ".join(
                            "%s=%r" % (k, v) for k, v in extra_params.items()
                        )
                    field_desc += ")"
                    if comment_notes:
                        field_desc += "  # " + " ".join(comment_notes)
                    yield "    %s" % field_desc
                comment = None
                if info := table_info.get(table_name):
                    is_view = info.type == "v"
                    is_partition = info.type == "p"
                    if connection.features.supports_comments:
                        comment = info.comment
                else:
                    is_view = False
                    is_partition = False
                yield from self.get_meta(
                    table_name,
                    constraints,
                    column_to_field_name,
                    is_view,
                    is_partition,
                    comment,
                )

    def normalize_col_name(self, col_name, used_column_names, is_relation):
        """
        Modify the column name to make it Python-compatible as a field name
        """
        field_params = {}
        field_notes = []

        new_name = col_name.lower()
        if new_name != col_name:
            field_notes.append("Field name made lowercase.")

        if is_relation:
            if new_name.endswith("_id"):
                new_name = new_name[:-3]
            else:
                field_params["db_column"] = col_name

        new_name, num_repl = re.subn(r"\W", "_", new_name)
        if num_repl > 0:
            field_notes.append("Field renamed to remove unsuitable characters.")

        if new_name.find(LOOKUP_SEP) >= 0:
            while new_name.find(LOOKUP_SEP) >= 0:
                new_name = new_name.replace(LOOKUP_SEP, "_")
            if col_name.lower().find(LOOKUP_SEP) >= 0:
                # Only add the comment if the double underscore was in the original name
                field_notes.append(
                    "Field renamed because it contained more than one '_' in a row."
                )

        if new_name.startswith("_"):
            new_name = "field%s" % new_name
            field_notes.append("Field renamed because it started with '_'.")

        if new_name.endswith("_"):
            new_name = "%sfield" % new_name
            field_notes.append("Field renamed because it ended with '_'.")

        if keyword.iskeyword(new_name):
            new_name += "_field"
            field_notes.append("Field renamed because it was a Python reserved word.")

        if new_name[0].isdigit():
            new_name = "number_%s" % new_name
            field_notes.append(
                "Field renamed because it wasn't a valid Python identifier."
            )

        if new_name in used_column_names:
            num = 0
            while "%s_%d" % (new_name, num) in used_column_names:
                num += 1
            new_name = "%s_%d" % (new_name, num)
            field_notes.append("Field renamed because of name conflict.")

        if col_name != new_name and field_notes:
            field_params["db_column"] = col_name

        return new_name, field_params, field_notes

    def get_field_type(self, connection, table_name, row):
        """
        Given the database connection, the table name, and the cursor row
        description, this routine will return the given field type name, as
        well as any additional keyword parameters and notes for the field.
        """
        field_params = {}
        field_notes = []

        try:
            field_type = connection.introspection.get_field_type(row.type_code, row)
        except KeyError:
            field_type = "TextField"
            field_notes.append("This field type is a guess.")

        # Add max_length for all CharFields.
        if field_type == "CharField" and row.display_size:
            if (size := int(row.display_size)) and size > 0:
                field_params["max_length"] = size

        if field_type in {"CharField", "TextField"} and row.collation:
            field_params["db_collation"] = row.collation

        if field_type == "DecimalField":
            if row.precision is None or row.scale is None:
                field_notes.append(
                    "max_digits and decimal_places have been guessed, as this "
                    "database handles decimal fields as float"
                )
                field_params["max_digits"] = (
                    row.precision if row.precision is not None else 10
                )
                field_params["decimal_places"] = (
                    row.scale if row.scale is not None else 5
                )
            else:
                field_params["max_digits"] = row.precision
                field_params["decimal_places"] = row.scale

        return field_type, field_params, field_notes

    def get_meta(
        self,
        table_name,
        constraints,
        column_to_field_name,
        is_view,
        is_partition,
        comment,
    ):
        """
        Return a sequence comprising the lines of code necessary
        to construct the inner Meta class for the model corresponding
        to the given database table name.
        """
        unique_together = []
        has_unsupported_constraint = False
        for params in constraints.values():
            if params["unique"]:
                columns = params["columns"]
                if None in columns:
                    has_unsupported_constraint = True
                columns = [
                    x for x in columns if x is not None and x in column_to_field_name
                ]
                if len(columns) > 1:
                    unique_together.append(
                        str(tuple(column_to_field_name[c] for c in columns))
                    )
        if is_view:
            managed_comment = "  # Created from a view. Don't remove."
        elif is_partition:
            managed_comment = "  # Created from a partition. Don't remove."
        else:
            managed_comment = ""
        meta = [""]
        if has_unsupported_constraint:
            meta.append("    # A unique constraint could not be introspected.")
        meta += [
            "    class Meta:",
            "        managed = False%s" % managed_comment,
            "        db_table = %r" % table_name,
        ]
        if unique_together:
            tup = "(" + ", ".join(unique_together) + ",)"
            meta += ["        unique_together = %s" % tup]
        if comment:
            meta += [f"        db_table_comment = {comment!r}"]
        return meta

virt/lib/python3.9/site-packages/django/core/management/commands/inspectdb.py
PrinceDisant/LostInNL
import operator

from django.db import DataError, InterfaceError
from django.db.backends.base.features import BaseDatabaseFeatures
from django.db.backends.postgresql.psycopg_any import is_psycopg3
from django.utils.functional import cached_property


class DatabaseFeatures(BaseDatabaseFeatures):
    minimum_database_version = (12,)
    allows_group_by_selected_pks = True
    can_return_columns_from_insert = True
    can_return_rows_from_bulk_insert = True
    has_real_datatype = True
    has_native_uuid_field = True
    has_native_duration_field = True
    has_native_json_field = True
    can_defer_constraint_checks = True
    has_select_for_update = True
    has_select_for_update_nowait = True
    has_select_for_update_of = True
    has_select_for_update_skip_locked = True
    has_select_for_no_key_update = True
    can_release_savepoints = True
    supports_comments = True
    supports_tablespaces = True
    supports_transactions = True
    can_introspect_materialized_views = True
    can_distinct_on_fields = True
    can_rollback_ddl = True
    schema_editor_uses_clientside_param_binding = True
    supports_combined_alters = True
    nulls_order_largest = True
    closed_cursor_error_class = InterfaceError
    greatest_least_ignores_nulls = True
    can_clone_databases = True
    supports_temporal_subtraction = True
    supports_slicing_ordering_in_compound = True
    create_test_procedure_without_params_sql = """
        CREATE FUNCTION test_procedure () RETURNS void AS $$
        DECLARE
            V_I INTEGER;
        BEGIN
            V_I := 1;
        END;
    $$ LANGUAGE plpgsql;"""
    create_test_procedure_with_int_param_sql = """
        CREATE FUNCTION test_procedure (P_I INTEGER) RETURNS void AS $$
        DECLARE
            V_I INTEGER;
        BEGIN
            V_I := P_I;
        END;
    $$ LANGUAGE plpgsql;"""
    create_test_table_with_composite_primary_key = """
        CREATE TABLE test_table_composite_pk (
            column_1 INTEGER NOT NULL,
            column_2 INTEGER NOT NULL,
            PRIMARY KEY(column_1, column_2)
        )
    """
    requires_casted_case_in_updates = True
    supports_over_clause = True
    only_supports_unbounded_with_preceding_and_following = True
    supports_aggregate_filter_clause = True
    supported_explain_formats = {"JSON", "TEXT", "XML", "YAML"}
    supports_deferrable_unique_constraints = True
    has_json_operators = True
    json_key_contains_list_matching_requires_list = True
    supports_update_conflicts = True
    supports_update_conflicts_with_target = True
    supports_covering_indexes = True
    can_rename_index = True
    test_collations = {
        "non_default": "sv-x-icu",
        "swedish_ci": "sv-x-icu",
    }
    test_now_utc_template = "STATEMENT_TIMESTAMP() AT TIME ZONE 'UTC'"

    django_test_skips = {
        "opclasses are PostgreSQL only.": {
            "indexes.tests.SchemaIndexesNotPostgreSQLTests."
            "test_create_index_ignores_opclasses",
        },
        "PostgreSQL requires casting to text.": {
            "lookup.tests.LookupTests.test_textfield_exact_null",
        },
    }

    @cached_property
    def django_test_expected_failures(self):
        expected_failures = set()
        if self.uses_server_side_binding:
            expected_failures.update(
                {
                    # Parameters passed to expressions in SELECT and GROUP BY
                    # clauses are not recognized as the same values when using
                    # server-side binding cursors (#34255).
                    "aggregation.tests.AggregateTestCase."
                    "test_group_by_nested_expression_with_params",
                }
            )
        return expected_failures

    @cached_property
    def uses_server_side_binding(self):
        options = self.connection.settings_dict["OPTIONS"]
        return is_psycopg3 and options.get("server_side_binding") is True

    @cached_property
    def prohibits_null_characters_in_text_exception(self):
        if is_psycopg3:
            return DataError, "PostgreSQL text fields cannot contain NUL (0x00) bytes"
        else:
            return ValueError, "A string literal cannot contain NUL (0x00) characters."

    @cached_property
    def introspected_field_types(self):
        return {
            **super().introspected_field_types,
            "PositiveBigIntegerField": "BigIntegerField",
            "PositiveIntegerField": "IntegerField",
            "PositiveSmallIntegerField": "SmallIntegerField",
        }

    @cached_property
    def is_postgresql_13(self):
        return self.connection.pg_version >= 130000

    @cached_property
    def is_postgresql_14(self):
        return self.connection.pg_version >= 140000

    has_bit_xor = property(operator.attrgetter("is_postgresql_14"))
    supports_covering_spgist_indexes = property(operator.attrgetter("is_postgresql_14"))
    supports_unlimited_charfield = True

virt/lib/python3.9/site-packages/django/db/backends/postgresql/features.py
PrinceDisant/LostInNL
import functools
import re
from collections import defaultdict
from itertools import chain

from django.conf import settings
from django.db import models
from django.db.migrations import operations
from django.db.migrations.migration import Migration
from django.db.migrations.operations.models import AlterModelOptions
from django.db.migrations.optimizer import MigrationOptimizer
from django.db.migrations.questioner import MigrationQuestioner
from django.db.migrations.utils import (
    COMPILED_REGEX_TYPE,
    RegexObject,
    resolve_relation,
)
from django.utils.topological_sort import stable_topological_sort


class MigrationAutodetector:
    """
    Take a pair of ProjectStates and compare them to see what the first would
    need doing to make it match the second (the second usually being the
    project's current state).

    Note that this naturally operates on entire projects at a time,
    as it's likely that changes interact (for example, you can't
    add a ForeignKey without having a migration to add the table it
    depends on first). A user interface may offer single-app usage
    if it wishes, with the caveat that it may not always be possible.
    """

    def __init__(self, from_state, to_state, questioner=None):
        self.from_state = from_state
        self.to_state = to_state
        self.questioner = questioner or MigrationQuestioner()
        self.existing_apps = {app for app, model in from_state.models}

    def changes(self, graph, trim_to_apps=None, convert_apps=None, migration_name=None):
        """
        Main entry point to produce a list of applicable changes.
        Take a graph to base names on and an optional set of apps
        to try and restrict to (restriction is not guaranteed)
        """
        changes = self._detect_changes(convert_apps, graph)
        changes = self.arrange_for_graph(changes, graph, migration_name)
        if trim_to_apps:
            changes = self._trim_to_apps(changes, trim_to_apps)
        return changes

    def deep_deconstruct(self, obj):
        """
        Recursive deconstruction for a field and its arguments.
        Used for full comparison for rename/alter; sometimes a single-level
        deconstruction will not compare correctly.
        """
        if isinstance(obj, list):
            return [self.deep_deconstruct(value) for value in obj]
        elif isinstance(obj, tuple):
            return tuple(self.deep_deconstruct(value) for value in obj)
        elif isinstance(obj, dict):
            return {key: self.deep_deconstruct(value) for key, value in obj.items()}
        elif isinstance(obj, functools.partial):
            return (
                obj.func,
                self.deep_deconstruct(obj.args),
                self.deep_deconstruct(obj.keywords),
            )
        elif isinstance(obj, COMPILED_REGEX_TYPE):
            return RegexObject(obj)
        elif isinstance(obj, type):
            # If this is a type that implements 'deconstruct' as an instance method,
            # avoid treating this as being deconstructible itself - see #22951
            return obj
        elif hasattr(obj, "deconstruct"):
            deconstructed = obj.deconstruct()
            if isinstance(obj, models.Field):
                # we have a field which also returns a name
                deconstructed = deconstructed[1:]
            path, args, kwargs = deconstructed
            return (
                path,
                [self.deep_deconstruct(value) for value in args],
                {key: self.deep_deconstruct(value) for key, value in kwargs.items()},
            )
        else:
            return obj

    def only_relation_agnostic_fields(self, fields):
        """
        Return a definition of the fields that ignores field names and
        what related fields actually relate to. Used for detecting renames (as
        the related fields change during renames).
        """
        fields_def = []
        for name, field in sorted(fields.items()):
            deconstruction = self.deep_deconstruct(field)
            if field.remote_field and field.remote_field.model:
                deconstruction[2].pop("to", None)
            fields_def.append(deconstruction)
        return fields_def

    def _detect_changes(self, convert_apps=None, graph=None):
        """
        Return a dict of migration plans which will achieve the
        change from from_state to to_state. The dict has app labels
        as keys and a list of migrations as values.

        The resulting migrations aren't specially named, but the names
        do matter for dependencies inside the set.

        convert_apps is the list of apps to convert to use migrations
        (i.e. to make initial migrations for, in the usual case)

        graph is an optional argument that, if provided, can help improve
        dependency generation and avoid potential circular dependencies.
        """
        # The first phase is generating all the operations for each app
        # and gathering them into a big per-app list.
        # Then go through that list, order it, and split into migrations to
        # resolve dependencies caused by M2Ms and FKs.
        self.generated_operations = {}
        self.altered_indexes = {}
        self.altered_constraints = {}
        self.renamed_fields = {}

        # Prepare some old/new state and model lists, separating
        # proxy models and ignoring unmigrated apps.
        self.old_model_keys = set()
        self.old_proxy_keys = set()
        self.old_unmanaged_keys = set()
        self.new_model_keys = set()
        self.new_proxy_keys = set()
        self.new_unmanaged_keys = set()
        for (app_label, model_name), model_state in self.from_state.models.items():
            if not model_state.options.get("managed", True):
                self.old_unmanaged_keys.add((app_label, model_name))
            elif app_label not in self.from_state.real_apps:
                if model_state.options.get("proxy"):
                    self.old_proxy_keys.add((app_label, model_name))
                else:
                    self.old_model_keys.add((app_label, model_name))

        for (app_label, model_name), model_state in self.to_state.models.items():
            if not model_state.options.get("managed", True):
                self.new_unmanaged_keys.add((app_label, model_name))
            elif app_label not in self.from_state.real_apps or (
                convert_apps and app_label in convert_apps
            ):
                if model_state.options.get("proxy"):
                    self.new_proxy_keys.add((app_label, model_name))
                else:
                    self.new_model_keys.add((app_label, model_name))

        self.from_state.resolve_fields_and_relations()
        self.to_state.resolve_fields_and_relations()

        # Renames have to come first
        self.generate_renamed_models()

        # Prepare lists of fields and generate through model map
        self._prepare_field_lists()
        self._generate_through_model_map()

        # Generate non-rename model operations
        self.generate_deleted_models()
        self.generate_created_models()
        self.generate_deleted_proxies()
        self.generate_created_proxies()
        self.generate_altered_options()
        self.generate_altered_managers()
        self.generate_altered_db_table_comment()

        # Create the renamed fields and store them in self.renamed_fields.
        # They are used by create_altered_indexes(), generate_altered_fields(),
        # generate_removed_altered_index/unique_together(), and
        # generate_altered_index/unique_together().
        self.create_renamed_fields()
        # Create the altered indexes and store them in self.altered_indexes.
        # This avoids the same computation in generate_removed_indexes()
        # and generate_added_indexes().
        self.create_altered_indexes()
        self.create_altered_constraints()
        # Generate index removal operations before field is removed
        self.generate_removed_constraints()
        self.generate_removed_indexes()
        # Generate field renaming operations.
        self.generate_renamed_fields()
        self.generate_renamed_indexes()
        # Generate removal of foo together.
        self.generate_removed_altered_unique_together()
        self.generate_removed_altered_index_together()  # RemovedInDjango51Warning.
        # Generate field operations.
        self.generate_removed_fields()
        self.generate_added_fields()
        self.generate_altered_fields()
        self.generate_altered_order_with_respect_to()
        self.generate_altered_unique_together()
        self.generate_altered_index_together()  # RemovedInDjango51Warning.
        self.generate_added_indexes()
        self.generate_added_constraints()
        self.generate_altered_db_table()

        self._sort_migrations()
        self._build_migration_list(graph)
        self._optimize_migrations()

        return self.migrations

    def _prepare_field_lists(self):
        """
        Prepare field lists and a list of the fields that used through models
        in the old state so dependencies can be made from the through model
        deletion to the field that uses it.
        """
        self.kept_model_keys = self.old_model_keys & self.new_model_keys
        self.kept_proxy_keys = self.old_proxy_keys & self.new_proxy_keys
        self.kept_unmanaged_keys = self.old_unmanaged_keys & self.new_unmanaged_keys
        self.through_users = {}
        self.old_field_keys = {
            (app_label, model_name, field_name)
            for app_label, model_name in self.kept_model_keys
            for field_name in self.from_state.models[
                app_label, self.renamed_models.get((app_label, model_name), model_name)
            ].fields
        }
        self.new_field_keys = {
            (app_label, model_name, field_name)
            for app_label, model_name in self.kept_model_keys
            for field_name in self.to_state.models[app_label, model_name].fields
        }

    def _generate_through_model_map(self):
        """Through model map generation."""
        for app_label, model_name in sorted(self.old_model_keys):
            old_model_name = self.renamed_models.get(
                (app_label, model_name), model_name
            )
            old_model_state = self.from_state.models[app_label, old_model_name]
            for field_name, field in old_model_state.fields.items():
                if hasattr(field, "remote_field") and getattr(
                    field.remote_field, "through", None
                ):
                    through_key = resolve_relation(
                        field.remote_field.through, app_label, model_name
                    )
                    self.through_users[through_key] = (
                        app_label,
                        old_model_name,
                        field_name,
                    )

    @staticmethod
    def _resolve_dependency(dependency):
        """
        Return the resolved dependency and a boolean denoting whether or not
        it was swappable.
        """
        if dependency[0] != "__setting__":
            return dependency, False
        resolved_app_label, resolved_object_name = getattr(
            settings, dependency[1]
        ).split(".")
        return (resolved_app_label, resolved_object_name.lower()) + dependency[2:], True

    def _build_migration_list(self, graph=None):
        """
        Chop the lists of operations up into migrations with dependencies on
        each other. Do this by going through an app's list of operations until
        one is found that has an outgoing dependency that isn't in another
        app's migration yet (hasn't been chopped off its list). Then chop off
        the operations before it into a migration and move onto the next app.
        If the loops completes without doing anything, there's a circular
        dependency (which _should_ be impossible as the operations are
        all split at this point so they can't depend and be depended on).
        """
        self.migrations = {}
        num_ops = sum(len(x) for x in self.generated_operations.values())
        chop_mode = False
        while num_ops:
            # On every iteration, we step through all the apps and see if there
            # is a completed set of operations.
            # If we find that a subset of the operations are complete we can
            # try to chop it off from the rest and continue, but we only
            # do this if we've already been through the list once before
            # without any chopping and nothing has changed.
            for app_label in sorted(self.generated_operations):
                chopped = []
                dependencies = set()
                for operation in list(self.generated_operations[app_label]):
                    deps_satisfied = True
                    operation_dependencies = set()
                    for dep in operation._auto_deps:
                        # Temporarily resolve the swappable dependency to
                        # prevent circular references. While keeping the
                        # dependency checks on the resolved model, add the
                        # swappable dependencies.
                        original_dep = dep
                        dep, is_swappable_dep = self._resolve_dependency(dep)
                        if dep[0] != app_label:
                            # External app dependency. See if it's not yet
                            # satisfied.
                            for other_operation in self.generated_operations.get(
                                dep[0], []
                            ):
                                if self.check_dependency(other_operation, dep):
                                    deps_satisfied = False
                                    break
                            if not deps_satisfied:
                                break
                            else:
                                if is_swappable_dep:
                                    operation_dependencies.add(
                                        (original_dep[0], original_dep[1])
                                    )
                                elif dep[0] in self.migrations:
                                    operation_dependencies.add(
                                        (dep[0], self.migrations[dep[0]][-1].name)
                                    )
                                else:
                                    # If we can't find the other app, we add a
                                    # first/last dependency, but only if we've
                                    # already been through once and checked
                                    # everything.
                                    if chop_mode:
                                        # If the app already exists, we add a
                                        # dependency on the last migration, as
                                        # we don't know which migration
                                        # contains the target field. If it's
                                        # not yet migrated or has no
                                        # migrations, we use __first__.
                                        if graph and graph.leaf_nodes(dep[0]):
                                            operation_dependencies.add(
                                                graph.leaf_nodes(dep[0])[0]
                                            )
                                        else:
                                            operation_dependencies.add(
                                                (dep[0], "__first__")
                                            )
                                    else:
                                        deps_satisfied = False
                    if deps_satisfied:
                        chopped.append(operation)
                        dependencies.update(operation_dependencies)
                        del self.generated_operations[app_label][0]
                    else:
                        break
                # Make a migration! Well, only if there's stuff to put in it
                if dependencies or chopped:
                    if not self.generated_operations[app_label] or chop_mode:
                        subclass = type(
                            "Migration",
                            (Migration,),
                            {"operations": [], "dependencies": []},
                        )
                        instance = subclass(
                            "auto_%i" % (len(self.migrations.get(app_label, [])) + 1),
                            app_label,
                        )
                        instance.dependencies = list(dependencies)
                        instance.operations = chopped
                        instance.initial = app_label not in self.existing_apps
                        self.migrations.setdefault(app_label, []).append(instance)
                        chop_mode = False
                    else:
                        self.generated_operations[app_label] = (
                            chopped + self.generated_operations[app_label]
                        )
            new_num_ops = sum(len(x) for x in self.generated_operations.values())
            if new_num_ops == num_ops:
                if not chop_mode:
                    chop_mode = True
                else:
                    raise ValueError(
                        "Cannot resolve operation dependencies: %r"
                        % self.generated_operations
                    )
            num_ops = new_num_ops

    def _sort_migrations(self):
        """
        Reorder to make things possible. Reordering may be needed so FKs work
        nicely inside the same app.
        """
        for app_label, ops in sorted(self.generated_operations.items()):
            # construct a dependency graph for intra-app dependencies
            dependency_graph = {op: set() for op in ops}
            for op in ops:
                for dep in op._auto_deps:
                    # Resolve intra-app dependencies to handle circular
                    # references involving a swappable model.
                    dep = self._resolve_dependency(dep)[0]
                    if dep[0] == app_label:
                        for op2 in ops:
                            if self.check_dependency(op2, dep):
                                dependency_graph[op].add(op2)

            # we use a stable sort for deterministic tests & general behavior
            self.generated_operations[app_label] = stable_topological_sort(
                ops, dependency_graph
            )

    def _optimize_migrations(self):
        # Add in internal dependencies among the migrations
        for app_label, migrations in self.migrations.items():
            for m1, m2 in zip(migrations, migrations[1:]):
                m2.dependencies.append((app_label, m1.name))

        # De-dupe dependencies
        for migrations in self.migrations.values():
            for migration in migrations:
                migration.dependencies = list(set(migration.dependencies))

        # Optimize migrations
        for app_label, migrations in self.migrations.items():
            for migration in migrations:
                migration.operations = MigrationOptimizer().optimize(
                    migration.operations, app_label
                )

    def check_dependency(self, operation, dependency):
        """
        Return True if the given operation depends on the given dependency,
        False otherwise.
        """
        # Created model
        if dependency[2] is None and dependency[3] is True:
            return (
                isinstance(operation, operations.CreateModel)
                and operation.name_lower == dependency[1].lower()
            )
        # Created field
        elif dependency[2] is not None and dependency[3] is True:
            return (
                isinstance(operation, operations.CreateModel)
                and operation.name_lower == dependency[1].lower()
                and any(dependency[2] == x for x, y in operation.fields)
            ) or (
                isinstance(operation, operations.AddField)
                and operation.model_name_lower == dependency[1].lower()
                and operation.name_lower == dependency[2].lower()
            )
        # Removed field
        elif dependency[2] is not None and dependency[3] is False:
            return (
                isinstance(operation, operations.RemoveField)
                and operation.model_name_lower == dependency[1].lower()
                and operation.name_lower == dependency[2].lower()
            )
        # Removed model
        elif dependency[2] is None and dependency[3] is False:
            return (
                isinstance(operation, operations.DeleteModel)
                and operation.name_lower == dependency[1].lower()
            )
        # Field being altered
        elif dependency[2] is not None and dependency[3] == "alter":
            return (
                isinstance(operation, operations.AlterField)
                and operation.model_name_lower == dependency[1].lower()
                and operation.name_lower == dependency[2].lower()
            )
        # order_with_respect_to being unset for a field
        elif dependency[2] is not None and dependency[3] == "order_wrt_unset":
            return (
                isinstance(operation, operations.AlterOrderWithRespectTo)
                and operation.name_lower == dependency[1].lower()
                and (operation.order_with_respect_to or "").lower()
                != dependency[2].lower()
            )
        # Field is removed and part of an index/unique_together
        elif dependency[2] is not None and dependency[3] == "foo_together_change":
            return (
                isinstance(
                    operation,
                    (operations.AlterUniqueTogether, operations.AlterIndexTogether),
                )
                and operation.name_lower == dependency[1].lower()
            )
        # Unknown dependency. Raise an error.
        else:
            raise ValueError("Can't handle dependency %r" % (dependency,))

    def add_operation(self, app_label, operation, dependencies=None, beginning=False):
        # Dependencies are
        # (app_label, model_name, field_name, create/delete as True/False)
        operation._auto_deps = dependencies or []
        if beginning:
            self.generated_operations.setdefault(app_label, []).insert(0, operation)
        else:
            self.generated_operations.setdefault(app_label, []).append(operation)

    def swappable_first_key(self, item):
        """
        Place potential swappable models first in lists of created models (only
        real way to solve #22783).
        """
        try:
            model_state = self.to_state.models[item]
            base_names = {
                base if isinstance(base, str) else base.__name__
                for base in model_state.bases
            }
            string_version = "%s.%s" % (item[0], item[1])
            if (
                model_state.options.get("swappable")
                or "AbstractUser" in base_names
                or "AbstractBaseUser" in base_names
                or settings.AUTH_USER_MODEL.lower() == string_version.lower()
            ):
                return ("___" + item[0], "___" + item[1])
        except LookupError:
            pass
        return item

    def generate_renamed_models(self):
        """
        Find any renamed models, generate the operations for them, and remove
        the old entry from the model lists. Must be run before other
        model-level generation.
        """
        self.renamed_models = {}
        self.renamed_models_rel = {}
        added_models = self.new_model_keys - self.old_model_keys
        for app_label, model_name in sorted(added_models):
            model_state = self.to_state.models[app_label, model_name]
            model_fields_def = self.only_relation_agnostic_fields(model_state.fields)

            removed_models = self.old_model_keys - self.new_model_keys
            for rem_app_label, rem_model_name in removed_models:
                if rem_app_label == app_label:
                    rem_model_state = self.from_state.models[
                        rem_app_label, rem_model_name
                    ]
                    rem_model_fields_def = self.only_relation_agnostic_fields(
                        rem_model_state.fields
                    )
                    if model_fields_def == rem_model_fields_def:
                        if self.questioner.ask_rename_model(
                            rem_model_state, model_state
                        ):
                            dependencies = []
                            fields = list(model_state.fields.values()) + [
                                field.remote_field
                                for relations in self.to_state.relations[
                                    app_label, model_name
                                ].values()
                                for field in relations.values()
                            ]
                            for field in fields:
                                if field.is_relation:
                                    dependencies.extend(
                                        self._get_dependencies_for_foreign_key(
                                            app_label,
                                            model_name,
                                            field,
                                            self.to_state,
                                        )
                                    )
                            self.add_operation(
                                app_label,
                                operations.RenameModel(
                                    old_name=rem_model_state.name,
                                    new_name=model_state.name,
                                ),
                                dependencies=dependencies,
                            )
                            self.renamed_models[app_label, model_name] = rem_model_name
                            renamed_models_rel_key = "%s.%s" % (
                                rem_model_state.app_label,
                                rem_model_state.name_lower,
                            )
                            self.renamed_models_rel[
                                renamed_models_rel_key
                            ] = "%s.%s" % (
                                model_state.app_label,
                                model_state.name_lower,
                            )
                            self.old_model_keys.remove((rem_app_label, rem_model_name))
                            self.old_model_keys.add((app_label, model_name))
                            break

    def generate_created_models(self):
        """
        Find all new models (both managed and unmanaged) and make create
        operations for them as well as separate operations to create any
        foreign key or M2M relationships (these are optimized later, if
        possible).

        Defer any model options that refer to collections of fields that might
        be deferred (e.g. unique_together, index_together).
        """
        old_keys = self.old_model_keys | self.old_unmanaged_keys
        added_models = self.new_model_keys - old_keys
        added_unmanaged_models = self.new_unmanaged_keys - old_keys
        all_added_models = chain(
            sorted(added_models, key=self.swappable_first_key, reverse=True),
            sorted(added_unmanaged_models, key=self.swappable_first_key, reverse=True),
        )
        for app_label, model_name in all_added_models:
            model_state = self.to_state.models[app_label, model_name]
            # Gather related fields
            related_fields = {}
            primary_key_rel = None
            for field_name, field in model_state.fields.items():
                if field.remote_field:
                    if field.remote_field.model:
                        if field.primary_key:
                            primary_key_rel = field.remote_field.model
                        elif not field.remote_field.parent_link:
                            related_fields[field_name] = field
                    if getattr(field.remote_field, "through", None):
                        related_fields[field_name] = field

            # Are there indexes/unique|index_together to defer?
            indexes = model_state.options.pop("indexes")
            constraints = model_state.options.pop("constraints")
            unique_together = model_state.options.pop("unique_together", None)
            # RemovedInDjango51Warning.
            index_together = model_state.options.pop("index_together", None)
            order_with_respect_to = model_state.options.pop(
                "order_with_respect_to", None
            )
            # Depend on the deletion of any possible proxy version of us
            dependencies = [
                (app_label, model_name, None, False),
            ]
            # Depend on all bases
            for base in model_state.bases:
                if isinstance(base, str) and "." in base:
                    base_app_label, base_name = base.split(".", 1)
                    dependencies.append((base_app_label, base_name, None, True))
                    # Depend on the removal of base fields if the new model has
                    # a field with the same name.
                    old_base_model_state = self.from_state.models.get(
                        (base_app_label, base_name)
                    )
                    new_base_model_state = self.to_state.models.get(
                        (base_app_label, base_name)
                    )
                    if old_base_model_state and new_base_model_state:
                        removed_base_fields = (
                            set(old_base_model_state.fields)
                            .difference(
                                new_base_model_state.fields,
                            )
                            .intersection(model_state.fields)
                        )
                        for removed_base_field in removed_base_fields:
                            dependencies.append(
                                (base_app_label, base_name, removed_base_field, False)
                            )
            # Depend on the other end of the primary key if it's a relation
            if primary_key_rel:
                dependencies.append(
                    resolve_relation(
                        primary_key_rel,
                        app_label,
                        model_name,
                    )
                    + (None, True)
                )
            # Generate creation operation
            self.add_operation(
                app_label,
                operations.CreateModel(
                    name=model_state.name,
                    fields=[
                        d
                        for d in model_state.fields.items()
                        if d[0] not in related_fields
                    ],
                    options=model_state.options,
                    bases=model_state.bases,
                    managers=model_state.managers,
                ),
                dependencies=dependencies,
                beginning=True,
            )

            # Don't add operations which modify the database for unmanaged models
            if not model_state.options.get("managed", True):
                continue

            # Generate operations for each related field
            for name, field in sorted(related_fields.items()):
                dependencies = self._get_dependencies_for_foreign_key(
                    app_label,
                    model_name,
                    field,
                    self.to_state,
                )
                # Depend on our own model being created
                dependencies.append((app_label, model_name, None, True))
                # Make operation
                self.add_operation(
                    app_label,
                    operations.AddField(
                        model_name=model_name,
                        name=name,
                        field=field,
                    ),
                    dependencies=list(set(dependencies)),
                )
            # Generate other opns
            if order_with_respect_to:
                self.add_operation(
                    app_label,
                    operations.AlterOrderWithRespectTo(
                        name=model_name,
                        order_with_respect_to=order_with_respect_to,
                    ),
                    dependencies=[
                        (app_label, model_name, order_with_respect_to, True),
                        (app_label, model_name, None, True),
                    ],
                )
            related_dependencies = [
                (app_label, model_name, name, True) for name in sorted(related_fields)
            ]
            related_dependencies.append((app_label, model_name, None, True))
            for index in indexes:
                self.add_operation(
                    app_label,
                    operations.AddIndex(
                        model_name=model_name,
                        index=index,
                    ),
                    dependencies=related_dependencies,
                )
            for constraint in constraints:
                self.add_operation(
                    app_label,
                    operations.AddConstraint(
                        model_name=model_name,
                        constraint=constraint,
                    ),
                    dependencies=related_dependencies,
                )
            if unique_together:
                self.add_operation(
                    app_label,
                    operations.AlterUniqueTogether(
                        name=model_name,
                        unique_together=unique_together,
                    ),
                    dependencies=related_dependencies,
                )
            # RemovedInDjango51Warning.
            if index_together:
                self.add_operation(
                    app_label,
                    operations.AlterIndexTogether(
                        name=model_name,
                        index_together=index_together,
                    ),
                    dependencies=related_dependencies,
                )
            # Fix relationships if the model changed from a proxy model to a
            # concrete model.
            relations = self.to_state.relations
            if (app_label, model_name) in self.old_proxy_keys:
                for related_model_key, related_fields in relations[
                    app_label, model_name
                ].items():
                    related_model_state = self.to_state.models[related_model_key]
                    for related_field_name, related_field in related_fields.items():
                        self.add_operation(
                            related_model_state.app_label,
                            operations.AlterField(
                                model_name=related_model_state.name,
                                name=related_field_name,
                                field=related_field,
                            ),
                            dependencies=[(app_label, model_name, None, True)],
                        )

    def generate_created_proxies(self):
        """
        Make CreateModel statements for proxy models. Use the same statements
        as that way there's less code duplication, but for proxy models it's
        safe to skip all the pointless field stuff and chuck out an operation.
        """
        added = self.new_proxy_keys - self.old_proxy_keys
        for app_label, model_name in sorted(added):
            model_state = self.to_state.models[app_label, model_name]
            assert model_state.options.get("proxy")
            # Depend on the deletion of any possible non-proxy version of us
            dependencies = [
                (app_label, model_name, None, False),
            ]
            # Depend on all bases
            for base in model_state.bases:
                if isinstance(base, str) and "." in base:
                    base_app_label, base_name = base.split(".", 1)
                    dependencies.append((base_app_label, base_name, None, True))
            # Generate creation operation
            self.add_operation(
                app_label,
                operations.CreateModel(
                    name=model_state.name,
                    fields=[],
                    options=model_state.options,
                    bases=model_state.bases,
                    managers=model_state.managers,
                ),
                # Depend on the deletion of any possible non-proxy version of us
                dependencies=dependencies,
            )

    def generate_deleted_models(self):
        """
        Find all deleted models (managed and unmanaged) and make delete
        operations for them as well as separate operations to delete any
        foreign key or M2M relationships (these are optimized later, if
        possible).

        Also bring forward removal of any model options that refer to
        collections of fields - the inverse of generate_created_models().
        """
        new_keys = self.new_model_keys | self.new_unmanaged_keys
        deleted_models = self.old_model_keys - new_keys
        deleted_unmanaged_models = self.old_unmanaged_keys - new_keys
        all_deleted_models = chain(
            sorted(deleted_models), sorted(deleted_unmanaged_models)
        )
        for app_label, model_name in all_deleted_models:
            model_state = self.from_state.models[app_label, model_name]
            # Gather related fields
            related_fields = {}
            for field_name, field in model_state.fields.items():
                if field.remote_field:
                    if field.remote_field.model:
                        related_fields[field_name] = field
                    if getattr(field.remote_field, "through", None):
                        related_fields[field_name] = field
            # Generate option removal first
            unique_together = model_state.options.pop("unique_together", None)
            # RemovedInDjango51Warning.
            index_together = model_state.options.pop("index_together", None)
            if unique_together:
                self.add_operation(
                    app_label,
                    operations.AlterUniqueTogether(
                        name=model_name,
                        unique_together=None,
                    ),
                )
            # RemovedInDjango51Warning.
            if index_together:
                self.add_operation(
                    app_label,
                    operations.AlterIndexTogether(
                        name=model_name,
                        index_together=None,
                    ),
                )
            # Then remove each related field
            for name in sorted(related_fields):
                self.add_operation(
                    app_label,
                    operations.RemoveField(
                        model_name=model_name,
                        name=name,
                    ),
                )
            # Finally, remove the model.
            # This depends on both the removal/alteration of all incoming fields
            # and the removal of all its own related fields, and if it's
            # a through model the field that references it.
            dependencies = []
            relations = self.from_state.relations
            for (
                related_object_app_label,
                object_name,
            ), relation_related_fields in relations[app_label, model_name].items():
                for field_name, field in relation_related_fields.items():
                    dependencies.append(
                        (related_object_app_label, object_name, field_name, False),
                    )
                    if not field.many_to_many:
                        dependencies.append(
                            (
                                related_object_app_label,
                                object_name,
                                field_name,
                                "alter",
                            ),
                        )

            for name in sorted(related_fields):
                dependencies.append((app_label, model_name, name, False))
            # We're referenced in another field's through=
            through_user = self.through_users.get((app_label, model_state.name_lower))
            if through_user:
                dependencies.append(
                    (through_user[0], through_user[1], through_user[2], False)
                )
            # Finally, make the operation, deduping any dependencies
            self.add_operation(
                app_label,
                operations.DeleteModel(
                    name=model_state.name,
                ),
                dependencies=list(set(dependencies)),
            )

    def generate_deleted_proxies(self):
        """Make DeleteModel options for proxy models."""
        deleted = self.old_proxy_keys - self.new_proxy_keys
        for app_label, model_name in sorted(deleted):
            model_state = self.from_state.models[app_label, model_name]
            assert model_state.options.get("proxy")
            self.add_operation(
                app_label,
                operations.DeleteModel(
                    name=model_state.name,
                ),
            )

    def create_renamed_fields(self):
        """Work out renamed fields."""
        self.renamed_operations = []
        old_field_keys = self.old_field_keys.copy()
        for app_label, model_name, field_name in sorted(
            self.new_field_keys - old_field_keys
        ):
            old_model_name = self.renamed_models.get(
                (app_label, model_name), model_name
            )
            old_model_state = self.from_state.models[app_label, old_model_name]
            new_model_state = self.to_state.models[app_label, model_name]
            field = new_model_state.get_field(field_name)
            # Scan to see if this is actually a rename!
            field_dec = self.deep_deconstruct(field)
            for rem_app_label, rem_model_name, rem_field_name in sorted(
                old_field_keys - self.new_field_keys
            ):
                if rem_app_label == app_label and rem_model_name == model_name:
                    old_field = old_model_state.get_field(rem_field_name)
                    old_field_dec = self.deep_deconstruct(old_field)
                    if (
                        field.remote_field
                        and field.remote_field.model
                        and "to" in old_field_dec[2]
                    ):
                        old_rel_to = old_field_dec[2]["to"]
                        if old_rel_to in self.renamed_models_rel:
                            old_field_dec[2]["to"] = self.renamed_models_rel[old_rel_to]
                    old_field.set_attributes_from_name(rem_field_name)
                    old_db_column = old_field.get_attname_column()[1]
                    if old_field_dec == field_dec or (
                        # Was the field renamed and db_column equal to the
                        # old field's column added?
                        old_field_dec[0:2] == field_dec[0:2]
                        and dict(old_field_dec[2], db_column=old_db_column)
                        == field_dec[2]
                    ):
                        if self.questioner.ask_rename(
                            model_name, rem_field_name, field_name, field
                        ):
                            self.renamed_operations.append(
                                (
                                    rem_app_label,
                                    rem_model_name,
                                    old_field.db_column,
                                    rem_field_name,
                                    app_label,
                                    model_name,
                                    field,
                                    field_name,
                                )
                            )
                            old_field_keys.remove(
                                (rem_app_label, rem_model_name, rem_field_name)
                            )
                            old_field_keys.add((app_label, model_name, field_name))
                            self.renamed_fields[
                                app_label, model_name, field_name
                            ] = rem_field_name
                            break

    def generate_renamed_fields(self):
        """Generate RenameField operations."""
        for (
            rem_app_label,
            rem_model_name,
            rem_db_column,
            rem_field_name,
            app_label,
            model_name,
            field,
            field_name,
        ) in self.renamed_operations:
            # A db_column mismatch requires a prior noop AlterField for the
            # subsequent RenameField to be a noop on attempts at preserving the
            # old name.
            if rem_db_column != field.db_column:
                altered_field = field.clone()
                altered_field.name = rem_field_name
                self.add_operation(
                    app_label,
                    operations.AlterField(
                        model_name=model_name,
                        name=rem_field_name,
                        field=altered_field,
                    ),
                )
            self.add_operation(
                app_label,
                operations.RenameField(
                    model_name=model_name,
                    old_name=rem_field_name,
                    new_name=field_name,
                ),
            )
            self.old_field_keys.remove((rem_app_label, rem_model_name, rem_field_name))
            self.old_field_keys.add((app_label, model_name, field_name))

    def generate_added_fields(self):
        """Make AddField operations."""
        for app_label, model_name, field_name in sorted(
            self.new_field_keys - self.old_field_keys
        ):
            self._generate_added_field(app_label, model_name, field_name)

    def _generate_added_field(self, app_label, model_name, field_name):
        field = self.to_state.models[app_label, model_name].get_field(field_name)
        # Adding a field always depends at least on its removal.
        dependencies = [(app_label, model_name, field_name, False)]
        # Fields that are foreignkeys/m2ms depend on stuff.
        if field.remote_field and field.remote_field.model:
            dependencies.extend(
                self._get_dependencies_for_foreign_key(
                    app_label,
                    model_name,
                    field,
                    self.to_state,
                )
            )
        # You can't just add NOT NULL fields with no default or fields
        # which don't allow empty strings as default.
        time_fields = (models.DateField, models.DateTimeField, models.TimeField)
        preserve_default = (
            field.null
            or field.has_default()
            or field.many_to_many
            or (field.blank and field.empty_strings_allowed)
            or (isinstance(field, time_fields) and field.auto_now)
        )
        if not preserve_default:
            field = field.clone()
            if isinstance(field, time_fields) and field.auto_now_add:
                field.default = self.questioner.ask_auto_now_add_addition(
                    field_name, model_name
                )
            else:
                field.default = self.questioner.ask_not_null_addition(
                    field_name, model_name
                )
        if (
            field.unique
            and field.default is not models.NOT_PROVIDED
            and callable(field.default)
        ):
            self.questioner.ask_unique_callable_default_addition(field_name, model_name)
        self.add_operation(
            app_label,
            operations.AddField(
                model_name=model_name,
                name=field_name,
                field=field,
                preserve_default=preserve_default,
            ),
            dependencies=dependencies,
        )

    def generate_removed_fields(self):
        """Make RemoveField operations."""
        for app_label, model_name, field_name in sorted(
            self.old_field_keys - self.new_field_keys
        ):
            self._generate_removed_field(app_label, model_name, field_name)

    def _generate_removed_field(self, app_label, model_name, field_name):
        self.add_operation(
            app_label,
            operations.RemoveField(
                model_name=model_name,
                name=field_name,
            ),
            # We might need to depend on the removal of an
            # order_with_respect_to or index/unique_together operation;
            # this is safely ignored if there isn't one
            dependencies=[
                (app_label, model_name, field_name, "order_wrt_unset"),
                (app_label, model_name, field_name, "foo_together_change"),
            ],
        )

    def generate_altered_fields(self):
        """
        Make AlterField operations, or possibly RemovedField/AddField if alter
        isn't possible.
        """
        for app_label, model_name, field_name in sorted(
            self.old_field_keys & self.new_field_keys
        ):
            # Did the field change?
            old_model_name = self.renamed_models.get(
                (app_label, model_name), model_name
            )
            old_field_name = self.renamed_fields.get(
                (app_label, model_name, field_name), field_name
            )
            old_field = self.from_state.models[app_label, old_model_name].get_field(
                old_field_name
            )
            new_field = self.to_state.models[app_label, model_name].get_field(
                field_name
            )
            dependencies = []
            # Implement any model renames on relations; these are handled by RenameModel
            # so we need to exclude them from the comparison
            if hasattr(new_field, "remote_field") and getattr(
                new_field.remote_field, "model", None
            ):
                rename_key = resolve_relation(
                    new_field.remote_field.model, app_label, model_name
                )
                if rename_key in self.renamed_models:
                    new_field.remote_field.model = old_field.remote_field.model
                # Handle ForeignKey which can only have a single to_field.
                remote_field_name = getattr(new_field.remote_field, "field_name", None)
                if remote_field_name:
                    to_field_rename_key = rename_key + (remote_field_name,)
                    if to_field_rename_key in self.renamed_fields:
                        # Repoint both model and field name because to_field
                        # inclusion in ForeignKey.deconstruct() is based on
                        # both.
                        new_field.remote_field.model = old_field.remote_field.model
                        new_field.remote_field.field_name = (
                            old_field.remote_field.field_name
                        )
                # Handle ForeignObjects which can have multiple from_fields/to_fields.
                from_fields = getattr(new_field, "from_fields", None)
                if from_fields:
                    from_rename_key = (app_label, model_name)
                    new_field.from_fields = tuple(
                        [
                            self.renamed_fields.get(
                                from_rename_key + (from_field,), from_field
                            )
                            for from_field in from_fields
                        ]
                    )
                    new_field.to_fields = tuple(
                        [
                            self.renamed_fields.get(rename_key + (to_field,), to_field)
                            for to_field in new_field.to_fields
                        ]
                    )
                dependencies.extend(
                    self._get_dependencies_for_foreign_key(
                        app_label,
                        model_name,
                        new_field,
                        self.to_state,
                    )
                )
            if hasattr(new_field, "remote_field") and getattr(
                new_field.remote_field, "through", None
            ):
                rename_key = resolve_relation(
                    new_field.remote_field.through, app_label, model_name
                )
                if rename_key in self.renamed_models:
                    new_field.remote_field.through = old_field.remote_field.through
            old_field_dec = self.deep_deconstruct(old_field)
            new_field_dec = self.deep_deconstruct(new_field)
            # If the field was confirmed to be renamed it means that only
            # db_column was allowed to change which generate_renamed_fields()
            # already accounts for by adding an AlterField operation.
            if old_field_dec != new_field_dec and old_field_name == field_name:
                both_m2m = old_field.many_to_many and new_field.many_to_many
                neither_m2m = not old_field.many_to_many and not new_field.many_to_many
                if both_m2m or neither_m2m:
                    # Either both fields are m2m or neither is
                    preserve_default = True
                    if (
                        old_field.null
                        and not new_field.null
                        and not new_field.has_default()
                        and not new_field.many_to_many
                    ):
                        field = new_field.clone()
                        new_default = self.questioner.ask_not_null_alteration(
                            field_name, model_name
                        )
                        if new_default is not models.NOT_PROVIDED:
                            field.default = new_default
                            preserve_default = False
                    else:
                        field = new_field
                    self.add_operation(
                        app_label,
                        operations.AlterField(
                            model_name=model_name,
                            name=field_name,
                            field=field,
                            preserve_default=preserve_default,
                        ),
                        dependencies=dependencies,
                    )
                else:
                    # We cannot alter between m2m and concrete fields
                    self._generate_removed_field(app_label, model_name, field_name)
                    self._generate_added_field(app_label, model_name, field_name)

    def create_altered_indexes(self):
        option_name = operations.AddIndex.option_name
        self.renamed_index_together_values = defaultdict(list)

        for app_label, model_name in sorted(self.kept_model_keys):
            old_model_name = self.renamed_models.get(
                (app_label, model_name), model_name
            )
            old_model_state = self.from_state.models[app_label, old_model_name]
            new_model_state = self.to_state.models[app_label, model_name]

            old_indexes = old_model_state.options[option_name]
            new_indexes = new_model_state.options[option_name]
            added_indexes = [idx for idx in new_indexes if idx not in old_indexes]
            removed_indexes = [idx for idx in old_indexes if idx not in new_indexes]
            renamed_indexes = []
            # Find renamed indexes.
            remove_from_added = []
            remove_from_removed = []
            for new_index in added_indexes:
                new_index_dec = new_index.deconstruct()
                new_index_name = new_index_dec[2].pop("name")
                for old_index in removed_indexes:
                    old_index_dec = old_index.deconstruct()
                    old_index_name = old_index_dec[2].pop("name")
                    # Indexes are the same except for the names.
                    if (
                        new_index_dec == old_index_dec
                        and new_index_name != old_index_name
                    ):
                        renamed_indexes.append((old_index_name, new_index_name, None))
                        remove_from_added.append(new_index)
                        remove_from_removed.append(old_index)
            # Find index_together changed to indexes.
            for (
                old_value,
                new_value,
                index_together_app_label,
                index_together_model_name,
                dependencies,
            ) in self._get_altered_foo_together_operations(
                operations.AlterIndexTogether.option_name
            ):
                if (
                    app_label != index_together_app_label
                    or model_name != index_together_model_name
                ):
                    continue
                removed_values = old_value.difference(new_value)
                for removed_index_together in removed_values:
                    renamed_index_together_indexes = []
                    for new_index in added_indexes:
                        _, args, kwargs = new_index.deconstruct()
                        # Ensure only 'fields' are defined in the Index.
                        if (
                            not args
                            and new_index.fields == list(removed_index_together)
                            and set(kwargs) == {"name", "fields"}
                        ):
                            renamed_index_together_indexes.append(new_index)

                    if len(renamed_index_together_indexes) == 1:
                        renamed_index = renamed_index_together_indexes[0]
                        remove_from_added.append(renamed_index)
                        renamed_indexes.append(
                            (None, renamed_index.name, removed_index_together)
                        )
                        self.renamed_index_together_values[
                            index_together_app_label, index_together_model_name
                        ].append(removed_index_together)
            # Remove renamed indexes from the lists of added and removed
            # indexes.
            added_indexes = [
                idx for idx in added_indexes if idx not in remove_from_added
            ]
            removed_indexes = [
                idx for idx in removed_indexes if idx not in remove_from_removed
            ]

            self.altered_indexes.update(
                {
                    (app_label, model_name): {
                        "added_indexes": added_indexes,
                        "removed_indexes": removed_indexes,
                        "renamed_indexes": renamed_indexes,
                    }
                }
            )

    def generate_added_indexes(self):
        for (app_label, model_name), alt_indexes in self.altered_indexes.items():
            for index in alt_indexes["added_indexes"]:
                self.add_operation(
                    app_label,
                    operations.AddIndex(
                        model_name=model_name,
                        index=index,
                    ),
                )

    def generate_removed_indexes(self):
        for (app_label, model_name), alt_indexes in self.altered_indexes.items():
            for index in alt_indexes["removed_indexes"]:
                self.add_operation(
                    app_label,
                    operations.RemoveIndex(
                        model_name=model_name,
                        name=index.name,
                    ),
                )

    def generate_renamed_indexes(self):
        for (app_label, model_name), alt_indexes in self.altered_indexes.items():
            for old_index_name, new_index_name, old_fields in alt_indexes[
                "renamed_indexes"
            ]:
                self.add_operation(
                    app_label,
                    operations.RenameIndex(
                        model_name=model_name,
                        new_name=new_index_name,
                        old_name=old_index_name,
                        old_fields=old_fields,
                    ),
                )

    def create_altered_constraints(self):
        option_name = operations.AddConstraint.option_name
        for app_label, model_name in sorted(self.kept_model_keys):
            old_model_name = self.renamed_models.get(
                (app_label, model_name), model_name
            )
            old_model_state = self.from_state.models[app_label, old_model_name]
            new_model_state = self.to_state.models[app_label, model_name]

            old_constraints = old_model_state.options[option_name]
            new_constraints = new_model_state.options[option_name]
            add_constraints = [c for c in new_constraints if c not in old_constraints]
            rem_constraints = [c for c in old_constraints if c not in new_constraints]

            self.altered_constraints.update(
                {
                    (app_label, model_name): {
                        "added_constraints": add_constraints,
                        "removed_constraints": rem_constraints,
                    }
                }
            )

    def generate_added_constraints(self):
        for (
            app_label,
            model_name,
        ), alt_constraints in self.altered_constraints.items():
            for constraint in alt_constraints["added_constraints"]:
                self.add_operation(
                    app_label,
                    operations.AddConstraint(
                        model_name=model_name,
                        constraint=constraint,
                    ),
                )

    def generate_removed_constraints(self):
        for (
            app_label,
            model_name,
        ), alt_constraints in self.altered_constraints.items():
            for constraint in alt_constraints["removed_constraints"]:
                self.add_operation(
                    app_label,
                    operations.RemoveConstraint(
                        model_name=model_name,
                        name=constraint.name,
                    ),
                )

    @staticmethod
    def _get_dependencies_for_foreign_key(app_label, model_name, field, project_state):
        remote_field_model = None
        if hasattr(field.remote_field, "model"):
            remote_field_model = field.remote_field.model
        else:
            relations = project_state.relations[app_label, model_name]
            for (remote_app_label, remote_model_name), fields in relations.items():
                if any(
                    field == related_field.remote_field
                    for related_field in fields.values()
                ):
                    remote_field_model = f"{remote_app_label}.{remote_model_name}"
                    break
        # Account for FKs to swappable models
        swappable_setting = getattr(field, "swappable_setting", None)
        if swappable_setting is not None:
            dep_app_label = "__setting__"
            dep_object_name = swappable_setting
        else:
            dep_app_label, dep_object_name = resolve_relation(
                remote_field_model,
                app_label,
                model_name,
            )
        dependencies = [(dep_app_label, dep_object_name, None, True)]
        if getattr(field.remote_field, "through", None):
            through_app_label, through_object_name = resolve_relation(
                field.remote_field.through,
                app_label,
                model_name,
            )
            dependencies.append((through_app_label, through_object_name, None, True))
        return dependencies

    def _get_altered_foo_together_operations(self, option_name):
        for app_label, model_name in sorted(self.kept_model_keys):
            old_model_name = self.renamed_models.get(
                (app_label, model_name), model_name
            )
            old_model_state = self.from_state.models[app_label, old_model_name]
            new_model_state = self.to_state.models[app_label, model_name]

            # We run the old version through the field renames to account for those
            old_value = old_model_state.options.get(option_name)
            old_value = (
                {
                    tuple(
                        self.renamed_fields.get((app_label, model_name, n), n)
                        for n in unique
                    )
                    for unique in old_value
                }
                if old_value
                else set()
            )

            new_value = new_model_state.options.get(option_name)
            new_value = set(new_value) if new_value else set()

            if old_value != new_value:
                dependencies = []
                for foo_togethers in new_value:
                    for field_name in foo_togethers:
                        field = new_model_state.get_field(field_name)
                        if field.remote_field and field.remote_field.model:
                            dependencies.extend(
                                self._get_dependencies_for_foreign_key(
                                    app_label,
                                    model_name,
                                    field,
                                    self.to_state,
                                )
                            )
                yield (
                    old_value,
                    new_value,
                    app_label,
                    model_name,
                    dependencies,
                )

    def _generate_removed_altered_foo_together(self, operation):
        for (
            old_value,
            new_value,
            app_label,
            model_name,
            dependencies,
        ) in self._get_altered_foo_together_operations(operation.option_name):
            if operation == operations.AlterIndexTogether:
                old_value = {
                    value
                    for value in old_value
                    if value
                    not in self.renamed_index_together_values[app_label, model_name]
                }
            removal_value = new_value.intersection(old_value)
            if removal_value or old_value:
                self.add_operation(
                    app_label,
                    operation(
                        name=model_name, **{operation.option_name: removal_value}
                    ),
                    dependencies=dependencies,
                )

    def generate_removed_altered_unique_together(self):
        self._generate_removed_altered_foo_together(operations.AlterUniqueTogether)

    # RemovedInDjango51Warning.
    def generate_removed_altered_index_together(self):
        self._generate_removed_altered_foo_together(operations.AlterIndexTogether)

    def _generate_altered_foo_together(self, operation):
        for (
            old_value,
            new_value,
            app_label,
            model_name,
            dependencies,
        ) in self._get_altered_foo_together_operations(operation.option_name):
            removal_value = new_value.intersection(old_value)
            if new_value != removal_value:
                self.add_operation(
                    app_label,
                    operation(name=model_name, **{operation.option_name: new_value}),
                    dependencies=dependencies,
                )

    def generate_altered_unique_together(self):
        self._generate_altered_foo_together(operations.AlterUniqueTogether)

    # RemovedInDjango51Warning.
    def generate_altered_index_together(self):
        self._generate_altered_foo_together(operations.AlterIndexTogether)

    def generate_altered_db_table(self):
        models_to_check = self.kept_model_keys.union(
            self.kept_proxy_keys, self.kept_unmanaged_keys
        )
        for app_label, model_name in sorted(models_to_check):
            old_model_name = self.renamed_models.get(
                (app_label, model_name), model_name
            )
            old_model_state = self.from_state.models[app_label, old_model_name]
            new_model_state = self.to_state.models[app_label, model_name]
            old_db_table_name = old_model_state.options.get("db_table")
            new_db_table_name = new_model_state.options.get("db_table")
            if old_db_table_name != new_db_table_name:
                self.add_operation(
                    app_label,
                    operations.AlterModelTable(
                        name=model_name,
                        table=new_db_table_name,
                    ),
                )

    def generate_altered_db_table_comment(self):
        models_to_check = self.kept_model_keys.union(
            self.kept_proxy_keys, self.kept_unmanaged_keys
        )
        for app_label, model_name in sorted(models_to_check):
            old_model_name = self.renamed_models.get(
                (app_label, model_name), model_name
            )
            old_model_state = self.from_state.models[app_label, old_model_name]
            new_model_state = self.to_state.models[app_label, model_name]

            old_db_table_comment = old_model_state.options.get("db_table_comment")
            new_db_table_comment = new_model_state.options.get("db_table_comment")
            if old_db_table_comment != new_db_table_comment:
                self.add_operation(
                    app_label,
                    operations.AlterModelTableComment(
                        name=model_name,
                        table_comment=new_db_table_comment,
                    ),
                )

    def generate_altered_options(self):
        """
        Work out if any non-schema-affecting options have changed and make an
        operation to represent them in state changes (in case Python code in
        migrations needs them).
        """
        models_to_check = self.kept_model_keys.union(
            self.kept_proxy_keys,
            self.kept_unmanaged_keys,
            # unmanaged converted to managed
            self.old_unmanaged_keys & self.new_model_keys,
            # managed converted to unmanaged
            self.old_model_keys & self.new_unmanaged_keys,
        )

        for app_label, model_name in sorted(models_to_check):
            old_model_name = self.renamed_models.get(
                (app_label, model_name), model_name
            )
            old_model_state = self.from_state.models[app_label, old_model_name]
            new_model_state = self.to_state.models[app_label, model_name]
            old_options = {
                key: value
                for key, value in old_model_state.options.items()
                if key in AlterModelOptions.ALTER_OPTION_KEYS
            }
            new_options = {
                key: value
                for key, value in new_model_state.options.items()
                if key in AlterModelOptions.ALTER_OPTION_KEYS
            }
            if old_options != new_options:
                self.add_operation(
                    app_label,
                    operations.AlterModelOptions(
                        name=model_name,
                        options=new_options,
                    ),
                )

    def generate_altered_order_with_respect_to(self):
        for app_label, model_name in sorted(self.kept_model_keys):
            old_model_name = self.renamed_models.get(
                (app_label, model_name), model_name
            )
            old_model_state = self.from_state.models[app_label, old_model_name]
            new_model_state = self.to_state.models[app_label, model_name]
            if old_model_state.options.get(
                "order_with_respect_to"
            ) != new_model_state.options.get("order_with_respect_to"):
                # Make sure it comes second if we're adding
                # (removal dependency is part of RemoveField)
                dependencies = []
                if new_model_state.options.get("order_with_respect_to"):
                    dependencies.append(
                        (
                            app_label,
                            model_name,
                            new_model_state.options["order_with_respect_to"],
                            True,
                        )
                    )
                # Actually generate the operation
                self.add_operation(
                    app_label,
                    operations.AlterOrderWithRespectTo(
                        name=model_name,
                        order_with_respect_to=new_model_state.options.get(
                            "order_with_respect_to"
                        ),
                    ),
                    dependencies=dependencies,
                )

    def generate_altered_managers(self):
        for app_label, model_name in sorted(self.kept_model_keys):
            old_model_name = self.renamed_models.get(
                (app_label, model_name), model_name
            )
            old_model_state = self.from_state.models[app_label, old_model_name]
            new_model_state = self.to_state.models[app_label, model_name]
            if old_model_state.managers != new_model_state.managers:
                self.add_operation(
                    app_label,
                    operations.AlterModelManagers(
                        name=model_name,
                        managers=new_model_state.managers,
                    ),
                )

    def arrange_for_graph(self, changes, graph, migration_name=None):
        """
        Take a result from changes() and a MigrationGraph, and fix the names
        and dependencies of the changes so they extend the graph from the leaf
        nodes for each app.
        """
        leaves = graph.leaf_nodes()
        name_map = {}
        for app_label, migrations in list(changes.items()):
            if not migrations:
                continue
            # Find the app label's current leaf node
            app_leaf = None
            for leaf in leaves:
                if leaf[0] == app_label:
                    app_leaf = leaf
                    break
            # Do they want an initial migration for this app?
            if app_leaf is None and not self.questioner.ask_initial(app_label):
                # They don't.
                for migration in migrations:
                    name_map[(app_label, migration.name)] = (app_label, "__first__")
                del changes[app_label]
                continue
            # Work out the next number in the sequence
            if app_leaf is None:
                next_number = 1
            else:
                next_number = (self.parse_number(app_leaf[1]) or 0) + 1
            # Name each migration
            for i, migration in enumerate(migrations):
                if i == 0 and app_leaf:
                    migration.dependencies.append(app_leaf)
                new_name_parts = ["%04i" % next_number]
                if migration_name:
                    new_name_parts.append(migration_name)
                elif i == 0 and not app_leaf:
                    new_name_parts.append("initial")
                else:
                    new_name_parts.append(migration.suggest_name()[:100])
                new_name = "_".join(new_name_parts)
                name_map[(app_label, migration.name)] = (app_label, new_name)
                next_number += 1
                migration.name = new_name
        # Now fix dependencies
        for migrations in changes.values():
            for migration in migrations:
                migration.dependencies = [
                    name_map.get(d, d) for d in migration.dependencies
                ]
        return changes

    def _trim_to_apps(self, changes, app_labels):
        """
        Take changes from arrange_for_graph() and set of app labels, and return
        a modified set of changes which trims out as many migrations that are
        not in app_labels as possible. Note that some other migrations may
        still be present as they may be required dependencies.
        """
        # Gather other app dependencies in a first pass
        app_dependencies = {}
        for app_label, migrations in changes.items():
            for migration in migrations:
                for dep_app_label, name in migration.dependencies:
                    app_dependencies.setdefault(app_label, set()).add(dep_app_label)
        required_apps = set(app_labels)
        # Keep resolving till there's no change
        old_required_apps = None
        while old_required_apps != required_apps:
            old_required_apps = set(required_apps)
            required_apps.update(
                *[app_dependencies.get(app_label, ()) for app_label in required_apps]
            )
        # Remove all migrations that aren't needed
        for app_label in list(changes):
            if app_label not in required_apps:
                del changes[app_label]
        return changes

    @classmethod
    def parse_number(cls, name):
        """
        Given a migration name, try to extract a number from the beginning of
        it. For a squashed migration such as '0001_squashed_0004…', return the
        second number. If no number is found, return None.
        """
        if squashed_match := re.search(r".*_squashed_(\d+)", name):
            return int(squashed_match[1])
        match = re.match(r"^\d+", name)
        if match:
            return int(match[0])
        return None

virt/lib/python3.9/site-packages/django/db/migrations/autodetector 2.py
PrinceDisant/LostInNL
import functools
import re
from collections import defaultdict
from itertools import chain

from django.conf import settings
from django.db import models
from django.db.migrations import operations
from django.db.migrations.migration import Migration
from django.db.migrations.operations.models import AlterModelOptions
from django.db.migrations.optimizer import MigrationOptimizer
from django.db.migrations.questioner import MigrationQuestioner
from django.db.migrations.utils import (
    COMPILED_REGEX_TYPE,
    RegexObject,
    resolve_relation,
)
from django.utils.topological_sort import stable_topological_sort


class MigrationAutodetector:
    """
    Take a pair of ProjectStates and compare them to see what the first would
    need doing to make it match the second (the second usually being the
    project's current state).

    Note that this naturally operates on entire projects at a time,
    as it's likely that changes interact (for example, you can't
    add a ForeignKey without having a migration to add the table it
    depends on first). A user interface may offer single-app usage
    if it wishes, with the caveat that it may not always be possible.
    """

    def __init__(self, from_state, to_state, questioner=None):
        self.from_state = from_state
        self.to_state = to_state
        self.questioner = questioner or MigrationQuestioner()
        self.existing_apps = {app for app, model in from_state.models}

    def changes(self, graph, trim_to_apps=None, convert_apps=None, migration_name=None):
        """
        Main entry point to produce a list of applicable changes.
        Take a graph to base names on and an optional set of apps
        to try and restrict to (restriction is not guaranteed)
        """
        changes = self._detect_changes(convert_apps, graph)
        changes = self.arrange_for_graph(changes, graph, migration_name)
        if trim_to_apps:
            changes = self._trim_to_apps(changes, trim_to_apps)
        return changes

    def deep_deconstruct(self, obj):
        """
        Recursive deconstruction for a field and its arguments.
        Used for full comparison for rename/alter; sometimes a single-level
        deconstruction will not compare correctly.
        """
        if isinstance(obj, list):
            return [self.deep_deconstruct(value) for value in obj]
        elif isinstance(obj, tuple):
            return tuple(self.deep_deconstruct(value) for value in obj)
        elif isinstance(obj, dict):
            return {key: self.deep_deconstruct(value) for key, value in obj.items()}
        elif isinstance(obj, functools.partial):
            return (
                obj.func,
                self.deep_deconstruct(obj.args),
                self.deep_deconstruct(obj.keywords),
            )
        elif isinstance(obj, COMPILED_REGEX_TYPE):
            return RegexObject(obj)
        elif isinstance(obj, type):
            # If this is a type that implements 'deconstruct' as an instance method,
            # avoid treating this as being deconstructible itself - see #22951
            return obj
        elif hasattr(obj, "deconstruct"):
            deconstructed = obj.deconstruct()
            if isinstance(obj, models.Field):
                # we have a field which also returns a name
                deconstructed = deconstructed[1:]
            path, args, kwargs = deconstructed
            return (
                path,
                [self.deep_deconstruct(value) for value in args],
                {key: self.deep_deconstruct(value) for key, value in kwargs.items()},
            )
        else:
            return obj

    def only_relation_agnostic_fields(self, fields):
        """
        Return a definition of the fields that ignores field names and
        what related fields actually relate to. Used for detecting renames (as
        the related fields change during renames).
        """
        fields_def = []
        for name, field in sorted(fields.items()):
            deconstruction = self.deep_deconstruct(field)
            if field.remote_field and field.remote_field.model:
                deconstruction[2].pop("to", None)
            fields_def.append(deconstruction)
        return fields_def

    def _detect_changes(self, convert_apps=None, graph=None):
        """
        Return a dict of migration plans which will achieve the
        change from from_state to to_state. The dict has app labels
        as keys and a list of migrations as values.

        The resulting migrations aren't specially named, but the names
        do matter for dependencies inside the set.

        convert_apps is the list of apps to convert to use migrations
        (i.e. to make initial migrations for, in the usual case)

        graph is an optional argument that, if provided, can help improve
        dependency generation and avoid potential circular dependencies.
        """
        # The first phase is generating all the operations for each app
        # and gathering them into a big per-app list.
        # Then go through that list, order it, and split into migrations to
        # resolve dependencies caused by M2Ms and FKs.
        self.generated_operations = {}
        self.altered_indexes = {}
        self.altered_constraints = {}
        self.renamed_fields = {}

        # Prepare some old/new state and model lists, separating
        # proxy models and ignoring unmigrated apps.
        self.old_model_keys = set()
        self.old_proxy_keys = set()
        self.old_unmanaged_keys = set()
        self.new_model_keys = set()
        self.new_proxy_keys = set()
        self.new_unmanaged_keys = set()
        for (app_label, model_name), model_state in self.from_state.models.items():
            if not model_state.options.get("managed", True):
                self.old_unmanaged_keys.add((app_label, model_name))
            elif app_label not in self.from_state.real_apps:
                if model_state.options.get("proxy"):
                    self.old_proxy_keys.add((app_label, model_name))
                else:
                    self.old_model_keys.add((app_label, model_name))

        for (app_label, model_name), model_state in self.to_state.models.items():
            if not model_state.options.get("managed", True):
                self.new_unmanaged_keys.add((app_label, model_name))
            elif app_label not in self.from_state.real_apps or (
                convert_apps and app_label in convert_apps
            ):
                if model_state.options.get("proxy"):
                    self.new_proxy_keys.add((app_label, model_name))
                else:
                    self.new_model_keys.add((app_label, model_name))

        self.from_state.resolve_fields_and_relations()
        self.to_state.resolve_fields_and_relations()

        # Renames have to come first
        self.generate_renamed_models()

        # Prepare lists of fields and generate through model map
        self._prepare_field_lists()
        self._generate_through_model_map()

        # Generate non-rename model operations
        self.generate_deleted_models()
        self.generate_created_models()
        self.generate_deleted_proxies()
        self.generate_created_proxies()
        self.generate_altered_options()
        self.generate_altered_managers()
        self.generate_altered_db_table_comment()

        # Create the renamed fields and store them in self.renamed_fields.
        # They are used by create_altered_indexes(), generate_altered_fields(),
        # generate_removed_altered_index/unique_together(), and
        # generate_altered_index/unique_together().
        self.create_renamed_fields()
        # Create the altered indexes and store them in self.altered_indexes.
        # This avoids the same computation in generate_removed_indexes()
        # and generate_added_indexes().
        self.create_altered_indexes()
        self.create_altered_constraints()
        # Generate index removal operations before field is removed
        self.generate_removed_constraints()
        self.generate_removed_indexes()
        # Generate field renaming operations.
        self.generate_renamed_fields()
        self.generate_renamed_indexes()
        # Generate removal of foo together.
        self.generate_removed_altered_unique_together()
        self.generate_removed_altered_index_together()  # RemovedInDjango51Warning.
        # Generate field operations.
        self.generate_removed_fields()
        self.generate_added_fields()
        self.generate_altered_fields()
        self.generate_altered_order_with_respect_to()
        self.generate_altered_unique_together()
        self.generate_altered_index_together()  # RemovedInDjango51Warning.
        self.generate_added_indexes()
        self.generate_added_constraints()
        self.generate_altered_db_table()

        self._sort_migrations()
        self._build_migration_list(graph)
        self._optimize_migrations()

        return self.migrations

    def _prepare_field_lists(self):
        """
        Prepare field lists and a list of the fields that used through models
        in the old state so dependencies can be made from the through model
        deletion to the field that uses it.
        """
        self.kept_model_keys = self.old_model_keys & self.new_model_keys
        self.kept_proxy_keys = self.old_proxy_keys & self.new_proxy_keys
        self.kept_unmanaged_keys = self.old_unmanaged_keys & self.new_unmanaged_keys
        self.through_users = {}
        self.old_field_keys = {
            (app_label, model_name, field_name)
            for app_label, model_name in self.kept_model_keys
            for field_name in self.from_state.models[
                app_label, self.renamed_models.get((app_label, model_name), model_name)
            ].fields
        }
        self.new_field_keys = {
            (app_label, model_name, field_name)
            for app_label, model_name in self.kept_model_keys
            for field_name in self.to_state.models[app_label, model_name].fields
        }

    def _generate_through_model_map(self):
        """Through model map generation."""
        for app_label, model_name in sorted(self.old_model_keys):
            old_model_name = self.renamed_models.get(
                (app_label, model_name), model_name
            )
            old_model_state = self.from_state.models[app_label, old_model_name]
            for field_name, field in old_model_state.fields.items():
                if hasattr(field, "remote_field") and getattr(
                    field.remote_field, "through", None
                ):
                    through_key = resolve_relation(
                        field.remote_field.through, app_label, model_name
                    )
                    self.through_users[through_key] = (
                        app_label,
                        old_model_name,
                        field_name,
                    )

    @staticmethod
    def _resolve_dependency(dependency):
        """
        Return the resolved dependency and a boolean denoting whether or not
        it was swappable.
        """
        if dependency[0] != "__setting__":
            return dependency, False
        resolved_app_label, resolved_object_name = getattr(
            settings, dependency[1]
        ).split(".")
        return (resolved_app_label, resolved_object_name.lower()) + dependency[2:], True

    def _build_migration_list(self, graph=None):
        """
        Chop the lists of operations up into migrations with dependencies on
        each other. Do this by going through an app's list of operations until
        one is found that has an outgoing dependency that isn't in another
        app's migration yet (hasn't been chopped off its list). Then chop off
        the operations before it into a migration and move onto the next app.
        If the loops completes without doing anything, there's a circular
        dependency (which _should_ be impossible as the operations are
        all split at this point so they can't depend and be depended on).
        """
        self.migrations = {}
        num_ops = sum(len(x) for x in self.generated_operations.values())
        chop_mode = False
        while num_ops:
            # On every iteration, we step through all the apps and see if there
            # is a completed set of operations.
            # If we find that a subset of the operations are complete we can
            # try to chop it off from the rest and continue, but we only
            # do this if we've already been through the list once before
            # without any chopping and nothing has changed.
            for app_label in sorted(self.generated_operations):
                chopped = []
                dependencies = set()
                for operation in list(self.generated_operations[app_label]):
                    deps_satisfied = True
                    operation_dependencies = set()
                    for dep in operation._auto_deps:
                        # Temporarily resolve the swappable dependency to
                        # prevent circular references. While keeping the
                        # dependency checks on the resolved model, add the
                        # swappable dependencies.
                        original_dep = dep
                        dep, is_swappable_dep = self._resolve_dependency(dep)
                        if dep[0] != app_label:
                            # External app dependency. See if it's not yet
                            # satisfied.
                            for other_operation in self.generated_operations.get(
                                dep[0], []
                            ):
                                if self.check_dependency(other_operation, dep):
                                    deps_satisfied = False
                                    break
                            if not deps_satisfied:
                                break
                            else:
                                if is_swappable_dep:
                                    operation_dependencies.add(
                                        (original_dep[0], original_dep[1])
                                    )
                                elif dep[0] in self.migrations:
                                    operation_dependencies.add(
                                        (dep[0], self.migrations[dep[0]][-1].name)
                                    )
                                else:
                                    # If we can't find the other app, we add a
                                    # first/last dependency, but only if we've
                                    # already been through once and checked
                                    # everything.
                                    if chop_mode:
                                        # If the app already exists, we add a
                                        # dependency on the last migration, as
                                        # we don't know which migration
                                        # contains the target field. If it's
                                        # not yet migrated or has no
                                        # migrations, we use __first__.
                                        if graph and graph.leaf_nodes(dep[0]):
                                            operation_dependencies.add(
                                                graph.leaf_nodes(dep[0])[0]
                                            )
                                        else:
                                            operation_dependencies.add(
                                                (dep[0], "__first__")
                                            )
                                    else:
                                        deps_satisfied = False
                    if deps_satisfied:
                        chopped.append(operation)
                        dependencies.update(operation_dependencies)
                        del self.generated_operations[app_label][0]
                    else:
                        break
                # Make a migration! Well, only if there's stuff to put in it
                if dependencies or chopped:
                    if not self.generated_operations[app_label] or chop_mode:
                        subclass = type(
                            "Migration",
                            (Migration,),
                            {"operations": [], "dependencies": []},
                        )
                        instance = subclass(
                            "auto_%i" % (len(self.migrations.get(app_label, [])) + 1),
                            app_label,
                        )
                        instance.dependencies = list(dependencies)
                        instance.operations = chopped
                        instance.initial = app_label not in self.existing_apps
                        self.migrations.setdefault(app_label, []).append(instance)
                        chop_mode = False
                    else:
                        self.generated_operations[app_label] = (
                            chopped + self.generated_operations[app_label]
                        )
            new_num_ops = sum(len(x) for x in self.generated_operations.values())
            if new_num_ops == num_ops:
                if not chop_mode:
                    chop_mode = True
                else:
                    raise ValueError(
                        "Cannot resolve operation dependencies: %r"
                        % self.generated_operations
                    )
            num_ops = new_num_ops

    def _sort_migrations(self):
        """
        Reorder to make things possible. Reordering may be needed so FKs work
        nicely inside the same app.
        """
        for app_label, ops in sorted(self.generated_operations.items()):
            # construct a dependency graph for intra-app dependencies
            dependency_graph = {op: set() for op in ops}
            for op in ops:
                for dep in op._auto_deps:
                    # Resolve intra-app dependencies to handle circular
                    # references involving a swappable model.
                    dep = self._resolve_dependency(dep)[0]
                    if dep[0] == app_label:
                        for op2 in ops:
                            if self.check_dependency(op2, dep):
                                dependency_graph[op].add(op2)

            # we use a stable sort for deterministic tests & general behavior
            self.generated_operations[app_label] = stable_topological_sort(
                ops, dependency_graph
            )

    def _optimize_migrations(self):
        # Add in internal dependencies among the migrations
        for app_label, migrations in self.migrations.items():
            for m1, m2 in zip(migrations, migrations[1:]):
                m2.dependencies.append((app_label, m1.name))

        # De-dupe dependencies
        for migrations in self.migrations.values():
            for migration in migrations:
                migration.dependencies = list(set(migration.dependencies))

        # Optimize migrations
        for app_label, migrations in self.migrations.items():
            for migration in migrations:
                migration.operations = MigrationOptimizer().optimize(
                    migration.operations, app_label
                )

    def check_dependency(self, operation, dependency):
        """
        Return True if the given operation depends on the given dependency,
        False otherwise.
        """
        # Created model
        if dependency[2] is None and dependency[3] is True:
            return (
                isinstance(operation, operations.CreateModel)
                and operation.name_lower == dependency[1].lower()
            )
        # Created field
        elif dependency[2] is not None and dependency[3] is True:
            return (
                isinstance(operation, operations.CreateModel)
                and operation.name_lower == dependency[1].lower()
                and any(dependency[2] == x for x, y in operation.fields)
            ) or (
                isinstance(operation, operations.AddField)
                and operation.model_name_lower == dependency[1].lower()
                and operation.name_lower == dependency[2].lower()
            )
        # Removed field
        elif dependency[2] is not None and dependency[3] is False:
            return (
                isinstance(operation, operations.RemoveField)
                and operation.model_name_lower == dependency[1].lower()
                and operation.name_lower == dependency[2].lower()
            )
        # Removed model
        elif dependency[2] is None and dependency[3] is False:
            return (
                isinstance(operation, operations.DeleteModel)
                and operation.name_lower == dependency[1].lower()
            )
        # Field being altered
        elif dependency[2] is not None and dependency[3] == "alter":
            return (
                isinstance(operation, operations.AlterField)
                and operation.model_name_lower == dependency[1].lower()
                and operation.name_lower == dependency[2].lower()
            )
        # order_with_respect_to being unset for a field
        elif dependency[2] is not None and dependency[3] == "order_wrt_unset":
            return (
                isinstance(operation, operations.AlterOrderWithRespectTo)
                and operation.name_lower == dependency[1].lower()
                and (operation.order_with_respect_to or "").lower()
                != dependency[2].lower()
            )
        # Field is removed and part of an index/unique_together
        elif dependency[2] is not None and dependency[3] == "foo_together_change":
            return (
                isinstance(
                    operation,
                    (operations.AlterUniqueTogether, operations.AlterIndexTogether),
                )
                and operation.name_lower == dependency[1].lower()
            )
        # Unknown dependency. Raise an error.
        else:
            raise ValueError("Can't handle dependency %r" % (dependency,))

    def add_operation(self, app_label, operation, dependencies=None, beginning=False):
        # Dependencies are
        # (app_label, model_name, field_name, create/delete as True/False)
        operation._auto_deps = dependencies or []
        if beginning:
            self.generated_operations.setdefault(app_label, []).insert(0, operation)
        else:
            self.generated_operations.setdefault(app_label, []).append(operation)

    def swappable_first_key(self, item):
        """
        Place potential swappable models first in lists of created models (only
        real way to solve #22783).
        """
        try:
            model_state = self.to_state.models[item]
            base_names = {
                base if isinstance(base, str) else base.__name__
                for base in model_state.bases
            }
            string_version = "%s.%s" % (item[0], item[1])
            if (
                model_state.options.get("swappable")
                or "AbstractUser" in base_names
                or "AbstractBaseUser" in base_names
                or settings.AUTH_USER_MODEL.lower() == string_version.lower()
            ):
                return ("___" + item[0], "___" + item[1])
        except LookupError:
            pass
        return item

    def generate_renamed_models(self):
        """
        Find any renamed models, generate the operations for them, and remove
        the old entry from the model lists. Must be run before other
        model-level generation.
        """
        self.renamed_models = {}
        self.renamed_models_rel = {}
        added_models = self.new_model_keys - self.old_model_keys
        for app_label, model_name in sorted(added_models):
            model_state = self.to_state.models[app_label, model_name]
            model_fields_def = self.only_relation_agnostic_fields(model_state.fields)

            removed_models = self.old_model_keys - self.new_model_keys
            for rem_app_label, rem_model_name in removed_models:
                if rem_app_label == app_label:
                    rem_model_state = self.from_state.models[
                        rem_app_label, rem_model_name
                    ]
                    rem_model_fields_def = self.only_relation_agnostic_fields(
                        rem_model_state.fields
                    )
                    if model_fields_def == rem_model_fields_def:
                        if self.questioner.ask_rename_model(
                            rem_model_state, model_state
                        ):
                            dependencies = []
                            fields = list(model_state.fields.values()) + [
                                field.remote_field
                                for relations in self.to_state.relations[
                                    app_label, model_name
                                ].values()
                                for field in relations.values()
                            ]
                            for field in fields:
                                if field.is_relation:
                                    dependencies.extend(
                                        self._get_dependencies_for_foreign_key(
                                            app_label,
                                            model_name,
                                            field,
                                            self.to_state,
                                        )
                                    )
                            self.add_operation(
                                app_label,
                                operations.RenameModel(
                                    old_name=rem_model_state.name,
                                    new_name=model_state.name,
                                ),
                                dependencies=dependencies,
                            )
                            self.renamed_models[app_label, model_name] = rem_model_name
                            renamed_models_rel_key = "%s.%s" % (
                                rem_model_state.app_label,
                                rem_model_state.name_lower,
                            )
                            self.renamed_models_rel[
                                renamed_models_rel_key
                            ] = "%s.%s" % (
                                model_state.app_label,
                                model_state.name_lower,
                            )
                            self.old_model_keys.remove((rem_app_label, rem_model_name))
                            self.old_model_keys.add((app_label, model_name))
                            break

    def generate_created_models(self):
        """
        Find all new models (both managed and unmanaged) and make create
        operations for them as well as separate operations to create any
        foreign key or M2M relationships (these are optimized later, if
        possible).

        Defer any model options that refer to collections of fields that might
        be deferred (e.g. unique_together, index_together).
        """
        old_keys = self.old_model_keys | self.old_unmanaged_keys
        added_models = self.new_model_keys - old_keys
        added_unmanaged_models = self.new_unmanaged_keys - old_keys
        all_added_models = chain(
            sorted(added_models, key=self.swappable_first_key, reverse=True),
            sorted(added_unmanaged_models, key=self.swappable_first_key, reverse=True),
        )
        for app_label, model_name in all_added_models:
            model_state = self.to_state.models[app_label, model_name]
            # Gather related fields
            related_fields = {}
            primary_key_rel = None
            for field_name, field in model_state.fields.items():
                if field.remote_field:
                    if field.remote_field.model:
                        if field.primary_key:
                            primary_key_rel = field.remote_field.model
                        elif not field.remote_field.parent_link:
                            related_fields[field_name] = field
                    if getattr(field.remote_field, "through", None):
                        related_fields[field_name] = field

            # Are there indexes/unique|index_together to defer?
            indexes = model_state.options.pop("indexes")
            constraints = model_state.options.pop("constraints")
            unique_together = model_state.options.pop("unique_together", None)
            # RemovedInDjango51Warning.
            index_together = model_state.options.pop("index_together", None)
            order_with_respect_to = model_state.options.pop(
                "order_with_respect_to", None
            )
            # Depend on the deletion of any possible proxy version of us
            dependencies = [
                (app_label, model_name, None, False),
            ]
            # Depend on all bases
            for base in model_state.bases:
                if isinstance(base, str) and "." in base:
                    base_app_label, base_name = base.split(".", 1)
                    dependencies.append((base_app_label, base_name, None, True))
                    # Depend on the removal of base fields if the new model has
                    # a field with the same name.
                    old_base_model_state = self.from_state.models.get(
                        (base_app_label, base_name)
                    )
                    new_base_model_state = self.to_state.models.get(
                        (base_app_label, base_name)
                    )
                    if old_base_model_state and new_base_model_state:
                        removed_base_fields = (
                            set(old_base_model_state.fields)
                            .difference(
                                new_base_model_state.fields,
                            )
                            .intersection(model_state.fields)
                        )
                        for removed_base_field in removed_base_fields:
                            dependencies.append(
                                (base_app_label, base_name, removed_base_field, False)
                            )
            # Depend on the other end of the primary key if it's a relation
            if primary_key_rel:
                dependencies.append(
                    resolve_relation(
                        primary_key_rel,
                        app_label,
                        model_name,
                    )
                    + (None, True)
                )
            # Generate creation operation
            self.add_operation(
                app_label,
                operations.CreateModel(
                    name=model_state.name,
                    fields=[
                        d
                        for d in model_state.fields.items()
                        if d[0] not in related_fields
                    ],
                    options=model_state.options,
                    bases=model_state.bases,
                    managers=model_state.managers,
                ),
                dependencies=dependencies,
                beginning=True,
            )

            # Don't add operations which modify the database for unmanaged models
            if not model_state.options.get("managed", True):
                continue

            # Generate operations for each related field
            for name, field in sorted(related_fields.items()):
                dependencies = self._get_dependencies_for_foreign_key(
                    app_label,
                    model_name,
                    field,
                    self.to_state,
                )
                # Depend on our own model being created
                dependencies.append((app_label, model_name, None, True))
                # Make operation
                self.add_operation(
                    app_label,
                    operations.AddField(
                        model_name=model_name,
                        name=name,
                        field=field,
                    ),
                    dependencies=list(set(dependencies)),
                )
            # Generate other opns
            if order_with_respect_to:
                self.add_operation(
                    app_label,
                    operations.AlterOrderWithRespectTo(
                        name=model_name,
                        order_with_respect_to=order_with_respect_to,
                    ),
                    dependencies=[
                        (app_label, model_name, order_with_respect_to, True),
                        (app_label, model_name, None, True),
                    ],
                )
            related_dependencies = [
                (app_label, model_name, name, True) for name in sorted(related_fields)
            ]
            related_dependencies.append((app_label, model_name, None, True))
            for index in indexes:
                self.add_operation(
                    app_label,
                    operations.AddIndex(
                        model_name=model_name,
                        index=index,
                    ),
                    dependencies=related_dependencies,
                )
            for constraint in constraints:
                self.add_operation(
                    app_label,
                    operations.AddConstraint(
                        model_name=model_name,
                        constraint=constraint,
                    ),
                    dependencies=related_dependencies,
                )
            if unique_together:
                self.add_operation(
                    app_label,
                    operations.AlterUniqueTogether(
                        name=model_name,
                        unique_together=unique_together,
                    ),
                    dependencies=related_dependencies,
                )
            # RemovedInDjango51Warning.
            if index_together:
                self.add_operation(
                    app_label,
                    operations.AlterIndexTogether(
                        name=model_name,
                        index_together=index_together,
                    ),
                    dependencies=related_dependencies,
                )
            # Fix relationships if the model changed from a proxy model to a
            # concrete model.
            relations = self.to_state.relations
            if (app_label, model_name) in self.old_proxy_keys:
                for related_model_key, related_fields in relations[
                    app_label, model_name
                ].items():
                    related_model_state = self.to_state.models[related_model_key]
                    for related_field_name, related_field in related_fields.items():
                        self.add_operation(
                            related_model_state.app_label,
                            operations.AlterField(
                                model_name=related_model_state.name,
                                name=related_field_name,
                                field=related_field,
                            ),
                            dependencies=[(app_label, model_name, None, True)],
                        )

    def generate_created_proxies(self):
        """
        Make CreateModel statements for proxy models. Use the same statements
        as that way there's less code duplication, but for proxy models it's
        safe to skip all the pointless field stuff and chuck out an operation.
        """
        added = self.new_proxy_keys - self.old_proxy_keys
        for app_label, model_name in sorted(added):
            model_state = self.to_state.models[app_label, model_name]
            assert model_state.options.get("proxy")
            # Depend on the deletion of any possible non-proxy version of us
            dependencies = [
                (app_label, model_name, None, False),
            ]
            # Depend on all bases
            for base in model_state.bases:
                if isinstance(base, str) and "." in base:
                    base_app_label, base_name = base.split(".", 1)
                    dependencies.append((base_app_label, base_name, None, True))
            # Generate creation operation
            self.add_operation(
                app_label,
                operations.CreateModel(
                    name=model_state.name,
                    fields=[],
                    options=model_state.options,
                    bases=model_state.bases,
                    managers=model_state.managers,
                ),
                # Depend on the deletion of any possible non-proxy version of us
                dependencies=dependencies,
            )

    def generate_deleted_models(self):
        """
        Find all deleted models (managed and unmanaged) and make delete
        operations for them as well as separate operations to delete any
        foreign key or M2M relationships (these are optimized later, if
        possible).

        Also bring forward removal of any model options that refer to
        collections of fields - the inverse of generate_created_models().
        """
        new_keys = self.new_model_keys | self.new_unmanaged_keys
        deleted_models = self.old_model_keys - new_keys
        deleted_unmanaged_models = self.old_unmanaged_keys - new_keys
        all_deleted_models = chain(
            sorted(deleted_models), sorted(deleted_unmanaged_models)
        )
        for app_label, model_name in all_deleted_models:
            model_state = self.from_state.models[app_label, model_name]
            # Gather related fields
            related_fields = {}
            for field_name, field in model_state.fields.items():
                if field.remote_field:
                    if field.remote_field.model:
                        related_fields[field_name] = field
                    if getattr(field.remote_field, "through", None):
                        related_fields[field_name] = field
            # Generate option removal first
            unique_together = model_state.options.pop("unique_together", None)
            # RemovedInDjango51Warning.
            index_together = model_state.options.pop("index_together", None)
            if unique_together:
                self.add_operation(
                    app_label,
                    operations.AlterUniqueTogether(
                        name=model_name,
                        unique_together=None,
                    ),
                )
            # RemovedInDjango51Warning.
            if index_together:
                self.add_operation(
                    app_label,
                    operations.AlterIndexTogether(
                        name=model_name,
                        index_together=None,
                    ),
                )
            # Then remove each related field
            for name in sorted(related_fields):
                self.add_operation(
                    app_label,
                    operations.RemoveField(
                        model_name=model_name,
                        name=name,
                    ),
                )
            # Finally, remove the model.
            # This depends on both the removal/alteration of all incoming fields
            # and the removal of all its own related fields, and if it's
            # a through model the field that references it.
            dependencies = []
            relations = self.from_state.relations
            for (
                related_object_app_label,
                object_name,
            ), relation_related_fields in relations[app_label, model_name].items():
                for field_name, field in relation_related_fields.items():
                    dependencies.append(
                        (related_object_app_label, object_name, field_name, False),
                    )
                    if not field.many_to_many:
                        dependencies.append(
                            (
                                related_object_app_label,
                                object_name,
                                field_name,
                                "alter",
                            ),
                        )

            for name in sorted(related_fields):
                dependencies.append((app_label, model_name, name, False))
            # We're referenced in another field's through=
            through_user = self.through_users.get((app_label, model_state.name_lower))
            if through_user:
                dependencies.append(
                    (through_user[0], through_user[1], through_user[2], False)
                )
            # Finally, make the operation, deduping any dependencies
            self.add_operation(
                app_label,
                operations.DeleteModel(
                    name=model_state.name,
                ),
                dependencies=list(set(dependencies)),
            )

    def generate_deleted_proxies(self):
        """Make DeleteModel options for proxy models."""
        deleted = self.old_proxy_keys - self.new_proxy_keys
        for app_label, model_name in sorted(deleted):
            model_state = self.from_state.models[app_label, model_name]
            assert model_state.options.get("proxy")
            self.add_operation(
                app_label,
                operations.DeleteModel(
                    name=model_state.name,
                ),
            )

    def create_renamed_fields(self):
        """Work out renamed fields."""
        self.renamed_operations = []
        old_field_keys = self.old_field_keys.copy()
        for app_label, model_name, field_name in sorted(
            self.new_field_keys - old_field_keys
        ):
            old_model_name = self.renamed_models.get(
                (app_label, model_name), model_name
            )
            old_model_state = self.from_state.models[app_label, old_model_name]
            new_model_state = self.to_state.models[app_label, model_name]
            field = new_model_state.get_field(field_name)
            # Scan to see if this is actually a rename!
            field_dec = self.deep_deconstruct(field)
            for rem_app_label, rem_model_name, rem_field_name in sorted(
                old_field_keys - self.new_field_keys
            ):
                if rem_app_label == app_label and rem_model_name == model_name:
                    old_field = old_model_state.get_field(rem_field_name)
                    old_field_dec = self.deep_deconstruct(old_field)
                    if (
                        field.remote_field
                        and field.remote_field.model
                        and "to" in old_field_dec[2]
                    ):
                        old_rel_to = old_field_dec[2]["to"]
                        if old_rel_to in self.renamed_models_rel:
                            old_field_dec[2]["to"] = self.renamed_models_rel[old_rel_to]
                    old_field.set_attributes_from_name(rem_field_name)
                    old_db_column = old_field.get_attname_column()[1]
                    if old_field_dec == field_dec or (
                        # Was the field renamed and db_column equal to the
                        # old field's column added?
                        old_field_dec[0:2] == field_dec[0:2]
                        and dict(old_field_dec[2], db_column=old_db_column)
                        == field_dec[2]
                    ):
                        if self.questioner.ask_rename(
                            model_name, rem_field_name, field_name, field
                        ):
                            self.renamed_operations.append(
                                (
                                    rem_app_label,
                                    rem_model_name,
                                    old_field.db_column,
                                    rem_field_name,
                                    app_label,
                                    model_name,
                                    field,
                                    field_name,
                                )
                            )
                            old_field_keys.remove(
                                (rem_app_label, rem_model_name, rem_field_name)
                            )
                            old_field_keys.add((app_label, model_name, field_name))
                            self.renamed_fields[
                                app_label, model_name, field_name
                            ] = rem_field_name
                            break

    def generate_renamed_fields(self):
        """Generate RenameField operations."""
        for (
            rem_app_label,
            rem_model_name,
            rem_db_column,
            rem_field_name,
            app_label,
            model_name,
            field,
            field_name,
        ) in self.renamed_operations:
            # A db_column mismatch requires a prior noop AlterField for the
            # subsequent RenameField to be a noop on attempts at preserving the
            # old name.
            if rem_db_column != field.db_column:
                altered_field = field.clone()
                altered_field.name = rem_field_name
                self.add_operation(
                    app_label,
                    operations.AlterField(
                        model_name=model_name,
                        name=rem_field_name,
                        field=altered_field,
                    ),
                )
            self.add_operation(
                app_label,
                operations.RenameField(
                    model_name=model_name,
                    old_name=rem_field_name,
                    new_name=field_name,
                ),
            )
            self.old_field_keys.remove((rem_app_label, rem_model_name, rem_field_name))
            self.old_field_keys.add((app_label, model_name, field_name))

    def generate_added_fields(self):
        """Make AddField operations."""
        for app_label, model_name, field_name in sorted(
            self.new_field_keys - self.old_field_keys
        ):
            self._generate_added_field(app_label, model_name, field_name)

    def _generate_added_field(self, app_label, model_name, field_name):
        field = self.to_state.models[app_label, model_name].get_field(field_name)
        # Adding a field always depends at least on its removal.
        dependencies = [(app_label, model_name, field_name, False)]
        # Fields that are foreignkeys/m2ms depend on stuff.
        if field.remote_field and field.remote_field.model:
            dependencies.extend(
                self._get_dependencies_for_foreign_key(
                    app_label,
                    model_name,
                    field,
                    self.to_state,
                )
            )
        # You can't just add NOT NULL fields with no default or fields
        # which don't allow empty strings as default.
        time_fields = (models.DateField, models.DateTimeField, models.TimeField)
        preserve_default = (
            field.null
            or field.has_default()
            or field.many_to_many
            or (field.blank and field.empty_strings_allowed)
            or (isinstance(field, time_fields) and field.auto_now)
        )
        if not preserve_default:
            field = field.clone()
            if isinstance(field, time_fields) and field.auto_now_add:
                field.default = self.questioner.ask_auto_now_add_addition(
                    field_name, model_name
                )
            else:
                field.default = self.questioner.ask_not_null_addition(
                    field_name, model_name
                )
        if (
            field.unique
            and field.default is not models.NOT_PROVIDED
            and callable(field.default)
        ):
            self.questioner.ask_unique_callable_default_addition(field_name, model_name)
        self.add_operation(
            app_label,
            operations.AddField(
                model_name=model_name,
                name=field_name,
                field=field,
                preserve_default=preserve_default,
            ),
            dependencies=dependencies,
        )

    def generate_removed_fields(self):
        """Make RemoveField operations."""
        for app_label, model_name, field_name in sorted(
            self.old_field_keys - self.new_field_keys
        ):
            self._generate_removed_field(app_label, model_name, field_name)

    def _generate_removed_field(self, app_label, model_name, field_name):
        self.add_operation(
            app_label,
            operations.RemoveField(
                model_name=model_name,
                name=field_name,
            ),
            # We might need to depend on the removal of an
            # order_with_respect_to or index/unique_together operation;
            # this is safely ignored if there isn't one
            dependencies=[
                (app_label, model_name, field_name, "order_wrt_unset"),
                (app_label, model_name, field_name, "foo_together_change"),
            ],
        )

    def generate_altered_fields(self):
        """
        Make AlterField operations, or possibly RemovedField/AddField if alter
        isn't possible.
        """
        for app_label, model_name, field_name in sorted(
            self.old_field_keys & self.new_field_keys
        ):
            # Did the field change?
            old_model_name = self.renamed_models.get(
                (app_label, model_name), model_name
            )
            old_field_name = self.renamed_fields.get(
                (app_label, model_name, field_name), field_name
            )
            old_field = self.from_state.models[app_label, old_model_name].get_field(
                old_field_name
            )
            new_field = self.to_state.models[app_label, model_name].get_field(
                field_name
            )
            dependencies = []
            # Implement any model renames on relations; these are handled by RenameModel
            # so we need to exclude them from the comparison
            if hasattr(new_field, "remote_field") and getattr(
                new_field.remote_field, "model", None
            ):
                rename_key = resolve_relation(
                    new_field.remote_field.model, app_label, model_name
                )
                if rename_key in self.renamed_models:
                    new_field.remote_field.model = old_field.remote_field.model
                # Handle ForeignKey which can only have a single to_field.
                remote_field_name = getattr(new_field.remote_field, "field_name", None)
                if remote_field_name:
                    to_field_rename_key = rename_key + (remote_field_name,)
                    if to_field_rename_key in self.renamed_fields:
                        # Repoint both model and field name because to_field
                        # inclusion in ForeignKey.deconstruct() is based on
                        # both.
                        new_field.remote_field.model = old_field.remote_field.model
                        new_field.remote_field.field_name = (
                            old_field.remote_field.field_name
                        )
                # Handle ForeignObjects which can have multiple from_fields/to_fields.
                from_fields = getattr(new_field, "from_fields", None)
                if from_fields:
                    from_rename_key = (app_label, model_name)
                    new_field.from_fields = tuple(
                        [
                            self.renamed_fields.get(
                                from_rename_key + (from_field,), from_field
                            )
                            for from_field in from_fields
                        ]
                    )
                    new_field.to_fields = tuple(
                        [
                            self.renamed_fields.get(rename_key + (to_field,), to_field)
                            for to_field in new_field.to_fields
                        ]
                    )
                dependencies.extend(
                    self._get_dependencies_for_foreign_key(
                        app_label,
                        model_name,
                        new_field,
                        self.to_state,
                    )
                )
            if hasattr(new_field, "remote_field") and getattr(
                new_field.remote_field, "through", None
            ):
                rename_key = resolve_relation(
                    new_field.remote_field.through, app_label, model_name
                )
                if rename_key in self.renamed_models:
                    new_field.remote_field.through = old_field.remote_field.through
            old_field_dec = self.deep_deconstruct(old_field)
            new_field_dec = self.deep_deconstruct(new_field)
            # If the field was confirmed to be renamed it means that only
            # db_column was allowed to change which generate_renamed_fields()
            # already accounts for by adding an AlterField operation.
            if old_field_dec != new_field_dec and old_field_name == field_name:
                both_m2m = old_field.many_to_many and new_field.many_to_many
                neither_m2m = not old_field.many_to_many and not new_field.many_to_many
                if both_m2m or neither_m2m:
                    # Either both fields are m2m or neither is
                    preserve_default = True
                    if (
                        old_field.null
                        and not new_field.null
                        and not new_field.has_default()
                        and not new_field.many_to_many
                    ):
                        field = new_field.clone()
                        new_default = self.questioner.ask_not_null_alteration(
                            field_name, model_name
                        )
                        if new_default is not models.NOT_PROVIDED:
                            field.default = new_default
                            preserve_default = False
                    else:
                        field = new_field
                    self.add_operation(
                        app_label,
                        operations.AlterField(
                            model_name=model_name,
                            name=field_name,
                            field=field,
                            preserve_default=preserve_default,
                        ),
                        dependencies=dependencies,
                    )
                else:
                    # We cannot alter between m2m and concrete fields
                    self._generate_removed_field(app_label, model_name, field_name)
                    self._generate_added_field(app_label, model_name, field_name)

    def create_altered_indexes(self):
        option_name = operations.AddIndex.option_name
        self.renamed_index_together_values = defaultdict(list)

        for app_label, model_name in sorted(self.kept_model_keys):
            old_model_name = self.renamed_models.get(
                (app_label, model_name), model_name
            )
            old_model_state = self.from_state.models[app_label, old_model_name]
            new_model_state = self.to_state.models[app_label, model_name]

            old_indexes = old_model_state.options[option_name]
            new_indexes = new_model_state.options[option_name]
            added_indexes = [idx for idx in new_indexes if idx not in old_indexes]
            removed_indexes = [idx for idx in old_indexes if idx not in new_indexes]
            renamed_indexes = []
            # Find renamed indexes.
            remove_from_added = []
            remove_from_removed = []
            for new_index in added_indexes:
                new_index_dec = new_index.deconstruct()
                new_index_name = new_index_dec[2].pop("name")
                for old_index in removed_indexes:
                    old_index_dec = old_index.deconstruct()
                    old_index_name = old_index_dec[2].pop("name")
                    # Indexes are the same except for the names.
                    if (
                        new_index_dec == old_index_dec
                        and new_index_name != old_index_name
                    ):
                        renamed_indexes.append((old_index_name, new_index_name, None))
                        remove_from_added.append(new_index)
                        remove_from_removed.append(old_index)
            # Find index_together changed to indexes.
            for (
                old_value,
                new_value,
                index_together_app_label,
                index_together_model_name,
                dependencies,
            ) in self._get_altered_foo_together_operations(
                operations.AlterIndexTogether.option_name
            ):
                if (
                    app_label != index_together_app_label
                    or model_name != index_together_model_name
                ):
                    continue
                removed_values = old_value.difference(new_value)
                for removed_index_together in removed_values:
                    renamed_index_together_indexes = []
                    for new_index in added_indexes:
                        _, args, kwargs = new_index.deconstruct()
                        # Ensure only 'fields' are defined in the Index.
                        if (
                            not args
                            and new_index.fields == list(removed_index_together)
                            and set(kwargs) == {"name", "fields"}
                        ):
                            renamed_index_together_indexes.append(new_index)

                    if len(renamed_index_together_indexes) == 1:
                        renamed_index = renamed_index_together_indexes[0]
                        remove_from_added.append(renamed_index)
                        renamed_indexes.append(
                            (None, renamed_index.name, removed_index_together)
                        )
                        self.renamed_index_together_values[
                            index_together_app_label, index_together_model_name
                        ].append(removed_index_together)
            # Remove renamed indexes from the lists of added and removed
            # indexes.
            added_indexes = [
                idx for idx in added_indexes if idx not in remove_from_added
            ]
            removed_indexes = [
                idx for idx in removed_indexes if idx not in remove_from_removed
            ]

            self.altered_indexes.update(
                {
                    (app_label, model_name): {
                        "added_indexes": added_indexes,
                        "removed_indexes": removed_indexes,
                        "renamed_indexes": renamed_indexes,
                    }
                }
            )

    def generate_added_indexes(self):
        for (app_label, model_name), alt_indexes in self.altered_indexes.items():
            for index in alt_indexes["added_indexes"]:
                self.add_operation(
                    app_label,
                    operations.AddIndex(
                        model_name=model_name,
                        index=index,
                    ),
                )

    def generate_removed_indexes(self):
        for (app_label, model_name), alt_indexes in self.altered_indexes.items():
            for index in alt_indexes["removed_indexes"]:
                self.add_operation(
                    app_label,
                    operations.RemoveIndex(
                        model_name=model_name,
                        name=index.name,
                    ),
                )

    def generate_renamed_indexes(self):
        for (app_label, model_name), alt_indexes in self.altered_indexes.items():
            for old_index_name, new_index_name, old_fields in alt_indexes[
                "renamed_indexes"
            ]:
                self.add_operation(
                    app_label,
                    operations.RenameIndex(
                        model_name=model_name,
                        new_name=new_index_name,
                        old_name=old_index_name,
                        old_fields=old_fields,
                    ),
                )

    def create_altered_constraints(self):
        option_name = operations.AddConstraint.option_name
        for app_label, model_name in sorted(self.kept_model_keys):
            old_model_name = self.renamed_models.get(
                (app_label, model_name), model_name
            )
            old_model_state = self.from_state.models[app_label, old_model_name]
            new_model_state = self.to_state.models[app_label, model_name]

            old_constraints = old_model_state.options[option_name]
            new_constraints = new_model_state.options[option_name]
            add_constraints = [c for c in new_constraints if c not in old_constraints]
            rem_constraints = [c for c in old_constraints if c not in new_constraints]

            self.altered_constraints.update(
                {
                    (app_label, model_name): {
                        "added_constraints": add_constraints,
                        "removed_constraints": rem_constraints,
                    }
                }
            )

    def generate_added_constraints(self):
        for (
            app_label,
            model_name,
        ), alt_constraints in self.altered_constraints.items():
            for constraint in alt_constraints["added_constraints"]:
                self.add_operation(
                    app_label,
                    operations.AddConstraint(
                        model_name=model_name,
                        constraint=constraint,
                    ),
                )

    def generate_removed_constraints(self):
        for (
            app_label,
            model_name,
        ), alt_constraints in self.altered_constraints.items():
            for constraint in alt_constraints["removed_constraints"]:
                self.add_operation(
                    app_label,
                    operations.RemoveConstraint(
                        model_name=model_name,
                        name=constraint.name,
                    ),
                )

    @staticmethod
    def _get_dependencies_for_foreign_key(app_label, model_name, field, project_state):
        remote_field_model = None
        if hasattr(field.remote_field, "model"):
            remote_field_model = field.remote_field.model
        else:
            relations = project_state.relations[app_label, model_name]
            for (remote_app_label, remote_model_name), fields in relations.items():
                if any(
                    field == related_field.remote_field
                    for related_field in fields.values()
                ):
                    remote_field_model = f"{remote_app_label}.{remote_model_name}"
                    break
        # Account for FKs to swappable models
        swappable_setting = getattr(field, "swappable_setting", None)
        if swappable_setting is not None:
            dep_app_label = "__setting__"
            dep_object_name = swappable_setting
        else:
            dep_app_label, dep_object_name = resolve_relation(
                remote_field_model,
                app_label,
                model_name,
            )
        dependencies = [(dep_app_label, dep_object_name, None, True)]
        if getattr(field.remote_field, "through", None):
            through_app_label, through_object_name = resolve_relation(
                field.remote_field.through,
                app_label,
                model_name,
            )
            dependencies.append((through_app_label, through_object_name, None, True))
        return dependencies

    def _get_altered_foo_together_operations(self, option_name):
        for app_label, model_name in sorted(self.kept_model_keys):
            old_model_name = self.renamed_models.get(
                (app_label, model_name), model_name
            )
            old_model_state = self.from_state.models[app_label, old_model_name]
            new_model_state = self.to_state.models[app_label, model_name]

            # We run the old version through the field renames to account for those
            old_value = old_model_state.options.get(option_name)
            old_value = (
                {
                    tuple(
                        self.renamed_fields.get((app_label, model_name, n), n)
                        for n in unique
                    )
                    for unique in old_value
                }
                if old_value
                else set()
            )

            new_value = new_model_state.options.get(option_name)
            new_value = set(new_value) if new_value else set()

            if old_value != new_value:
                dependencies = []
                for foo_togethers in new_value:
                    for field_name in foo_togethers:
                        field = new_model_state.get_field(field_name)
                        if field.remote_field and field.remote_field.model:
                            dependencies.extend(
                                self._get_dependencies_for_foreign_key(
                                    app_label,
                                    model_name,
                                    field,
                                    self.to_state,
                                )
                            )
                yield (
                    old_value,
                    new_value,
                    app_label,
                    model_name,
                    dependencies,
                )

    def _generate_removed_altered_foo_together(self, operation):
        for (
            old_value,
            new_value,
            app_label,
            model_name,
            dependencies,
        ) in self._get_altered_foo_together_operations(operation.option_name):
            if operation == operations.AlterIndexTogether:
                old_value = {
                    value
                    for value in old_value
                    if value
                    not in self.renamed_index_together_values[app_label, model_name]
                }
            removal_value = new_value.intersection(old_value)
            if removal_value or old_value:
                self.add_operation(
                    app_label,
                    operation(
                        name=model_name, **{operation.option_name: removal_value}
                    ),
                    dependencies=dependencies,
                )

    def generate_removed_altered_unique_together(self):
        self._generate_removed_altered_foo_together(operations.AlterUniqueTogether)

    # RemovedInDjango51Warning.
    def generate_removed_altered_index_together(self):
        self._generate_removed_altered_foo_together(operations.AlterIndexTogether)

    def _generate_altered_foo_together(self, operation):
        for (
            old_value,
            new_value,
            app_label,
            model_name,
            dependencies,
        ) in self._get_altered_foo_together_operations(operation.option_name):
            removal_value = new_value.intersection(old_value)
            if new_value != removal_value:
                self.add_operation(
                    app_label,
                    operation(name=model_name, **{operation.option_name: new_value}),
                    dependencies=dependencies,
                )

    def generate_altered_unique_together(self):
        self._generate_altered_foo_together(operations.AlterUniqueTogether)

    # RemovedInDjango51Warning.
    def generate_altered_index_together(self):
        self._generate_altered_foo_together(operations.AlterIndexTogether)

    def generate_altered_db_table(self):
        models_to_check = self.kept_model_keys.union(
            self.kept_proxy_keys, self.kept_unmanaged_keys
        )
        for app_label, model_name in sorted(models_to_check):
            old_model_name = self.renamed_models.get(
                (app_label, model_name), model_name
            )
            old_model_state = self.from_state.models[app_label, old_model_name]
            new_model_state = self.to_state.models[app_label, model_name]
            old_db_table_name = old_model_state.options.get("db_table")
            new_db_table_name = new_model_state.options.get("db_table")
            if old_db_table_name != new_db_table_name:
                self.add_operation(
                    app_label,
                    operations.AlterModelTable(
                        name=model_name,
                        table=new_db_table_name,
                    ),
                )

    def generate_altered_db_table_comment(self):
        models_to_check = self.kept_model_keys.union(
            self.kept_proxy_keys, self.kept_unmanaged_keys
        )
        for app_label, model_name in sorted(models_to_check):
            old_model_name = self.renamed_models.get(
                (app_label, model_name), model_name
            )
            old_model_state = self.from_state.models[app_label, old_model_name]
            new_model_state = self.to_state.models[app_label, model_name]

            old_db_table_comment = old_model_state.options.get("db_table_comment")
            new_db_table_comment = new_model_state.options.get("db_table_comment")
            if old_db_table_comment != new_db_table_comment:
                self.add_operation(
                    app_label,
                    operations.AlterModelTableComment(
                        name=model_name,
                        table_comment=new_db_table_comment,
                    ),
                )

    def generate_altered_options(self):
        """
        Work out if any non-schema-affecting options have changed and make an
        operation to represent them in state changes (in case Python code in
        migrations needs them).
        """
        models_to_check = self.kept_model_keys.union(
            self.kept_proxy_keys,
            self.kept_unmanaged_keys,
            # unmanaged converted to managed
            self.old_unmanaged_keys & self.new_model_keys,
            # managed converted to unmanaged
            self.old_model_keys & self.new_unmanaged_keys,
        )

        for app_label, model_name in sorted(models_to_check):
            old_model_name = self.renamed_models.get(
                (app_label, model_name), model_name
            )
            old_model_state = self.from_state.models[app_label, old_model_name]
            new_model_state = self.to_state.models[app_label, model_name]
            old_options = {
                key: value
                for key, value in old_model_state.options.items()
                if key in AlterModelOptions.ALTER_OPTION_KEYS
            }
            new_options = {
                key: value
                for key, value in new_model_state.options.items()
                if key in AlterModelOptions.ALTER_OPTION_KEYS
            }
            if old_options != new_options:
                self.add_operation(
                    app_label,
                    operations.AlterModelOptions(
                        name=model_name,
                        options=new_options,
                    ),
                )

    def generate_altered_order_with_respect_to(self):
        for app_label, model_name in sorted(self.kept_model_keys):
            old_model_name = self.renamed_models.get(
                (app_label, model_name), model_name
            )
            old_model_state = self.from_state.models[app_label, old_model_name]
            new_model_state = self.to_state.models[app_label, model_name]
            if old_model_state.options.get(
                "order_with_respect_to"
            ) != new_model_state.options.get("order_with_respect_to"):
                # Make sure it comes second if we're adding
                # (removal dependency is part of RemoveField)
                dependencies = []
                if new_model_state.options.get("order_with_respect_to"):
                    dependencies.append(
                        (
                            app_label,
                            model_name,
                            new_model_state.options["order_with_respect_to"],
                            True,
                        )
                    )
                # Actually generate the operation
                self.add_operation(
                    app_label,
                    operations.AlterOrderWithRespectTo(
                        name=model_name,
                        order_with_respect_to=new_model_state.options.get(
                            "order_with_respect_to"
                        ),
                    ),
                    dependencies=dependencies,
                )

    def generate_altered_managers(self):
        for app_label, model_name in sorted(self.kept_model_keys):
            old_model_name = self.renamed_models.get(
                (app_label, model_name), model_name
            )
            old_model_state = self.from_state.models[app_label, old_model_name]
            new_model_state = self.to_state.models[app_label, model_name]
            if old_model_state.managers != new_model_state.managers:
                self.add_operation(
                    app_label,
                    operations.AlterModelManagers(
                        name=model_name,
                        managers=new_model_state.managers,
                    ),
                )

    def arrange_for_graph(self, changes, graph, migration_name=None):
        """
        Take a result from changes() and a MigrationGraph, and fix the names
        and dependencies of the changes so they extend the graph from the leaf
        nodes for each app.
        """
        leaves = graph.leaf_nodes()
        name_map = {}
        for app_label, migrations in list(changes.items()):
            if not migrations:
                continue
            # Find the app label's current leaf node
            app_leaf = None
            for leaf in leaves:
                if leaf[0] == app_label:
                    app_leaf = leaf
                    break
            # Do they want an initial migration for this app?
            if app_leaf is None and not self.questioner.ask_initial(app_label):
                # They don't.
                for migration in migrations:
                    name_map[(app_label, migration.name)] = (app_label, "__first__")
                del changes[app_label]
                continue
            # Work out the next number in the sequence
            if app_leaf is None:
                next_number = 1
            else:
                next_number = (self.parse_number(app_leaf[1]) or 0) + 1
            # Name each migration
            for i, migration in enumerate(migrations):
                if i == 0 and app_leaf:
                    migration.dependencies.append(app_leaf)
                new_name_parts = ["%04i" % next_number]
                if migration_name:
                    new_name_parts.append(migration_name)
                elif i == 0 and not app_leaf:
                    new_name_parts.append("initial")
                else:
                    new_name_parts.append(migration.suggest_name()[:100])
                new_name = "_".join(new_name_parts)
                name_map[(app_label, migration.name)] = (app_label, new_name)
                next_number += 1
                migration.name = new_name
        # Now fix dependencies
        for migrations in changes.values():
            for migration in migrations:
                migration.dependencies = [
                    name_map.get(d, d) for d in migration.dependencies
                ]
        return changes

    def _trim_to_apps(self, changes, app_labels):
        """
        Take changes from arrange_for_graph() and set of app labels, and return
        a modified set of changes which trims out as many migrations that are
        not in app_labels as possible. Note that some other migrations may
        still be present as they may be required dependencies.
        """
        # Gather other app dependencies in a first pass
        app_dependencies = {}
        for app_label, migrations in changes.items():
            for migration in migrations:
                for dep_app_label, name in migration.dependencies:
                    app_dependencies.setdefault(app_label, set()).add(dep_app_label)
        required_apps = set(app_labels)
        # Keep resolving till there's no change
        old_required_apps = None
        while old_required_apps != required_apps:
            old_required_apps = set(required_apps)
            required_apps.update(
                *[app_dependencies.get(app_label, ()) for app_label in required_apps]
            )
        # Remove all migrations that aren't needed
        for app_label in list(changes):
            if app_label not in required_apps:
                del changes[app_label]
        return changes

    @classmethod
    def parse_number(cls, name):
        """
        Given a migration name, try to extract a number from the beginning of
        it. For a squashed migration such as '0001_squashed_0004…', return the
        second number. If no number is found, return None.
        """
        if squashed_match := re.search(r".*_squashed_(\d+)", name):
            return int(squashed_match[1])
        match = re.match(r"^\d+", name)
        if match:
            return int(match[0])
        return None

virt/lib/python3.9/site-packages/django/db/migrations/autodetector 3.py
PrinceDisant/LostInNL
import functools
import re
from collections import defaultdict
from itertools import chain

from django.conf import settings
from django.db import models
from django.db.migrations import operations
from django.db.migrations.migration import Migration
from django.db.migrations.operations.models import AlterModelOptions
from django.db.migrations.optimizer import MigrationOptimizer
from django.db.migrations.questioner import MigrationQuestioner
from django.db.migrations.utils import (
    COMPILED_REGEX_TYPE,
    RegexObject,
    resolve_relation,
)
from django.utils.topological_sort import stable_topological_sort


class MigrationAutodetector:
    """
    Take a pair of ProjectStates and compare them to see what the first would
    need doing to make it match the second (the second usually being the
    project's current state).

    Note that this naturally operates on entire projects at a time,
    as it's likely that changes interact (for example, you can't
    add a ForeignKey without having a migration to add the table it
    depends on first). A user interface may offer single-app usage
    if it wishes, with the caveat that it may not always be possible.
    """

    def __init__(self, from_state, to_state, questioner=None):
        self.from_state = from_state
        self.to_state = to_state
        self.questioner = questioner or MigrationQuestioner()
        self.existing_apps = {app for app, model in from_state.models}

    def changes(self, graph, trim_to_apps=None, convert_apps=None, migration_name=None):
        """
        Main entry point to produce a list of applicable changes.
        Take a graph to base names on and an optional set of apps
        to try and restrict to (restriction is not guaranteed)
        """
        changes = self._detect_changes(convert_apps, graph)
        changes = self.arrange_for_graph(changes, graph, migration_name)
        if trim_to_apps:
            changes = self._trim_to_apps(changes, trim_to_apps)
        return changes

    def deep_deconstruct(self, obj):
        """
        Recursive deconstruction for a field and its arguments.
        Used for full comparison for rename/alter; sometimes a single-level
        deconstruction will not compare correctly.
        """
        if isinstance(obj, list):
            return [self.deep_deconstruct(value) for value in obj]
        elif isinstance(obj, tuple):
            return tuple(self.deep_deconstruct(value) for value in obj)
        elif isinstance(obj, dict):
            return {key: self.deep_deconstruct(value) for key, value in obj.items()}
        elif isinstance(obj, functools.partial):
            return (
                obj.func,
                self.deep_deconstruct(obj.args),
                self.deep_deconstruct(obj.keywords),
            )
        elif isinstance(obj, COMPILED_REGEX_TYPE):
            return RegexObject(obj)
        elif isinstance(obj, type):
            # If this is a type that implements 'deconstruct' as an instance method,
            # avoid treating this as being deconstructible itself - see #22951
            return obj
        elif hasattr(obj, "deconstruct"):
            deconstructed = obj.deconstruct()
            if isinstance(obj, models.Field):
                # we have a field which also returns a name
                deconstructed = deconstructed[1:]
            path, args, kwargs = deconstructed
            return (
                path,
                [self.deep_deconstruct(value) for value in args],
                {key: self.deep_deconstruct(value) for key, value in kwargs.items()},
            )
        else:
            return obj

    def only_relation_agnostic_fields(self, fields):
        """
        Return a definition of the fields that ignores field names and
        what related fields actually relate to. Used for detecting renames (as
        the related fields change during renames).
        """
        fields_def = []
        for name, field in sorted(fields.items()):
            deconstruction = self.deep_deconstruct(field)
            if field.remote_field and field.remote_field.model:
                deconstruction[2].pop("to", None)
            fields_def.append(deconstruction)
        return fields_def

    def _detect_changes(self, convert_apps=None, graph=None):
        """
        Return a dict of migration plans which will achieve the
        change from from_state to to_state. The dict has app labels
        as keys and a list of migrations as values.

        The resulting migrations aren't specially named, but the names
        do matter for dependencies inside the set.

        convert_apps is the list of apps to convert to use migrations
        (i.e. to make initial migrations for, in the usual case)

        graph is an optional argument that, if provided, can help improve
        dependency generation and avoid potential circular dependencies.
        """
        # The first phase is generating all the operations for each app
        # and gathering them into a big per-app list.
        # Then go through that list, order it, and split into migrations to
        # resolve dependencies caused by M2Ms and FKs.
        self.generated_operations = {}
        self.altered_indexes = {}
        self.altered_constraints = {}
        self.renamed_fields = {}

        # Prepare some old/new state and model lists, separating
        # proxy models and ignoring unmigrated apps.
        self.old_model_keys = set()
        self.old_proxy_keys = set()
        self.old_unmanaged_keys = set()
        self.new_model_keys = set()
        self.new_proxy_keys = set()
        self.new_unmanaged_keys = set()
        for (app_label, model_name), model_state in self.from_state.models.items():
            if not model_state.options.get("managed", True):
                self.old_unmanaged_keys.add((app_label, model_name))
            elif app_label not in self.from_state.real_apps:
                if model_state.options.get("proxy"):
                    self.old_proxy_keys.add((app_label, model_name))
                else:
                    self.old_model_keys.add((app_label, model_name))

        for (app_label, model_name), model_state in self.to_state.models.items():
            if not model_state.options.get("managed", True):
                self.new_unmanaged_keys.add((app_label, model_name))
            elif app_label not in self.from_state.real_apps or (
                convert_apps and app_label in convert_apps
            ):
                if model_state.options.get("proxy"):
                    self.new_proxy_keys.add((app_label, model_name))
                else:
                    self.new_model_keys.add((app_label, model_name))

        self.from_state.resolve_fields_and_relations()
        self.to_state.resolve_fields_and_relations()

        # Renames have to come first
        self.generate_renamed_models()

        # Prepare lists of fields and generate through model map
        self._prepare_field_lists()
        self._generate_through_model_map()

        # Generate non-rename model operations
        self.generate_deleted_models()
        self.generate_created_models()
        self.generate_deleted_proxies()
        self.generate_created_proxies()
        self.generate_altered_options()
        self.generate_altered_managers()
        self.generate_altered_db_table_comment()

        # Create the renamed fields and store them in self.renamed_fields.
        # They are used by create_altered_indexes(), generate_altered_fields(),
        # generate_removed_altered_index/unique_together(), and
        # generate_altered_index/unique_together().
        self.create_renamed_fields()
        # Create the altered indexes and store them in self.altered_indexes.
        # This avoids the same computation in generate_removed_indexes()
        # and generate_added_indexes().
        self.create_altered_indexes()
        self.create_altered_constraints()
        # Generate index removal operations before field is removed
        self.generate_removed_constraints()
        self.generate_removed_indexes()
        # Generate field renaming operations.
        self.generate_renamed_fields()
        self.generate_renamed_indexes()
        # Generate removal of foo together.
        self.generate_removed_altered_unique_together()
        self.generate_removed_altered_index_together()  # RemovedInDjango51Warning.
        # Generate field operations.
        self.generate_removed_fields()
        self.generate_added_fields()
        self.generate_altered_fields()
        self.generate_altered_order_with_respect_to()
        self.generate_altered_unique_together()
        self.generate_altered_index_together()  # RemovedInDjango51Warning.
        self.generate_added_indexes()
        self.generate_added_constraints()
        self.generate_altered_db_table()

        self._sort_migrations()
        self._build_migration_list(graph)
        self._optimize_migrations()

        return self.migrations

    def _prepare_field_lists(self):
        """
        Prepare field lists and a list of the fields that used through models
        in the old state so dependencies can be made from the through model
        deletion to the field that uses it.
        """
        self.kept_model_keys = self.old_model_keys & self.new_model_keys
        self.kept_proxy_keys = self.old_proxy_keys & self.new_proxy_keys
        self.kept_unmanaged_keys = self.old_unmanaged_keys & self.new_unmanaged_keys
        self.through_users = {}
        self.old_field_keys = {
            (app_label, model_name, field_name)
            for app_label, model_name in self.kept_model_keys
            for field_name in self.from_state.models[
                app_label, self.renamed_models.get((app_label, model_name), model_name)
            ].fields
        }
        self.new_field_keys = {
            (app_label, model_name, field_name)
            for app_label, model_name in self.kept_model_keys
            for field_name in self.to_state.models[app_label, model_name].fields
        }

    def _generate_through_model_map(self):
        """Through model map generation."""
        for app_label, model_name in sorted(self.old_model_keys):
            old_model_name = self.renamed_models.get(
                (app_label, model_name), model_name
            )
            old_model_state = self.from_state.models[app_label, old_model_name]
            for field_name, field in old_model_state.fields.items():
                if hasattr(field, "remote_field") and getattr(
                    field.remote_field, "through", None
                ):
                    through_key = resolve_relation(
                        field.remote_field.through, app_label, model_name
                    )
                    self.through_users[through_key] = (
                        app_label,
                        old_model_name,
                        field_name,
                    )

    @staticmethod
    def _resolve_dependency(dependency):
        """
        Return the resolved dependency and a boolean denoting whether or not
        it was swappable.
        """
        if dependency[0] != "__setting__":
            return dependency, False
        resolved_app_label, resolved_object_name = getattr(
            settings, dependency[1]
        ).split(".")
        return (resolved_app_label, resolved_object_name.lower()) + dependency[2:], True

    def _build_migration_list(self, graph=None):
        """
        Chop the lists of operations up into migrations with dependencies on
        each other. Do this by going through an app's list of operations until
        one is found that has an outgoing dependency that isn't in another
        app's migration yet (hasn't been chopped off its list). Then chop off
        the operations before it into a migration and move onto the next app.
        If the loops completes without doing anything, there's a circular
        dependency (which _should_ be impossible as the operations are
        all split at this point so they can't depend and be depended on).
        """
        self.migrations = {}
        num_ops = sum(len(x) for x in self.generated_operations.values())
        chop_mode = False
        while num_ops:
            # On every iteration, we step through all the apps and see if there
            # is a completed set of operations.
            # If we find that a subset of the operations are complete we can
            # try to chop it off from the rest and continue, but we only
            # do this if we've already been through the list once before
            # without any chopping and nothing has changed.
            for app_label in sorted(self.generated_operations):
                chopped = []
                dependencies = set()
                for operation in list(self.generated_operations[app_label]):
                    deps_satisfied = True
                    operation_dependencies = set()
                    for dep in operation._auto_deps:
                        # Temporarily resolve the swappable dependency to
                        # prevent circular references. While keeping the
                        # dependency checks on the resolved model, add the
                        # swappable dependencies.
                        original_dep = dep
                        dep, is_swappable_dep = self._resolve_dependency(dep)
                        if dep[0] != app_label:
                            # External app dependency. See if it's not yet
                            # satisfied.
                            for other_operation in self.generated_operations.get(
                                dep[0], []
                            ):
                                if self.check_dependency(other_operation, dep):
                                    deps_satisfied = False
                                    break
                            if not deps_satisfied:
                                break
                            else:
                                if is_swappable_dep:
                                    operation_dependencies.add(
                                        (original_dep[0], original_dep[1])
                                    )
                                elif dep[0] in self.migrations:
                                    operation_dependencies.add(
                                        (dep[0], self.migrations[dep[0]][-1].name)
                                    )
                                else:
                                    # If we can't find the other app, we add a
                                    # first/last dependency, but only if we've
                                    # already been through once and checked
                                    # everything.
                                    if chop_mode:
                                        # If the app already exists, we add a
                                        # dependency on the last migration, as
                                        # we don't know which migration
                                        # contains the target field. If it's
                                        # not yet migrated or has no
                                        # migrations, we use __first__.
                                        if graph and graph.leaf_nodes(dep[0]):
                                            operation_dependencies.add(
                                                graph.leaf_nodes(dep[0])[0]
                                            )
                                        else:
                                            operation_dependencies.add(
                                                (dep[0], "__first__")
                                            )
                                    else:
                                        deps_satisfied = False
                    if deps_satisfied:
                        chopped.append(operation)
                        dependencies.update(operation_dependencies)
                        del self.generated_operations[app_label][0]
                    else:
                        break
                # Make a migration! Well, only if there's stuff to put in it
                if dependencies or chopped:
                    if not self.generated_operations[app_label] or chop_mode:
                        subclass = type(
                            "Migration",
                            (Migration,),
                            {"operations": [], "dependencies": []},
                        )
                        instance = subclass(
                            "auto_%i" % (len(self.migrations.get(app_label, [])) + 1),
                            app_label,
                        )
                        instance.dependencies = list(dependencies)
                        instance.operations = chopped
                        instance.initial = app_label not in self.existing_apps
                        self.migrations.setdefault(app_label, []).append(instance)
                        chop_mode = False
                    else:
                        self.generated_operations[app_label] = (
                            chopped + self.generated_operations[app_label]
                        )
            new_num_ops = sum(len(x) for x in self.generated_operations.values())
            if new_num_ops == num_ops:
                if not chop_mode:
                    chop_mode = True
                else:
                    raise ValueError(
                        "Cannot resolve operation dependencies: %r"
                        % self.generated_operations
                    )
            num_ops = new_num_ops

    def _sort_migrations(self):
        """
        Reorder to make things possible. Reordering may be needed so FKs work
        nicely inside the same app.
        """
        for app_label, ops in sorted(self.generated_operations.items()):
            # construct a dependency graph for intra-app dependencies
            dependency_graph = {op: set() for op in ops}
            for op in ops:
                for dep in op._auto_deps:
                    # Resolve intra-app dependencies to handle circular
                    # references involving a swappable model.
                    dep = self._resolve_dependency(dep)[0]
                    if dep[0] == app_label:
                        for op2 in ops:
                            if self.check_dependency(op2, dep):
                                dependency_graph[op].add(op2)

            # we use a stable sort for deterministic tests & general behavior
            self.generated_operations[app_label] = stable_topological_sort(
                ops, dependency_graph
            )

    def _optimize_migrations(self):
        # Add in internal dependencies among the migrations
        for app_label, migrations in self.migrations.items():
            for m1, m2 in zip(migrations, migrations[1:]):
                m2.dependencies.append((app_label, m1.name))

        # De-dupe dependencies
        for migrations in self.migrations.values():
            for migration in migrations:
                migration.dependencies = list(set(migration.dependencies))

        # Optimize migrations
        for app_label, migrations in self.migrations.items():
            for migration in migrations:
                migration.operations = MigrationOptimizer().optimize(
                    migration.operations, app_label
                )

    def check_dependency(self, operation, dependency):
        """
        Return True if the given operation depends on the given dependency,
        False otherwise.
        """
        # Created model
        if dependency[2] is None and dependency[3] is True:
            return (
                isinstance(operation, operations.CreateModel)
                and operation.name_lower == dependency[1].lower()
            )
        # Created field
        elif dependency[2] is not None and dependency[3] is True:
            return (
                isinstance(operation, operations.CreateModel)
                and operation.name_lower == dependency[1].lower()
                and any(dependency[2] == x for x, y in operation.fields)
            ) or (
                isinstance(operation, operations.AddField)
                and operation.model_name_lower == dependency[1].lower()
                and operation.name_lower == dependency[2].lower()
            )
        # Removed field
        elif dependency[2] is not None and dependency[3] is False:
            return (
                isinstance(operation, operations.RemoveField)
                and operation.model_name_lower == dependency[1].lower()
                and operation.name_lower == dependency[2].lower()
            )
        # Removed model
        elif dependency[2] is None and dependency[3] is False:
            return (
                isinstance(operation, operations.DeleteModel)
                and operation.name_lower == dependency[1].lower()
            )
        # Field being altered
        elif dependency[2] is not None and dependency[3] == "alter":
            return (
                isinstance(operation, operations.AlterField)
                and operation.model_name_lower == dependency[1].lower()
                and operation.name_lower == dependency[2].lower()
            )
        # order_with_respect_to being unset for a field
        elif dependency[2] is not None and dependency[3] == "order_wrt_unset":
            return (
                isinstance(operation, operations.AlterOrderWithRespectTo)
                and operation.name_lower == dependency[1].lower()
                and (operation.order_with_respect_to or "").lower()
                != dependency[2].lower()
            )
        # Field is removed and part of an index/unique_together
        elif dependency[2] is not None and dependency[3] == "foo_together_change":
            return (
                isinstance(
                    operation,
                    (operations.AlterUniqueTogether, operations.AlterIndexTogether),
                )
                and operation.name_lower == dependency[1].lower()
            )
        # Unknown dependency. Raise an error.
        else:
            raise ValueError("Can't handle dependency %r" % (dependency,))

    def add_operation(self, app_label, operation, dependencies=None, beginning=False):
        # Dependencies are
        # (app_label, model_name, field_name, create/delete as True/False)
        operation._auto_deps = dependencies or []
        if beginning:
            self.generated_operations.setdefault(app_label, []).insert(0, operation)
        else:
            self.generated_operations.setdefault(app_label, []).append(operation)

    def swappable_first_key(self, item):
        """
        Place potential swappable models first in lists of created models (only
        real way to solve #22783).
        """
        try:
            model_state = self.to_state.models[item]
            base_names = {
                base if isinstance(base, str) else base.__name__
                for base in model_state.bases
            }
            string_version = "%s.%s" % (item[0], item[1])
            if (
                model_state.options.get("swappable")
                or "AbstractUser" in base_names
                or "AbstractBaseUser" in base_names
                or settings.AUTH_USER_MODEL.lower() == string_version.lower()
            ):
                return ("___" + item[0], "___" + item[1])
        except LookupError:
            pass
        return item

    def generate_renamed_models(self):
        """
        Find any renamed models, generate the operations for them, and remove
        the old entry from the model lists. Must be run before other
        model-level generation.
        """
        self.renamed_models = {}
        self.renamed_models_rel = {}
        added_models = self.new_model_keys - self.old_model_keys
        for app_label, model_name in sorted(added_models):
            model_state = self.to_state.models[app_label, model_name]
            model_fields_def = self.only_relation_agnostic_fields(model_state.fields)

            removed_models = self.old_model_keys - self.new_model_keys
            for rem_app_label, rem_model_name in removed_models:
                if rem_app_label == app_label:
                    rem_model_state = self.from_state.models[
                        rem_app_label, rem_model_name
                    ]
                    rem_model_fields_def = self.only_relation_agnostic_fields(
                        rem_model_state.fields
                    )
                    if model_fields_def == rem_model_fields_def:
                        if self.questioner.ask_rename_model(
                            rem_model_state, model_state
                        ):
                            dependencies = []
                            fields = list(model_state.fields.values()) + [
                                field.remote_field
                                for relations in self.to_state.relations[
                                    app_label, model_name
                                ].values()
                                for field in relations.values()
                            ]
                            for field in fields:
                                if field.is_relation:
                                    dependencies.extend(
                                        self._get_dependencies_for_foreign_key(
                                            app_label,
                                            model_name,
                                            field,
                                            self.to_state,
                                        )
                                    )
                            self.add_operation(
                                app_label,
                                operations.RenameModel(
                                    old_name=rem_model_state.name,
                                    new_name=model_state.name,
                                ),
                                dependencies=dependencies,
                            )
                            self.renamed_models[app_label, model_name] = rem_model_name
                            renamed_models_rel_key = "%s.%s" % (
                                rem_model_state.app_label,
                                rem_model_state.name_lower,
                            )
                            self.renamed_models_rel[
                                renamed_models_rel_key
                            ] = "%s.%s" % (
                                model_state.app_label,
                                model_state.name_lower,
                            )
                            self.old_model_keys.remove((rem_app_label, rem_model_name))
                            self.old_model_keys.add((app_label, model_name))
                            break

    def generate_created_models(self):
        """
        Find all new models (both managed and unmanaged) and make create
        operations for them as well as separate operations to create any
        foreign key or M2M relationships (these are optimized later, if
        possible).

        Defer any model options that refer to collections of fields that might
        be deferred (e.g. unique_together, index_together).
        """
        old_keys = self.old_model_keys | self.old_unmanaged_keys
        added_models = self.new_model_keys - old_keys
        added_unmanaged_models = self.new_unmanaged_keys - old_keys
        all_added_models = chain(
            sorted(added_models, key=self.swappable_first_key, reverse=True),
            sorted(added_unmanaged_models, key=self.swappable_first_key, reverse=True),
        )
        for app_label, model_name in all_added_models:
            model_state = self.to_state.models[app_label, model_name]
            # Gather related fields
            related_fields = {}
            primary_key_rel = None
            for field_name, field in model_state.fields.items():
                if field.remote_field:
                    if field.remote_field.model:
                        if field.primary_key:
                            primary_key_rel = field.remote_field.model
                        elif not field.remote_field.parent_link:
                            related_fields[field_name] = field
                    if getattr(field.remote_field, "through", None):
                        related_fields[field_name] = field

            # Are there indexes/unique|index_together to defer?
            indexes = model_state.options.pop("indexes")
            constraints = model_state.options.pop("constraints")
            unique_together = model_state.options.pop("unique_together", None)
            # RemovedInDjango51Warning.
            index_together = model_state.options.pop("index_together", None)
            order_with_respect_to = model_state.options.pop(
                "order_with_respect_to", None
            )
            # Depend on the deletion of any possible proxy version of us
            dependencies = [
                (app_label, model_name, None, False),
            ]
            # Depend on all bases
            for base in model_state.bases:
                if isinstance(base, str) and "." in base:
                    base_app_label, base_name = base.split(".", 1)
                    dependencies.append((base_app_label, base_name, None, True))
                    # Depend on the removal of base fields if the new model has
                    # a field with the same name.
                    old_base_model_state = self.from_state.models.get(
                        (base_app_label, base_name)
                    )
                    new_base_model_state = self.to_state.models.get(
                        (base_app_label, base_name)
                    )
                    if old_base_model_state and new_base_model_state:
                        removed_base_fields = (
                            set(old_base_model_state.fields)
                            .difference(
                                new_base_model_state.fields,
                            )
                            .intersection(model_state.fields)
                        )
                        for removed_base_field in removed_base_fields:
                            dependencies.append(
                                (base_app_label, base_name, removed_base_field, False)
                            )
            # Depend on the other end of the primary key if it's a relation
            if primary_key_rel:
                dependencies.append(
                    resolve_relation(
                        primary_key_rel,
                        app_label,
                        model_name,
                    )
                    + (None, True)
                )
            # Generate creation operation
            self.add_operation(
                app_label,
                operations.CreateModel(
                    name=model_state.name,
                    fields=[
                        d
                        for d in model_state.fields.items()
                        if d[0] not in related_fields
                    ],
                    options=model_state.options,
                    bases=model_state.bases,
                    managers=model_state.managers,
                ),
                dependencies=dependencies,
                beginning=True,
            )

            # Don't add operations which modify the database for unmanaged models
            if not model_state.options.get("managed", True):
                continue

            # Generate operations for each related field
            for name, field in sorted(related_fields.items()):
                dependencies = self._get_dependencies_for_foreign_key(
                    app_label,
                    model_name,
                    field,
                    self.to_state,
                )
                # Depend on our own model being created
                dependencies.append((app_label, model_name, None, True))
                # Make operation
                self.add_operation(
                    app_label,
                    operations.AddField(
                        model_name=model_name,
                        name=name,
                        field=field,
                    ),
                    dependencies=list(set(dependencies)),
                )
            # Generate other opns
            if order_with_respect_to:
                self.add_operation(
                    app_label,
                    operations.AlterOrderWithRespectTo(
                        name=model_name,
                        order_with_respect_to=order_with_respect_to,
                    ),
                    dependencies=[
                        (app_label, model_name, order_with_respect_to, True),
                        (app_label, model_name, None, True),
                    ],
                )
            related_dependencies = [
                (app_label, model_name, name, True) for name in sorted(related_fields)
            ]
            related_dependencies.append((app_label, model_name, None, True))
            for index in indexes:
                self.add_operation(
                    app_label,
                    operations.AddIndex(
                        model_name=model_name,
                        index=index,
                    ),
                    dependencies=related_dependencies,
                )
            for constraint in constraints:
                self.add_operation(
                    app_label,
                    operations.AddConstraint(
                        model_name=model_name,
                        constraint=constraint,
                    ),
                    dependencies=related_dependencies,
                )
            if unique_together:
                self.add_operation(
                    app_label,
                    operations.AlterUniqueTogether(
                        name=model_name,
                        unique_together=unique_together,
                    ),
                    dependencies=related_dependencies,
                )
            # RemovedInDjango51Warning.
            if index_together:
                self.add_operation(
                    app_label,
                    operations.AlterIndexTogether(
                        name=model_name,
                        index_together=index_together,
                    ),
                    dependencies=related_dependencies,
                )
            # Fix relationships if the model changed from a proxy model to a
            # concrete model.
            relations = self.to_state.relations
            if (app_label, model_name) in self.old_proxy_keys:
                for related_model_key, related_fields in relations[
                    app_label, model_name
                ].items():
                    related_model_state = self.to_state.models[related_model_key]
                    for related_field_name, related_field in related_fields.items():
                        self.add_operation(
                            related_model_state.app_label,
                            operations.AlterField(
                                model_name=related_model_state.name,
                                name=related_field_name,
                                field=related_field,
                            ),
                            dependencies=[(app_label, model_name, None, True)],
                        )

    def generate_created_proxies(self):
        """
        Make CreateModel statements for proxy models. Use the same statements
        as that way there's less code duplication, but for proxy models it's
        safe to skip all the pointless field stuff and chuck out an operation.
        """
        added = self.new_proxy_keys - self.old_proxy_keys
        for app_label, model_name in sorted(added):
            model_state = self.to_state.models[app_label, model_name]
            assert model_state.options.get("proxy")
            # Depend on the deletion of any possible non-proxy version of us
            dependencies = [
                (app_label, model_name, None, False),
            ]
            # Depend on all bases
            for base in model_state.bases:
                if isinstance(base, str) and "." in base:
                    base_app_label, base_name = base.split(".", 1)
                    dependencies.append((base_app_label, base_name, None, True))
            # Generate creation operation
            self.add_operation(
                app_label,
                operations.CreateModel(
                    name=model_state.name,
                    fields=[],
                    options=model_state.options,
                    bases=model_state.bases,
                    managers=model_state.managers,
                ),
                # Depend on the deletion of any possible non-proxy version of us
                dependencies=dependencies,
            )

    def generate_deleted_models(self):
        """
        Find all deleted models (managed and unmanaged) and make delete
        operations for them as well as separate operations to delete any
        foreign key or M2M relationships (these are optimized later, if
        possible).

        Also bring forward removal of any model options that refer to
        collections of fields - the inverse of generate_created_models().
        """
        new_keys = self.new_model_keys | self.new_unmanaged_keys
        deleted_models = self.old_model_keys - new_keys
        deleted_unmanaged_models = self.old_unmanaged_keys - new_keys
        all_deleted_models = chain(
            sorted(deleted_models), sorted(deleted_unmanaged_models)
        )
        for app_label, model_name in all_deleted_models:
            model_state = self.from_state.models[app_label, model_name]
            # Gather related fields
            related_fields = {}
            for field_name, field in model_state.fields.items():
                if field.remote_field:
                    if field.remote_field.model:
                        related_fields[field_name] = field
                    if getattr(field.remote_field, "through", None):
                        related_fields[field_name] = field
            # Generate option removal first
            unique_together = model_state.options.pop("unique_together", None)
            # RemovedInDjango51Warning.
            index_together = model_state.options.pop("index_together", None)
            if unique_together:
                self.add_operation(
                    app_label,
                    operations.AlterUniqueTogether(
                        name=model_name,
                        unique_together=None,
                    ),
                )
            # RemovedInDjango51Warning.
            if index_together:
                self.add_operation(
                    app_label,
                    operations.AlterIndexTogether(
                        name=model_name,
                        index_together=None,
                    ),
                )
            # Then remove each related field
            for name in sorted(related_fields):
                self.add_operation(
                    app_label,
                    operations.RemoveField(
                        model_name=model_name,
                        name=name,
                    ),
                )
            # Finally, remove the model.
            # This depends on both the removal/alteration of all incoming fields
            # and the removal of all its own related fields, and if it's
            # a through model the field that references it.
            dependencies = []
            relations = self.from_state.relations
            for (
                related_object_app_label,
                object_name,
            ), relation_related_fields in relations[app_label, model_name].items():
                for field_name, field in relation_related_fields.items():
                    dependencies.append(
                        (related_object_app_label, object_name, field_name, False),
                    )
                    if not field.many_to_many:
                        dependencies.append(
                            (
                                related_object_app_label,
                                object_name,
                                field_name,
                                "alter",
                            ),
                        )

            for name in sorted(related_fields):
                dependencies.append((app_label, model_name, name, False))
            # We're referenced in another field's through=
            through_user = self.through_users.get((app_label, model_state.name_lower))
            if through_user:
                dependencies.append(
                    (through_user[0], through_user[1], through_user[2], False)
                )
            # Finally, make the operation, deduping any dependencies
            self.add_operation(
                app_label,
                operations.DeleteModel(
                    name=model_state.name,
                ),
                dependencies=list(set(dependencies)),
            )

    def generate_deleted_proxies(self):
        """Make DeleteModel options for proxy models."""
        deleted = self.old_proxy_keys - self.new_proxy_keys
        for app_label, model_name in sorted(deleted):
            model_state = self.from_state.models[app_label, model_name]
            assert model_state.options.get("proxy")
            self.add_operation(
                app_label,
                operations.DeleteModel(
                    name=model_state.name,
                ),
            )

    def create_renamed_fields(self):
        """Work out renamed fields."""
        self.renamed_operations = []
        old_field_keys = self.old_field_keys.copy()
        for app_label, model_name, field_name in sorted(
            self.new_field_keys - old_field_keys
        ):
            old_model_name = self.renamed_models.get(
                (app_label, model_name), model_name
            )
            old_model_state = self.from_state.models[app_label, old_model_name]
            new_model_state = self.to_state.models[app_label, model_name]
            field = new_model_state.get_field(field_name)
            # Scan to see if this is actually a rename!
            field_dec = self.deep_deconstruct(field)
            for rem_app_label, rem_model_name, rem_field_name in sorted(
                old_field_keys - self.new_field_keys
            ):
                if rem_app_label == app_label and rem_model_name == model_name:
                    old_field = old_model_state.get_field(rem_field_name)
                    old_field_dec = self.deep_deconstruct(old_field)
                    if (
                        field.remote_field
                        and field.remote_field.model
                        and "to" in old_field_dec[2]
                    ):
                        old_rel_to = old_field_dec[2]["to"]
                        if old_rel_to in self.renamed_models_rel:
                            old_field_dec[2]["to"] = self.renamed_models_rel[old_rel_to]
                    old_field.set_attributes_from_name(rem_field_name)
                    old_db_column = old_field.get_attname_column()[1]
                    if old_field_dec == field_dec or (
                        # Was the field renamed and db_column equal to the
                        # old field's column added?
                        old_field_dec[0:2] == field_dec[0:2]
                        and dict(old_field_dec[2], db_column=old_db_column)
                        == field_dec[2]
                    ):
                        if self.questioner.ask_rename(
                            model_name, rem_field_name, field_name, field
                        ):
                            self.renamed_operations.append(
                                (
                                    rem_app_label,
                                    rem_model_name,
                                    old_field.db_column,
                                    rem_field_name,
                                    app_label,
                                    model_name,
                                    field,
                                    field_name,
                                )
                            )
                            old_field_keys.remove(
                                (rem_app_label, rem_model_name, rem_field_name)
                            )
                            old_field_keys.add((app_label, model_name, field_name))
                            self.renamed_fields[
                                app_label, model_name, field_name
                            ] = rem_field_name
                            break

    def generate_renamed_fields(self):
        """Generate RenameField operations."""
        for (
            rem_app_label,
            rem_model_name,
            rem_db_column,
            rem_field_name,
            app_label,
            model_name,
            field,
            field_name,
        ) in self.renamed_operations:
            # A db_column mismatch requires a prior noop AlterField for the
            # subsequent RenameField to be a noop on attempts at preserving the
            # old name.
            if rem_db_column != field.db_column:
                altered_field = field.clone()
                altered_field.name = rem_field_name
                self.add_operation(
                    app_label,
                    operations.AlterField(
                        model_name=model_name,
                        name=rem_field_name,
                        field=altered_field,
                    ),
                )
            self.add_operation(
                app_label,
                operations.RenameField(
                    model_name=model_name,
                    old_name=rem_field_name,
                    new_name=field_name,
                ),
            )
            self.old_field_keys.remove((rem_app_label, rem_model_name, rem_field_name))
            self.old_field_keys.add((app_label, model_name, field_name))

    def generate_added_fields(self):
        """Make AddField operations."""
        for app_label, model_name, field_name in sorted(
            self.new_field_keys - self.old_field_keys
        ):
            self._generate_added_field(app_label, model_name, field_name)

    def _generate_added_field(self, app_label, model_name, field_name):
        field = self.to_state.models[app_label, model_name].get_field(field_name)
        # Adding a field always depends at least on its removal.
        dependencies = [(app_label, model_name, field_name, False)]
        # Fields that are foreignkeys/m2ms depend on stuff.
        if field.remote_field and field.remote_field.model:
            dependencies.extend(
                self._get_dependencies_for_foreign_key(
                    app_label,
                    model_name,
                    field,
                    self.to_state,
                )
            )
        # You can't just add NOT NULL fields with no default or fields
        # which don't allow empty strings as default.
        time_fields = (models.DateField, models.DateTimeField, models.TimeField)
        preserve_default = (
            field.null
            or field.has_default()
            or field.many_to_many
            or (field.blank and field.empty_strings_allowed)
            or (isinstance(field, time_fields) and field.auto_now)
        )
        if not preserve_default:
            field = field.clone()
            if isinstance(field, time_fields) and field.auto_now_add:
                field.default = self.questioner.ask_auto_now_add_addition(
                    field_name, model_name
                )
            else:
                field.default = self.questioner.ask_not_null_addition(
                    field_name, model_name
                )
        if (
            field.unique
            and field.default is not models.NOT_PROVIDED
            and callable(field.default)
        ):
            self.questioner.ask_unique_callable_default_addition(field_name, model_name)
        self.add_operation(
            app_label,
            operations.AddField(
                model_name=model_name,
                name=field_name,
                field=field,
                preserve_default=preserve_default,
            ),
            dependencies=dependencies,
        )

    def generate_removed_fields(self):
        """Make RemoveField operations."""
        for app_label, model_name, field_name in sorted(
            self.old_field_keys - self.new_field_keys
        ):
            self._generate_removed_field(app_label, model_name, field_name)

    def _generate_removed_field(self, app_label, model_name, field_name):
        self.add_operation(
            app_label,
            operations.RemoveField(
                model_name=model_name,
                name=field_name,
            ),
            # We might need to depend on the removal of an
            # order_with_respect_to or index/unique_together operation;
            # this is safely ignored if there isn't one
            dependencies=[
                (app_label, model_name, field_name, "order_wrt_unset"),
                (app_label, model_name, field_name, "foo_together_change"),
            ],
        )

    def generate_altered_fields(self):
        """
        Make AlterField operations, or possibly RemovedField/AddField if alter
        isn't possible.
        """
        for app_label, model_name, field_name in sorted(
            self.old_field_keys & self.new_field_keys
        ):
            # Did the field change?
            old_model_name = self.renamed_models.get(
                (app_label, model_name), model_name
            )
            old_field_name = self.renamed_fields.get(
                (app_label, model_name, field_name), field_name
            )
            old_field = self.from_state.models[app_label, old_model_name].get_field(
                old_field_name
            )
            new_field = self.to_state.models[app_label, model_name].get_field(
                field_name
            )
            dependencies = []
            # Implement any model renames on relations; these are handled by RenameModel
            # so we need to exclude them from the comparison
            if hasattr(new_field, "remote_field") and getattr(
                new_field.remote_field, "model", None
            ):
                rename_key = resolve_relation(
                    new_field.remote_field.model, app_label, model_name
                )
                if rename_key in self.renamed_models:
                    new_field.remote_field.model = old_field.remote_field.model
                # Handle ForeignKey which can only have a single to_field.
                remote_field_name = getattr(new_field.remote_field, "field_name", None)
                if remote_field_name:
                    to_field_rename_key = rename_key + (remote_field_name,)
                    if to_field_rename_key in self.renamed_fields:
                        # Repoint both model and field name because to_field
                        # inclusion in ForeignKey.deconstruct() is based on
                        # both.
                        new_field.remote_field.model = old_field.remote_field.model
                        new_field.remote_field.field_name = (
                            old_field.remote_field.field_name
                        )
                # Handle ForeignObjects which can have multiple from_fields/to_fields.
                from_fields = getattr(new_field, "from_fields", None)
                if from_fields:
                    from_rename_key = (app_label, model_name)
                    new_field.from_fields = tuple(
                        [
                            self.renamed_fields.get(
                                from_rename_key + (from_field,), from_field
                            )
                            for from_field in from_fields
                        ]
                    )
                    new_field.to_fields = tuple(
                        [
                            self.renamed_fields.get(rename_key + (to_field,), to_field)
                            for to_field in new_field.to_fields
                        ]
                    )
                dependencies.extend(
                    self._get_dependencies_for_foreign_key(
                        app_label,
                        model_name,
                        new_field,
                        self.to_state,
                    )
                )
            if hasattr(new_field, "remote_field") and getattr(
                new_field.remote_field, "through", None
            ):
                rename_key = resolve_relation(
                    new_field.remote_field.through, app_label, model_name
                )
                if rename_key in self.renamed_models:
                    new_field.remote_field.through = old_field.remote_field.through
            old_field_dec = self.deep_deconstruct(old_field)
            new_field_dec = self.deep_deconstruct(new_field)
            # If the field was confirmed to be renamed it means that only
            # db_column was allowed to change which generate_renamed_fields()
            # already accounts for by adding an AlterField operation.
            if old_field_dec != new_field_dec and old_field_name == field_name:
                both_m2m = old_field.many_to_many and new_field.many_to_many
                neither_m2m = not old_field.many_to_many and not new_field.many_to_many
                if both_m2m or neither_m2m:
                    # Either both fields are m2m or neither is
                    preserve_default = True
                    if (
                        old_field.null
                        and not new_field.null
                        and not new_field.has_default()
                        and not new_field.many_to_many
                    ):
                        field = new_field.clone()
                        new_default = self.questioner.ask_not_null_alteration(
                            field_name, model_name
                        )
                        if new_default is not models.NOT_PROVIDED:
                            field.default = new_default
                            preserve_default = False
                    else:
                        field = new_field
                    self.add_operation(
                        app_label,
                        operations.AlterField(
                            model_name=model_name,
                            name=field_name,
                            field=field,
                            preserve_default=preserve_default,
                        ),
                        dependencies=dependencies,
                    )
                else:
                    # We cannot alter between m2m and concrete fields
                    self._generate_removed_field(app_label, model_name, field_name)
                    self._generate_added_field(app_label, model_name, field_name)

    def create_altered_indexes(self):
        option_name = operations.AddIndex.option_name
        self.renamed_index_together_values = defaultdict(list)

        for app_label, model_name in sorted(self.kept_model_keys):
            old_model_name = self.renamed_models.get(
                (app_label, model_name), model_name
            )
            old_model_state = self.from_state.models[app_label, old_model_name]
            new_model_state = self.to_state.models[app_label, model_name]

            old_indexes = old_model_state.options[option_name]
            new_indexes = new_model_state.options[option_name]
            added_indexes = [idx for idx in new_indexes if idx not in old_indexes]
            removed_indexes = [idx for idx in old_indexes if idx not in new_indexes]
            renamed_indexes = []
            # Find renamed indexes.
            remove_from_added = []
            remove_from_removed = []
            for new_index in added_indexes:
                new_index_dec = new_index.deconstruct()
                new_index_name = new_index_dec[2].pop("name")
                for old_index in removed_indexes:
                    old_index_dec = old_index.deconstruct()
                    old_index_name = old_index_dec[2].pop("name")
                    # Indexes are the same except for the names.
                    if (
                        new_index_dec == old_index_dec
                        and new_index_name != old_index_name
                    ):
                        renamed_indexes.append((old_index_name, new_index_name, None))
                        remove_from_added.append(new_index)
                        remove_from_removed.append(old_index)
            # Find index_together changed to indexes.
            for (
                old_value,
                new_value,
                index_together_app_label,
                index_together_model_name,
                dependencies,
            ) in self._get_altered_foo_together_operations(
                operations.AlterIndexTogether.option_name
            ):
                if (
                    app_label != index_together_app_label
                    or model_name != index_together_model_name
                ):
                    continue
                removed_values = old_value.difference(new_value)
                for removed_index_together in removed_values:
                    renamed_index_together_indexes = []
                    for new_index in added_indexes:
                        _, args, kwargs = new_index.deconstruct()
                        # Ensure only 'fields' are defined in the Index.
                        if (
                            not args
                            and new_index.fields == list(removed_index_together)
                            and set(kwargs) == {"name", "fields"}
                        ):
                            renamed_index_together_indexes.append(new_index)

                    if len(renamed_index_together_indexes) == 1:
                        renamed_index = renamed_index_together_indexes[0]
                        remove_from_added.append(renamed_index)
                        renamed_indexes.append(
                            (None, renamed_index.name, removed_index_together)
                        )
                        self.renamed_index_together_values[
                            index_together_app_label, index_together_model_name
                        ].append(removed_index_together)
            # Remove renamed indexes from the lists of added and removed
            # indexes.
            added_indexes = [
                idx for idx in added_indexes if idx not in remove_from_added
            ]
            removed_indexes = [
                idx for idx in removed_indexes if idx not in remove_from_removed
            ]

            self.altered_indexes.update(
                {
                    (app_label, model_name): {
                        "added_indexes": added_indexes,
                        "removed_indexes": removed_indexes,
                        "renamed_indexes": renamed_indexes,
                    }
                }
            )

    def generate_added_indexes(self):
        for (app_label, model_name), alt_indexes in self.altered_indexes.items():
            for index in alt_indexes["added_indexes"]:
                self.add_operation(
                    app_label,
                    operations.AddIndex(
                        model_name=model_name,
                        index=index,
                    ),
                )

    def generate_removed_indexes(self):
        for (app_label, model_name), alt_indexes in self.altered_indexes.items():
            for index in alt_indexes["removed_indexes"]:
                self.add_operation(
                    app_label,
                    operations.RemoveIndex(
                        model_name=model_name,
                        name=index.name,
                    ),
                )

    def generate_renamed_indexes(self):
        for (app_label, model_name), alt_indexes in self.altered_indexes.items():
            for old_index_name, new_index_name, old_fields in alt_indexes[
                "renamed_indexes"
            ]:
                self.add_operation(
                    app_label,
                    operations.RenameIndex(
                        model_name=model_name,
                        new_name=new_index_name,
                        old_name=old_index_name,
                        old_fields=old_fields,
                    ),
                )

    def create_altered_constraints(self):
        option_name = operations.AddConstraint.option_name
        for app_label, model_name in sorted(self.kept_model_keys):
            old_model_name = self.renamed_models.get(
                (app_label, model_name), model_name
            )
            old_model_state = self.from_state.models[app_label, old_model_name]
            new_model_state = self.to_state.models[app_label, model_name]

            old_constraints = old_model_state.options[option_name]
            new_constraints = new_model_state.options[option_name]
            add_constraints = [c for c in new_constraints if c not in old_constraints]
            rem_constraints = [c for c in old_constraints if c not in new_constraints]

            self.altered_constraints.update(
                {
                    (app_label, model_name): {
                        "added_constraints": add_constraints,
                        "removed_constraints": rem_constraints,
                    }
                }
            )

    def generate_added_constraints(self):
        for (
            app_label,
            model_name,
        ), alt_constraints in self.altered_constraints.items():
            for constraint in alt_constraints["added_constraints"]:
                self.add_operation(
                    app_label,
                    operations.AddConstraint(
                        model_name=model_name,
                        constraint=constraint,
                    ),
                )

    def generate_removed_constraints(self):
        for (
            app_label,
            model_name,
        ), alt_constraints in self.altered_constraints.items():
            for constraint in alt_constraints["removed_constraints"]:
                self.add_operation(
                    app_label,
                    operations.RemoveConstraint(
                        model_name=model_name,
                        name=constraint.name,
                    ),
                )

    @staticmethod
    def _get_dependencies_for_foreign_key(app_label, model_name, field, project_state):
        remote_field_model = None
        if hasattr(field.remote_field, "model"):
            remote_field_model = field.remote_field.model
        else:
            relations = project_state.relations[app_label, model_name]
            for (remote_app_label, remote_model_name), fields in relations.items():
                if any(
                    field == related_field.remote_field
                    for related_field in fields.values()
                ):
                    remote_field_model = f"{remote_app_label}.{remote_model_name}"
                    break
        # Account for FKs to swappable models
        swappable_setting = getattr(field, "swappable_setting", None)
        if swappable_setting is not None:
            dep_app_label = "__setting__"
            dep_object_name = swappable_setting
        else:
            dep_app_label, dep_object_name = resolve_relation(
                remote_field_model,
                app_label,
                model_name,
            )
        dependencies = [(dep_app_label, dep_object_name, None, True)]
        if getattr(field.remote_field, "through", None):
            through_app_label, through_object_name = resolve_relation(
                field.remote_field.through,
                app_label,
                model_name,
            )
            dependencies.append((through_app_label, through_object_name, None, True))
        return dependencies

    def _get_altered_foo_together_operations(self, option_name):
        for app_label, model_name in sorted(self.kept_model_keys):
            old_model_name = self.renamed_models.get(
                (app_label, model_name), model_name
            )
            old_model_state = self.from_state.models[app_label, old_model_name]
            new_model_state = self.to_state.models[app_label, model_name]

            # We run the old version through the field renames to account for those
            old_value = old_model_state.options.get(option_name)
            old_value = (
                {
                    tuple(
                        self.renamed_fields.get((app_label, model_name, n), n)
                        for n in unique
                    )
                    for unique in old_value
                }
                if old_value
                else set()
            )

            new_value = new_model_state.options.get(option_name)
            new_value = set(new_value) if new_value else set()

            if old_value != new_value:
                dependencies = []
                for foo_togethers in new_value:
                    for field_name in foo_togethers:
                        field = new_model_state.get_field(field_name)
                        if field.remote_field and field.remote_field.model:
                            dependencies.extend(
                                self._get_dependencies_for_foreign_key(
                                    app_label,
                                    model_name,
                                    field,
                                    self.to_state,
                                )
                            )
                yield (
                    old_value,
                    new_value,
                    app_label,
                    model_name,
                    dependencies,
                )

    def _generate_removed_altered_foo_together(self, operation):
        for (
            old_value,
            new_value,
            app_label,
            model_name,
            dependencies,
        ) in self._get_altered_foo_together_operations(operation.option_name):
            if operation == operations.AlterIndexTogether:
                old_value = {
                    value
                    for value in old_value
                    if value
                    not in self.renamed_index_together_values[app_label, model_name]
                }
            removal_value = new_value.intersection(old_value)
            if removal_value or old_value:
                self.add_operation(
                    app_label,
                    operation(
                        name=model_name, **{operation.option_name: removal_value}
                    ),
                    dependencies=dependencies,
                )

    def generate_removed_altered_unique_together(self):
        self._generate_removed_altered_foo_together(operations.AlterUniqueTogether)

    # RemovedInDjango51Warning.
    def generate_removed_altered_index_together(self):
        self._generate_removed_altered_foo_together(operations.AlterIndexTogether)

    def _generate_altered_foo_together(self, operation):
        for (
            old_value,
            new_value,
            app_label,
            model_name,
            dependencies,
        ) in self._get_altered_foo_together_operations(operation.option_name):
            removal_value = new_value.intersection(old_value)
            if new_value != removal_value:
                self.add_operation(
                    app_label,
                    operation(name=model_name, **{operation.option_name: new_value}),
                    dependencies=dependencies,
                )

    def generate_altered_unique_together(self):
        self._generate_altered_foo_together(operations.AlterUniqueTogether)

    # RemovedInDjango51Warning.
    def generate_altered_index_together(self):
        self._generate_altered_foo_together(operations.AlterIndexTogether)

    def generate_altered_db_table(self):
        models_to_check = self.kept_model_keys.union(
            self.kept_proxy_keys, self.kept_unmanaged_keys
        )
        for app_label, model_name in sorted(models_to_check):
            old_model_name = self.renamed_models.get(
                (app_label, model_name), model_name
            )
            old_model_state = self.from_state.models[app_label, old_model_name]
            new_model_state = self.to_state.models[app_label, model_name]
            old_db_table_name = old_model_state.options.get("db_table")
            new_db_table_name = new_model_state.options.get("db_table")
            if old_db_table_name != new_db_table_name:
                self.add_operation(
                    app_label,
                    operations.AlterModelTable(
                        name=model_name,
                        table=new_db_table_name,
                    ),
                )

    def generate_altered_db_table_comment(self):
        models_to_check = self.kept_model_keys.union(
            self.kept_proxy_keys, self.kept_unmanaged_keys
        )
        for app_label, model_name in sorted(models_to_check):
            old_model_name = self.renamed_models.get(
                (app_label, model_name), model_name
            )
            old_model_state = self.from_state.models[app_label, old_model_name]
            new_model_state = self.to_state.models[app_label, model_name]

            old_db_table_comment = old_model_state.options.get("db_table_comment")
            new_db_table_comment = new_model_state.options.get("db_table_comment")
            if old_db_table_comment != new_db_table_comment:
                self.add_operation(
                    app_label,
                    operations.AlterModelTableComment(
                        name=model_name,
                        table_comment=new_db_table_comment,
                    ),
                )

    def generate_altered_options(self):
        """
        Work out if any non-schema-affecting options have changed and make an
        operation to represent them in state changes (in case Python code in
        migrations needs them).
        """
        models_to_check = self.kept_model_keys.union(
            self.kept_proxy_keys,
            self.kept_unmanaged_keys,
            # unmanaged converted to managed
            self.old_unmanaged_keys & self.new_model_keys,
            # managed converted to unmanaged
            self.old_model_keys & self.new_unmanaged_keys,
        )

        for app_label, model_name in sorted(models_to_check):
            old_model_name = self.renamed_models.get(
                (app_label, model_name), model_name
            )
            old_model_state = self.from_state.models[app_label, old_model_name]
            new_model_state = self.to_state.models[app_label, model_name]
            old_options = {
                key: value
                for key, value in old_model_state.options.items()
                if key in AlterModelOptions.ALTER_OPTION_KEYS
            }
            new_options = {
                key: value
                for key, value in new_model_state.options.items()
                if key in AlterModelOptions.ALTER_OPTION_KEYS
            }
            if old_options != new_options:
                self.add_operation(
                    app_label,
                    operations.AlterModelOptions(
                        name=model_name,
                        options=new_options,
                    ),
                )

    def generate_altered_order_with_respect_to(self):
        for app_label, model_name in sorted(self.kept_model_keys):
            old_model_name = self.renamed_models.get(
                (app_label, model_name), model_name
            )
            old_model_state = self.from_state.models[app_label, old_model_name]
            new_model_state = self.to_state.models[app_label, model_name]
            if old_model_state.options.get(
                "order_with_respect_to"
            ) != new_model_state.options.get("order_with_respect_to"):
                # Make sure it comes second if we're adding
                # (removal dependency is part of RemoveField)
                dependencies = []
                if new_model_state.options.get("order_with_respect_to"):
                    dependencies.append(
                        (
                            app_label,
                            model_name,
                            new_model_state.options["order_with_respect_to"],
                            True,
                        )
                    )
                # Actually generate the operation
                self.add_operation(
                    app_label,
                    operations.AlterOrderWithRespectTo(
                        name=model_name,
                        order_with_respect_to=new_model_state.options.get(
                            "order_with_respect_to"
                        ),
                    ),
                    dependencies=dependencies,
                )

    def generate_altered_managers(self):
        for app_label, model_name in sorted(self.kept_model_keys):
            old_model_name = self.renamed_models.get(
                (app_label, model_name), model_name
            )
            old_model_state = self.from_state.models[app_label, old_model_name]
            new_model_state = self.to_state.models[app_label, model_name]
            if old_model_state.managers != new_model_state.managers:
                self.add_operation(
                    app_label,
                    operations.AlterModelManagers(
                        name=model_name,
                        managers=new_model_state.managers,
                    ),
                )

    def arrange_for_graph(self, changes, graph, migration_name=None):
        """
        Take a result from changes() and a MigrationGraph, and fix the names
        and dependencies of the changes so they extend the graph from the leaf
        nodes for each app.
        """
        leaves = graph.leaf_nodes()
        name_map = {}
        for app_label, migrations in list(changes.items()):
            if not migrations:
                continue
            # Find the app label's current leaf node
            app_leaf = None
            for leaf in leaves:
                if leaf[0] == app_label:
                    app_leaf = leaf
                    break
            # Do they want an initial migration for this app?
            if app_leaf is None and not self.questioner.ask_initial(app_label):
                # They don't.
                for migration in migrations:
                    name_map[(app_label, migration.name)] = (app_label, "__first__")
                del changes[app_label]
                continue
            # Work out the next number in the sequence
            if app_leaf is None:
                next_number = 1
            else:
                next_number = (self.parse_number(app_leaf[1]) or 0) + 1
            # Name each migration
            for i, migration in enumerate(migrations):
                if i == 0 and app_leaf:
                    migration.dependencies.append(app_leaf)
                new_name_parts = ["%04i" % next_number]
                if migration_name:
                    new_name_parts.append(migration_name)
                elif i == 0 and not app_leaf:
                    new_name_parts.append("initial")
                else:
                    new_name_parts.append(migration.suggest_name()[:100])
                new_name = "_".join(new_name_parts)
                name_map[(app_label, migration.name)] = (app_label, new_name)
                next_number += 1
                migration.name = new_name
        # Now fix dependencies
        for migrations in changes.values():
            for migration in migrations:
                migration.dependencies = [
                    name_map.get(d, d) for d in migration.dependencies
                ]
        return changes

    def _trim_to_apps(self, changes, app_labels):
        """
        Take changes from arrange_for_graph() and set of app labels, and return
        a modified set of changes which trims out as many migrations that are
        not in app_labels as possible. Note that some other migrations may
        still be present as they may be required dependencies.
        """
        # Gather other app dependencies in a first pass
        app_dependencies = {}
        for app_label, migrations in changes.items():
            for migration in migrations:
                for dep_app_label, name in migration.dependencies:
                    app_dependencies.setdefault(app_label, set()).add(dep_app_label)
        required_apps = set(app_labels)
        # Keep resolving till there's no change
        old_required_apps = None
        while old_required_apps != required_apps:
            old_required_apps = set(required_apps)
            required_apps.update(
                *[app_dependencies.get(app_label, ()) for app_label in required_apps]
            )
        # Remove all migrations that aren't needed
        for app_label in list(changes):
            if app_label not in required_apps:
                del changes[app_label]
        return changes

    @classmethod
    def parse_number(cls, name):
        """
        Given a migration name, try to extract a number from the beginning of
        it. For a squashed migration such as '0001_squashed_0004…', return the
        second number. If no number is found, return None.
        """
        if squashed_match := re.search(r".*_squashed_(\d+)", name):
            return int(squashed_match[1])
        match = re.match(r"^\d+", name)
        if match:
            return int(match[0])
        return None

virt/lib/python3.9/site-packages/django/db/migrations/autodetector.py
PrinceDisant/LostInNL
import copy
import datetime
import functools
import inspect
import warnings
from collections import defaultdict
from decimal import Decimal
from uuid import UUID

from django.core.exceptions import EmptyResultSet, FieldError, FullResultSet
from django.db import DatabaseError, NotSupportedError, connection
from django.db.models import fields
from django.db.models.constants import LOOKUP_SEP
from django.db.models.query_utils import Q
from django.utils.deconstruct import deconstructible
from django.utils.deprecation import RemovedInDjango50Warning
from django.utils.functional import cached_property
from django.utils.hashable import make_hashable


class SQLiteNumericMixin:
    """
    Some expressions with output_field=DecimalField() must be cast to
    numeric to be properly filtered.
    """

    def as_sqlite(self, compiler, connection, **extra_context):
        sql, params = self.as_sql(compiler, connection, **extra_context)
        try:
            if self.output_field.get_internal_type() == "DecimalField":
                sql = "CAST(%s AS NUMERIC)" % sql
        except FieldError:
            pass
        return sql, params


class Combinable:
    """
    Provide the ability to combine one or two objects with
    some connector. For example F('foo') + F('bar').
    """

    # Arithmetic connectors
    ADD = "+"
    SUB = "-"
    MUL = "*"
    DIV = "/"
    POW = "^"
    # The following is a quoted % operator - it is quoted because it can be
    # used in strings that also have parameter substitution.
    MOD = "%%"

    # Bitwise operators - note that these are generated by .bitand()
    # and .bitor(), the '&' and '|' are reserved for boolean operator
    # usage.
    BITAND = "&"
    BITOR = "|"
    BITLEFTSHIFT = "<<"
    BITRIGHTSHIFT = ">>"
    BITXOR = "#"

    def _combine(self, other, connector, reversed):
        if not hasattr(other, "resolve_expression"):
            # everything must be resolvable to an expression
            other = Value(other)

        if reversed:
            return CombinedExpression(other, connector, self)
        return CombinedExpression(self, connector, other)

    #############
    # OPERATORS #
    #############

    def __neg__(self):
        return self._combine(-1, self.MUL, False)

    def __add__(self, other):
        return self._combine(other, self.ADD, False)

    def __sub__(self, other):
        return self._combine(other, self.SUB, False)

    def __mul__(self, other):
        return self._combine(other, self.MUL, False)

    def __truediv__(self, other):
        return self._combine(other, self.DIV, False)

    def __mod__(self, other):
        return self._combine(other, self.MOD, False)

    def __pow__(self, other):
        return self._combine(other, self.POW, False)

    def __and__(self, other):
        if getattr(self, "conditional", False) and getattr(other, "conditional", False):
            return Q(self) & Q(other)
        raise NotImplementedError(
            "Use .bitand(), .bitor(), and .bitxor() for bitwise logical operations."
        )

    def bitand(self, other):
        return self._combine(other, self.BITAND, False)

    def bitleftshift(self, other):
        return self._combine(other, self.BITLEFTSHIFT, False)

    def bitrightshift(self, other):
        return self._combine(other, self.BITRIGHTSHIFT, False)

    def __xor__(self, other):
        if getattr(self, "conditional", False) and getattr(other, "conditional", False):
            return Q(self) ^ Q(other)
        raise NotImplementedError(
            "Use .bitand(), .bitor(), and .bitxor() for bitwise logical operations."
        )

    def bitxor(self, other):
        return self._combine(other, self.BITXOR, False)

    def __or__(self, other):
        if getattr(self, "conditional", False) and getattr(other, "conditional", False):
            return Q(self) | Q(other)
        raise NotImplementedError(
            "Use .bitand(), .bitor(), and .bitxor() for bitwise logical operations."
        )

    def bitor(self, other):
        return self._combine(other, self.BITOR, False)

    def __radd__(self, other):
        return self._combine(other, self.ADD, True)

    def __rsub__(self, other):
        return self._combine(other, self.SUB, True)

    def __rmul__(self, other):
        return self._combine(other, self.MUL, True)

    def __rtruediv__(self, other):
        return self._combine(other, self.DIV, True)

    def __rmod__(self, other):
        return self._combine(other, self.MOD, True)

    def __rpow__(self, other):
        return self._combine(other, self.POW, True)

    def __rand__(self, other):
        raise NotImplementedError(
            "Use .bitand(), .bitor(), and .bitxor() for bitwise logical operations."
        )

    def __ror__(self, other):
        raise NotImplementedError(
            "Use .bitand(), .bitor(), and .bitxor() for bitwise logical operations."
        )

    def __rxor__(self, other):
        raise NotImplementedError(
            "Use .bitand(), .bitor(), and .bitxor() for bitwise logical operations."
        )

    def __invert__(self):
        return NegatedExpression(self)


class BaseExpression:
    """Base class for all query expressions."""

    empty_result_set_value = NotImplemented
    # aggregate specific fields
    is_summary = False
    _output_field_resolved_to_none = False
    # Can the expression be used in a WHERE clause?
    filterable = True
    # Can the expression can be used as a source expression in Window?
    window_compatible = False

    def __init__(self, output_field=None):
        if output_field is not None:
            self.output_field = output_field

    def __getstate__(self):
        state = self.__dict__.copy()
        state.pop("convert_value", None)
        return state

    def get_db_converters(self, connection):
        return (
            []
            if self.convert_value is self._convert_value_noop
            else [self.convert_value]
        ) + self.output_field.get_db_converters(connection)

    def get_source_expressions(self):
        return []

    def set_source_expressions(self, exprs):
        assert not exprs

    def _parse_expressions(self, *expressions):
        return [
            arg
            if hasattr(arg, "resolve_expression")
            else (F(arg) if isinstance(arg, str) else Value(arg))
            for arg in expressions
        ]

    def as_sql(self, compiler, connection):
        """
        Responsible for returning a (sql, [params]) tuple to be included
        in the current query.

        Different backends can provide their own implementation, by
        providing an `as_{vendor}` method and patching the Expression:

        ```
        def override_as_sql(self, compiler, connection):
            # custom logic
            return super().as_sql(compiler, connection)
        setattr(Expression, 'as_' + connection.vendor, override_as_sql)
        ```

        Arguments:
         * compiler: the query compiler responsible for generating the query.
           Must have a compile method, returning a (sql, [params]) tuple.
           Calling compiler(value) will return a quoted `value`.

         * connection: the database connection used for the current query.

        Return: (sql, params)
          Where `sql` is a string containing ordered sql parameters to be
          replaced with the elements of the list `params`.
        """
        raise NotImplementedError("Subclasses must implement as_sql()")

    @cached_property
    def contains_aggregate(self):
        return any(
            expr and expr.contains_aggregate for expr in self.get_source_expressions()
        )

    @cached_property
    def contains_over_clause(self):
        return any(
            expr and expr.contains_over_clause for expr in self.get_source_expressions()
        )

    @cached_property
    def contains_column_references(self):
        return any(
            expr and expr.contains_column_references
            for expr in self.get_source_expressions()
        )

    def resolve_expression(
        self, query=None, allow_joins=True, reuse=None, summarize=False, for_save=False
    ):
        """
        Provide the chance to do any preprocessing or validation before being
        added to the query.

        Arguments:
         * query: the backend query implementation
         * allow_joins: boolean allowing or denying use of joins
           in this query
         * reuse: a set of reusable joins for multijoins
         * summarize: a terminal aggregate clause
         * for_save: whether this expression about to be used in a save or update

        Return: an Expression to be added to the query.
        """
        c = self.copy()
        c.is_summary = summarize
        c.set_source_expressions(
            [
                expr.resolve_expression(query, allow_joins, reuse, summarize)
                if expr
                else None
                for expr in c.get_source_expressions()
            ]
        )
        return c

    @property
    def conditional(self):
        return isinstance(self.output_field, fields.BooleanField)

    @property
    def field(self):
        return self.output_field

    @cached_property
    def output_field(self):
        """Return the output type of this expressions."""
        output_field = self._resolve_output_field()
        if output_field is None:
            self._output_field_resolved_to_none = True
            raise FieldError("Cannot resolve expression type, unknown output_field")
        return output_field

    @cached_property
    def _output_field_or_none(self):
        """
        Return the output field of this expression, or None if
        _resolve_output_field() didn't return an output type.
        """
        try:
            return self.output_field
        except FieldError:
            if not self._output_field_resolved_to_none:
                raise

    def _resolve_output_field(self):
        """
        Attempt to infer the output type of the expression.

        As a guess, if the output fields of all source fields match then simply
        infer the same type here.

        If a source's output field resolves to None, exclude it from this check.
        If all sources are None, then an error is raised higher up the stack in
        the output_field property.
        """
        # This guess is mostly a bad idea, but there is quite a lot of code
        # (especially 3rd party Func subclasses) that depend on it, we'd need a
        # deprecation path to fix it.
        sources_iter = (
            source for source in self.get_source_fields() if source is not None
        )
        for output_field in sources_iter:
            for source in sources_iter:
                if not isinstance(output_field, source.__class__):
                    raise FieldError(
                        "Expression contains mixed types: %s, %s. You must "
                        "set output_field."
                        % (
                            output_field.__class__.__name__,
                            source.__class__.__name__,
                        )
                    )
            return output_field

    @staticmethod
    def _convert_value_noop(value, expression, connection):
        return value

    @cached_property
    def convert_value(self):
        """
        Expressions provide their own converters because users have the option
        of manually specifying the output_field which may be a different type
        from the one the database returns.
        """
        field = self.output_field
        internal_type = field.get_internal_type()
        if internal_type == "FloatField":
            return (
                lambda value, expression, connection: None
                if value is None
                else float(value)
            )
        elif internal_type.endswith("IntegerField"):
            return (
                lambda value, expression, connection: None
                if value is None
                else int(value)
            )
        elif internal_type == "DecimalField":
            return (
                lambda value, expression, connection: None
                if value is None
                else Decimal(value)
            )
        return self._convert_value_noop

    def get_lookup(self, lookup):
        return self.output_field.get_lookup(lookup)

    def get_transform(self, name):
        return self.output_field.get_transform(name)

    def relabeled_clone(self, change_map):
        clone = self.copy()
        clone.set_source_expressions(
            [
                e.relabeled_clone(change_map) if e is not None else None
                for e in self.get_source_expressions()
            ]
        )
        return clone

    def replace_expressions(self, replacements):
        if replacement := replacements.get(self):
            return replacement
        clone = self.copy()
        source_expressions = clone.get_source_expressions()
        clone.set_source_expressions(
            [
                expr.replace_expressions(replacements) if expr else None
                for expr in source_expressions
            ]
        )
        return clone

    def get_refs(self):
        refs = set()
        for expr in self.get_source_expressions():
            refs |= expr.get_refs()
        return refs

    def copy(self):
        return copy.copy(self)

    def prefix_references(self, prefix):
        clone = self.copy()
        clone.set_source_expressions(
            [
                F(f"{prefix}{expr.name}")
                if isinstance(expr, F)
                else expr.prefix_references(prefix)
                for expr in self.get_source_expressions()
            ]
        )
        return clone

    def get_group_by_cols(self):
        if not self.contains_aggregate:
            return [self]
        cols = []
        for source in self.get_source_expressions():
            cols.extend(source.get_group_by_cols())
        return cols

    def get_source_fields(self):
        """Return the underlying field types used by this aggregate."""
        return [e._output_field_or_none for e in self.get_source_expressions()]

    def asc(self, **kwargs):
        return OrderBy(self, **kwargs)

    def desc(self, **kwargs):
        return OrderBy(self, descending=True, **kwargs)

    def reverse_ordering(self):
        return self

    def flatten(self):
        """
        Recursively yield this expression and all subexpressions, in
        depth-first order.
        """
        yield self
        for expr in self.get_source_expressions():
            if expr:
                if hasattr(expr, "flatten"):
                    yield from expr.flatten()
                else:
                    yield expr

    def select_format(self, compiler, sql, params):
        """
        Custom format for select clauses. For example, EXISTS expressions need
        to be wrapped in CASE WHEN on Oracle.
        """
        if hasattr(self.output_field, "select_format"):
            return self.output_field.select_format(compiler, sql, params)
        return sql, params


@deconstructible
class Expression(BaseExpression, Combinable):
    """An expression that can be combined with other expressions."""

    @cached_property
    def identity(self):
        constructor_signature = inspect.signature(self.__init__)
        args, kwargs = self._constructor_args
        signature = constructor_signature.bind_partial(*args, **kwargs)
        signature.apply_defaults()
        arguments = signature.arguments.items()
        identity = [self.__class__]
        for arg, value in arguments:
            if isinstance(value, fields.Field):
                if value.name and value.model:
                    value = (value.model._meta.label, value.name)
                else:
                    value = type(value)
            else:
                value = make_hashable(value)
            identity.append((arg, value))
        return tuple(identity)

    def __eq__(self, other):
        if not isinstance(other, Expression):
            return NotImplemented
        return other.identity == self.identity

    def __hash__(self):
        return hash(self.identity)


# Type inference for CombinedExpression.output_field.
# Missing items will result in FieldError, by design.
#
# The current approach for NULL is based on lowest common denominator behavior
# i.e. if one of the supported databases is raising an error (rather than
# return NULL) for `val <op> NULL`, then Django raises FieldError.
NoneType = type(None)

_connector_combinations = [
    # Numeric operations - operands of same type.
    {
        connector: [
            (fields.IntegerField, fields.IntegerField, fields.IntegerField),
            (fields.FloatField, fields.FloatField, fields.FloatField),
            (fields.DecimalField, fields.DecimalField, fields.DecimalField),
        ]
        for connector in (
            Combinable.ADD,
            Combinable.SUB,
            Combinable.MUL,
            # Behavior for DIV with integer arguments follows Postgres/SQLite,
            # not MySQL/Oracle.
            Combinable.DIV,
            Combinable.MOD,
            Combinable.POW,
        )
    },
    # Numeric operations - operands of different type.
    {
        connector: [
            (fields.IntegerField, fields.DecimalField, fields.DecimalField),
            (fields.DecimalField, fields.IntegerField, fields.DecimalField),
            (fields.IntegerField, fields.FloatField, fields.FloatField),
            (fields.FloatField, fields.IntegerField, fields.FloatField),
        ]
        for connector in (
            Combinable.ADD,
            Combinable.SUB,
            Combinable.MUL,
            Combinable.DIV,
            Combinable.MOD,
        )
    },
    # Bitwise operators.
    {
        connector: [
            (fields.IntegerField, fields.IntegerField, fields.IntegerField),
        ]
        for connector in (
            Combinable.BITAND,
            Combinable.BITOR,
            Combinable.BITLEFTSHIFT,
            Combinable.BITRIGHTSHIFT,
            Combinable.BITXOR,
        )
    },
    # Numeric with NULL.
    {
        connector: [
            (field_type, NoneType, field_type),
            (NoneType, field_type, field_type),
        ]
        for connector in (
            Combinable.ADD,
            Combinable.SUB,
            Combinable.MUL,
            Combinable.DIV,
            Combinable.MOD,
            Combinable.POW,
        )
        for field_type in (fields.IntegerField, fields.DecimalField, fields.FloatField)
    },
    # Date/DateTimeField/DurationField/TimeField.
    {
        Combinable.ADD: [
            # Date/DateTimeField.
            (fields.DateField, fields.DurationField, fields.DateTimeField),
            (fields.DateTimeField, fields.DurationField, fields.DateTimeField),
            (fields.DurationField, fields.DateField, fields.DateTimeField),
            (fields.DurationField, fields.DateTimeField, fields.DateTimeField),
            # DurationField.
            (fields.DurationField, fields.DurationField, fields.DurationField),
            # TimeField.
            (fields.TimeField, fields.DurationField, fields.TimeField),
            (fields.DurationField, fields.TimeField, fields.TimeField),
        ],
    },
    {
        Combinable.SUB: [
            # Date/DateTimeField.
            (fields.DateField, fields.DurationField, fields.DateTimeField),
            (fields.DateTimeField, fields.DurationField, fields.DateTimeField),
            (fields.DateField, fields.DateField, fields.DurationField),
            (fields.DateField, fields.DateTimeField, fields.DurationField),
            (fields.DateTimeField, fields.DateField, fields.DurationField),
            (fields.DateTimeField, fields.DateTimeField, fields.DurationField),
            # DurationField.
            (fields.DurationField, fields.DurationField, fields.DurationField),
            # TimeField.
            (fields.TimeField, fields.DurationField, fields.TimeField),
            (fields.TimeField, fields.TimeField, fields.DurationField),
        ],
    },
]

_connector_combinators = defaultdict(list)


def register_combinable_fields(lhs, connector, rhs, result):
    """
    Register combinable types:
        lhs <connector> rhs -> result
    e.g.
        register_combinable_fields(
            IntegerField, Combinable.ADD, FloatField, FloatField
        )
    """
    _connector_combinators[connector].append((lhs, rhs, result))


for d in _connector_combinations:
    for connector, field_types in d.items():
        for lhs, rhs, result in field_types:
            register_combinable_fields(lhs, connector, rhs, result)


@functools.lru_cache(maxsize=128)
def _resolve_combined_type(connector, lhs_type, rhs_type):
    combinators = _connector_combinators.get(connector, ())
    for combinator_lhs_type, combinator_rhs_type, combined_type in combinators:
        if issubclass(lhs_type, combinator_lhs_type) and issubclass(
            rhs_type, combinator_rhs_type
        ):
            return combined_type


class CombinedExpression(SQLiteNumericMixin, Expression):
    def __init__(self, lhs, connector, rhs, output_field=None):
        super().__init__(output_field=output_field)
        self.connector = connector
        self.lhs = lhs
        self.rhs = rhs

    def __repr__(self):
        return "<{}: {}>".format(self.__class__.__name__, self)

    def __str__(self):
        return "{} {} {}".format(self.lhs, self.connector, self.rhs)

    def get_source_expressions(self):
        return [self.lhs, self.rhs]

    def set_source_expressions(self, exprs):
        self.lhs, self.rhs = exprs

    def _resolve_output_field(self):
        # We avoid using super() here for reasons given in
        # Expression._resolve_output_field()
        combined_type = _resolve_combined_type(
            self.connector,
            type(self.lhs._output_field_or_none),
            type(self.rhs._output_field_or_none),
        )
        if combined_type is None:
            raise FieldError(
                f"Cannot infer type of {self.connector!r} expression involving these "
                f"types: {self.lhs.output_field.__class__.__name__}, "
                f"{self.rhs.output_field.__class__.__name__}. You must set "
                f"output_field."
            )
        return combined_type()

    def as_sql(self, compiler, connection):
        expressions = []
        expression_params = []
        sql, params = compiler.compile(self.lhs)
        expressions.append(sql)
        expression_params.extend(params)
        sql, params = compiler.compile(self.rhs)
        expressions.append(sql)
        expression_params.extend(params)
        # order of precedence
        expression_wrapper = "(%s)"
        sql = connection.ops.combine_expression(self.connector, expressions)
        return expression_wrapper % sql, expression_params

    def resolve_expression(
        self, query=None, allow_joins=True, reuse=None, summarize=False, for_save=False
    ):
        lhs = self.lhs.resolve_expression(
            query, allow_joins, reuse, summarize, for_save
        )
        rhs = self.rhs.resolve_expression(
            query, allow_joins, reuse, summarize, for_save
        )
        if not isinstance(self, (DurationExpression, TemporalSubtraction)):
            try:
                lhs_type = lhs.output_field.get_internal_type()
            except (AttributeError, FieldError):
                lhs_type = None
            try:
                rhs_type = rhs.output_field.get_internal_type()
            except (AttributeError, FieldError):
                rhs_type = None
            if "DurationField" in {lhs_type, rhs_type} and lhs_type != rhs_type:
                return DurationExpression(
                    self.lhs, self.connector, self.rhs
                ).resolve_expression(
                    query,
                    allow_joins,
                    reuse,
                    summarize,
                    for_save,
                )
            datetime_fields = {"DateField", "DateTimeField", "TimeField"}
            if (
                self.connector == self.SUB
                and lhs_type in datetime_fields
                and lhs_type == rhs_type
            ):
                return TemporalSubtraction(self.lhs, self.rhs).resolve_expression(
                    query,
                    allow_joins,
                    reuse,
                    summarize,
                    for_save,
                )
        c = self.copy()
        c.is_summary = summarize
        c.lhs = lhs
        c.rhs = rhs
        return c


class DurationExpression(CombinedExpression):
    def compile(self, side, compiler, connection):
        try:
            output = side.output_field
        except FieldError:
            pass
        else:
            if output.get_internal_type() == "DurationField":
                sql, params = compiler.compile(side)
                return connection.ops.format_for_duration_arithmetic(sql), params
        return compiler.compile(side)

    def as_sql(self, compiler, connection):
        if connection.features.has_native_duration_field:
            return super().as_sql(compiler, connection)
        connection.ops.check_expression_support(self)
        expressions = []
        expression_params = []
        sql, params = self.compile(self.lhs, compiler, connection)
        expressions.append(sql)
        expression_params.extend(params)
        sql, params = self.compile(self.rhs, compiler, connection)
        expressions.append(sql)
        expression_params.extend(params)
        # order of precedence
        expression_wrapper = "(%s)"
        sql = connection.ops.combine_duration_expression(self.connector, expressions)
        return expression_wrapper % sql, expression_params

    def as_sqlite(self, compiler, connection, **extra_context):
        sql, params = self.as_sql(compiler, connection, **extra_context)
        if self.connector in {Combinable.MUL, Combinable.DIV}:
            try:
                lhs_type = self.lhs.output_field.get_internal_type()
                rhs_type = self.rhs.output_field.get_internal_type()
            except (AttributeError, FieldError):
                pass
            else:
                allowed_fields = {
                    "DecimalField",
                    "DurationField",
                    "FloatField",
                    "IntegerField",
                }
                if lhs_type not in allowed_fields or rhs_type not in allowed_fields:
                    raise DatabaseError(
                        f"Invalid arguments for operator {self.connector}."
                    )
        return sql, params


class TemporalSubtraction(CombinedExpression):
    output_field = fields.DurationField()

    def __init__(self, lhs, rhs):
        super().__init__(lhs, self.SUB, rhs)

    def as_sql(self, compiler, connection):
        connection.ops.check_expression_support(self)
        lhs = compiler.compile(self.lhs)
        rhs = compiler.compile(self.rhs)
        return connection.ops.subtract_temporals(
            self.lhs.output_field.get_internal_type(), lhs, rhs
        )


@deconstructible(path="django.db.models.F")
class F(Combinable):
    """An object capable of resolving references to existing query objects."""

    def __init__(self, name):
        """
        Arguments:
         * name: the name of the field this expression references
        """
        self.name = name

    def __repr__(self):
        return "{}({})".format(self.__class__.__name__, self.name)

    def resolve_expression(
        self, query=None, allow_joins=True, reuse=None, summarize=False, for_save=False
    ):
        return query.resolve_ref(self.name, allow_joins, reuse, summarize)

    def replace_expressions(self, replacements):
        return replacements.get(self, self)

    def asc(self, **kwargs):
        return OrderBy(self, **kwargs)

    def desc(self, **kwargs):
        return OrderBy(self, descending=True, **kwargs)

    def __eq__(self, other):
        return self.__class__ == other.__class__ and self.name == other.name

    def __hash__(self):
        return hash(self.name)

    def copy(self):
        return copy.copy(self)


class ResolvedOuterRef(F):
    """
    An object that contains a reference to an outer query.

    In this case, the reference to the outer query has been resolved because
    the inner query has been used as a subquery.
    """

    contains_aggregate = False
    contains_over_clause = False

    def as_sql(self, *args, **kwargs):
        raise ValueError(
            "This queryset contains a reference to an outer query and may "
            "only be used in a subquery."
        )

    def resolve_expression(self, *args, **kwargs):
        col = super().resolve_expression(*args, **kwargs)
        if col.contains_over_clause:
            raise NotSupportedError(
                f"Referencing outer query window expression is not supported: "
                f"{self.name}."
            )
        # FIXME: Rename possibly_multivalued to multivalued and fix detection
        # for non-multivalued JOINs (e.g. foreign key fields). This should take
        # into account only many-to-many and one-to-many relationships.
        col.possibly_multivalued = LOOKUP_SEP in self.name
        return col

    def relabeled_clone(self, relabels):
        return self

    def get_group_by_cols(self):
        return []


class OuterRef(F):
    contains_aggregate = False
    contains_over_clause = False

    def resolve_expression(self, *args, **kwargs):
        if isinstance(self.name, self.__class__):
            return self.name
        return ResolvedOuterRef(self.name)

    def relabeled_clone(self, relabels):
        return self


@deconstructible(path="django.db.models.Func")
class Func(SQLiteNumericMixin, Expression):
    """An SQL function call."""

    function = None
    template = "%(function)s(%(expressions)s)"
    arg_joiner = ", "
    arity = None  # The number of arguments the function accepts.

    def __init__(self, *expressions, output_field=None, **extra):
        if self.arity is not None and len(expressions) != self.arity:
            raise TypeError(
                "'%s' takes exactly %s %s (%s given)"
                % (
                    self.__class__.__name__,
                    self.arity,
                    "argument" if self.arity == 1 else "arguments",
                    len(expressions),
                )
            )
        super().__init__(output_field=output_field)
        self.source_expressions = self._parse_expressions(*expressions)
        self.extra = extra

    def __repr__(self):
        args = self.arg_joiner.join(str(arg) for arg in self.source_expressions)
        extra = {**self.extra, **self._get_repr_options()}
        if extra:
            extra = ", ".join(
                str(key) + "=" + str(val) for key, val in sorted(extra.items())
            )
            return "{}({}, {})".format(self.__class__.__name__, args, extra)
        return "{}({})".format(self.__class__.__name__, args)

    def _get_repr_options(self):
        """Return a dict of extra __init__() options to include in the repr."""
        return {}

    def get_source_expressions(self):
        return self.source_expressions

    def set_source_expressions(self, exprs):
        self.source_expressions = exprs

    def resolve_expression(
        self, query=None, allow_joins=True, reuse=None, summarize=False, for_save=False
    ):
        c = self.copy()
        c.is_summary = summarize
        for pos, arg in enumerate(c.source_expressions):
            c.source_expressions[pos] = arg.resolve_expression(
                query, allow_joins, reuse, summarize, for_save
            )
        return c

    def as_sql(
        self,
        compiler,
        connection,
        function=None,
        template=None,
        arg_joiner=None,
        **extra_context,
    ):
        connection.ops.check_expression_support(self)
        sql_parts = []
        params = []
        for arg in self.source_expressions:
            try:
                arg_sql, arg_params = compiler.compile(arg)
            except EmptyResultSet:
                empty_result_set_value = getattr(
                    arg, "empty_result_set_value", NotImplemented
                )
                if empty_result_set_value is NotImplemented:
                    raise
                arg_sql, arg_params = compiler.compile(Value(empty_result_set_value))
            except FullResultSet:
                arg_sql, arg_params = compiler.compile(Value(True))
            sql_parts.append(arg_sql)
            params.extend(arg_params)
        data = {**self.extra, **extra_context}
        # Use the first supplied value in this order: the parameter to this
        # method, a value supplied in __init__()'s **extra (the value in
        # `data`), or the value defined on the class.
        if function is not None:
            data["function"] = function
        else:
            data.setdefault("function", self.function)
        template = template or data.get("template", self.template)
        arg_joiner = arg_joiner or data.get("arg_joiner", self.arg_joiner)
        data["expressions"] = data["field"] = arg_joiner.join(sql_parts)
        return template % data, params

    def copy(self):
        copy = super().copy()
        copy.source_expressions = self.source_expressions[:]
        copy.extra = self.extra.copy()
        return copy


@deconstructible(path="django.db.models.Value")
class Value(SQLiteNumericMixin, Expression):
    """Represent a wrapped value as a node within an expression."""

    # Provide a default value for `for_save` in order to allow unresolved
    # instances to be compiled until a decision is taken in #25425.
    for_save = False

    def __init__(self, value, output_field=None):
        """
        Arguments:
         * value: the value this expression represents. The value will be
           added into the sql parameter list and properly quoted.

         * output_field: an instance of the model field type that this
           expression will return, such as IntegerField() or CharField().
        """
        super().__init__(output_field=output_field)
        self.value = value

    def __repr__(self):
        return f"{self.__class__.__name__}({self.value!r})"

    def as_sql(self, compiler, connection):
        connection.ops.check_expression_support(self)
        val = self.value
        output_field = self._output_field_or_none
        if output_field is not None:
            if self.for_save:
                val = output_field.get_db_prep_save(val, connection=connection)
            else:
                val = output_field.get_db_prep_value(val, connection=connection)
            if hasattr(output_field, "get_placeholder"):
                return output_field.get_placeholder(val, compiler, connection), [val]
        if val is None:
            # cx_Oracle does not always convert None to the appropriate
            # NULL type (like in case expressions using numbers), so we
            # use a literal SQL NULL
            return "NULL", []
        return "%s", [val]

    def resolve_expression(
        self, query=None, allow_joins=True, reuse=None, summarize=False, for_save=False
    ):
        c = super().resolve_expression(query, allow_joins, reuse, summarize, for_save)
        c.for_save = for_save
        return c

    def get_group_by_cols(self):
        return []

    def _resolve_output_field(self):
        if isinstance(self.value, str):
            return fields.CharField()
        if isinstance(self.value, bool):
            return fields.BooleanField()
        if isinstance(self.value, int):
            return fields.IntegerField()
        if isinstance(self.value, float):
            return fields.FloatField()
        if isinstance(self.value, datetime.datetime):
            return fields.DateTimeField()
        if isinstance(self.value, datetime.date):
            return fields.DateField()
        if isinstance(self.value, datetime.time):
            return fields.TimeField()
        if isinstance(self.value, datetime.timedelta):
            return fields.DurationField()
        if isinstance(self.value, Decimal):
            return fields.DecimalField()
        if isinstance(self.value, bytes):
            return fields.BinaryField()
        if isinstance(self.value, UUID):
            return fields.UUIDField()

    @property
    def empty_result_set_value(self):
        return self.value


class RawSQL(Expression):
    def __init__(self, sql, params, output_field=None):
        if output_field is None:
            output_field = fields.Field()
        self.sql, self.params = sql, params
        super().__init__(output_field=output_field)

    def __repr__(self):
        return "{}({}, {})".format(self.__class__.__name__, self.sql, self.params)

    def as_sql(self, compiler, connection):
        return "(%s)" % self.sql, self.params

    def get_group_by_cols(self):
        return [self]

    def resolve_expression(
        self, query=None, allow_joins=True, reuse=None, summarize=False, for_save=False
    ):
        # Resolve parents fields used in raw SQL.
        if query.model:
            for parent in query.model._meta.get_parent_list():
                for parent_field in parent._meta.local_fields:
                    _, column_name = parent_field.get_attname_column()
                    if column_name.lower() in self.sql.lower():
                        query.resolve_ref(
                            parent_field.name, allow_joins, reuse, summarize
                        )
                        break
        return super().resolve_expression(
            query, allow_joins, reuse, summarize, for_save
        )


class Star(Expression):
    def __repr__(self):
        return "'*'"

    def as_sql(self, compiler, connection):
        return "*", []


class Col(Expression):
    contains_column_references = True
    possibly_multivalued = False

    def __init__(self, alias, target, output_field=None):
        if output_field is None:
            output_field = target
        super().__init__(output_field=output_field)
        self.alias, self.target = alias, target

    def __repr__(self):
        alias, target = self.alias, self.target
        identifiers = (alias, str(target)) if alias else (str(target),)
        return "{}({})".format(self.__class__.__name__, ", ".join(identifiers))

    def as_sql(self, compiler, connection):
        alias, column = self.alias, self.target.column
        identifiers = (alias, column) if alias else (column,)
        sql = ".".join(map(compiler.quote_name_unless_alias, identifiers))
        return sql, []

    def relabeled_clone(self, relabels):
        if self.alias is None:
            return self
        return self.__class__(
            relabels.get(self.alias, self.alias), self.target, self.output_field
        )

    def get_group_by_cols(self):
        return [self]

    def get_db_converters(self, connection):
        if self.target == self.output_field:
            return self.output_field.get_db_converters(connection)
        return self.output_field.get_db_converters(
            connection
        ) + self.target.get_db_converters(connection)


class Ref(Expression):
    """
    Reference to column alias of the query. For example, Ref('sum_cost') in
    qs.annotate(sum_cost=Sum('cost')) query.
    """

    def __init__(self, refs, source):
        super().__init__()
        self.refs, self.source = refs, source

    def __repr__(self):
        return "{}({}, {})".format(self.__class__.__name__, self.refs, self.source)

    def get_source_expressions(self):
        return [self.source]

    def set_source_expressions(self, exprs):
        (self.source,) = exprs

    def resolve_expression(
        self, query=None, allow_joins=True, reuse=None, summarize=False, for_save=False
    ):
        # The sub-expression `source` has already been resolved, as this is
        # just a reference to the name of `source`.
        return self

    def get_refs(self):
        return {self.refs}

    def relabeled_clone(self, relabels):
        clone = self.copy()
        clone.source = self.source.relabeled_clone(relabels)
        return clone

    def as_sql(self, compiler, connection):
        return connection.ops.quote_name(self.refs), []

    def get_group_by_cols(self):
        return [self]


class ExpressionList(Func):
    """
    An expression containing multiple expressions. Can be used to provide a
    list of expressions as an argument to another expression, like a partition
    clause.
    """

    template = "%(expressions)s"

    def __init__(self, *expressions, **extra):
        if not expressions:
            raise ValueError(
                "%s requires at least one expression." % self.__class__.__name__
            )
        super().__init__(*expressions, **extra)

    def __str__(self):
        return self.arg_joiner.join(str(arg) for arg in self.source_expressions)

    def as_sqlite(self, compiler, connection, **extra_context):
        # Casting to numeric is unnecessary.
        return self.as_sql(compiler, connection, **extra_context)


class OrderByList(Func):
    template = "ORDER BY %(expressions)s"

    def __init__(self, *expressions, **extra):
        expressions = (
            (
                OrderBy(F(expr[1:]), descending=True)
                if isinstance(expr, str) and expr[0] == "-"
                else expr
            )
            for expr in expressions
        )
        super().__init__(*expressions, **extra)

    def as_sql(self, *args, **kwargs):
        if not self.source_expressions:
            return "", ()
        return super().as_sql(*args, **kwargs)

    def get_group_by_cols(self):
        group_by_cols = []
        for order_by in self.get_source_expressions():
            group_by_cols.extend(order_by.get_group_by_cols())
        return group_by_cols


@deconstructible(path="django.db.models.ExpressionWrapper")
class ExpressionWrapper(SQLiteNumericMixin, Expression):
    """
    An expression that can wrap another expression so that it can provide
    extra context to the inner expression, such as the output_field.
    """

    def __init__(self, expression, output_field):
        super().__init__(output_field=output_field)
        self.expression = expression

    def set_source_expressions(self, exprs):
        self.expression = exprs[0]

    def get_source_expressions(self):
        return [self.expression]

    def get_group_by_cols(self):
        if isinstance(self.expression, Expression):
            expression = self.expression.copy()
            expression.output_field = self.output_field
            return expression.get_group_by_cols()
        # For non-expressions e.g. an SQL WHERE clause, the entire
        # `expression` must be included in the GROUP BY clause.
        return super().get_group_by_cols()

    def as_sql(self, compiler, connection):
        return compiler.compile(self.expression)

    def __repr__(self):
        return "{}({})".format(self.__class__.__name__, self.expression)


class NegatedExpression(ExpressionWrapper):
    """The logical negation of a conditional expression."""

    def __init__(self, expression):
        super().__init__(expression, output_field=fields.BooleanField())

    def __invert__(self):
        return self.expression.copy()

    def as_sql(self, compiler, connection):
        try:
            sql, params = super().as_sql(compiler, connection)
        except EmptyResultSet:
            features = compiler.connection.features
            if not features.supports_boolean_expr_in_select_clause:
                return "1=1", ()
            return compiler.compile(Value(True))
        ops = compiler.connection.ops
        # Some database backends (e.g. Oracle) don't allow EXISTS() and filters
        # to be compared to another expression unless they're wrapped in a CASE
        # WHEN.
        if not ops.conditional_expression_supported_in_where_clause(self.expression):
            return f"CASE WHEN {sql} = 0 THEN 1 ELSE 0 END", params
        return f"NOT {sql}", params

    def resolve_expression(
        self, query=None, allow_joins=True, reuse=None, summarize=False, for_save=False
    ):
        resolved = super().resolve_expression(
            query, allow_joins, reuse, summarize, for_save
        )
        if not getattr(resolved.expression, "conditional", False):
            raise TypeError("Cannot negate non-conditional expressions.")
        return resolved

    def select_format(self, compiler, sql, params):
        # Wrap boolean expressions with a CASE WHEN expression if a database
        # backend (e.g. Oracle) doesn't support boolean expression in SELECT or
        # GROUP BY list.
        expression_supported_in_where_clause = (
            compiler.connection.ops.conditional_expression_supported_in_where_clause
        )
        if (
            not compiler.connection.features.supports_boolean_expr_in_select_clause
            # Avoid double wrapping.
            and expression_supported_in_where_clause(self.expression)
        ):
            sql = "CASE WHEN {} THEN 1 ELSE 0 END".format(sql)
        return sql, params


@deconstructible(path="django.db.models.When")
class When(Expression):
    template = "WHEN %(condition)s THEN %(result)s"
    # This isn't a complete conditional expression, must be used in Case().
    conditional = False

    def __init__(self, condition=None, then=None, **lookups):
        if lookups:
            if condition is None:
                condition, lookups = Q(**lookups), None
            elif getattr(condition, "conditional", False):
                condition, lookups = Q(condition, **lookups), None
        if condition is None or not getattr(condition, "conditional", False) or lookups:
            raise TypeError(
                "When() supports a Q object, a boolean expression, or lookups "
                "as a condition."
            )
        if isinstance(condition, Q) and not condition:
            raise ValueError("An empty Q() can't be used as a When() condition.")
        super().__init__(output_field=None)
        self.condition = condition
        self.result = self._parse_expressions(then)[0]

    def __str__(self):
        return "WHEN %r THEN %r" % (self.condition, self.result)

    def __repr__(self):
        return "<%s: %s>" % (self.__class__.__name__, self)

    def get_source_expressions(self):
        return [self.condition, self.result]

    def set_source_expressions(self, exprs):
        self.condition, self.result = exprs

    def get_source_fields(self):
        # We're only interested in the fields of the result expressions.
        return [self.result._output_field_or_none]

    def resolve_expression(
        self, query=None, allow_joins=True, reuse=None, summarize=False, for_save=False
    ):
        c = self.copy()
        c.is_summary = summarize
        if hasattr(c.condition, "resolve_expression"):
            c.condition = c.condition.resolve_expression(
                query, allow_joins, reuse, summarize, False
            )
        c.result = c.result.resolve_expression(
            query, allow_joins, reuse, summarize, for_save
        )
        return c

    def as_sql(self, compiler, connection, template=None, **extra_context):
        connection.ops.check_expression_support(self)
        template_params = extra_context
        sql_params = []
        condition_sql, condition_params = compiler.compile(self.condition)
        template_params["condition"] = condition_sql
        result_sql, result_params = compiler.compile(self.result)
        template_params["result"] = result_sql
        template = template or self.template
        return template % template_params, (
            *sql_params,
            *condition_params,
            *result_params,
        )

    def get_group_by_cols(self):
        # This is not a complete expression and cannot be used in GROUP BY.
        cols = []
        for source in self.get_source_expressions():
            cols.extend(source.get_group_by_cols())
        return cols


@deconstructible(path="django.db.models.Case")
class Case(SQLiteNumericMixin, Expression):
    """
    An SQL searched CASE expression:

        CASE
            WHEN n > 0
                THEN 'positive'
            WHEN n < 0
                THEN 'negative'
            ELSE 'zero'
        END
    """

    template = "CASE %(cases)s ELSE %(default)s END"
    case_joiner = " "

    def __init__(self, *cases, default=None, output_field=None, **extra):
        if not all(isinstance(case, When) for case in cases):
            raise TypeError("Positional arguments must all be When objects.")
        super().__init__(output_field)
        self.cases = list(cases)
        self.default = self._parse_expressions(default)[0]
        self.extra = extra

    def __str__(self):
        return "CASE %s, ELSE %r" % (
            ", ".join(str(c) for c in self.cases),
            self.default,
        )

    def __repr__(self):
        return "<%s: %s>" % (self.__class__.__name__, self)

    def get_source_expressions(self):
        return self.cases + [self.default]

    def set_source_expressions(self, exprs):
        *self.cases, self.default = exprs

    def resolve_expression(
        self, query=None, allow_joins=True, reuse=None, summarize=False, for_save=False
    ):
        c = self.copy()
        c.is_summary = summarize
        for pos, case in enumerate(c.cases):
            c.cases[pos] = case.resolve_expression(
                query, allow_joins, reuse, summarize, for_save
            )
        c.default = c.default.resolve_expression(
            query, allow_joins, reuse, summarize, for_save
        )
        return c

    def copy(self):
        c = super().copy()
        c.cases = c.cases[:]
        return c

    def as_sql(
        self, compiler, connection, template=None, case_joiner=None, **extra_context
    ):
        connection.ops.check_expression_support(self)
        if not self.cases:
            return compiler.compile(self.default)
        template_params = {**self.extra, **extra_context}
        case_parts = []
        sql_params = []
        default_sql, default_params = compiler.compile(self.default)
        for case in self.cases:
            try:
                case_sql, case_params = compiler.compile(case)
            except EmptyResultSet:
                continue
            except FullResultSet:
                default_sql, default_params = compiler.compile(case.result)
                break
            case_parts.append(case_sql)
            sql_params.extend(case_params)
        if not case_parts:
            return default_sql, default_params
        case_joiner = case_joiner or self.case_joiner
        template_params["cases"] = case_joiner.join(case_parts)
        template_params["default"] = default_sql
        sql_params.extend(default_params)
        template = template or template_params.get("template", self.template)
        sql = template % template_params
        if self._output_field_or_none is not None:
            sql = connection.ops.unification_cast_sql(self.output_field) % sql
        return sql, sql_params

    def get_group_by_cols(self):
        if not self.cases:
            return self.default.get_group_by_cols()
        return super().get_group_by_cols()


class Subquery(BaseExpression, Combinable):
    """
    An explicit subquery. It may contain OuterRef() references to the outer
    query which will be resolved when it is applied to that query.
    """

    template = "(%(subquery)s)"
    contains_aggregate = False
    empty_result_set_value = None
    subquery = True

    def __init__(self, queryset, output_field=None, **extra):
        # Allow the usage of both QuerySet and sql.Query objects.
        self.query = getattr(queryset, "query", queryset).clone()
        self.query.subquery = True
        self.extra = extra
        super().__init__(output_field)

    def get_source_expressions(self):
        return [self.query]

    def set_source_expressions(self, exprs):
        self.query = exprs[0]

    def _resolve_output_field(self):
        return self.query.output_field

    def copy(self):
        clone = super().copy()
        clone.query = clone.query.clone()
        return clone

    @property
    def external_aliases(self):
        return self.query.external_aliases

    def get_external_cols(self):
        return self.query.get_external_cols()

    def as_sql(self, compiler, connection, template=None, **extra_context):
        connection.ops.check_expression_support(self)
        template_params = {**self.extra, **extra_context}
        subquery_sql, sql_params = self.query.as_sql(compiler, connection)
        template_params["subquery"] = subquery_sql[1:-1]

        template = template or template_params.get("template", self.template)
        sql = template % template_params
        return sql, sql_params

    def get_group_by_cols(self):
        return self.query.get_group_by_cols(wrapper=self)


class Exists(Subquery):
    template = "EXISTS(%(subquery)s)"
    output_field = fields.BooleanField()
    empty_result_set_value = False

    def __init__(self, queryset, **kwargs):
        super().__init__(queryset, **kwargs)
        self.query = self.query.exists()

    def select_format(self, compiler, sql, params):
        # Wrap EXISTS() with a CASE WHEN expression if a database backend
        # (e.g. Oracle) doesn't support boolean expression in SELECT or GROUP
        # BY list.
        if not compiler.connection.features.supports_boolean_expr_in_select_clause:
            sql = "CASE WHEN {} THEN 1 ELSE 0 END".format(sql)
        return sql, params


@deconstructible(path="django.db.models.OrderBy")
class OrderBy(Expression):
    template = "%(expression)s %(ordering)s"
    conditional = False

    def __init__(self, expression, descending=False, nulls_first=None, nulls_last=None):
        if nulls_first and nulls_last:
            raise ValueError("nulls_first and nulls_last are mutually exclusive")
        if nulls_first is False or nulls_last is False:
            # When the deprecation ends, replace with:
            # raise ValueError(
            #     "nulls_first and nulls_last values must be True or None."
            # )
            warnings.warn(
                "Passing nulls_first=False or nulls_last=False is deprecated, use None "
                "instead.",
                RemovedInDjango50Warning,
                stacklevel=2,
            )
        self.nulls_first = nulls_first
        self.nulls_last = nulls_last
        self.descending = descending
        if not hasattr(expression, "resolve_expression"):
            raise ValueError("expression must be an expression type")
        self.expression = expression

    def __repr__(self):
        return "{}({}, descending={})".format(
            self.__class__.__name__, self.expression, self.descending
        )

    def set_source_expressions(self, exprs):
        self.expression = exprs[0]

    def get_source_expressions(self):
        return [self.expression]

    def as_sql(self, compiler, connection, template=None, **extra_context):
        template = template or self.template
        if connection.features.supports_order_by_nulls_modifier:
            if self.nulls_last:
                template = "%s NULLS LAST" % template
            elif self.nulls_first:
                template = "%s NULLS FIRST" % template
        else:
            if self.nulls_last and not (
                self.descending and connection.features.order_by_nulls_first
            ):
                template = "%%(expression)s IS NULL, %s" % template
            elif self.nulls_first and not (
                not self.descending and connection.features.order_by_nulls_first
            ):
                template = "%%(expression)s IS NOT NULL, %s" % template
        connection.ops.check_expression_support(self)
        expression_sql, params = compiler.compile(self.expression)
        placeholders = {
            "expression": expression_sql,
            "ordering": "DESC" if self.descending else "ASC",
            **extra_context,
        }
        params *= template.count("%(expression)s")
        return (template % placeholders).rstrip(), params

    def as_oracle(self, compiler, connection):
        # Oracle doesn't allow ORDER BY EXISTS() or filters unless it's wrapped
        # in a CASE WHEN.
        if connection.ops.conditional_expression_supported_in_where_clause(
            self.expression
        ):
            copy = self.copy()
            copy.expression = Case(
                When(self.expression, then=True),
                default=False,
            )
            return copy.as_sql(compiler, connection)
        return self.as_sql(compiler, connection)

    def get_group_by_cols(self):
        cols = []
        for source in self.get_source_expressions():
            cols.extend(source.get_group_by_cols())
        return cols

    def reverse_ordering(self):
        self.descending = not self.descending
        if self.nulls_first:
            self.nulls_last = True
            self.nulls_first = None
        elif self.nulls_last:
            self.nulls_first = True
            self.nulls_last = None
        return self

    def asc(self):
        self.descending = False

    def desc(self):
        self.descending = True


class Window(SQLiteNumericMixin, Expression):
    template = "%(expression)s OVER (%(window)s)"
    # Although the main expression may either be an aggregate or an
    # expression with an aggregate function, the GROUP BY that will
    # be introduced in the query as a result is not desired.
    contains_aggregate = False
    contains_over_clause = True

    def __init__(
        self,
        expression,
        partition_by=None,
        order_by=None,
        frame=None,
        output_field=None,
    ):
        self.partition_by = partition_by
        self.order_by = order_by
        self.frame = frame

        if not getattr(expression, "window_compatible", False):
            raise ValueError(
                "Expression '%s' isn't compatible with OVER clauses."
                % expression.__class__.__name__
            )

        if self.partition_by is not None:
            if not isinstance(self.partition_by, (tuple, list)):
                self.partition_by = (self.partition_by,)
            self.partition_by = ExpressionList(*self.partition_by)

        if self.order_by is not None:
            if isinstance(self.order_by, (list, tuple)):
                self.order_by = OrderByList(*self.order_by)
            elif isinstance(self.order_by, (BaseExpression, str)):
                self.order_by = OrderByList(self.order_by)
            else:
                raise ValueError(
                    "Window.order_by must be either a string reference to a "
                    "field, an expression, or a list or tuple of them."
                )
        super().__init__(output_field=output_field)
        self.source_expression = self._parse_expressions(expression)[0]

    def _resolve_output_field(self):
        return self.source_expression.output_field

    def get_source_expressions(self):
        return [self.source_expression, self.partition_by, self.order_by, self.frame]

    def set_source_expressions(self, exprs):
        self.source_expression, self.partition_by, self.order_by, self.frame = exprs

    def as_sql(self, compiler, connection, template=None):
        connection.ops.check_expression_support(self)
        if not connection.features.supports_over_clause:
            raise NotSupportedError("This backend does not support window expressions.")
        expr_sql, params = compiler.compile(self.source_expression)
        window_sql, window_params = [], ()

        if self.partition_by is not None:
            sql_expr, sql_params = self.partition_by.as_sql(
                compiler=compiler,
                connection=connection,
                template="PARTITION BY %(expressions)s",
            )
            window_sql.append(sql_expr)
            window_params += tuple(sql_params)

        if self.order_by is not None:
            order_sql, order_params = compiler.compile(self.order_by)
            window_sql.append(order_sql)
            window_params += tuple(order_params)

        if self.frame:
            frame_sql, frame_params = compiler.compile(self.frame)
            window_sql.append(frame_sql)
            window_params += tuple(frame_params)

        template = template or self.template

        return (
            template % {"expression": expr_sql, "window": " ".join(window_sql).strip()},
            (*params, *window_params),
        )

    def as_sqlite(self, compiler, connection):
        if isinstance(self.output_field, fields.DecimalField):
            # Casting to numeric must be outside of the window expression.
            copy = self.copy()
            source_expressions = copy.get_source_expressions()
            source_expressions[0].output_field = fields.FloatField()
            copy.set_source_expressions(source_expressions)
            return super(Window, copy).as_sqlite(compiler, connection)
        return self.as_sql(compiler, connection)

    def __str__(self):
        return "{} OVER ({}{}{})".format(
            str(self.source_expression),
            "PARTITION BY " + str(self.partition_by) if self.partition_by else "",
            str(self.order_by or ""),
            str(self.frame or ""),
        )

    def __repr__(self):
        return "<%s: %s>" % (self.__class__.__name__, self)

    def get_group_by_cols(self):
        group_by_cols = []
        if self.partition_by:
            group_by_cols.extend(self.partition_by.get_group_by_cols())
        if self.order_by is not None:
            group_by_cols.extend(self.order_by.get_group_by_cols())
        return group_by_cols


class WindowFrame(Expression):
    """
    Model the frame clause in window expressions. There are two types of frame
    clauses which are subclasses, however, all processing and validation (by no
    means intended to be complete) is done here. Thus, providing an end for a
    frame is optional (the default is UNBOUNDED FOLLOWING, which is the last
    row in the frame).
    """

    template = "%(frame_type)s BETWEEN %(start)s AND %(end)s"

    def __init__(self, start=None, end=None):
        self.start = Value(start)
        self.end = Value(end)

    def set_source_expressions(self, exprs):
        self.start, self.end = exprs

    def get_source_expressions(self):
        return [self.start, self.end]

    def as_sql(self, compiler, connection):
        connection.ops.check_expression_support(self)
        start, end = self.window_frame_start_end(
            connection, self.start.value, self.end.value
        )
        return (
            self.template
            % {
                "frame_type": self.frame_type,
                "start": start,
                "end": end,
            },
            [],
        )

    def __repr__(self):
        return "<%s: %s>" % (self.__class__.__name__, self)

    def get_group_by_cols(self):
        return []

    def __str__(self):
        if self.start.value is not None and self.start.value < 0:
            start = "%d %s" % (abs(self.start.value), connection.ops.PRECEDING)
        elif self.start.value is not None and self.start.value == 0:
            start = connection.ops.CURRENT_ROW
        else:
            start = connection.ops.UNBOUNDED_PRECEDING

        if self.end.value is not None and self.end.value > 0:
            end = "%d %s" % (self.end.value, connection.ops.FOLLOWING)
        elif self.end.value is not None and self.end.value == 0:
            end = connection.ops.CURRENT_ROW
        else:
            end = connection.ops.UNBOUNDED_FOLLOWING
        return self.template % {
            "frame_type": self.frame_type,
            "start": start,
            "end": end,
        }

    def window_frame_start_end(self, connection, start, end):
        raise NotImplementedError("Subclasses must implement window_frame_start_end().")


class RowRange(WindowFrame):
    frame_type = "ROWS"

    def window_frame_start_end(self, connection, start, end):
        return connection.ops.window_frame_rows_start_end(start, end)


class ValueRange(WindowFrame):
    frame_type = "RANGE"

    def window_frame_start_end(self, connection, start, end):
        return connection.ops.window_frame_range_start_end(start, end)

virt/lib/python3.9/site-packages/django/db/models/expressions 2.py
PrinceDisant/LostInNL
import copy
import datetime
import functools
import inspect
import warnings
from collections import defaultdict
from decimal import Decimal
from uuid import UUID

from django.core.exceptions import EmptyResultSet, FieldError, FullResultSet
from django.db import DatabaseError, NotSupportedError, connection
from django.db.models import fields
from django.db.models.constants import LOOKUP_SEP
from django.db.models.query_utils import Q
from django.utils.deconstruct import deconstructible
from django.utils.deprecation import RemovedInDjango50Warning
from django.utils.functional import cached_property
from django.utils.hashable import make_hashable


class SQLiteNumericMixin:
    """
    Some expressions with output_field=DecimalField() must be cast to
    numeric to be properly filtered.
    """

    def as_sqlite(self, compiler, connection, **extra_context):
        sql, params = self.as_sql(compiler, connection, **extra_context)
        try:
            if self.output_field.get_internal_type() == "DecimalField":
                sql = "CAST(%s AS NUMERIC)" % sql
        except FieldError:
            pass
        return sql, params


class Combinable:
    """
    Provide the ability to combine one or two objects with
    some connector. For example F('foo') + F('bar').
    """

    # Arithmetic connectors
    ADD = "+"
    SUB = "-"
    MUL = "*"
    DIV = "/"
    POW = "^"
    # The following is a quoted % operator - it is quoted because it can be
    # used in strings that also have parameter substitution.
    MOD = "%%"

    # Bitwise operators - note that these are generated by .bitand()
    # and .bitor(), the '&' and '|' are reserved for boolean operator
    # usage.
    BITAND = "&"
    BITOR = "|"
    BITLEFTSHIFT = "<<"
    BITRIGHTSHIFT = ">>"
    BITXOR = "#"

    def _combine(self, other, connector, reversed):
        if not hasattr(other, "resolve_expression"):
            # everything must be resolvable to an expression
            other = Value(other)

        if reversed:
            return CombinedExpression(other, connector, self)
        return CombinedExpression(self, connector, other)

    #############
    # OPERATORS #
    #############

    def __neg__(self):
        return self._combine(-1, self.MUL, False)

    def __add__(self, other):
        return self._combine(other, self.ADD, False)

    def __sub__(self, other):
        return self._combine(other, self.SUB, False)

    def __mul__(self, other):
        return self._combine(other, self.MUL, False)

    def __truediv__(self, other):
        return self._combine(other, self.DIV, False)

    def __mod__(self, other):
        return self._combine(other, self.MOD, False)

    def __pow__(self, other):
        return self._combine(other, self.POW, False)

    def __and__(self, other):
        if getattr(self, "conditional", False) and getattr(other, "conditional", False):
            return Q(self) & Q(other)
        raise NotImplementedError(
            "Use .bitand(), .bitor(), and .bitxor() for bitwise logical operations."
        )

    def bitand(self, other):
        return self._combine(other, self.BITAND, False)

    def bitleftshift(self, other):
        return self._combine(other, self.BITLEFTSHIFT, False)

    def bitrightshift(self, other):
        return self._combine(other, self.BITRIGHTSHIFT, False)

    def __xor__(self, other):
        if getattr(self, "conditional", False) and getattr(other, "conditional", False):
            return Q(self) ^ Q(other)
        raise NotImplementedError(
            "Use .bitand(), .bitor(), and .bitxor() for bitwise logical operations."
        )

    def bitxor(self, other):
        return self._combine(other, self.BITXOR, False)

    def __or__(self, other):
        if getattr(self, "conditional", False) and getattr(other, "conditional", False):
            return Q(self) | Q(other)
        raise NotImplementedError(
            "Use .bitand(), .bitor(), and .bitxor() for bitwise logical operations."
        )

    def bitor(self, other):
        return self._combine(other, self.BITOR, False)

    def __radd__(self, other):
        return self._combine(other, self.ADD, True)

    def __rsub__(self, other):
        return self._combine(other, self.SUB, True)

    def __rmul__(self, other):
        return self._combine(other, self.MUL, True)

    def __rtruediv__(self, other):
        return self._combine(other, self.DIV, True)

    def __rmod__(self, other):
        return self._combine(other, self.MOD, True)

    def __rpow__(self, other):
        return self._combine(other, self.POW, True)

    def __rand__(self, other):
        raise NotImplementedError(
            "Use .bitand(), .bitor(), and .bitxor() for bitwise logical operations."
        )

    def __ror__(self, other):
        raise NotImplementedError(
            "Use .bitand(), .bitor(), and .bitxor() for bitwise logical operations."
        )

    def __rxor__(self, other):
        raise NotImplementedError(
            "Use .bitand(), .bitor(), and .bitxor() for bitwise logical operations."
        )

    def __invert__(self):
        return NegatedExpression(self)


class BaseExpression:
    """Base class for all query expressions."""

    empty_result_set_value = NotImplemented
    # aggregate specific fields
    is_summary = False
    _output_field_resolved_to_none = False
    # Can the expression be used in a WHERE clause?
    filterable = True
    # Can the expression can be used as a source expression in Window?
    window_compatible = False

    def __init__(self, output_field=None):
        if output_field is not None:
            self.output_field = output_field

    def __getstate__(self):
        state = self.__dict__.copy()
        state.pop("convert_value", None)
        return state

    def get_db_converters(self, connection):
        return (
            []
            if self.convert_value is self._convert_value_noop
            else [self.convert_value]
        ) + self.output_field.get_db_converters(connection)

    def get_source_expressions(self):
        return []

    def set_source_expressions(self, exprs):
        assert not exprs

    def _parse_expressions(self, *expressions):
        return [
            arg
            if hasattr(arg, "resolve_expression")
            else (F(arg) if isinstance(arg, str) else Value(arg))
            for arg in expressions
        ]

    def as_sql(self, compiler, connection):
        """
        Responsible for returning a (sql, [params]) tuple to be included
        in the current query.

        Different backends can provide their own implementation, by
        providing an `as_{vendor}` method and patching the Expression:

        ```
        def override_as_sql(self, compiler, connection):
            # custom logic
            return super().as_sql(compiler, connection)
        setattr(Expression, 'as_' + connection.vendor, override_as_sql)
        ```

        Arguments:
         * compiler: the query compiler responsible for generating the query.
           Must have a compile method, returning a (sql, [params]) tuple.
           Calling compiler(value) will return a quoted `value`.

         * connection: the database connection used for the current query.

        Return: (sql, params)
          Where `sql` is a string containing ordered sql parameters to be
          replaced with the elements of the list `params`.
        """
        raise NotImplementedError("Subclasses must implement as_sql()")

    @cached_property
    def contains_aggregate(self):
        return any(
            expr and expr.contains_aggregate for expr in self.get_source_expressions()
        )

    @cached_property
    def contains_over_clause(self):
        return any(
            expr and expr.contains_over_clause for expr in self.get_source_expressions()
        )

    @cached_property
    def contains_column_references(self):
        return any(
            expr and expr.contains_column_references
            for expr in self.get_source_expressions()
        )

    def resolve_expression(
        self, query=None, allow_joins=True, reuse=None, summarize=False, for_save=False
    ):
        """
        Provide the chance to do any preprocessing or validation before being
        added to the query.

        Arguments:
         * query: the backend query implementation
         * allow_joins: boolean allowing or denying use of joins
           in this query
         * reuse: a set of reusable joins for multijoins
         * summarize: a terminal aggregate clause
         * for_save: whether this expression about to be used in a save or update

        Return: an Expression to be added to the query.
        """
        c = self.copy()
        c.is_summary = summarize
        c.set_source_expressions(
            [
                expr.resolve_expression(query, allow_joins, reuse, summarize)
                if expr
                else None
                for expr in c.get_source_expressions()
            ]
        )
        return c

    @property
    def conditional(self):
        return isinstance(self.output_field, fields.BooleanField)

    @property
    def field(self):
        return self.output_field

    @cached_property
    def output_field(self):
        """Return the output type of this expressions."""
        output_field = self._resolve_output_field()
        if output_field is None:
            self._output_field_resolved_to_none = True
            raise FieldError("Cannot resolve expression type, unknown output_field")
        return output_field

    @cached_property
    def _output_field_or_none(self):
        """
        Return the output field of this expression, or None if
        _resolve_output_field() didn't return an output type.
        """
        try:
            return self.output_field
        except FieldError:
            if not self._output_field_resolved_to_none:
                raise

    def _resolve_output_field(self):
        """
        Attempt to infer the output type of the expression.

        As a guess, if the output fields of all source fields match then simply
        infer the same type here.

        If a source's output field resolves to None, exclude it from this check.
        If all sources are None, then an error is raised higher up the stack in
        the output_field property.
        """
        # This guess is mostly a bad idea, but there is quite a lot of code
        # (especially 3rd party Func subclasses) that depend on it, we'd need a
        # deprecation path to fix it.
        sources_iter = (
            source for source in self.get_source_fields() if source is not None
        )
        for output_field in sources_iter:
            for source in sources_iter:
                if not isinstance(output_field, source.__class__):
                    raise FieldError(
                        "Expression contains mixed types: %s, %s. You must "
                        "set output_field."
                        % (
                            output_field.__class__.__name__,
                            source.__class__.__name__,
                        )
                    )
            return output_field

    @staticmethod
    def _convert_value_noop(value, expression, connection):
        return value

    @cached_property
    def convert_value(self):
        """
        Expressions provide their own converters because users have the option
        of manually specifying the output_field which may be a different type
        from the one the database returns.
        """
        field = self.output_field
        internal_type = field.get_internal_type()
        if internal_type == "FloatField":
            return (
                lambda value, expression, connection: None
                if value is None
                else float(value)
            )
        elif internal_type.endswith("IntegerField"):
            return (
                lambda value, expression, connection: None
                if value is None
                else int(value)
            )
        elif internal_type == "DecimalField":
            return (
                lambda value, expression, connection: None
                if value is None
                else Decimal(value)
            )
        return self._convert_value_noop

    def get_lookup(self, lookup):
        return self.output_field.get_lookup(lookup)

    def get_transform(self, name):
        return self.output_field.get_transform(name)

    def relabeled_clone(self, change_map):
        clone = self.copy()
        clone.set_source_expressions(
            [
                e.relabeled_clone(change_map) if e is not None else None
                for e in self.get_source_expressions()
            ]
        )
        return clone

    def replace_expressions(self, replacements):
        if replacement := replacements.get(self):
            return replacement
        clone = self.copy()
        source_expressions = clone.get_source_expressions()
        clone.set_source_expressions(
            [
                expr.replace_expressions(replacements) if expr else None
                for expr in source_expressions
            ]
        )
        return clone

    def get_refs(self):
        refs = set()
        for expr in self.get_source_expressions():
            refs |= expr.get_refs()
        return refs

    def copy(self):
        return copy.copy(self)

    def prefix_references(self, prefix):
        clone = self.copy()
        clone.set_source_expressions(
            [
                F(f"{prefix}{expr.name}")
                if isinstance(expr, F)
                else expr.prefix_references(prefix)
                for expr in self.get_source_expressions()
            ]
        )
        return clone

    def get_group_by_cols(self):
        if not self.contains_aggregate:
            return [self]
        cols = []
        for source in self.get_source_expressions():
            cols.extend(source.get_group_by_cols())
        return cols

    def get_source_fields(self):
        """Return the underlying field types used by this aggregate."""
        return [e._output_field_or_none for e in self.get_source_expressions()]

    def asc(self, **kwargs):
        return OrderBy(self, **kwargs)

    def desc(self, **kwargs):
        return OrderBy(self, descending=True, **kwargs)

    def reverse_ordering(self):
        return self

    def flatten(self):
        """
        Recursively yield this expression and all subexpressions, in
        depth-first order.
        """
        yield self
        for expr in self.get_source_expressions():
            if expr:
                if hasattr(expr, "flatten"):
                    yield from expr.flatten()
                else:
                    yield expr

    def select_format(self, compiler, sql, params):
        """
        Custom format for select clauses. For example, EXISTS expressions need
        to be wrapped in CASE WHEN on Oracle.
        """
        if hasattr(self.output_field, "select_format"):
            return self.output_field.select_format(compiler, sql, params)
        return sql, params


@deconstructible
class Expression(BaseExpression, Combinable):
    """An expression that can be combined with other expressions."""

    @cached_property
    def identity(self):
        constructor_signature = inspect.signature(self.__init__)
        args, kwargs = self._constructor_args
        signature = constructor_signature.bind_partial(*args, **kwargs)
        signature.apply_defaults()
        arguments = signature.arguments.items()
        identity = [self.__class__]
        for arg, value in arguments:
            if isinstance(value, fields.Field):
                if value.name and value.model:
                    value = (value.model._meta.label, value.name)
                else:
                    value = type(value)
            else:
                value = make_hashable(value)
            identity.append((arg, value))
        return tuple(identity)

    def __eq__(self, other):
        if not isinstance(other, Expression):
            return NotImplemented
        return other.identity == self.identity

    def __hash__(self):
        return hash(self.identity)


# Type inference for CombinedExpression.output_field.
# Missing items will result in FieldError, by design.
#
# The current approach for NULL is based on lowest common denominator behavior
# i.e. if one of the supported databases is raising an error (rather than
# return NULL) for `val <op> NULL`, then Django raises FieldError.
NoneType = type(None)

_connector_combinations = [
    # Numeric operations - operands of same type.
    {
        connector: [
            (fields.IntegerField, fields.IntegerField, fields.IntegerField),
            (fields.FloatField, fields.FloatField, fields.FloatField),
            (fields.DecimalField, fields.DecimalField, fields.DecimalField),
        ]
        for connector in (
            Combinable.ADD,
            Combinable.SUB,
            Combinable.MUL,
            # Behavior for DIV with integer arguments follows Postgres/SQLite,
            # not MySQL/Oracle.
            Combinable.DIV,
            Combinable.MOD,
            Combinable.POW,
        )
    },
    # Numeric operations - operands of different type.
    {
        connector: [
            (fields.IntegerField, fields.DecimalField, fields.DecimalField),
            (fields.DecimalField, fields.IntegerField, fields.DecimalField),
            (fields.IntegerField, fields.FloatField, fields.FloatField),
            (fields.FloatField, fields.IntegerField, fields.FloatField),
        ]
        for connector in (
            Combinable.ADD,
            Combinable.SUB,
            Combinable.MUL,
            Combinable.DIV,
            Combinable.MOD,
        )
    },
    # Bitwise operators.
    {
        connector: [
            (fields.IntegerField, fields.IntegerField, fields.IntegerField),
        ]
        for connector in (
            Combinable.BITAND,
            Combinable.BITOR,
            Combinable.BITLEFTSHIFT,
            Combinable.BITRIGHTSHIFT,
            Combinable.BITXOR,
        )
    },
    # Numeric with NULL.
    {
        connector: [
            (field_type, NoneType, field_type),
            (NoneType, field_type, field_type),
        ]
        for connector in (
            Combinable.ADD,
            Combinable.SUB,
            Combinable.MUL,
            Combinable.DIV,
            Combinable.MOD,
            Combinable.POW,
        )
        for field_type in (fields.IntegerField, fields.DecimalField, fields.FloatField)
    },
    # Date/DateTimeField/DurationField/TimeField.
    {
        Combinable.ADD: [
            # Date/DateTimeField.
            (fields.DateField, fields.DurationField, fields.DateTimeField),
            (fields.DateTimeField, fields.DurationField, fields.DateTimeField),
            (fields.DurationField, fields.DateField, fields.DateTimeField),
            (fields.DurationField, fields.DateTimeField, fields.DateTimeField),
            # DurationField.
            (fields.DurationField, fields.DurationField, fields.DurationField),
            # TimeField.
            (fields.TimeField, fields.DurationField, fields.TimeField),
            (fields.DurationField, fields.TimeField, fields.TimeField),
        ],
    },
    {
        Combinable.SUB: [
            # Date/DateTimeField.
            (fields.DateField, fields.DurationField, fields.DateTimeField),
            (fields.DateTimeField, fields.DurationField, fields.DateTimeField),
            (fields.DateField, fields.DateField, fields.DurationField),
            (fields.DateField, fields.DateTimeField, fields.DurationField),
            (fields.DateTimeField, fields.DateField, fields.DurationField),
            (fields.DateTimeField, fields.DateTimeField, fields.DurationField),
            # DurationField.
            (fields.DurationField, fields.DurationField, fields.DurationField),
            # TimeField.
            (fields.TimeField, fields.DurationField, fields.TimeField),
            (fields.TimeField, fields.TimeField, fields.DurationField),
        ],
    },
]

_connector_combinators = defaultdict(list)


def register_combinable_fields(lhs, connector, rhs, result):
    """
    Register combinable types:
        lhs <connector> rhs -> result
    e.g.
        register_combinable_fields(
            IntegerField, Combinable.ADD, FloatField, FloatField
        )
    """
    _connector_combinators[connector].append((lhs, rhs, result))


for d in _connector_combinations:
    for connector, field_types in d.items():
        for lhs, rhs, result in field_types:
            register_combinable_fields(lhs, connector, rhs, result)


@functools.lru_cache(maxsize=128)
def _resolve_combined_type(connector, lhs_type, rhs_type):
    combinators = _connector_combinators.get(connector, ())
    for combinator_lhs_type, combinator_rhs_type, combined_type in combinators:
        if issubclass(lhs_type, combinator_lhs_type) and issubclass(
            rhs_type, combinator_rhs_type
        ):
            return combined_type


class CombinedExpression(SQLiteNumericMixin, Expression):
    def __init__(self, lhs, connector, rhs, output_field=None):
        super().__init__(output_field=output_field)
        self.connector = connector
        self.lhs = lhs
        self.rhs = rhs

    def __repr__(self):
        return "<{}: {}>".format(self.__class__.__name__, self)

    def __str__(self):
        return "{} {} {}".format(self.lhs, self.connector, self.rhs)

    def get_source_expressions(self):
        return [self.lhs, self.rhs]

    def set_source_expressions(self, exprs):
        self.lhs, self.rhs = exprs

    def _resolve_output_field(self):
        # We avoid using super() here for reasons given in
        # Expression._resolve_output_field()
        combined_type = _resolve_combined_type(
            self.connector,
            type(self.lhs._output_field_or_none),
            type(self.rhs._output_field_or_none),
        )
        if combined_type is None:
            raise FieldError(
                f"Cannot infer type of {self.connector!r} expression involving these "
                f"types: {self.lhs.output_field.__class__.__name__}, "
                f"{self.rhs.output_field.__class__.__name__}. You must set "
                f"output_field."
            )
        return combined_type()

    def as_sql(self, compiler, connection):
        expressions = []
        expression_params = []
        sql, params = compiler.compile(self.lhs)
        expressions.append(sql)
        expression_params.extend(params)
        sql, params = compiler.compile(self.rhs)
        expressions.append(sql)
        expression_params.extend(params)
        # order of precedence
        expression_wrapper = "(%s)"
        sql = connection.ops.combine_expression(self.connector, expressions)
        return expression_wrapper % sql, expression_params

    def resolve_expression(
        self, query=None, allow_joins=True, reuse=None, summarize=False, for_save=False
    ):
        lhs = self.lhs.resolve_expression(
            query, allow_joins, reuse, summarize, for_save
        )
        rhs = self.rhs.resolve_expression(
            query, allow_joins, reuse, summarize, for_save
        )
        if not isinstance(self, (DurationExpression, TemporalSubtraction)):
            try:
                lhs_type = lhs.output_field.get_internal_type()
            except (AttributeError, FieldError):
                lhs_type = None
            try:
                rhs_type = rhs.output_field.get_internal_type()
            except (AttributeError, FieldError):
                rhs_type = None
            if "DurationField" in {lhs_type, rhs_type} and lhs_type != rhs_type:
                return DurationExpression(
                    self.lhs, self.connector, self.rhs
                ).resolve_expression(
                    query,
                    allow_joins,
                    reuse,
                    summarize,
                    for_save,
                )
            datetime_fields = {"DateField", "DateTimeField", "TimeField"}
            if (
                self.connector == self.SUB
                and lhs_type in datetime_fields
                and lhs_type == rhs_type
            ):
                return TemporalSubtraction(self.lhs, self.rhs).resolve_expression(
                    query,
                    allow_joins,
                    reuse,
                    summarize,
                    for_save,
                )
        c = self.copy()
        c.is_summary = summarize
        c.lhs = lhs
        c.rhs = rhs
        return c


class DurationExpression(CombinedExpression):
    def compile(self, side, compiler, connection):
        try:
            output = side.output_field
        except FieldError:
            pass
        else:
            if output.get_internal_type() == "DurationField":
                sql, params = compiler.compile(side)
                return connection.ops.format_for_duration_arithmetic(sql), params
        return compiler.compile(side)

    def as_sql(self, compiler, connection):
        if connection.features.has_native_duration_field:
            return super().as_sql(compiler, connection)
        connection.ops.check_expression_support(self)
        expressions = []
        expression_params = []
        sql, params = self.compile(self.lhs, compiler, connection)
        expressions.append(sql)
        expression_params.extend(params)
        sql, params = self.compile(self.rhs, compiler, connection)
        expressions.append(sql)
        expression_params.extend(params)
        # order of precedence
        expression_wrapper = "(%s)"
        sql = connection.ops.combine_duration_expression(self.connector, expressions)
        return expression_wrapper % sql, expression_params

    def as_sqlite(self, compiler, connection, **extra_context):
        sql, params = self.as_sql(compiler, connection, **extra_context)
        if self.connector in {Combinable.MUL, Combinable.DIV}:
            try:
                lhs_type = self.lhs.output_field.get_internal_type()
                rhs_type = self.rhs.output_field.get_internal_type()
            except (AttributeError, FieldError):
                pass
            else:
                allowed_fields = {
                    "DecimalField",
                    "DurationField",
                    "FloatField",
                    "IntegerField",
                }
                if lhs_type not in allowed_fields or rhs_type not in allowed_fields:
                    raise DatabaseError(
                        f"Invalid arguments for operator {self.connector}."
                    )
        return sql, params


class TemporalSubtraction(CombinedExpression):
    output_field = fields.DurationField()

    def __init__(self, lhs, rhs):
        super().__init__(lhs, self.SUB, rhs)

    def as_sql(self, compiler, connection):
        connection.ops.check_expression_support(self)
        lhs = compiler.compile(self.lhs)
        rhs = compiler.compile(self.rhs)
        return connection.ops.subtract_temporals(
            self.lhs.output_field.get_internal_type(), lhs, rhs
        )


@deconstructible(path="django.db.models.F")
class F(Combinable):
    """An object capable of resolving references to existing query objects."""

    def __init__(self, name):
        """
        Arguments:
         * name: the name of the field this expression references
        """
        self.name = name

    def __repr__(self):
        return "{}({})".format(self.__class__.__name__, self.name)

    def resolve_expression(
        self, query=None, allow_joins=True, reuse=None, summarize=False, for_save=False
    ):
        return query.resolve_ref(self.name, allow_joins, reuse, summarize)

    def replace_expressions(self, replacements):
        return replacements.get(self, self)

    def asc(self, **kwargs):
        return OrderBy(self, **kwargs)

    def desc(self, **kwargs):
        return OrderBy(self, descending=True, **kwargs)

    def __eq__(self, other):
        return self.__class__ == other.__class__ and self.name == other.name

    def __hash__(self):
        return hash(self.name)

    def copy(self):
        return copy.copy(self)


class ResolvedOuterRef(F):
    """
    An object that contains a reference to an outer query.

    In this case, the reference to the outer query has been resolved because
    the inner query has been used as a subquery.
    """

    contains_aggregate = False
    contains_over_clause = False

    def as_sql(self, *args, **kwargs):
        raise ValueError(
            "This queryset contains a reference to an outer query and may "
            "only be used in a subquery."
        )

    def resolve_expression(self, *args, **kwargs):
        col = super().resolve_expression(*args, **kwargs)
        if col.contains_over_clause:
            raise NotSupportedError(
                f"Referencing outer query window expression is not supported: "
                f"{self.name}."
            )
        # FIXME: Rename possibly_multivalued to multivalued and fix detection
        # for non-multivalued JOINs (e.g. foreign key fields). This should take
        # into account only many-to-many and one-to-many relationships.
        col.possibly_multivalued = LOOKUP_SEP in self.name
        return col

    def relabeled_clone(self, relabels):
        return self

    def get_group_by_cols(self):
        return []


class OuterRef(F):
    contains_aggregate = False
    contains_over_clause = False

    def resolve_expression(self, *args, **kwargs):
        if isinstance(self.name, self.__class__):
            return self.name
        return ResolvedOuterRef(self.name)

    def relabeled_clone(self, relabels):
        return self


@deconstructible(path="django.db.models.Func")
class Func(SQLiteNumericMixin, Expression):
    """An SQL function call."""

    function = None
    template = "%(function)s(%(expressions)s)"
    arg_joiner = ", "
    arity = None  # The number of arguments the function accepts.

    def __init__(self, *expressions, output_field=None, **extra):
        if self.arity is not None and len(expressions) != self.arity:
            raise TypeError(
                "'%s' takes exactly %s %s (%s given)"
                % (
                    self.__class__.__name__,
                    self.arity,
                    "argument" if self.arity == 1 else "arguments",
                    len(expressions),
                )
            )
        super().__init__(output_field=output_field)
        self.source_expressions = self._parse_expressions(*expressions)
        self.extra = extra

    def __repr__(self):
        args = self.arg_joiner.join(str(arg) for arg in self.source_expressions)
        extra = {**self.extra, **self._get_repr_options()}
        if extra:
            extra = ", ".join(
                str(key) + "=" + str(val) for key, val in sorted(extra.items())
            )
            return "{}({}, {})".format(self.__class__.__name__, args, extra)
        return "{}({})".format(self.__class__.__name__, args)

    def _get_repr_options(self):
        """Return a dict of extra __init__() options to include in the repr."""
        return {}

    def get_source_expressions(self):
        return self.source_expressions

    def set_source_expressions(self, exprs):
        self.source_expressions = exprs

    def resolve_expression(
        self, query=None, allow_joins=True, reuse=None, summarize=False, for_save=False
    ):
        c = self.copy()
        c.is_summary = summarize
        for pos, arg in enumerate(c.source_expressions):
            c.source_expressions[pos] = arg.resolve_expression(
                query, allow_joins, reuse, summarize, for_save
            )
        return c

    def as_sql(
        self,
        compiler,
        connection,
        function=None,
        template=None,
        arg_joiner=None,
        **extra_context,
    ):
        connection.ops.check_expression_support(self)
        sql_parts = []
        params = []
        for arg in self.source_expressions:
            try:
                arg_sql, arg_params = compiler.compile(arg)
            except EmptyResultSet:
                empty_result_set_value = getattr(
                    arg, "empty_result_set_value", NotImplemented
                )
                if empty_result_set_value is NotImplemented:
                    raise
                arg_sql, arg_params = compiler.compile(Value(empty_result_set_value))
            except FullResultSet:
                arg_sql, arg_params = compiler.compile(Value(True))
            sql_parts.append(arg_sql)
            params.extend(arg_params)
        data = {**self.extra, **extra_context}
        # Use the first supplied value in this order: the parameter to this
        # method, a value supplied in __init__()'s **extra (the value in
        # `data`), or the value defined on the class.
        if function is not None:
            data["function"] = function
        else:
            data.setdefault("function", self.function)
        template = template or data.get("template", self.template)
        arg_joiner = arg_joiner or data.get("arg_joiner", self.arg_joiner)
        data["expressions"] = data["field"] = arg_joiner.join(sql_parts)
        return template % data, params

    def copy(self):
        copy = super().copy()
        copy.source_expressions = self.source_expressions[:]
        copy.extra = self.extra.copy()
        return copy


@deconstructible(path="django.db.models.Value")
class Value(SQLiteNumericMixin, Expression):
    """Represent a wrapped value as a node within an expression."""

    # Provide a default value for `for_save` in order to allow unresolved
    # instances to be compiled until a decision is taken in #25425.
    for_save = False

    def __init__(self, value, output_field=None):
        """
        Arguments:
         * value: the value this expression represents. The value will be
           added into the sql parameter list and properly quoted.

         * output_field: an instance of the model field type that this
           expression will return, such as IntegerField() or CharField().
        """
        super().__init__(output_field=output_field)
        self.value = value

    def __repr__(self):
        return f"{self.__class__.__name__}({self.value!r})"

    def as_sql(self, compiler, connection):
        connection.ops.check_expression_support(self)
        val = self.value
        output_field = self._output_field_or_none
        if output_field is not None:
            if self.for_save:
                val = output_field.get_db_prep_save(val, connection=connection)
            else:
                val = output_field.get_db_prep_value(val, connection=connection)
            if hasattr(output_field, "get_placeholder"):
                return output_field.get_placeholder(val, compiler, connection), [val]
        if val is None:
            # cx_Oracle does not always convert None to the appropriate
            # NULL type (like in case expressions using numbers), so we
            # use a literal SQL NULL
            return "NULL", []
        return "%s", [val]

    def resolve_expression(
        self, query=None, allow_joins=True, reuse=None, summarize=False, for_save=False
    ):
        c = super().resolve_expression(query, allow_joins, reuse, summarize, for_save)
        c.for_save = for_save
        return c

    def get_group_by_cols(self):
        return []

    def _resolve_output_field(self):
        if isinstance(self.value, str):
            return fields.CharField()
        if isinstance(self.value, bool):
            return fields.BooleanField()
        if isinstance(self.value, int):
            return fields.IntegerField()
        if isinstance(self.value, float):
            return fields.FloatField()
        if isinstance(self.value, datetime.datetime):
            return fields.DateTimeField()
        if isinstance(self.value, datetime.date):
            return fields.DateField()
        if isinstance(self.value, datetime.time):
            return fields.TimeField()
        if isinstance(self.value, datetime.timedelta):
            return fields.DurationField()
        if isinstance(self.value, Decimal):
            return fields.DecimalField()
        if isinstance(self.value, bytes):
            return fields.BinaryField()
        if isinstance(self.value, UUID):
            return fields.UUIDField()

    @property
    def empty_result_set_value(self):
        return self.value


class RawSQL(Expression):
    def __init__(self, sql, params, output_field=None):
        if output_field is None:
            output_field = fields.Field()
        self.sql, self.params = sql, params
        super().__init__(output_field=output_field)

    def __repr__(self):
        return "{}({}, {})".format(self.__class__.__name__, self.sql, self.params)

    def as_sql(self, compiler, connection):
        return "(%s)" % self.sql, self.params

    def get_group_by_cols(self):
        return [self]

    def resolve_expression(
        self, query=None, allow_joins=True, reuse=None, summarize=False, for_save=False
    ):
        # Resolve parents fields used in raw SQL.
        if query.model:
            for parent in query.model._meta.get_parent_list():
                for parent_field in parent._meta.local_fields:
                    _, column_name = parent_field.get_attname_column()
                    if column_name.lower() in self.sql.lower():
                        query.resolve_ref(
                            parent_field.name, allow_joins, reuse, summarize
                        )
                        break
        return super().resolve_expression(
            query, allow_joins, reuse, summarize, for_save
        )


class Star(Expression):
    def __repr__(self):
        return "'*'"

    def as_sql(self, compiler, connection):
        return "*", []


class Col(Expression):
    contains_column_references = True
    possibly_multivalued = False

    def __init__(self, alias, target, output_field=None):
        if output_field is None:
            output_field = target
        super().__init__(output_field=output_field)
        self.alias, self.target = alias, target

    def __repr__(self):
        alias, target = self.alias, self.target
        identifiers = (alias, str(target)) if alias else (str(target),)
        return "{}({})".format(self.__class__.__name__, ", ".join(identifiers))

    def as_sql(self, compiler, connection):
        alias, column = self.alias, self.target.column
        identifiers = (alias, column) if alias else (column,)
        sql = ".".join(map(compiler.quote_name_unless_alias, identifiers))
        return sql, []

    def relabeled_clone(self, relabels):
        if self.alias is None:
            return self
        return self.__class__(
            relabels.get(self.alias, self.alias), self.target, self.output_field
        )

    def get_group_by_cols(self):
        return [self]

    def get_db_converters(self, connection):
        if self.target == self.output_field:
            return self.output_field.get_db_converters(connection)
        return self.output_field.get_db_converters(
            connection
        ) + self.target.get_db_converters(connection)


class Ref(Expression):
    """
    Reference to column alias of the query. For example, Ref('sum_cost') in
    qs.annotate(sum_cost=Sum('cost')) query.
    """

    def __init__(self, refs, source):
        super().__init__()
        self.refs, self.source = refs, source

    def __repr__(self):
        return "{}({}, {})".format(self.__class__.__name__, self.refs, self.source)

    def get_source_expressions(self):
        return [self.source]

    def set_source_expressions(self, exprs):
        (self.source,) = exprs

    def resolve_expression(
        self, query=None, allow_joins=True, reuse=None, summarize=False, for_save=False
    ):
        # The sub-expression `source` has already been resolved, as this is
        # just a reference to the name of `source`.
        return self

    def get_refs(self):
        return {self.refs}

    def relabeled_clone(self, relabels):
        clone = self.copy()
        clone.source = self.source.relabeled_clone(relabels)
        return clone

    def as_sql(self, compiler, connection):
        return connection.ops.quote_name(self.refs), []

    def get_group_by_cols(self):
        return [self]


class ExpressionList(Func):
    """
    An expression containing multiple expressions. Can be used to provide a
    list of expressions as an argument to another expression, like a partition
    clause.
    """

    template = "%(expressions)s"

    def __init__(self, *expressions, **extra):
        if not expressions:
            raise ValueError(
                "%s requires at least one expression." % self.__class__.__name__
            )
        super().__init__(*expressions, **extra)

    def __str__(self):
        return self.arg_joiner.join(str(arg) for arg in self.source_expressions)

    def as_sqlite(self, compiler, connection, **extra_context):
        # Casting to numeric is unnecessary.
        return self.as_sql(compiler, connection, **extra_context)


class OrderByList(Func):
    template = "ORDER BY %(expressions)s"

    def __init__(self, *expressions, **extra):
        expressions = (
            (
                OrderBy(F(expr[1:]), descending=True)
                if isinstance(expr, str) and expr[0] == "-"
                else expr
            )
            for expr in expressions
        )
        super().__init__(*expressions, **extra)

    def as_sql(self, *args, **kwargs):
        if not self.source_expressions:
            return "", ()
        return super().as_sql(*args, **kwargs)

    def get_group_by_cols(self):
        group_by_cols = []
        for order_by in self.get_source_expressions():
            group_by_cols.extend(order_by.get_group_by_cols())
        return group_by_cols


@deconstructible(path="django.db.models.ExpressionWrapper")
class ExpressionWrapper(SQLiteNumericMixin, Expression):
    """
    An expression that can wrap another expression so that it can provide
    extra context to the inner expression, such as the output_field.
    """

    def __init__(self, expression, output_field):
        super().__init__(output_field=output_field)
        self.expression = expression

    def set_source_expressions(self, exprs):
        self.expression = exprs[0]

    def get_source_expressions(self):
        return [self.expression]

    def get_group_by_cols(self):
        if isinstance(self.expression, Expression):
            expression = self.expression.copy()
            expression.output_field = self.output_field
            return expression.get_group_by_cols()
        # For non-expressions e.g. an SQL WHERE clause, the entire
        # `expression` must be included in the GROUP BY clause.
        return super().get_group_by_cols()

    def as_sql(self, compiler, connection):
        return compiler.compile(self.expression)

    def __repr__(self):
        return "{}({})".format(self.__class__.__name__, self.expression)


class NegatedExpression(ExpressionWrapper):
    """The logical negation of a conditional expression."""

    def __init__(self, expression):
        super().__init__(expression, output_field=fields.BooleanField())

    def __invert__(self):
        return self.expression.copy()

    def as_sql(self, compiler, connection):
        try:
            sql, params = super().as_sql(compiler, connection)
        except EmptyResultSet:
            features = compiler.connection.features
            if not features.supports_boolean_expr_in_select_clause:
                return "1=1", ()
            return compiler.compile(Value(True))
        ops = compiler.connection.ops
        # Some database backends (e.g. Oracle) don't allow EXISTS() and filters
        # to be compared to another expression unless they're wrapped in a CASE
        # WHEN.
        if not ops.conditional_expression_supported_in_where_clause(self.expression):
            return f"CASE WHEN {sql} = 0 THEN 1 ELSE 0 END", params
        return f"NOT {sql}", params

    def resolve_expression(
        self, query=None, allow_joins=True, reuse=None, summarize=False, for_save=False
    ):
        resolved = super().resolve_expression(
            query, allow_joins, reuse, summarize, for_save
        )
        if not getattr(resolved.expression, "conditional", False):
            raise TypeError("Cannot negate non-conditional expressions.")
        return resolved

    def select_format(self, compiler, sql, params):
        # Wrap boolean expressions with a CASE WHEN expression if a database
        # backend (e.g. Oracle) doesn't support boolean expression in SELECT or
        # GROUP BY list.
        expression_supported_in_where_clause = (
            compiler.connection.ops.conditional_expression_supported_in_where_clause
        )
        if (
            not compiler.connection.features.supports_boolean_expr_in_select_clause
            # Avoid double wrapping.
            and expression_supported_in_where_clause(self.expression)
        ):
            sql = "CASE WHEN {} THEN 1 ELSE 0 END".format(sql)
        return sql, params


@deconstructible(path="django.db.models.When")
class When(Expression):
    template = "WHEN %(condition)s THEN %(result)s"
    # This isn't a complete conditional expression, must be used in Case().
    conditional = False

    def __init__(self, condition=None, then=None, **lookups):
        if lookups:
            if condition is None:
                condition, lookups = Q(**lookups), None
            elif getattr(condition, "conditional", False):
                condition, lookups = Q(condition, **lookups), None
        if condition is None or not getattr(condition, "conditional", False) or lookups:
            raise TypeError(
                "When() supports a Q object, a boolean expression, or lookups "
                "as a condition."
            )
        if isinstance(condition, Q) and not condition:
            raise ValueError("An empty Q() can't be used as a When() condition.")
        super().__init__(output_field=None)
        self.condition = condition
        self.result = self._parse_expressions(then)[0]

    def __str__(self):
        return "WHEN %r THEN %r" % (self.condition, self.result)

    def __repr__(self):
        return "<%s: %s>" % (self.__class__.__name__, self)

    def get_source_expressions(self):
        return [self.condition, self.result]

    def set_source_expressions(self, exprs):
        self.condition, self.result = exprs

    def get_source_fields(self):
        # We're only interested in the fields of the result expressions.
        return [self.result._output_field_or_none]

    def resolve_expression(
        self, query=None, allow_joins=True, reuse=None, summarize=False, for_save=False
    ):
        c = self.copy()
        c.is_summary = summarize
        if hasattr(c.condition, "resolve_expression"):
            c.condition = c.condition.resolve_expression(
                query, allow_joins, reuse, summarize, False
            )
        c.result = c.result.resolve_expression(
            query, allow_joins, reuse, summarize, for_save
        )
        return c

    def as_sql(self, compiler, connection, template=None, **extra_context):
        connection.ops.check_expression_support(self)
        template_params = extra_context
        sql_params = []
        condition_sql, condition_params = compiler.compile(self.condition)
        template_params["condition"] = condition_sql
        result_sql, result_params = compiler.compile(self.result)
        template_params["result"] = result_sql
        template = template or self.template
        return template % template_params, (
            *sql_params,
            *condition_params,
            *result_params,
        )

    def get_group_by_cols(self):
        # This is not a complete expression and cannot be used in GROUP BY.
        cols = []
        for source in self.get_source_expressions():
            cols.extend(source.get_group_by_cols())
        return cols


@deconstructible(path="django.db.models.Case")
class Case(SQLiteNumericMixin, Expression):
    """
    An SQL searched CASE expression:

        CASE
            WHEN n > 0
                THEN 'positive'
            WHEN n < 0
                THEN 'negative'
            ELSE 'zero'
        END
    """

    template = "CASE %(cases)s ELSE %(default)s END"
    case_joiner = " "

    def __init__(self, *cases, default=None, output_field=None, **extra):
        if not all(isinstance(case, When) for case in cases):
            raise TypeError("Positional arguments must all be When objects.")
        super().__init__(output_field)
        self.cases = list(cases)
        self.default = self._parse_expressions(default)[0]
        self.extra = extra

    def __str__(self):
        return "CASE %s, ELSE %r" % (
            ", ".join(str(c) for c in self.cases),
            self.default,
        )

    def __repr__(self):
        return "<%s: %s>" % (self.__class__.__name__, self)

    def get_source_expressions(self):
        return self.cases + [self.default]

    def set_source_expressions(self, exprs):
        *self.cases, self.default = exprs

    def resolve_expression(
        self, query=None, allow_joins=True, reuse=None, summarize=False, for_save=False
    ):
        c = self.copy()
        c.is_summary = summarize
        for pos, case in enumerate(c.cases):
            c.cases[pos] = case.resolve_expression(
                query, allow_joins, reuse, summarize, for_save
            )
        c.default = c.default.resolve_expression(
            query, allow_joins, reuse, summarize, for_save
        )
        return c

    def copy(self):
        c = super().copy()
        c.cases = c.cases[:]
        return c

    def as_sql(
        self, compiler, connection, template=None, case_joiner=None, **extra_context
    ):
        connection.ops.check_expression_support(self)
        if not self.cases:
            return compiler.compile(self.default)
        template_params = {**self.extra, **extra_context}
        case_parts = []
        sql_params = []
        default_sql, default_params = compiler.compile(self.default)
        for case in self.cases:
            try:
                case_sql, case_params = compiler.compile(case)
            except EmptyResultSet:
                continue
            except FullResultSet:
                default_sql, default_params = compiler.compile(case.result)
                break
            case_parts.append(case_sql)
            sql_params.extend(case_params)
        if not case_parts:
            return default_sql, default_params
        case_joiner = case_joiner or self.case_joiner
        template_params["cases"] = case_joiner.join(case_parts)
        template_params["default"] = default_sql
        sql_params.extend(default_params)
        template = template or template_params.get("template", self.template)
        sql = template % template_params
        if self._output_field_or_none is not None:
            sql = connection.ops.unification_cast_sql(self.output_field) % sql
        return sql, sql_params

    def get_group_by_cols(self):
        if not self.cases:
            return self.default.get_group_by_cols()
        return super().get_group_by_cols()


class Subquery(BaseExpression, Combinable):
    """
    An explicit subquery. It may contain OuterRef() references to the outer
    query which will be resolved when it is applied to that query.
    """

    template = "(%(subquery)s)"
    contains_aggregate = False
    empty_result_set_value = None
    subquery = True

    def __init__(self, queryset, output_field=None, **extra):
        # Allow the usage of both QuerySet and sql.Query objects.
        self.query = getattr(queryset, "query", queryset).clone()
        self.query.subquery = True
        self.extra = extra
        super().__init__(output_field)

    def get_source_expressions(self):
        return [self.query]

    def set_source_expressions(self, exprs):
        self.query = exprs[0]

    def _resolve_output_field(self):
        return self.query.output_field

    def copy(self):
        clone = super().copy()
        clone.query = clone.query.clone()
        return clone

    @property
    def external_aliases(self):
        return self.query.external_aliases

    def get_external_cols(self):
        return self.query.get_external_cols()

    def as_sql(self, compiler, connection, template=None, **extra_context):
        connection.ops.check_expression_support(self)
        template_params = {**self.extra, **extra_context}
        subquery_sql, sql_params = self.query.as_sql(compiler, connection)
        template_params["subquery"] = subquery_sql[1:-1]

        template = template or template_params.get("template", self.template)
        sql = template % template_params
        return sql, sql_params

    def get_group_by_cols(self):
        return self.query.get_group_by_cols(wrapper=self)


class Exists(Subquery):
    template = "EXISTS(%(subquery)s)"
    output_field = fields.BooleanField()
    empty_result_set_value = False

    def __init__(self, queryset, **kwargs):
        super().__init__(queryset, **kwargs)
        self.query = self.query.exists()

    def select_format(self, compiler, sql, params):
        # Wrap EXISTS() with a CASE WHEN expression if a database backend
        # (e.g. Oracle) doesn't support boolean expression in SELECT or GROUP
        # BY list.
        if not compiler.connection.features.supports_boolean_expr_in_select_clause:
            sql = "CASE WHEN {} THEN 1 ELSE 0 END".format(sql)
        return sql, params


@deconstructible(path="django.db.models.OrderBy")
class OrderBy(Expression):
    template = "%(expression)s %(ordering)s"
    conditional = False

    def __init__(self, expression, descending=False, nulls_first=None, nulls_last=None):
        if nulls_first and nulls_last:
            raise ValueError("nulls_first and nulls_last are mutually exclusive")
        if nulls_first is False or nulls_last is False:
            # When the deprecation ends, replace with:
            # raise ValueError(
            #     "nulls_first and nulls_last values must be True or None."
            # )
            warnings.warn(
                "Passing nulls_first=False or nulls_last=False is deprecated, use None "
                "instead.",
                RemovedInDjango50Warning,
                stacklevel=2,
            )
        self.nulls_first = nulls_first
        self.nulls_last = nulls_last
        self.descending = descending
        if not hasattr(expression, "resolve_expression"):
            raise ValueError("expression must be an expression type")
        self.expression = expression

    def __repr__(self):
        return "{}({}, descending={})".format(
            self.__class__.__name__, self.expression, self.descending
        )

    def set_source_expressions(self, exprs):
        self.expression = exprs[0]

    def get_source_expressions(self):
        return [self.expression]

    def as_sql(self, compiler, connection, template=None, **extra_context):
        template = template or self.template
        if connection.features.supports_order_by_nulls_modifier:
            if self.nulls_last:
                template = "%s NULLS LAST" % template
            elif self.nulls_first:
                template = "%s NULLS FIRST" % template
        else:
            if self.nulls_last and not (
                self.descending and connection.features.order_by_nulls_first
            ):
                template = "%%(expression)s IS NULL, %s" % template
            elif self.nulls_first and not (
                not self.descending and connection.features.order_by_nulls_first
            ):
                template = "%%(expression)s IS NOT NULL, %s" % template
        connection.ops.check_expression_support(self)
        expression_sql, params = compiler.compile(self.expression)
        placeholders = {
            "expression": expression_sql,
            "ordering": "DESC" if self.descending else "ASC",
            **extra_context,
        }
        params *= template.count("%(expression)s")
        return (template % placeholders).rstrip(), params

    def as_oracle(self, compiler, connection):
        # Oracle doesn't allow ORDER BY EXISTS() or filters unless it's wrapped
        # in a CASE WHEN.
        if connection.ops.conditional_expression_supported_in_where_clause(
            self.expression
        ):
            copy = self.copy()
            copy.expression = Case(
                When(self.expression, then=True),
                default=False,
            )
            return copy.as_sql(compiler, connection)
        return self.as_sql(compiler, connection)

    def get_group_by_cols(self):
        cols = []
        for source in self.get_source_expressions():
            cols.extend(source.get_group_by_cols())
        return cols

    def reverse_ordering(self):
        self.descending = not self.descending
        if self.nulls_first:
            self.nulls_last = True
            self.nulls_first = None
        elif self.nulls_last:
            self.nulls_first = True
            self.nulls_last = None
        return self

    def asc(self):
        self.descending = False

    def desc(self):
        self.descending = True


class Window(SQLiteNumericMixin, Expression):
    template = "%(expression)s OVER (%(window)s)"
    # Although the main expression may either be an aggregate or an
    # expression with an aggregate function, the GROUP BY that will
    # be introduced in the query as a result is not desired.
    contains_aggregate = False
    contains_over_clause = True

    def __init__(
        self,
        expression,
        partition_by=None,
        order_by=None,
        frame=None,
        output_field=None,
    ):
        self.partition_by = partition_by
        self.order_by = order_by
        self.frame = frame

        if not getattr(expression, "window_compatible", False):
            raise ValueError(
                "Expression '%s' isn't compatible with OVER clauses."
                % expression.__class__.__name__
            )

        if self.partition_by is not None:
            if not isinstance(self.partition_by, (tuple, list)):
                self.partition_by = (self.partition_by,)
            self.partition_by = ExpressionList(*self.partition_by)

        if self.order_by is not None:
            if isinstance(self.order_by, (list, tuple)):
                self.order_by = OrderByList(*self.order_by)
            elif isinstance(self.order_by, (BaseExpression, str)):
                self.order_by = OrderByList(self.order_by)
            else:
                raise ValueError(
                    "Window.order_by must be either a string reference to a "
                    "field, an expression, or a list or tuple of them."
                )
        super().__init__(output_field=output_field)
        self.source_expression = self._parse_expressions(expression)[0]

    def _resolve_output_field(self):
        return self.source_expression.output_field

    def get_source_expressions(self):
        return [self.source_expression, self.partition_by, self.order_by, self.frame]

    def set_source_expressions(self, exprs):
        self.source_expression, self.partition_by, self.order_by, self.frame = exprs

    def as_sql(self, compiler, connection, template=None):
        connection.ops.check_expression_support(self)
        if not connection.features.supports_over_clause:
            raise NotSupportedError("This backend does not support window expressions.")
        expr_sql, params = compiler.compile(self.source_expression)
        window_sql, window_params = [], ()

        if self.partition_by is not None:
            sql_expr, sql_params = self.partition_by.as_sql(
                compiler=compiler,
                connection=connection,
                template="PARTITION BY %(expressions)s",
            )
            window_sql.append(sql_expr)
            window_params += tuple(sql_params)

        if self.order_by is not None:
            order_sql, order_params = compiler.compile(self.order_by)
            window_sql.append(order_sql)
            window_params += tuple(order_params)

        if self.frame:
            frame_sql, frame_params = compiler.compile(self.frame)
            window_sql.append(frame_sql)
            window_params += tuple(frame_params)

        template = template or self.template

        return (
            template % {"expression": expr_sql, "window": " ".join(window_sql).strip()},
            (*params, *window_params),
        )

    def as_sqlite(self, compiler, connection):
        if isinstance(self.output_field, fields.DecimalField):
            # Casting to numeric must be outside of the window expression.
            copy = self.copy()
            source_expressions = copy.get_source_expressions()
            source_expressions[0].output_field = fields.FloatField()
            copy.set_source_expressions(source_expressions)
            return super(Window, copy).as_sqlite(compiler, connection)
        return self.as_sql(compiler, connection)

    def __str__(self):
        return "{} OVER ({}{}{})".format(
            str(self.source_expression),
            "PARTITION BY " + str(self.partition_by) if self.partition_by else "",
            str(self.order_by or ""),
            str(self.frame or ""),
        )

    def __repr__(self):
        return "<%s: %s>" % (self.__class__.__name__, self)

    def get_group_by_cols(self):
        group_by_cols = []
        if self.partition_by:
            group_by_cols.extend(self.partition_by.get_group_by_cols())
        if self.order_by is not None:
            group_by_cols.extend(self.order_by.get_group_by_cols())
        return group_by_cols


class WindowFrame(Expression):
    """
    Model the frame clause in window expressions. There are two types of frame
    clauses which are subclasses, however, all processing and validation (by no
    means intended to be complete) is done here. Thus, providing an end for a
    frame is optional (the default is UNBOUNDED FOLLOWING, which is the last
    row in the frame).
    """

    template = "%(frame_type)s BETWEEN %(start)s AND %(end)s"

    def __init__(self, start=None, end=None):
        self.start = Value(start)
        self.end = Value(end)

    def set_source_expressions(self, exprs):
        self.start, self.end = exprs

    def get_source_expressions(self):
        return [self.start, self.end]

    def as_sql(self, compiler, connection):
        connection.ops.check_expression_support(self)
        start, end = self.window_frame_start_end(
            connection, self.start.value, self.end.value
        )
        return (
            self.template
            % {
                "frame_type": self.frame_type,
                "start": start,
                "end": end,
            },
            [],
        )

    def __repr__(self):
        return "<%s: %s>" % (self.__class__.__name__, self)

    def get_group_by_cols(self):
        return []

    def __str__(self):
        if self.start.value is not None and self.start.value < 0:
            start = "%d %s" % (abs(self.start.value), connection.ops.PRECEDING)
        elif self.start.value is not None and self.start.value == 0:
            start = connection.ops.CURRENT_ROW
        else:
            start = connection.ops.UNBOUNDED_PRECEDING

        if self.end.value is not None and self.end.value > 0:
            end = "%d %s" % (self.end.value, connection.ops.FOLLOWING)
        elif self.end.value is not None and self.end.value == 0:
            end = connection.ops.CURRENT_ROW
        else:
            end = connection.ops.UNBOUNDED_FOLLOWING
        return self.template % {
            "frame_type": self.frame_type,
            "start": start,
            "end": end,
        }

    def window_frame_start_end(self, connection, start, end):
        raise NotImplementedError("Subclasses must implement window_frame_start_end().")


class RowRange(WindowFrame):
    frame_type = "ROWS"

    def window_frame_start_end(self, connection, start, end):
        return connection.ops.window_frame_rows_start_end(start, end)


class ValueRange(WindowFrame):
    frame_type = "RANGE"

    def window_frame_start_end(self, connection, start, end):
        return connection.ops.window_frame_range_start_end(start, end)

virt/lib/python3.9/site-packages/django/db/models/expressions 3.py
PrinceDisant/LostInNL
import copy
import datetime
import functools
import inspect
import warnings
from collections import defaultdict
from decimal import Decimal
from uuid import UUID

from django.core.exceptions import EmptyResultSet, FieldError, FullResultSet
from django.db import DatabaseError, NotSupportedError, connection
from django.db.models import fields
from django.db.models.constants import LOOKUP_SEP
from django.db.models.query_utils import Q
from django.utils.deconstruct import deconstructible
from django.utils.deprecation import RemovedInDjango50Warning
from django.utils.functional import cached_property
from django.utils.hashable import make_hashable


class SQLiteNumericMixin:
    """
    Some expressions with output_field=DecimalField() must be cast to
    numeric to be properly filtered.
    """

    def as_sqlite(self, compiler, connection, **extra_context):
        sql, params = self.as_sql(compiler, connection, **extra_context)
        try:
            if self.output_field.get_internal_type() == "DecimalField":
                sql = "CAST(%s AS NUMERIC)" % sql
        except FieldError:
            pass
        return sql, params


class Combinable:
    """
    Provide the ability to combine one or two objects with
    some connector. For example F('foo') + F('bar').
    """

    # Arithmetic connectors
    ADD = "+"
    SUB = "-"
    MUL = "*"
    DIV = "/"
    POW = "^"
    # The following is a quoted % operator - it is quoted because it can be
    # used in strings that also have parameter substitution.
    MOD = "%%"

    # Bitwise operators - note that these are generated by .bitand()
    # and .bitor(), the '&' and '|' are reserved for boolean operator
    # usage.
    BITAND = "&"
    BITOR = "|"
    BITLEFTSHIFT = "<<"
    BITRIGHTSHIFT = ">>"
    BITXOR = "#"

    def _combine(self, other, connector, reversed):
        if not hasattr(other, "resolve_expression"):
            # everything must be resolvable to an expression
            other = Value(other)

        if reversed:
            return CombinedExpression(other, connector, self)
        return CombinedExpression(self, connector, other)

    #############
    # OPERATORS #
    #############

    def __neg__(self):
        return self._combine(-1, self.MUL, False)

    def __add__(self, other):
        return self._combine(other, self.ADD, False)

    def __sub__(self, other):
        return self._combine(other, self.SUB, False)

    def __mul__(self, other):
        return self._combine(other, self.MUL, False)

    def __truediv__(self, other):
        return self._combine(other, self.DIV, False)

    def __mod__(self, other):
        return self._combine(other, self.MOD, False)

    def __pow__(self, other):
        return self._combine(other, self.POW, False)

    def __and__(self, other):
        if getattr(self, "conditional", False) and getattr(other, "conditional", False):
            return Q(self) & Q(other)
        raise NotImplementedError(
            "Use .bitand(), .bitor(), and .bitxor() for bitwise logical operations."
        )

    def bitand(self, other):
        return self._combine(other, self.BITAND, False)

    def bitleftshift(self, other):
        return self._combine(other, self.BITLEFTSHIFT, False)

    def bitrightshift(self, other):
        return self._combine(other, self.BITRIGHTSHIFT, False)

    def __xor__(self, other):
        if getattr(self, "conditional", False) and getattr(other, "conditional", False):
            return Q(self) ^ Q(other)
        raise NotImplementedError(
            "Use .bitand(), .bitor(), and .bitxor() for bitwise logical operations."
        )

    def bitxor(self, other):
        return self._combine(other, self.BITXOR, False)

    def __or__(self, other):
        if getattr(self, "conditional", False) and getattr(other, "conditional", False):
            return Q(self) | Q(other)
        raise NotImplementedError(
            "Use .bitand(), .bitor(), and .bitxor() for bitwise logical operations."
        )

    def bitor(self, other):
        return self._combine(other, self.BITOR, False)

    def __radd__(self, other):
        return self._combine(other, self.ADD, True)

    def __rsub__(self, other):
        return self._combine(other, self.SUB, True)

    def __rmul__(self, other):
        return self._combine(other, self.MUL, True)

    def __rtruediv__(self, other):
        return self._combine(other, self.DIV, True)

    def __rmod__(self, other):
        return self._combine(other, self.MOD, True)

    def __rpow__(self, other):
        return self._combine(other, self.POW, True)

    def __rand__(self, other):
        raise NotImplementedError(
            "Use .bitand(), .bitor(), and .bitxor() for bitwise logical operations."
        )

    def __ror__(self, other):
        raise NotImplementedError(
            "Use .bitand(), .bitor(), and .bitxor() for bitwise logical operations."
        )

    def __rxor__(self, other):
        raise NotImplementedError(
            "Use .bitand(), .bitor(), and .bitxor() for bitwise logical operations."
        )

    def __invert__(self):
        return NegatedExpression(self)


class BaseExpression:
    """Base class for all query expressions."""

    empty_result_set_value = NotImplemented
    # aggregate specific fields
    is_summary = False
    _output_field_resolved_to_none = False
    # Can the expression be used in a WHERE clause?
    filterable = True
    # Can the expression can be used as a source expression in Window?
    window_compatible = False

    def __init__(self, output_field=None):
        if output_field is not None:
            self.output_field = output_field

    def __getstate__(self):
        state = self.__dict__.copy()
        state.pop("convert_value", None)
        return state

    def get_db_converters(self, connection):
        return (
            []
            if self.convert_value is self._convert_value_noop
            else [self.convert_value]
        ) + self.output_field.get_db_converters(connection)

    def get_source_expressions(self):
        return []

    def set_source_expressions(self, exprs):
        assert not exprs

    def _parse_expressions(self, *expressions):
        return [
            arg
            if hasattr(arg, "resolve_expression")
            else (F(arg) if isinstance(arg, str) else Value(arg))
            for arg in expressions
        ]

    def as_sql(self, compiler, connection):
        """
        Responsible for returning a (sql, [params]) tuple to be included
        in the current query.

        Different backends can provide their own implementation, by
        providing an `as_{vendor}` method and patching the Expression:

        ```
        def override_as_sql(self, compiler, connection):
            # custom logic
            return super().as_sql(compiler, connection)
        setattr(Expression, 'as_' + connection.vendor, override_as_sql)
        ```

        Arguments:
         * compiler: the query compiler responsible for generating the query.
           Must have a compile method, returning a (sql, [params]) tuple.
           Calling compiler(value) will return a quoted `value`.

         * connection: the database connection used for the current query.

        Return: (sql, params)
          Where `sql` is a string containing ordered sql parameters to be
          replaced with the elements of the list `params`.
        """
        raise NotImplementedError("Subclasses must implement as_sql()")

    @cached_property
    def contains_aggregate(self):
        return any(
            expr and expr.contains_aggregate for expr in self.get_source_expressions()
        )

    @cached_property
    def contains_over_clause(self):
        return any(
            expr and expr.contains_over_clause for expr in self.get_source_expressions()
        )

    @cached_property
    def contains_column_references(self):
        return any(
            expr and expr.contains_column_references
            for expr in self.get_source_expressions()
        )

    def resolve_expression(
        self, query=None, allow_joins=True, reuse=None, summarize=False, for_save=False
    ):
        """
        Provide the chance to do any preprocessing or validation before being
        added to the query.

        Arguments:
         * query: the backend query implementation
         * allow_joins: boolean allowing or denying use of joins
           in this query
         * reuse: a set of reusable joins for multijoins
         * summarize: a terminal aggregate clause
         * for_save: whether this expression about to be used in a save or update

        Return: an Expression to be added to the query.
        """
        c = self.copy()
        c.is_summary = summarize
        c.set_source_expressions(
            [
                expr.resolve_expression(query, allow_joins, reuse, summarize)
                if expr
                else None
                for expr in c.get_source_expressions()
            ]
        )
        return c

    @property
    def conditional(self):
        return isinstance(self.output_field, fields.BooleanField)

    @property
    def field(self):
        return self.output_field

    @cached_property
    def output_field(self):
        """Return the output type of this expressions."""
        output_field = self._resolve_output_field()
        if output_field is None:
            self._output_field_resolved_to_none = True
            raise FieldError("Cannot resolve expression type, unknown output_field")
        return output_field

    @cached_property
    def _output_field_or_none(self):
        """
        Return the output field of this expression, or None if
        _resolve_output_field() didn't return an output type.
        """
        try:
            return self.output_field
        except FieldError:
            if not self._output_field_resolved_to_none:
                raise

    def _resolve_output_field(self):
        """
        Attempt to infer the output type of the expression.

        As a guess, if the output fields of all source fields match then simply
        infer the same type here.

        If a source's output field resolves to None, exclude it from this check.
        If all sources are None, then an error is raised higher up the stack in
        the output_field property.
        """
        # This guess is mostly a bad idea, but there is quite a lot of code
        # (especially 3rd party Func subclasses) that depend on it, we'd need a
        # deprecation path to fix it.
        sources_iter = (
            source for source in self.get_source_fields() if source is not None
        )
        for output_field in sources_iter:
            for source in sources_iter:
                if not isinstance(output_field, source.__class__):
                    raise FieldError(
                        "Expression contains mixed types: %s, %s. You must "
                        "set output_field."
                        % (
                            output_field.__class__.__name__,
                            source.__class__.__name__,
                        )
                    )
            return output_field

    @staticmethod
    def _convert_value_noop(value, expression, connection):
        return value

    @cached_property
    def convert_value(self):
        """
        Expressions provide their own converters because users have the option
        of manually specifying the output_field which may be a different type
        from the one the database returns.
        """
        field = self.output_field
        internal_type = field.get_internal_type()
        if internal_type == "FloatField":
            return (
                lambda value, expression, connection: None
                if value is None
                else float(value)
            )
        elif internal_type.endswith("IntegerField"):
            return (
                lambda value, expression, connection: None
                if value is None
                else int(value)
            )
        elif internal_type == "DecimalField":
            return (
                lambda value, expression, connection: None
                if value is None
                else Decimal(value)
            )
        return self._convert_value_noop

    def get_lookup(self, lookup):
        return self.output_field.get_lookup(lookup)

    def get_transform(self, name):
        return self.output_field.get_transform(name)

    def relabeled_clone(self, change_map):
        clone = self.copy()
        clone.set_source_expressions(
            [
                e.relabeled_clone(change_map) if e is not None else None
                for e in self.get_source_expressions()
            ]
        )
        return clone

    def replace_expressions(self, replacements):
        if replacement := replacements.get(self):
            return replacement
        clone = self.copy()
        source_expressions = clone.get_source_expressions()
        clone.set_source_expressions(
            [
                expr.replace_expressions(replacements) if expr else None
                for expr in source_expressions
            ]
        )
        return clone

    def get_refs(self):
        refs = set()
        for expr in self.get_source_expressions():
            refs |= expr.get_refs()
        return refs

    def copy(self):
        return copy.copy(self)

    def prefix_references(self, prefix):
        clone = self.copy()
        clone.set_source_expressions(
            [
                F(f"{prefix}{expr.name}")
                if isinstance(expr, F)
                else expr.prefix_references(prefix)
                for expr in self.get_source_expressions()
            ]
        )
        return clone

    def get_group_by_cols(self):
        if not self.contains_aggregate:
            return [self]
        cols = []
        for source in self.get_source_expressions():
            cols.extend(source.get_group_by_cols())
        return cols

    def get_source_fields(self):
        """Return the underlying field types used by this aggregate."""
        return [e._output_field_or_none for e in self.get_source_expressions()]

    def asc(self, **kwargs):
        return OrderBy(self, **kwargs)

    def desc(self, **kwargs):
        return OrderBy(self, descending=True, **kwargs)

    def reverse_ordering(self):
        return self

    def flatten(self):
        """
        Recursively yield this expression and all subexpressions, in
        depth-first order.
        """
        yield self
        for expr in self.get_source_expressions():
            if expr:
                if hasattr(expr, "flatten"):
                    yield from expr.flatten()
                else:
                    yield expr

    def select_format(self, compiler, sql, params):
        """
        Custom format for select clauses. For example, EXISTS expressions need
        to be wrapped in CASE WHEN on Oracle.
        """
        if hasattr(self.output_field, "select_format"):
            return self.output_field.select_format(compiler, sql, params)
        return sql, params


@deconstructible
class Expression(BaseExpression, Combinable):
    """An expression that can be combined with other expressions."""

    @cached_property
    def identity(self):
        constructor_signature = inspect.signature(self.__init__)
        args, kwargs = self._constructor_args
        signature = constructor_signature.bind_partial(*args, **kwargs)
        signature.apply_defaults()
        arguments = signature.arguments.items()
        identity = [self.__class__]
        for arg, value in arguments:
            if isinstance(value, fields.Field):
                if value.name and value.model:
                    value = (value.model._meta.label, value.name)
                else:
                    value = type(value)
            else:
                value = make_hashable(value)
            identity.append((arg, value))
        return tuple(identity)

    def __eq__(self, other):
        if not isinstance(other, Expression):
            return NotImplemented
        return other.identity == self.identity

    def __hash__(self):
        return hash(self.identity)


# Type inference for CombinedExpression.output_field.
# Missing items will result in FieldError, by design.
#
# The current approach for NULL is based on lowest common denominator behavior
# i.e. if one of the supported databases is raising an error (rather than
# return NULL) for `val <op> NULL`, then Django raises FieldError.
NoneType = type(None)

_connector_combinations = [
    # Numeric operations - operands of same type.
    {
        connector: [
            (fields.IntegerField, fields.IntegerField, fields.IntegerField),
            (fields.FloatField, fields.FloatField, fields.FloatField),
            (fields.DecimalField, fields.DecimalField, fields.DecimalField),
        ]
        for connector in (
            Combinable.ADD,
            Combinable.SUB,
            Combinable.MUL,
            # Behavior for DIV with integer arguments follows Postgres/SQLite,
            # not MySQL/Oracle.
            Combinable.DIV,
            Combinable.MOD,
            Combinable.POW,
        )
    },
    # Numeric operations - operands of different type.
    {
        connector: [
            (fields.IntegerField, fields.DecimalField, fields.DecimalField),
            (fields.DecimalField, fields.IntegerField, fields.DecimalField),
            (fields.IntegerField, fields.FloatField, fields.FloatField),
            (fields.FloatField, fields.IntegerField, fields.FloatField),
        ]
        for connector in (
            Combinable.ADD,
            Combinable.SUB,
            Combinable.MUL,
            Combinable.DIV,
            Combinable.MOD,
        )
    },
    # Bitwise operators.
    {
        connector: [
            (fields.IntegerField, fields.IntegerField, fields.IntegerField),
        ]
        for connector in (
            Combinable.BITAND,
            Combinable.BITOR,
            Combinable.BITLEFTSHIFT,
            Combinable.BITRIGHTSHIFT,
            Combinable.BITXOR,
        )
    },
    # Numeric with NULL.
    {
        connector: [
            (field_type, NoneType, field_type),
            (NoneType, field_type, field_type),
        ]
        for connector in (
            Combinable.ADD,
            Combinable.SUB,
            Combinable.MUL,
            Combinable.DIV,
            Combinable.MOD,
            Combinable.POW,
        )
        for field_type in (fields.IntegerField, fields.DecimalField, fields.FloatField)
    },
    # Date/DateTimeField/DurationField/TimeField.
    {
        Combinable.ADD: [
            # Date/DateTimeField.
            (fields.DateField, fields.DurationField, fields.DateTimeField),
            (fields.DateTimeField, fields.DurationField, fields.DateTimeField),
            (fields.DurationField, fields.DateField, fields.DateTimeField),
            (fields.DurationField, fields.DateTimeField, fields.DateTimeField),
            # DurationField.
            (fields.DurationField, fields.DurationField, fields.DurationField),
            # TimeField.
            (fields.TimeField, fields.DurationField, fields.TimeField),
            (fields.DurationField, fields.TimeField, fields.TimeField),
        ],
    },
    {
        Combinable.SUB: [
            # Date/DateTimeField.
            (fields.DateField, fields.DurationField, fields.DateTimeField),
            (fields.DateTimeField, fields.DurationField, fields.DateTimeField),
            (fields.DateField, fields.DateField, fields.DurationField),
            (fields.DateField, fields.DateTimeField, fields.DurationField),
            (fields.DateTimeField, fields.DateField, fields.DurationField),
            (fields.DateTimeField, fields.DateTimeField, fields.DurationField),
            # DurationField.
            (fields.DurationField, fields.DurationField, fields.DurationField),
            # TimeField.
            (fields.TimeField, fields.DurationField, fields.TimeField),
            (fields.TimeField, fields.TimeField, fields.DurationField),
        ],
    },
]

_connector_combinators = defaultdict(list)


def register_combinable_fields(lhs, connector, rhs, result):
    """
    Register combinable types:
        lhs <connector> rhs -> result
    e.g.
        register_combinable_fields(
            IntegerField, Combinable.ADD, FloatField, FloatField
        )
    """
    _connector_combinators[connector].append((lhs, rhs, result))


for d in _connector_combinations:
    for connector, field_types in d.items():
        for lhs, rhs, result in field_types:
            register_combinable_fields(lhs, connector, rhs, result)


@functools.lru_cache(maxsize=128)
def _resolve_combined_type(connector, lhs_type, rhs_type):
    combinators = _connector_combinators.get(connector, ())
    for combinator_lhs_type, combinator_rhs_type, combined_type in combinators:
        if issubclass(lhs_type, combinator_lhs_type) and issubclass(
            rhs_type, combinator_rhs_type
        ):
            return combined_type


class CombinedExpression(SQLiteNumericMixin, Expression):
    def __init__(self, lhs, connector, rhs, output_field=None):
        super().__init__(output_field=output_field)
        self.connector = connector
        self.lhs = lhs
        self.rhs = rhs

    def __repr__(self):
        return "<{}: {}>".format(self.__class__.__name__, self)

    def __str__(self):
        return "{} {} {}".format(self.lhs, self.connector, self.rhs)

    def get_source_expressions(self):
        return [self.lhs, self.rhs]

    def set_source_expressions(self, exprs):
        self.lhs, self.rhs = exprs

    def _resolve_output_field(self):
        # We avoid using super() here for reasons given in
        # Expression._resolve_output_field()
        combined_type = _resolve_combined_type(
            self.connector,
            type(self.lhs._output_field_or_none),
            type(self.rhs._output_field_or_none),
        )
        if combined_type is None:
            raise FieldError(
                f"Cannot infer type of {self.connector!r} expression involving these "
                f"types: {self.lhs.output_field.__class__.__name__}, "
                f"{self.rhs.output_field.__class__.__name__}. You must set "
                f"output_field."
            )
        return combined_type()

    def as_sql(self, compiler, connection):
        expressions = []
        expression_params = []
        sql, params = compiler.compile(self.lhs)
        expressions.append(sql)
        expression_params.extend(params)
        sql, params = compiler.compile(self.rhs)
        expressions.append(sql)
        expression_params.extend(params)
        # order of precedence
        expression_wrapper = "(%s)"
        sql = connection.ops.combine_expression(self.connector, expressions)
        return expression_wrapper % sql, expression_params

    def resolve_expression(
        self, query=None, allow_joins=True, reuse=None, summarize=False, for_save=False
    ):
        lhs = self.lhs.resolve_expression(
            query, allow_joins, reuse, summarize, for_save
        )
        rhs = self.rhs.resolve_expression(
            query, allow_joins, reuse, summarize, for_save
        )
        if not isinstance(self, (DurationExpression, TemporalSubtraction)):
            try:
                lhs_type = lhs.output_field.get_internal_type()
            except (AttributeError, FieldError):
                lhs_type = None
            try:
                rhs_type = rhs.output_field.get_internal_type()
            except (AttributeError, FieldError):
                rhs_type = None
            if "DurationField" in {lhs_type, rhs_type} and lhs_type != rhs_type:
                return DurationExpression(
                    self.lhs, self.connector, self.rhs
                ).resolve_expression(
                    query,
                    allow_joins,
                    reuse,
                    summarize,
                    for_save,
                )
            datetime_fields = {"DateField", "DateTimeField", "TimeField"}
            if (
                self.connector == self.SUB
                and lhs_type in datetime_fields
                and lhs_type == rhs_type
            ):
                return TemporalSubtraction(self.lhs, self.rhs).resolve_expression(
                    query,
                    allow_joins,
                    reuse,
                    summarize,
                    for_save,
                )
        c = self.copy()
        c.is_summary = summarize
        c.lhs = lhs
        c.rhs = rhs
        return c


class DurationExpression(CombinedExpression):
    def compile(self, side, compiler, connection):
        try:
            output = side.output_field
        except FieldError:
            pass
        else:
            if output.get_internal_type() == "DurationField":
                sql, params = compiler.compile(side)
                return connection.ops.format_for_duration_arithmetic(sql), params
        return compiler.compile(side)

    def as_sql(self, compiler, connection):
        if connection.features.has_native_duration_field:
            return super().as_sql(compiler, connection)
        connection.ops.check_expression_support(self)
        expressions = []
        expression_params = []
        sql, params = self.compile(self.lhs, compiler, connection)
        expressions.append(sql)
        expression_params.extend(params)
        sql, params = self.compile(self.rhs, compiler, connection)
        expressions.append(sql)
        expression_params.extend(params)
        # order of precedence
        expression_wrapper = "(%s)"
        sql = connection.ops.combine_duration_expression(self.connector, expressions)
        return expression_wrapper % sql, expression_params

    def as_sqlite(self, compiler, connection, **extra_context):
        sql, params = self.as_sql(compiler, connection, **extra_context)
        if self.connector in {Combinable.MUL, Combinable.DIV}:
            try:
                lhs_type = self.lhs.output_field.get_internal_type()
                rhs_type = self.rhs.output_field.get_internal_type()
            except (AttributeError, FieldError):
                pass
            else:
                allowed_fields = {
                    "DecimalField",
                    "DurationField",
                    "FloatField",
                    "IntegerField",
                }
                if lhs_type not in allowed_fields or rhs_type not in allowed_fields:
                    raise DatabaseError(
                        f"Invalid arguments for operator {self.connector}."
                    )
        return sql, params


class TemporalSubtraction(CombinedExpression):
    output_field = fields.DurationField()

    def __init__(self, lhs, rhs):
        super().__init__(lhs, self.SUB, rhs)

    def as_sql(self, compiler, connection):
        connection.ops.check_expression_support(self)
        lhs = compiler.compile(self.lhs)
        rhs = compiler.compile(self.rhs)
        return connection.ops.subtract_temporals(
            self.lhs.output_field.get_internal_type(), lhs, rhs
        )


@deconstructible(path="django.db.models.F")
class F(Combinable):
    """An object capable of resolving references to existing query objects."""

    def __init__(self, name):
        """
        Arguments:
         * name: the name of the field this expression references
        """
        self.name = name

    def __repr__(self):
        return "{}({})".format(self.__class__.__name__, self.name)

    def resolve_expression(
        self, query=None, allow_joins=True, reuse=None, summarize=False, for_save=False
    ):
        return query.resolve_ref(self.name, allow_joins, reuse, summarize)

    def replace_expressions(self, replacements):
        return replacements.get(self, self)

    def asc(self, **kwargs):
        return OrderBy(self, **kwargs)

    def desc(self, **kwargs):
        return OrderBy(self, descending=True, **kwargs)

    def __eq__(self, other):
        return self.__class__ == other.__class__ and self.name == other.name

    def __hash__(self):
        return hash(self.name)

    def copy(self):
        return copy.copy(self)


class ResolvedOuterRef(F):
    """
    An object that contains a reference to an outer query.

    In this case, the reference to the outer query has been resolved because
    the inner query has been used as a subquery.
    """

    contains_aggregate = False
    contains_over_clause = False

    def as_sql(self, *args, **kwargs):
        raise ValueError(
            "This queryset contains a reference to an outer query and may "
            "only be used in a subquery."
        )

    def resolve_expression(self, *args, **kwargs):
        col = super().resolve_expression(*args, **kwargs)
        if col.contains_over_clause:
            raise NotSupportedError(
                f"Referencing outer query window expression is not supported: "
                f"{self.name}."
            )
        # FIXME: Rename possibly_multivalued to multivalued and fix detection
        # for non-multivalued JOINs (e.g. foreign key fields). This should take
        # into account only many-to-many and one-to-many relationships.
        col.possibly_multivalued = LOOKUP_SEP in self.name
        return col

    def relabeled_clone(self, relabels):
        return self

    def get_group_by_cols(self):
        return []


class OuterRef(F):
    contains_aggregate = False
    contains_over_clause = False

    def resolve_expression(self, *args, **kwargs):
        if isinstance(self.name, self.__class__):
            return self.name
        return ResolvedOuterRef(self.name)

    def relabeled_clone(self, relabels):
        return self


@deconstructible(path="django.db.models.Func")
class Func(SQLiteNumericMixin, Expression):
    """An SQL function call."""

    function = None
    template = "%(function)s(%(expressions)s)"
    arg_joiner = ", "
    arity = None  # The number of arguments the function accepts.

    def __init__(self, *expressions, output_field=None, **extra):
        if self.arity is not None and len(expressions) != self.arity:
            raise TypeError(
                "'%s' takes exactly %s %s (%s given)"
                % (
                    self.__class__.__name__,
                    self.arity,
                    "argument" if self.arity == 1 else "arguments",
                    len(expressions),
                )
            )
        super().__init__(output_field=output_field)
        self.source_expressions = self._parse_expressions(*expressions)
        self.extra = extra

    def __repr__(self):
        args = self.arg_joiner.join(str(arg) for arg in self.source_expressions)
        extra = {**self.extra, **self._get_repr_options()}
        if extra:
            extra = ", ".join(
                str(key) + "=" + str(val) for key, val in sorted(extra.items())
            )
            return "{}({}, {})".format(self.__class__.__name__, args, extra)
        return "{}({})".format(self.__class__.__name__, args)

    def _get_repr_options(self):
        """Return a dict of extra __init__() options to include in the repr."""
        return {}

    def get_source_expressions(self):
        return self.source_expressions

    def set_source_expressions(self, exprs):
        self.source_expressions = exprs

    def resolve_expression(
        self, query=None, allow_joins=True, reuse=None, summarize=False, for_save=False
    ):
        c = self.copy()
        c.is_summary = summarize
        for pos, arg in enumerate(c.source_expressions):
            c.source_expressions[pos] = arg.resolve_expression(
                query, allow_joins, reuse, summarize, for_save
            )
        return c

    def as_sql(
        self,
        compiler,
        connection,
        function=None,
        template=None,
        arg_joiner=None,
        **extra_context,
    ):
        connection.ops.check_expression_support(self)
        sql_parts = []
        params = []
        for arg in self.source_expressions:
            try:
                arg_sql, arg_params = compiler.compile(arg)
            except EmptyResultSet:
                empty_result_set_value = getattr(
                    arg, "empty_result_set_value", NotImplemented
                )
                if empty_result_set_value is NotImplemented:
                    raise
                arg_sql, arg_params = compiler.compile(Value(empty_result_set_value))
            except FullResultSet:
                arg_sql, arg_params = compiler.compile(Value(True))
            sql_parts.append(arg_sql)
            params.extend(arg_params)
        data = {**self.extra, **extra_context}
        # Use the first supplied value in this order: the parameter to this
        # method, a value supplied in __init__()'s **extra (the value in
        # `data`), or the value defined on the class.
        if function is not None:
            data["function"] = function
        else:
            data.setdefault("function", self.function)
        template = template or data.get("template", self.template)
        arg_joiner = arg_joiner or data.get("arg_joiner", self.arg_joiner)
        data["expressions"] = data["field"] = arg_joiner.join(sql_parts)
        return template % data, params

    def copy(self):
        copy = super().copy()
        copy.source_expressions = self.source_expressions[:]
        copy.extra = self.extra.copy()
        return copy


@deconstructible(path="django.db.models.Value")
class Value(SQLiteNumericMixin, Expression):
    """Represent a wrapped value as a node within an expression."""

    # Provide a default value for `for_save` in order to allow unresolved
    # instances to be compiled until a decision is taken in #25425.
    for_save = False

    def __init__(self, value, output_field=None):
        """
        Arguments:
         * value: the value this expression represents. The value will be
           added into the sql parameter list and properly quoted.

         * output_field: an instance of the model field type that this
           expression will return, such as IntegerField() or CharField().
        """
        super().__init__(output_field=output_field)
        self.value = value

    def __repr__(self):
        return f"{self.__class__.__name__}({self.value!r})"

    def as_sql(self, compiler, connection):
        connection.ops.check_expression_support(self)
        val = self.value
        output_field = self._output_field_or_none
        if output_field is not None:
            if self.for_save:
                val = output_field.get_db_prep_save(val, connection=connection)
            else:
                val = output_field.get_db_prep_value(val, connection=connection)
            if hasattr(output_field, "get_placeholder"):
                return output_field.get_placeholder(val, compiler, connection), [val]
        if val is None:
            # cx_Oracle does not always convert None to the appropriate
            # NULL type (like in case expressions using numbers), so we
            # use a literal SQL NULL
            return "NULL", []
        return "%s", [val]

    def resolve_expression(
        self, query=None, allow_joins=True, reuse=None, summarize=False, for_save=False
    ):
        c = super().resolve_expression(query, allow_joins, reuse, summarize, for_save)
        c.for_save = for_save
        return c

    def get_group_by_cols(self):
        return []

    def _resolve_output_field(self):
        if isinstance(self.value, str):
            return fields.CharField()
        if isinstance(self.value, bool):
            return fields.BooleanField()
        if isinstance(self.value, int):
            return fields.IntegerField()
        if isinstance(self.value, float):
            return fields.FloatField()
        if isinstance(self.value, datetime.datetime):
            return fields.DateTimeField()
        if isinstance(self.value, datetime.date):
            return fields.DateField()
        if isinstance(self.value, datetime.time):
            return fields.TimeField()
        if isinstance(self.value, datetime.timedelta):
            return fields.DurationField()
        if isinstance(self.value, Decimal):
            return fields.DecimalField()
        if isinstance(self.value, bytes):
            return fields.BinaryField()
        if isinstance(self.value, UUID):
            return fields.UUIDField()

    @property
    def empty_result_set_value(self):
        return self.value


class RawSQL(Expression):
    def __init__(self, sql, params, output_field=None):
        if output_field is None:
            output_field = fields.Field()
        self.sql, self.params = sql, params
        super().__init__(output_field=output_field)

    def __repr__(self):
        return "{}({}, {})".format(self.__class__.__name__, self.sql, self.params)

    def as_sql(self, compiler, connection):
        return "(%s)" % self.sql, self.params

    def get_group_by_cols(self):
        return [self]

    def resolve_expression(
        self, query=None, allow_joins=True, reuse=None, summarize=False, for_save=False
    ):
        # Resolve parents fields used in raw SQL.
        if query.model:
            for parent in query.model._meta.get_parent_list():
                for parent_field in parent._meta.local_fields:
                    _, column_name = parent_field.get_attname_column()
                    if column_name.lower() in self.sql.lower():
                        query.resolve_ref(
                            parent_field.name, allow_joins, reuse, summarize
                        )
                        break
        return super().resolve_expression(
            query, allow_joins, reuse, summarize, for_save
        )


class Star(Expression):
    def __repr__(self):
        return "'*'"

    def as_sql(self, compiler, connection):
        return "*", []


class Col(Expression):
    contains_column_references = True
    possibly_multivalued = False

    def __init__(self, alias, target, output_field=None):
        if output_field is None:
            output_field = target
        super().__init__(output_field=output_field)
        self.alias, self.target = alias, target

    def __repr__(self):
        alias, target = self.alias, self.target
        identifiers = (alias, str(target)) if alias else (str(target),)
        return "{}({})".format(self.__class__.__name__, ", ".join(identifiers))

    def as_sql(self, compiler, connection):
        alias, column = self.alias, self.target.column
        identifiers = (alias, column) if alias else (column,)
        sql = ".".join(map(compiler.quote_name_unless_alias, identifiers))
        return sql, []

    def relabeled_clone(self, relabels):
        if self.alias is None:
            return self
        return self.__class__(
            relabels.get(self.alias, self.alias), self.target, self.output_field
        )

    def get_group_by_cols(self):
        return [self]

    def get_db_converters(self, connection):
        if self.target == self.output_field:
            return self.output_field.get_db_converters(connection)
        return self.output_field.get_db_converters(
            connection
        ) + self.target.get_db_converters(connection)


class Ref(Expression):
    """
    Reference to column alias of the query. For example, Ref('sum_cost') in
    qs.annotate(sum_cost=Sum('cost')) query.
    """

    def __init__(self, refs, source):
        super().__init__()
        self.refs, self.source = refs, source

    def __repr__(self):
        return "{}({}, {})".format(self.__class__.__name__, self.refs, self.source)

    def get_source_expressions(self):
        return [self.source]

    def set_source_expressions(self, exprs):
        (self.source,) = exprs

    def resolve_expression(
        self, query=None, allow_joins=True, reuse=None, summarize=False, for_save=False
    ):
        # The sub-expression `source` has already been resolved, as this is
        # just a reference to the name of `source`.
        return self

    def get_refs(self):
        return {self.refs}

    def relabeled_clone(self, relabels):
        clone = self.copy()
        clone.source = self.source.relabeled_clone(relabels)
        return clone

    def as_sql(self, compiler, connection):
        return connection.ops.quote_name(self.refs), []

    def get_group_by_cols(self):
        return [self]


class ExpressionList(Func):
    """
    An expression containing multiple expressions. Can be used to provide a
    list of expressions as an argument to another expression, like a partition
    clause.
    """

    template = "%(expressions)s"

    def __init__(self, *expressions, **extra):
        if not expressions:
            raise ValueError(
                "%s requires at least one expression." % self.__class__.__name__
            )
        super().__init__(*expressions, **extra)

    def __str__(self):
        return self.arg_joiner.join(str(arg) for arg in self.source_expressions)

    def as_sqlite(self, compiler, connection, **extra_context):
        # Casting to numeric is unnecessary.
        return self.as_sql(compiler, connection, **extra_context)


class OrderByList(Func):
    template = "ORDER BY %(expressions)s"

    def __init__(self, *expressions, **extra):
        expressions = (
            (
                OrderBy(F(expr[1:]), descending=True)
                if isinstance(expr, str) and expr[0] == "-"
                else expr
            )
            for expr in expressions
        )
        super().__init__(*expressions, **extra)

    def as_sql(self, *args, **kwargs):
        if not self.source_expressions:
            return "", ()
        return super().as_sql(*args, **kwargs)

    def get_group_by_cols(self):
        group_by_cols = []
        for order_by in self.get_source_expressions():
            group_by_cols.extend(order_by.get_group_by_cols())
        return group_by_cols


@deconstructible(path="django.db.models.ExpressionWrapper")
class ExpressionWrapper(SQLiteNumericMixin, Expression):
    """
    An expression that can wrap another expression so that it can provide
    extra context to the inner expression, such as the output_field.
    """

    def __init__(self, expression, output_field):
        super().__init__(output_field=output_field)
        self.expression = expression

    def set_source_expressions(self, exprs):
        self.expression = exprs[0]

    def get_source_expressions(self):
        return [self.expression]

    def get_group_by_cols(self):
        if isinstance(self.expression, Expression):
            expression = self.expression.copy()
            expression.output_field = self.output_field
            return expression.get_group_by_cols()
        # For non-expressions e.g. an SQL WHERE clause, the entire
        # `expression` must be included in the GROUP BY clause.
        return super().get_group_by_cols()

    def as_sql(self, compiler, connection):
        return compiler.compile(self.expression)

    def __repr__(self):
        return "{}({})".format(self.__class__.__name__, self.expression)


class NegatedExpression(ExpressionWrapper):
    """The logical negation of a conditional expression."""

    def __init__(self, expression):
        super().__init__(expression, output_field=fields.BooleanField())

    def __invert__(self):
        return self.expression.copy()

    def as_sql(self, compiler, connection):
        try:
            sql, params = super().as_sql(compiler, connection)
        except EmptyResultSet:
            features = compiler.connection.features
            if not features.supports_boolean_expr_in_select_clause:
                return "1=1", ()
            return compiler.compile(Value(True))
        ops = compiler.connection.ops
        # Some database backends (e.g. Oracle) don't allow EXISTS() and filters
        # to be compared to another expression unless they're wrapped in a CASE
        # WHEN.
        if not ops.conditional_expression_supported_in_where_clause(self.expression):
            return f"CASE WHEN {sql} = 0 THEN 1 ELSE 0 END", params
        return f"NOT {sql}", params

    def resolve_expression(
        self, query=None, allow_joins=True, reuse=None, summarize=False, for_save=False
    ):
        resolved = super().resolve_expression(
            query, allow_joins, reuse, summarize, for_save
        )
        if not getattr(resolved.expression, "conditional", False):
            raise TypeError("Cannot negate non-conditional expressions.")
        return resolved

    def select_format(self, compiler, sql, params):
        # Wrap boolean expressions with a CASE WHEN expression if a database
        # backend (e.g. Oracle) doesn't support boolean expression in SELECT or
        # GROUP BY list.
        expression_supported_in_where_clause = (
            compiler.connection.ops.conditional_expression_supported_in_where_clause
        )
        if (
            not compiler.connection.features.supports_boolean_expr_in_select_clause
            # Avoid double wrapping.
            and expression_supported_in_where_clause(self.expression)
        ):
            sql = "CASE WHEN {} THEN 1 ELSE 0 END".format(sql)
        return sql, params


@deconstructible(path="django.db.models.When")
class When(Expression):
    template = "WHEN %(condition)s THEN %(result)s"
    # This isn't a complete conditional expression, must be used in Case().
    conditional = False

    def __init__(self, condition=None, then=None, **lookups):
        if lookups:
            if condition is None:
                condition, lookups = Q(**lookups), None
            elif getattr(condition, "conditional", False):
                condition, lookups = Q(condition, **lookups), None
        if condition is None or not getattr(condition, "conditional", False) or lookups:
            raise TypeError(
                "When() supports a Q object, a boolean expression, or lookups "
                "as a condition."
            )
        if isinstance(condition, Q) and not condition:
            raise ValueError("An empty Q() can't be used as a When() condition.")
        super().__init__(output_field=None)
        self.condition = condition
        self.result = self._parse_expressions(then)[0]

    def __str__(self):
        return "WHEN %r THEN %r" % (self.condition, self.result)

    def __repr__(self):
        return "<%s: %s>" % (self.__class__.__name__, self)

    def get_source_expressions(self):
        return [self.condition, self.result]

    def set_source_expressions(self, exprs):
        self.condition, self.result = exprs

    def get_source_fields(self):
        # We're only interested in the fields of the result expressions.
        return [self.result._output_field_or_none]

    def resolve_expression(
        self, query=None, allow_joins=True, reuse=None, summarize=False, for_save=False
    ):
        c = self.copy()
        c.is_summary = summarize
        if hasattr(c.condition, "resolve_expression"):
            c.condition = c.condition.resolve_expression(
                query, allow_joins, reuse, summarize, False
            )
        c.result = c.result.resolve_expression(
            query, allow_joins, reuse, summarize, for_save
        )
        return c

    def as_sql(self, compiler, connection, template=None, **extra_context):
        connection.ops.check_expression_support(self)
        template_params = extra_context
        sql_params = []
        condition_sql, condition_params = compiler.compile(self.condition)
        template_params["condition"] = condition_sql
        result_sql, result_params = compiler.compile(self.result)
        template_params["result"] = result_sql
        template = template or self.template
        return template % template_params, (
            *sql_params,
            *condition_params,
            *result_params,
        )

    def get_group_by_cols(self):
        # This is not a complete expression and cannot be used in GROUP BY.
        cols = []
        for source in self.get_source_expressions():
            cols.extend(source.get_group_by_cols())
        return cols


@deconstructible(path="django.db.models.Case")
class Case(SQLiteNumericMixin, Expression):
    """
    An SQL searched CASE expression:

        CASE
            WHEN n > 0
                THEN 'positive'
            WHEN n < 0
                THEN 'negative'
            ELSE 'zero'
        END
    """

    template = "CASE %(cases)s ELSE %(default)s END"
    case_joiner = " "

    def __init__(self, *cases, default=None, output_field=None, **extra):
        if not all(isinstance(case, When) for case in cases):
            raise TypeError("Positional arguments must all be When objects.")
        super().__init__(output_field)
        self.cases = list(cases)
        self.default = self._parse_expressions(default)[0]
        self.extra = extra

    def __str__(self):
        return "CASE %s, ELSE %r" % (
            ", ".join(str(c) for c in self.cases),
            self.default,
        )

    def __repr__(self):
        return "<%s: %s>" % (self.__class__.__name__, self)

    def get_source_expressions(self):
        return self.cases + [self.default]

    def set_source_expressions(self, exprs):
        *self.cases, self.default = exprs

    def resolve_expression(
        self, query=None, allow_joins=True, reuse=None, summarize=False, for_save=False
    ):
        c = self.copy()
        c.is_summary = summarize
        for pos, case in enumerate(c.cases):
            c.cases[pos] = case.resolve_expression(
                query, allow_joins, reuse, summarize, for_save
            )
        c.default = c.default.resolve_expression(
            query, allow_joins, reuse, summarize, for_save
        )
        return c

    def copy(self):
        c = super().copy()
        c.cases = c.cases[:]
        return c

    def as_sql(
        self, compiler, connection, template=None, case_joiner=None, **extra_context
    ):
        connection.ops.check_expression_support(self)
        if not self.cases:
            return compiler.compile(self.default)
        template_params = {**self.extra, **extra_context}
        case_parts = []
        sql_params = []
        default_sql, default_params = compiler.compile(self.default)
        for case in self.cases:
            try:
                case_sql, case_params = compiler.compile(case)
            except EmptyResultSet:
                continue
            except FullResultSet:
                default_sql, default_params = compiler.compile(case.result)
                break
            case_parts.append(case_sql)
            sql_params.extend(case_params)
        if not case_parts:
            return default_sql, default_params
        case_joiner = case_joiner or self.case_joiner
        template_params["cases"] = case_joiner.join(case_parts)
        template_params["default"] = default_sql
        sql_params.extend(default_params)
        template = template or template_params.get("template", self.template)
        sql = template % template_params
        if self._output_field_or_none is not None:
            sql = connection.ops.unification_cast_sql(self.output_field) % sql
        return sql, sql_params

    def get_group_by_cols(self):
        if not self.cases:
            return self.default.get_group_by_cols()
        return super().get_group_by_cols()


class Subquery(BaseExpression, Combinable):
    """
    An explicit subquery. It may contain OuterRef() references to the outer
    query which will be resolved when it is applied to that query.
    """

    template = "(%(subquery)s)"
    contains_aggregate = False
    empty_result_set_value = None
    subquery = True

    def __init__(self, queryset, output_field=None, **extra):
        # Allow the usage of both QuerySet and sql.Query objects.
        self.query = getattr(queryset, "query", queryset).clone()
        self.query.subquery = True
        self.extra = extra
        super().__init__(output_field)

    def get_source_expressions(self):
        return [self.query]

    def set_source_expressions(self, exprs):
        self.query = exprs[0]

    def _resolve_output_field(self):
        return self.query.output_field

    def copy(self):
        clone = super().copy()
        clone.query = clone.query.clone()
        return clone

    @property
    def external_aliases(self):
        return self.query.external_aliases

    def get_external_cols(self):
        return self.query.get_external_cols()

    def as_sql(self, compiler, connection, template=None, **extra_context):
        connection.ops.check_expression_support(self)
        template_params = {**self.extra, **extra_context}
        subquery_sql, sql_params = self.query.as_sql(compiler, connection)
        template_params["subquery"] = subquery_sql[1:-1]

        template = template or template_params.get("template", self.template)
        sql = template % template_params
        return sql, sql_params

    def get_group_by_cols(self):
        return self.query.get_group_by_cols(wrapper=self)


class Exists(Subquery):
    template = "EXISTS(%(subquery)s)"
    output_field = fields.BooleanField()
    empty_result_set_value = False

    def __init__(self, queryset, **kwargs):
        super().__init__(queryset, **kwargs)
        self.query = self.query.exists()

    def select_format(self, compiler, sql, params):
        # Wrap EXISTS() with a CASE WHEN expression if a database backend
        # (e.g. Oracle) doesn't support boolean expression in SELECT or GROUP
        # BY list.
        if not compiler.connection.features.supports_boolean_expr_in_select_clause:
            sql = "CASE WHEN {} THEN 1 ELSE 0 END".format(sql)
        return sql, params


@deconstructible(path="django.db.models.OrderBy")
class OrderBy(Expression):
    template = "%(expression)s %(ordering)s"
    conditional = False

    def __init__(self, expression, descending=False, nulls_first=None, nulls_last=None):
        if nulls_first and nulls_last:
            raise ValueError("nulls_first and nulls_last are mutually exclusive")
        if nulls_first is False or nulls_last is False:
            # When the deprecation ends, replace with:
            # raise ValueError(
            #     "nulls_first and nulls_last values must be True or None."
            # )
            warnings.warn(
                "Passing nulls_first=False or nulls_last=False is deprecated, use None "
                "instead.",
                RemovedInDjango50Warning,
                stacklevel=2,
            )
        self.nulls_first = nulls_first
        self.nulls_last = nulls_last
        self.descending = descending
        if not hasattr(expression, "resolve_expression"):
            raise ValueError("expression must be an expression type")
        self.expression = expression

    def __repr__(self):
        return "{}({}, descending={})".format(
            self.__class__.__name__, self.expression, self.descending
        )

    def set_source_expressions(self, exprs):
        self.expression = exprs[0]

    def get_source_expressions(self):
        return [self.expression]

    def as_sql(self, compiler, connection, template=None, **extra_context):
        template = template or self.template
        if connection.features.supports_order_by_nulls_modifier:
            if self.nulls_last:
                template = "%s NULLS LAST" % template
            elif self.nulls_first:
                template = "%s NULLS FIRST" % template
        else:
            if self.nulls_last and not (
                self.descending and connection.features.order_by_nulls_first
            ):
                template = "%%(expression)s IS NULL, %s" % template
            elif self.nulls_first and not (
                not self.descending and connection.features.order_by_nulls_first
            ):
                template = "%%(expression)s IS NOT NULL, %s" % template
        connection.ops.check_expression_support(self)
        expression_sql, params = compiler.compile(self.expression)
        placeholders = {
            "expression": expression_sql,
            "ordering": "DESC" if self.descending else "ASC",
            **extra_context,
        }
        params *= template.count("%(expression)s")
        return (template % placeholders).rstrip(), params

    def as_oracle(self, compiler, connection):
        # Oracle doesn't allow ORDER BY EXISTS() or filters unless it's wrapped
        # in a CASE WHEN.
        if connection.ops.conditional_expression_supported_in_where_clause(
            self.expression
        ):
            copy = self.copy()
            copy.expression = Case(
                When(self.expression, then=True),
                default=False,
            )
            return copy.as_sql(compiler, connection)
        return self.as_sql(compiler, connection)

    def get_group_by_cols(self):
        cols = []
        for source in self.get_source_expressions():
            cols.extend(source.get_group_by_cols())
        return cols

    def reverse_ordering(self):
        self.descending = not self.descending
        if self.nulls_first:
            self.nulls_last = True
            self.nulls_first = None
        elif self.nulls_last:
            self.nulls_first = True
            self.nulls_last = None
        return self

    def asc(self):
        self.descending = False

    def desc(self):
        self.descending = True


class Window(SQLiteNumericMixin, Expression):
    template = "%(expression)s OVER (%(window)s)"
    # Although the main expression may either be an aggregate or an
    # expression with an aggregate function, the GROUP BY that will
    # be introduced in the query as a result is not desired.
    contains_aggregate = False
    contains_over_clause = True

    def __init__(
        self,
        expression,
        partition_by=None,
        order_by=None,
        frame=None,
        output_field=None,
    ):
        self.partition_by = partition_by
        self.order_by = order_by
        self.frame = frame

        if not getattr(expression, "window_compatible", False):
            raise ValueError(
                "Expression '%s' isn't compatible with OVER clauses."
                % expression.__class__.__name__
            )

        if self.partition_by is not None:
            if not isinstance(self.partition_by, (tuple, list)):
                self.partition_by = (self.partition_by,)
            self.partition_by = ExpressionList(*self.partition_by)

        if self.order_by is not None:
            if isinstance(self.order_by, (list, tuple)):
                self.order_by = OrderByList(*self.order_by)
            elif isinstance(self.order_by, (BaseExpression, str)):
                self.order_by = OrderByList(self.order_by)
            else:
                raise ValueError(
                    "Window.order_by must be either a string reference to a "
                    "field, an expression, or a list or tuple of them."
                )
        super().__init__(output_field=output_field)
        self.source_expression = self._parse_expressions(expression)[0]

    def _resolve_output_field(self):
        return self.source_expression.output_field

    def get_source_expressions(self):
        return [self.source_expression, self.partition_by, self.order_by, self.frame]

    def set_source_expressions(self, exprs):
        self.source_expression, self.partition_by, self.order_by, self.frame = exprs

    def as_sql(self, compiler, connection, template=None):
        connection.ops.check_expression_support(self)
        if not connection.features.supports_over_clause:
            raise NotSupportedError("This backend does not support window expressions.")
        expr_sql, params = compiler.compile(self.source_expression)
        window_sql, window_params = [], ()

        if self.partition_by is not None:
            sql_expr, sql_params = self.partition_by.as_sql(
                compiler=compiler,
                connection=connection,
                template="PARTITION BY %(expressions)s",
            )
            window_sql.append(sql_expr)
            window_params += tuple(sql_params)

        if self.order_by is not None:
            order_sql, order_params = compiler.compile(self.order_by)
            window_sql.append(order_sql)
            window_params += tuple(order_params)

        if self.frame:
            frame_sql, frame_params = compiler.compile(self.frame)
            window_sql.append(frame_sql)
            window_params += tuple(frame_params)

        template = template or self.template

        return (
            template % {"expression": expr_sql, "window": " ".join(window_sql).strip()},
            (*params, *window_params),
        )

    def as_sqlite(self, compiler, connection):
        if isinstance(self.output_field, fields.DecimalField):
            # Casting to numeric must be outside of the window expression.
            copy = self.copy()
            source_expressions = copy.get_source_expressions()
            source_expressions[0].output_field = fields.FloatField()
            copy.set_source_expressions(source_expressions)
            return super(Window, copy).as_sqlite(compiler, connection)
        return self.as_sql(compiler, connection)

    def __str__(self):
        return "{} OVER ({}{}{})".format(
            str(self.source_expression),
            "PARTITION BY " + str(self.partition_by) if self.partition_by else "",
            str(self.order_by or ""),
            str(self.frame or ""),
        )

    def __repr__(self):
        return "<%s: %s>" % (self.__class__.__name__, self)

    def get_group_by_cols(self):
        group_by_cols = []
        if self.partition_by:
            group_by_cols.extend(self.partition_by.get_group_by_cols())
        if self.order_by is not None:
            group_by_cols.extend(self.order_by.get_group_by_cols())
        return group_by_cols


class WindowFrame(Expression):
    """
    Model the frame clause in window expressions. There are two types of frame
    clauses which are subclasses, however, all processing and validation (by no
    means intended to be complete) is done here. Thus, providing an end for a
    frame is optional (the default is UNBOUNDED FOLLOWING, which is the last
    row in the frame).
    """

    template = "%(frame_type)s BETWEEN %(start)s AND %(end)s"

    def __init__(self, start=None, end=None):
        self.start = Value(start)
        self.end = Value(end)

    def set_source_expressions(self, exprs):
        self.start, self.end = exprs

    def get_source_expressions(self):
        return [self.start, self.end]

    def as_sql(self, compiler, connection):
        connection.ops.check_expression_support(self)
        start, end = self.window_frame_start_end(
            connection, self.start.value, self.end.value
        )
        return (
            self.template
            % {
                "frame_type": self.frame_type,
                "start": start,
                "end": end,
            },
            [],
        )

    def __repr__(self):
        return "<%s: %s>" % (self.__class__.__name__, self)

    def get_group_by_cols(self):
        return []

    def __str__(self):
        if self.start.value is not None and self.start.value < 0:
            start = "%d %s" % (abs(self.start.value), connection.ops.PRECEDING)
        elif self.start.value is not None and self.start.value == 0:
            start = connection.ops.CURRENT_ROW
        else:
            start = connection.ops.UNBOUNDED_PRECEDING

        if self.end.value is not None and self.end.value > 0:
            end = "%d %s" % (self.end.value, connection.ops.FOLLOWING)
        elif self.end.value is not None and self.end.value == 0:
            end = connection.ops.CURRENT_ROW
        else:
            end = connection.ops.UNBOUNDED_FOLLOWING
        return self.template % {
            "frame_type": self.frame_type,
            "start": start,
            "end": end,
        }

    def window_frame_start_end(self, connection, start, end):
        raise NotImplementedError("Subclasses must implement window_frame_start_end().")


class RowRange(WindowFrame):
    frame_type = "ROWS"

    def window_frame_start_end(self, connection, start, end):
        return connection.ops.window_frame_rows_start_end(start, end)


class ValueRange(WindowFrame):
    frame_type = "RANGE"

    def window_frame_start_end(self, connection, start, end):
        return connection.ops.window_frame_range_start_end(start, end)

virt/lib/python3.9/site-packages/django/db/models/expressions.py
PrinceDisant/LostInNL
"""
"Rel objects" for related fields.

"Rel objects" (for lack of a better name) carry information about the relation
modeled by a related field and provide some utility functions. They're stored
in the ``remote_field`` attribute of the field.

They also act as reverse fields for the purposes of the Meta API because
they're the closest concept currently available.
"""

from django.core import exceptions
from django.utils.functional import cached_property
from django.utils.hashable import make_hashable

from . import BLANK_CHOICE_DASH
from .mixins import FieldCacheMixin


class ForeignObjectRel(FieldCacheMixin):
    """
    Used by ForeignObject to store information about the relation.

    ``_meta.get_fields()`` returns this class to provide access to the field
    flags for the reverse relation.
    """

    # Field flags
    auto_created = True
    concrete = False
    editable = False
    is_relation = True

    # Reverse relations are always nullable (Django can't enforce that a
    # foreign key on the related model points to this model).
    null = True
    empty_strings_allowed = False

    def __init__(
        self,
        field,
        to,
        related_name=None,
        related_query_name=None,
        limit_choices_to=None,
        parent_link=False,
        on_delete=None,
    ):
        self.field = field
        self.model = to
        self.related_name = related_name
        self.related_query_name = related_query_name
        self.limit_choices_to = {} if limit_choices_to is None else limit_choices_to
        self.parent_link = parent_link
        self.on_delete = on_delete

        self.symmetrical = False
        self.multiple = True

    # Some of the following cached_properties can't be initialized in
    # __init__ as the field doesn't have its model yet. Calling these methods
    # before field.contribute_to_class() has been called will result in
    # AttributeError
    @cached_property
    def hidden(self):
        return self.is_hidden()

    @cached_property
    def name(self):
        return self.field.related_query_name()

    @property
    def remote_field(self):
        return self.field

    @property
    def target_field(self):
        """
        When filtering against this relation, return the field on the remote
        model against which the filtering should happen.
        """
        target_fields = self.path_infos[-1].target_fields
        if len(target_fields) > 1:
            raise exceptions.FieldError(
                "Can't use target_field for multicolumn relations."
            )
        return target_fields[0]

    @cached_property
    def related_model(self):
        if not self.field.model:
            raise AttributeError(
                "This property can't be accessed before self.field.contribute_to_class "
                "has been called."
            )
        return self.field.model

    @cached_property
    def many_to_many(self):
        return self.field.many_to_many

    @cached_property
    def many_to_one(self):
        return self.field.one_to_many

    @cached_property
    def one_to_many(self):
        return self.field.many_to_one

    @cached_property
    def one_to_one(self):
        return self.field.one_to_one

    def get_lookup(self, lookup_name):
        return self.field.get_lookup(lookup_name)

    def get_lookups(self):
        return self.field.get_lookups()

    def get_transform(self, name):
        return self.field.get_transform(name)

    def get_internal_type(self):
        return self.field.get_internal_type()

    @property
    def db_type(self):
        return self.field.db_type

    def __repr__(self):
        return "<%s: %s.%s>" % (
            type(self).__name__,
            self.related_model._meta.app_label,
            self.related_model._meta.model_name,
        )

    @property
    def identity(self):
        return (
            self.field,
            self.model,
            self.related_name,
            self.related_query_name,
            make_hashable(self.limit_choices_to),
            self.parent_link,
            self.on_delete,
            self.symmetrical,
            self.multiple,
        )

    def __eq__(self, other):
        if not isinstance(other, self.__class__):
            return NotImplemented
        return self.identity == other.identity

    def __hash__(self):
        return hash(self.identity)

    def __getstate__(self):
        state = self.__dict__.copy()
        # Delete the path_infos cached property because it can be recalculated
        # at first invocation after deserialization. The attribute must be
        # removed because subclasses like ManyToOneRel may have a PathInfo
        # which contains an intermediate M2M table that's been dynamically
        # created and doesn't exist in the .models module.
        # This is a reverse relation, so there is no reverse_path_infos to
        # delete.
        state.pop("path_infos", None)
        return state

    def get_choices(
        self,
        include_blank=True,
        blank_choice=BLANK_CHOICE_DASH,
        limit_choices_to=None,
        ordering=(),
    ):
        """
        Return choices with a default blank choices included, for use
        as <select> choices for this field.

        Analog of django.db.models.fields.Field.get_choices(), provided
        initially for utilization by RelatedFieldListFilter.
        """
        limit_choices_to = limit_choices_to or self.limit_choices_to
        qs = self.related_model._default_manager.complex_filter(limit_choices_to)
        if ordering:
            qs = qs.order_by(*ordering)
        return (blank_choice if include_blank else []) + [(x.pk, str(x)) for x in qs]

    def is_hidden(self):
        """Should the related object be hidden?"""
        return bool(self.related_name) and self.related_name[-1] == "+"

    def get_joining_columns(self):
        return self.field.get_reverse_joining_columns()

    def get_extra_restriction(self, alias, related_alias):
        return self.field.get_extra_restriction(related_alias, alias)

    def set_field_name(self):
        """
        Set the related field's name, this is not available until later stages
        of app loading, so set_field_name is called from
        set_attributes_from_rel()
        """
        # By default foreign object doesn't relate to any remote field (for
        # example custom multicolumn joins currently have no remote field).
        self.field_name = None

    def get_accessor_name(self, model=None):
        # This method encapsulates the logic that decides what name to give an
        # accessor descriptor that retrieves related many-to-one or
        # many-to-many objects. It uses the lowercased object_name + "_set",
        # but this can be overridden with the "related_name" option. Due to
        # backwards compatibility ModelForms need to be able to provide an
        # alternate model. See BaseInlineFormSet.get_default_prefix().
        opts = model._meta if model else self.related_model._meta
        model = model or self.related_model
        if self.multiple:
            # If this is a symmetrical m2m relation on self, there is no
            # reverse accessor.
            if self.symmetrical and model == self.model:
                return None
        if self.related_name:
            return self.related_name
        return opts.model_name + ("_set" if self.multiple else "")

    def get_path_info(self, filtered_relation=None):
        if filtered_relation:
            return self.field.get_reverse_path_info(filtered_relation)
        else:
            return self.field.reverse_path_infos

    @cached_property
    def path_infos(self):
        return self.get_path_info()

    def get_cache_name(self):
        """
        Return the name of the cache key to use for storing an instance of the
        forward model on the reverse model.
        """
        return self.get_accessor_name()


class ManyToOneRel(ForeignObjectRel):
    """
    Used by the ForeignKey field to store information about the relation.

    ``_meta.get_fields()`` returns this class to provide access to the field
    flags for the reverse relation.

    Note: Because we somewhat abuse the Rel objects by using them as reverse
    fields we get the funny situation where
    ``ManyToOneRel.many_to_one == False`` and
    ``ManyToOneRel.one_to_many == True``. This is unfortunate but the actual
    ManyToOneRel class is a private API and there is work underway to turn
    reverse relations into actual fields.
    """

    def __init__(
        self,
        field,
        to,
        field_name,
        related_name=None,
        related_query_name=None,
        limit_choices_to=None,
        parent_link=False,
        on_delete=None,
    ):
        super().__init__(
            field,
            to,
            related_name=related_name,
            related_query_name=related_query_name,
            limit_choices_to=limit_choices_to,
            parent_link=parent_link,
            on_delete=on_delete,
        )

        self.field_name = field_name

    def __getstate__(self):
        state = super().__getstate__()
        state.pop("related_model", None)
        return state

    @property
    def identity(self):
        return super().identity + (self.field_name,)

    def get_related_field(self):
        """
        Return the Field in the 'to' object to which this relationship is tied.
        """
        field = self.model._meta.get_field(self.field_name)
        if not field.concrete:
            raise exceptions.FieldDoesNotExist(
                "No related field named '%s'" % self.field_name
            )
        return field

    def set_field_name(self):
        self.field_name = self.field_name or self.model._meta.pk.name


class OneToOneRel(ManyToOneRel):
    """
    Used by OneToOneField to store information about the relation.

    ``_meta.get_fields()`` returns this class to provide access to the field
    flags for the reverse relation.
    """

    def __init__(
        self,
        field,
        to,
        field_name,
        related_name=None,
        related_query_name=None,
        limit_choices_to=None,
        parent_link=False,
        on_delete=None,
    ):
        super().__init__(
            field,
            to,
            field_name,
            related_name=related_name,
            related_query_name=related_query_name,
            limit_choices_to=limit_choices_to,
            parent_link=parent_link,
            on_delete=on_delete,
        )

        self.multiple = False


class ManyToManyRel(ForeignObjectRel):
    """
    Used by ManyToManyField to store information about the relation.

    ``_meta.get_fields()`` returns this class to provide access to the field
    flags for the reverse relation.
    """

    def __init__(
        self,
        field,
        to,
        related_name=None,
        related_query_name=None,
        limit_choices_to=None,
        symmetrical=True,
        through=None,
        through_fields=None,
        db_constraint=True,
    ):
        super().__init__(
            field,
            to,
            related_name=related_name,
            related_query_name=related_query_name,
            limit_choices_to=limit_choices_to,
        )

        if through and not db_constraint:
            raise ValueError("Can't supply a through model and db_constraint=False")
        self.through = through

        if through_fields and not through:
            raise ValueError("Cannot specify through_fields without a through model")
        self.through_fields = through_fields

        self.symmetrical = symmetrical
        self.db_constraint = db_constraint

    @property
    def identity(self):
        return super().identity + (
            self.through,
            make_hashable(self.through_fields),
            self.db_constraint,
        )

    def get_related_field(self):
        """
        Return the field in the 'to' object to which this relationship is tied.
        Provided for symmetry with ManyToOneRel.
        """
        opts = self.through._meta
        if self.through_fields:
            field = opts.get_field(self.through_fields[0])
        else:
            for field in opts.fields:
                rel = getattr(field, "remote_field", None)
                if rel and rel.model == self.model:
                    break
        return field.foreign_related_fields[0]

virt/lib/python3.9/site-packages/django/db/models/fields/reverse_related.py
PrinceDisant/LostInNL
import itertools
import math

from django.core.exceptions import EmptyResultSet, FullResultSet
from django.db.models.expressions import Case, Expression, Func, Value, When
from django.db.models.fields import (
    BooleanField,
    CharField,
    DateTimeField,
    Field,
    IntegerField,
    UUIDField,
)
from django.db.models.query_utils import RegisterLookupMixin
from django.utils.datastructures import OrderedSet
from django.utils.functional import cached_property
from django.utils.hashable import make_hashable


class Lookup(Expression):
    lookup_name = None
    prepare_rhs = True
    can_use_none_as_rhs = False

    def __init__(self, lhs, rhs):
        self.lhs, self.rhs = lhs, rhs
        self.rhs = self.get_prep_lookup()
        self.lhs = self.get_prep_lhs()
        if hasattr(self.lhs, "get_bilateral_transforms"):
            bilateral_transforms = self.lhs.get_bilateral_transforms()
        else:
            bilateral_transforms = []
        if bilateral_transforms:
            # Warn the user as soon as possible if they are trying to apply
            # a bilateral transformation on a nested QuerySet: that won't work.
            from django.db.models.sql.query import Query  # avoid circular import

            if isinstance(rhs, Query):
                raise NotImplementedError(
                    "Bilateral transformations on nested querysets are not implemented."
                )
        self.bilateral_transforms = bilateral_transforms

    def apply_bilateral_transforms(self, value):
        for transform in self.bilateral_transforms:
            value = transform(value)
        return value

    def __repr__(self):
        return f"{self.__class__.__name__}({self.lhs!r}, {self.rhs!r})"

    def batch_process_rhs(self, compiler, connection, rhs=None):
        if rhs is None:
            rhs = self.rhs
        if self.bilateral_transforms:
            sqls, sqls_params = [], []
            for p in rhs:
                value = Value(p, output_field=self.lhs.output_field)
                value = self.apply_bilateral_transforms(value)
                value = value.resolve_expression(compiler.query)
                sql, sql_params = compiler.compile(value)
                sqls.append(sql)
                sqls_params.extend(sql_params)
        else:
            _, params = self.get_db_prep_lookup(rhs, connection)
            sqls, sqls_params = ["%s"] * len(params), params
        return sqls, sqls_params

    def get_source_expressions(self):
        if self.rhs_is_direct_value():
            return [self.lhs]
        return [self.lhs, self.rhs]

    def set_source_expressions(self, new_exprs):
        if len(new_exprs) == 1:
            self.lhs = new_exprs[0]
        else:
            self.lhs, self.rhs = new_exprs

    def get_prep_lookup(self):
        if not self.prepare_rhs or hasattr(self.rhs, "resolve_expression"):
            return self.rhs
        if hasattr(self.lhs, "output_field"):
            if hasattr(self.lhs.output_field, "get_prep_value"):
                return self.lhs.output_field.get_prep_value(self.rhs)
        elif self.rhs_is_direct_value():
            return Value(self.rhs)
        return self.rhs

    def get_prep_lhs(self):
        if hasattr(self.lhs, "resolve_expression"):
            return self.lhs
        return Value(self.lhs)

    def get_db_prep_lookup(self, value, connection):
        return ("%s", [value])

    def process_lhs(self, compiler, connection, lhs=None):
        lhs = lhs or self.lhs
        if hasattr(lhs, "resolve_expression"):
            lhs = lhs.resolve_expression(compiler.query)
        sql, params = compiler.compile(lhs)
        if isinstance(lhs, Lookup):
            # Wrapped in parentheses to respect operator precedence.
            sql = f"({sql})"
        return sql, params

    def process_rhs(self, compiler, connection):
        value = self.rhs
        if self.bilateral_transforms:
            if self.rhs_is_direct_value():
                # Do not call get_db_prep_lookup here as the value will be
                # transformed before being used for lookup
                value = Value(value, output_field=self.lhs.output_field)
            value = self.apply_bilateral_transforms(value)
            value = value.resolve_expression(compiler.query)
        if hasattr(value, "as_sql"):
            sql, params = compiler.compile(value)
            # Ensure expression is wrapped in parentheses to respect operator
            # precedence but avoid double wrapping as it can be misinterpreted
            # on some backends (e.g. subqueries on SQLite).
            if sql and sql[0] != "(":
                sql = "(%s)" % sql
            return sql, params
        else:
            return self.get_db_prep_lookup(value, connection)

    def rhs_is_direct_value(self):
        return not hasattr(self.rhs, "as_sql")

    def get_group_by_cols(self):
        cols = []
        for source in self.get_source_expressions():
            cols.extend(source.get_group_by_cols())
        return cols

    def as_oracle(self, compiler, connection):
        # Oracle doesn't allow EXISTS() and filters to be compared to another
        # expression unless they're wrapped in a CASE WHEN.
        wrapped = False
        exprs = []
        for expr in (self.lhs, self.rhs):
            if connection.ops.conditional_expression_supported_in_where_clause(expr):
                expr = Case(When(expr, then=True), default=False)
                wrapped = True
            exprs.append(expr)
        lookup = type(self)(*exprs) if wrapped else self
        return lookup.as_sql(compiler, connection)

    @cached_property
    def output_field(self):
        return BooleanField()

    @property
    def identity(self):
        return self.__class__, self.lhs, self.rhs

    def __eq__(self, other):
        if not isinstance(other, Lookup):
            return NotImplemented
        return self.identity == other.identity

    def __hash__(self):
        return hash(make_hashable(self.identity))

    def resolve_expression(
        self, query=None, allow_joins=True, reuse=None, summarize=False, for_save=False
    ):
        c = self.copy()
        c.is_summary = summarize
        c.lhs = self.lhs.resolve_expression(
            query, allow_joins, reuse, summarize, for_save
        )
        if hasattr(self.rhs, "resolve_expression"):
            c.rhs = self.rhs.resolve_expression(
                query, allow_joins, reuse, summarize, for_save
            )
        return c

    def select_format(self, compiler, sql, params):
        # Wrap filters with a CASE WHEN expression if a database backend
        # (e.g. Oracle) doesn't support boolean expression in SELECT or GROUP
        # BY list.
        if not compiler.connection.features.supports_boolean_expr_in_select_clause:
            sql = f"CASE WHEN {sql} THEN 1 ELSE 0 END"
        return sql, params


class Transform(RegisterLookupMixin, Func):
    """
    RegisterLookupMixin() is first so that get_lookup() and get_transform()
    first examine self and then check output_field.
    """

    bilateral = False
    arity = 1

    @property
    def lhs(self):
        return self.get_source_expressions()[0]

    def get_bilateral_transforms(self):
        if hasattr(self.lhs, "get_bilateral_transforms"):
            bilateral_transforms = self.lhs.get_bilateral_transforms()
        else:
            bilateral_transforms = []
        if self.bilateral:
            bilateral_transforms.append(self.__class__)
        return bilateral_transforms


class BuiltinLookup(Lookup):
    def process_lhs(self, compiler, connection, lhs=None):
        lhs_sql, params = super().process_lhs(compiler, connection, lhs)
        field_internal_type = self.lhs.output_field.get_internal_type()
        db_type = self.lhs.output_field.db_type(connection=connection)
        lhs_sql = connection.ops.field_cast_sql(db_type, field_internal_type) % lhs_sql
        lhs_sql = (
            connection.ops.lookup_cast(self.lookup_name, field_internal_type) % lhs_sql
        )
        return lhs_sql, list(params)

    def as_sql(self, compiler, connection):
        lhs_sql, params = self.process_lhs(compiler, connection)
        rhs_sql, rhs_params = self.process_rhs(compiler, connection)
        params.extend(rhs_params)
        rhs_sql = self.get_rhs_op(connection, rhs_sql)
        return "%s %s" % (lhs_sql, rhs_sql), params

    def get_rhs_op(self, connection, rhs):
        return connection.operators[self.lookup_name] % rhs


class FieldGetDbPrepValueMixin:
    """
    Some lookups require Field.get_db_prep_value() to be called on their
    inputs.
    """

    get_db_prep_lookup_value_is_iterable = False

    def get_db_prep_lookup(self, value, connection):
        # For relational fields, use the 'target_field' attribute of the
        # output_field.
        field = getattr(self.lhs.output_field, "target_field", None)
        get_db_prep_value = (
            getattr(field, "get_db_prep_value", None)
            or self.lhs.output_field.get_db_prep_value
        )
        return (
            "%s",
            [get_db_prep_value(v, connection, prepared=True) for v in value]
            if self.get_db_prep_lookup_value_is_iterable
            else [get_db_prep_value(value, connection, prepared=True)],
        )


class FieldGetDbPrepValueIterableMixin(FieldGetDbPrepValueMixin):
    """
    Some lookups require Field.get_db_prep_value() to be called on each value
    in an iterable.
    """

    get_db_prep_lookup_value_is_iterable = True

    def get_prep_lookup(self):
        if hasattr(self.rhs, "resolve_expression"):
            return self.rhs
        prepared_values = []
        for rhs_value in self.rhs:
            if hasattr(rhs_value, "resolve_expression"):
                # An expression will be handled by the database but can coexist
                # alongside real values.
                pass
            elif self.prepare_rhs and hasattr(self.lhs.output_field, "get_prep_value"):
                rhs_value = self.lhs.output_field.get_prep_value(rhs_value)
            prepared_values.append(rhs_value)
        return prepared_values

    def process_rhs(self, compiler, connection):
        if self.rhs_is_direct_value():
            # rhs should be an iterable of values. Use batch_process_rhs()
            # to prepare/transform those values.
            return self.batch_process_rhs(compiler, connection)
        else:
            return super().process_rhs(compiler, connection)

    def resolve_expression_parameter(self, compiler, connection, sql, param):
        params = [param]
        if hasattr(param, "resolve_expression"):
            param = param.resolve_expression(compiler.query)
        if hasattr(param, "as_sql"):
            sql, params = compiler.compile(param)
        return sql, params

    def batch_process_rhs(self, compiler, connection, rhs=None):
        pre_processed = super().batch_process_rhs(compiler, connection, rhs)
        # The params list may contain expressions which compile to a
        # sql/param pair. Zip them to get sql and param pairs that refer to the
        # same argument and attempt to replace them with the result of
        # compiling the param step.
        sql, params = zip(
            *(
                self.resolve_expression_parameter(compiler, connection, sql, param)
                for sql, param in zip(*pre_processed)
            )
        )
        params = itertools.chain.from_iterable(params)
        return sql, tuple(params)


class PostgresOperatorLookup(Lookup):
    """Lookup defined by operators on PostgreSQL."""

    postgres_operator = None

    def as_postgresql(self, compiler, connection):
        lhs, lhs_params = self.process_lhs(compiler, connection)
        rhs, rhs_params = self.process_rhs(compiler, connection)
        params = tuple(lhs_params) + tuple(rhs_params)
        return "%s %s %s" % (lhs, self.postgres_operator, rhs), params


@Field.register_lookup
class Exact(FieldGetDbPrepValueMixin, BuiltinLookup):
    lookup_name = "exact"

    def get_prep_lookup(self):
        from django.db.models.sql.query import Query  # avoid circular import

        if isinstance(self.rhs, Query):
            if self.rhs.has_limit_one():
                if not self.rhs.has_select_fields:
                    self.rhs.clear_select_clause()
                    self.rhs.add_fields(["pk"])
            else:
                raise ValueError(
                    "The QuerySet value for an exact lookup must be limited to "
                    "one result using slicing."
                )
        return super().get_prep_lookup()

    def as_sql(self, compiler, connection):
        # Avoid comparison against direct rhs if lhs is a boolean value. That
        # turns "boolfield__exact=True" into "WHERE boolean_field" instead of
        # "WHERE boolean_field = True" when allowed.
        if (
            isinstance(self.rhs, bool)
            and getattr(self.lhs, "conditional", False)
            and connection.ops.conditional_expression_supported_in_where_clause(
                self.lhs
            )
        ):
            lhs_sql, params = self.process_lhs(compiler, connection)
            template = "%s" if self.rhs else "NOT %s"
            return template % lhs_sql, params
        return super().as_sql(compiler, connection)


@Field.register_lookup
class IExact(BuiltinLookup):
    lookup_name = "iexact"
    prepare_rhs = False

    def process_rhs(self, qn, connection):
        rhs, params = super().process_rhs(qn, connection)
        if params:
            params[0] = connection.ops.prep_for_iexact_query(params[0])
        return rhs, params


@Field.register_lookup
class GreaterThan(FieldGetDbPrepValueMixin, BuiltinLookup):
    lookup_name = "gt"


@Field.register_lookup
class GreaterThanOrEqual(FieldGetDbPrepValueMixin, BuiltinLookup):
    lookup_name = "gte"


@Field.register_lookup
class LessThan(FieldGetDbPrepValueMixin, BuiltinLookup):
    lookup_name = "lt"


@Field.register_lookup
class LessThanOrEqual(FieldGetDbPrepValueMixin, BuiltinLookup):
    lookup_name = "lte"


class IntegerFieldFloatRounding:
    """
    Allow floats to work as query values for IntegerField. Without this, the
    decimal portion of the float would always be discarded.
    """

    def get_prep_lookup(self):
        if isinstance(self.rhs, float):
            self.rhs = math.ceil(self.rhs)
        return super().get_prep_lookup()


@IntegerField.register_lookup
class IntegerGreaterThanOrEqual(IntegerFieldFloatRounding, GreaterThanOrEqual):
    pass


@IntegerField.register_lookup
class IntegerLessThan(IntegerFieldFloatRounding, LessThan):
    pass


@Field.register_lookup
class In(FieldGetDbPrepValueIterableMixin, BuiltinLookup):
    lookup_name = "in"

    def get_prep_lookup(self):
        from django.db.models.sql.query import Query  # avoid circular import

        if isinstance(self.rhs, Query):
            self.rhs.clear_ordering(clear_default=True)
            if not self.rhs.has_select_fields:
                self.rhs.clear_select_clause()
                self.rhs.add_fields(["pk"])
        return super().get_prep_lookup()

    def process_rhs(self, compiler, connection):
        db_rhs = getattr(self.rhs, "_db", None)
        if db_rhs is not None and db_rhs != connection.alias:
            raise ValueError(
                "Subqueries aren't allowed across different databases. Force "
                "the inner query to be evaluated using `list(inner_query)`."
            )

        if self.rhs_is_direct_value():
            # Remove None from the list as NULL is never equal to anything.
            try:
                rhs = OrderedSet(self.rhs)
                rhs.discard(None)
            except TypeError:  # Unhashable items in self.rhs
                rhs = [r for r in self.rhs if r is not None]

            if not rhs:
                raise EmptyResultSet

            # rhs should be an iterable; use batch_process_rhs() to
            # prepare/transform those values.
            sqls, sqls_params = self.batch_process_rhs(compiler, connection, rhs)
            placeholder = "(" + ", ".join(sqls) + ")"
            return (placeholder, sqls_params)
        return super().process_rhs(compiler, connection)

    def get_rhs_op(self, connection, rhs):
        return "IN %s" % rhs

    def as_sql(self, compiler, connection):
        max_in_list_size = connection.ops.max_in_list_size()
        if (
            self.rhs_is_direct_value()
            and max_in_list_size
            and len(self.rhs) > max_in_list_size
        ):
            return self.split_parameter_list_as_sql(compiler, connection)
        return super().as_sql(compiler, connection)

    def split_parameter_list_as_sql(self, compiler, connection):
        # This is a special case for databases which limit the number of
        # elements which can appear in an 'IN' clause.
        max_in_list_size = connection.ops.max_in_list_size()
        lhs, lhs_params = self.process_lhs(compiler, connection)
        rhs, rhs_params = self.batch_process_rhs(compiler, connection)
        in_clause_elements = ["("]
        params = []
        for offset in range(0, len(rhs_params), max_in_list_size):
            if offset > 0:
                in_clause_elements.append(" OR ")
            in_clause_elements.append("%s IN (" % lhs)
            params.extend(lhs_params)
            sqls = rhs[offset : offset + max_in_list_size]
            sqls_params = rhs_params[offset : offset + max_in_list_size]
            param_group = ", ".join(sqls)
            in_clause_elements.append(param_group)
            in_clause_elements.append(")")
            params.extend(sqls_params)
        in_clause_elements.append(")")
        return "".join(in_clause_elements), params


class PatternLookup(BuiltinLookup):
    param_pattern = "%%%s%%"
    prepare_rhs = False

    def get_rhs_op(self, connection, rhs):
        # Assume we are in startswith. We need to produce SQL like:
        #     col LIKE %s, ['thevalue%']
        # For python values we can (and should) do that directly in Python,
        # but if the value is for example reference to other column, then
        # we need to add the % pattern match to the lookup by something like
        #     col LIKE othercol || '%%'
        # So, for Python values we don't need any special pattern, but for
        # SQL reference values or SQL transformations we need the correct
        # pattern added.
        if hasattr(self.rhs, "as_sql") or self.bilateral_transforms:
            pattern = connection.pattern_ops[self.lookup_name].format(
                connection.pattern_esc
            )
            return pattern.format(rhs)
        else:
            return super().get_rhs_op(connection, rhs)

    def process_rhs(self, qn, connection):
        rhs, params = super().process_rhs(qn, connection)
        if self.rhs_is_direct_value() and params and not self.bilateral_transforms:
            params[0] = self.param_pattern % connection.ops.prep_for_like_query(
                params[0]
            )
        return rhs, params


@Field.register_lookup
class Contains(PatternLookup):
    lookup_name = "contains"


@Field.register_lookup
class IContains(Contains):
    lookup_name = "icontains"


@Field.register_lookup
class StartsWith(PatternLookup):
    lookup_name = "startswith"
    param_pattern = "%s%%"


@Field.register_lookup
class IStartsWith(StartsWith):
    lookup_name = "istartswith"


@Field.register_lookup
class EndsWith(PatternLookup):
    lookup_name = "endswith"
    param_pattern = "%%%s"


@Field.register_lookup
class IEndsWith(EndsWith):
    lookup_name = "iendswith"


@Field.register_lookup
class Range(FieldGetDbPrepValueIterableMixin, BuiltinLookup):
    lookup_name = "range"

    def get_rhs_op(self, connection, rhs):
        return "BETWEEN %s AND %s" % (rhs[0], rhs[1])


@Field.register_lookup
class IsNull(BuiltinLookup):
    lookup_name = "isnull"
    prepare_rhs = False

    def as_sql(self, compiler, connection):
        if not isinstance(self.rhs, bool):
            raise ValueError(
                "The QuerySet value for an isnull lookup must be True or False."
            )
        if isinstance(self.lhs, Value) and self.lhs.value is None:
            if self.rhs:
                raise FullResultSet
            else:
                raise EmptyResultSet
        sql, params = self.process_lhs(compiler, connection)
        if self.rhs:
            return "%s IS NULL" % sql, params
        else:
            return "%s IS NOT NULL" % sql, params


@Field.register_lookup
class Regex(BuiltinLookup):
    lookup_name = "regex"
    prepare_rhs = False

    def as_sql(self, compiler, connection):
        if self.lookup_name in connection.operators:
            return super().as_sql(compiler, connection)
        else:
            lhs, lhs_params = self.process_lhs(compiler, connection)
            rhs, rhs_params = self.process_rhs(compiler, connection)
            sql_template = connection.ops.regex_lookup(self.lookup_name)
            return sql_template % (lhs, rhs), lhs_params + rhs_params


@Field.register_lookup
class IRegex(Regex):
    lookup_name = "iregex"


class YearLookup(Lookup):
    def year_lookup_bounds(self, connection, year):
        from django.db.models.functions import ExtractIsoYear

        iso_year = isinstance(self.lhs, ExtractIsoYear)
        output_field = self.lhs.lhs.output_field
        if isinstance(output_field, DateTimeField):
            bounds = connection.ops.year_lookup_bounds_for_datetime_field(
                year,
                iso_year=iso_year,
            )
        else:
            bounds = connection.ops.year_lookup_bounds_for_date_field(
                year,
                iso_year=iso_year,
            )
        return bounds

    def as_sql(self, compiler, connection):
        # Avoid the extract operation if the rhs is a direct value to allow
        # indexes to be used.
        if self.rhs_is_direct_value():
            # Skip the extract part by directly using the originating field,
            # that is self.lhs.lhs.
            lhs_sql, params = self.process_lhs(compiler, connection, self.lhs.lhs)
            rhs_sql, _ = self.process_rhs(compiler, connection)
            rhs_sql = self.get_direct_rhs_sql(connection, rhs_sql)
            start, finish = self.year_lookup_bounds(connection, self.rhs)
            params.extend(self.get_bound_params(start, finish))
            return "%s %s" % (lhs_sql, rhs_sql), params
        return super().as_sql(compiler, connection)

    def get_direct_rhs_sql(self, connection, rhs):
        return connection.operators[self.lookup_name] % rhs

    def get_bound_params(self, start, finish):
        raise NotImplementedError(
            "subclasses of YearLookup must provide a get_bound_params() method"
        )


class YearExact(YearLookup, Exact):
    def get_direct_rhs_sql(self, connection, rhs):
        return "BETWEEN %s AND %s"

    def get_bound_params(self, start, finish):
        return (start, finish)


class YearGt(YearLookup, GreaterThan):
    def get_bound_params(self, start, finish):
        return (finish,)


class YearGte(YearLookup, GreaterThanOrEqual):
    def get_bound_params(self, start, finish):
        return (start,)


class YearLt(YearLookup, LessThan):
    def get_bound_params(self, start, finish):
        return (start,)


class YearLte(YearLookup, LessThanOrEqual):
    def get_bound_params(self, start, finish):
        return (finish,)


class UUIDTextMixin:
    """
    Strip hyphens from a value when filtering a UUIDField on backends without
    a native datatype for UUID.
    """

    def process_rhs(self, qn, connection):
        if not connection.features.has_native_uuid_field:
            from django.db.models.functions import Replace

            if self.rhs_is_direct_value():
                self.rhs = Value(self.rhs)
            self.rhs = Replace(
                self.rhs, Value("-"), Value(""), output_field=CharField()
            )
        rhs, params = super().process_rhs(qn, connection)
        return rhs, params


@UUIDField.register_lookup
class UUIDIExact(UUIDTextMixin, IExact):
    pass


@UUIDField.register_lookup
class UUIDContains(UUIDTextMixin, Contains):
    pass


@UUIDField.register_lookup
class UUIDIContains(UUIDTextMixin, IContains):
    pass


@UUIDField.register_lookup
class UUIDStartsWith(UUIDTextMixin, StartsWith):
    pass


@UUIDField.register_lookup
class UUIDIStartsWith(UUIDTextMixin, IStartsWith):
    pass


@UUIDField.register_lookup
class UUIDEndsWith(UUIDTextMixin, EndsWith):
    pass


@UUIDField.register_lookup
class UUIDIEndsWith(UUIDTextMixin, IEndsWith):
    pass

virt/lib/python3.9/site-packages/django/db/models/lookups 2.py
PrinceDisant/LostInNL
import itertools
import math

from django.core.exceptions import EmptyResultSet, FullResultSet
from django.db.models.expressions import Case, Expression, Func, Value, When
from django.db.models.fields import (
    BooleanField,
    CharField,
    DateTimeField,
    Field,
    IntegerField,
    UUIDField,
)
from django.db.models.query_utils import RegisterLookupMixin
from django.utils.datastructures import OrderedSet
from django.utils.functional import cached_property
from django.utils.hashable import make_hashable


class Lookup(Expression):
    lookup_name = None
    prepare_rhs = True
    can_use_none_as_rhs = False

    def __init__(self, lhs, rhs):
        self.lhs, self.rhs = lhs, rhs
        self.rhs = self.get_prep_lookup()
        self.lhs = self.get_prep_lhs()
        if hasattr(self.lhs, "get_bilateral_transforms"):
            bilateral_transforms = self.lhs.get_bilateral_transforms()
        else:
            bilateral_transforms = []
        if bilateral_transforms:
            # Warn the user as soon as possible if they are trying to apply
            # a bilateral transformation on a nested QuerySet: that won't work.
            from django.db.models.sql.query import Query  # avoid circular import

            if isinstance(rhs, Query):
                raise NotImplementedError(
                    "Bilateral transformations on nested querysets are not implemented."
                )
        self.bilateral_transforms = bilateral_transforms

    def apply_bilateral_transforms(self, value):
        for transform in self.bilateral_transforms:
            value = transform(value)
        return value

    def __repr__(self):
        return f"{self.__class__.__name__}({self.lhs!r}, {self.rhs!r})"

    def batch_process_rhs(self, compiler, connection, rhs=None):
        if rhs is None:
            rhs = self.rhs
        if self.bilateral_transforms:
            sqls, sqls_params = [], []
            for p in rhs:
                value = Value(p, output_field=self.lhs.output_field)
                value = self.apply_bilateral_transforms(value)
                value = value.resolve_expression(compiler.query)
                sql, sql_params = compiler.compile(value)
                sqls.append(sql)
                sqls_params.extend(sql_params)
        else:
            _, params = self.get_db_prep_lookup(rhs, connection)
            sqls, sqls_params = ["%s"] * len(params), params
        return sqls, sqls_params

    def get_source_expressions(self):
        if self.rhs_is_direct_value():
            return [self.lhs]
        return [self.lhs, self.rhs]

    def set_source_expressions(self, new_exprs):
        if len(new_exprs) == 1:
            self.lhs = new_exprs[0]
        else:
            self.lhs, self.rhs = new_exprs

    def get_prep_lookup(self):
        if not self.prepare_rhs or hasattr(self.rhs, "resolve_expression"):
            return self.rhs
        if hasattr(self.lhs, "output_field"):
            if hasattr(self.lhs.output_field, "get_prep_value"):
                return self.lhs.output_field.get_prep_value(self.rhs)
        elif self.rhs_is_direct_value():
            return Value(self.rhs)
        return self.rhs

    def get_prep_lhs(self):
        if hasattr(self.lhs, "resolve_expression"):
            return self.lhs
        return Value(self.lhs)

    def get_db_prep_lookup(self, value, connection):
        return ("%s", [value])

    def process_lhs(self, compiler, connection, lhs=None):
        lhs = lhs or self.lhs
        if hasattr(lhs, "resolve_expression"):
            lhs = lhs.resolve_expression(compiler.query)
        sql, params = compiler.compile(lhs)
        if isinstance(lhs, Lookup):
            # Wrapped in parentheses to respect operator precedence.
            sql = f"({sql})"
        return sql, params

    def process_rhs(self, compiler, connection):
        value = self.rhs
        if self.bilateral_transforms:
            if self.rhs_is_direct_value():
                # Do not call get_db_prep_lookup here as the value will be
                # transformed before being used for lookup
                value = Value(value, output_field=self.lhs.output_field)
            value = self.apply_bilateral_transforms(value)
            value = value.resolve_expression(compiler.query)
        if hasattr(value, "as_sql"):
            sql, params = compiler.compile(value)
            # Ensure expression is wrapped in parentheses to respect operator
            # precedence but avoid double wrapping as it can be misinterpreted
            # on some backends (e.g. subqueries on SQLite).
            if sql and sql[0] != "(":
                sql = "(%s)" % sql
            return sql, params
        else:
            return self.get_db_prep_lookup(value, connection)

    def rhs_is_direct_value(self):
        return not hasattr(self.rhs, "as_sql")

    def get_group_by_cols(self):
        cols = []
        for source in self.get_source_expressions():
            cols.extend(source.get_group_by_cols())
        return cols

    def as_oracle(self, compiler, connection):
        # Oracle doesn't allow EXISTS() and filters to be compared to another
        # expression unless they're wrapped in a CASE WHEN.
        wrapped = False
        exprs = []
        for expr in (self.lhs, self.rhs):
            if connection.ops.conditional_expression_supported_in_where_clause(expr):
                expr = Case(When(expr, then=True), default=False)
                wrapped = True
            exprs.append(expr)
        lookup = type(self)(*exprs) if wrapped else self
        return lookup.as_sql(compiler, connection)

    @cached_property
    def output_field(self):
        return BooleanField()

    @property
    def identity(self):
        return self.__class__, self.lhs, self.rhs

    def __eq__(self, other):
        if not isinstance(other, Lookup):
            return NotImplemented
        return self.identity == other.identity

    def __hash__(self):
        return hash(make_hashable(self.identity))

    def resolve_expression(
        self, query=None, allow_joins=True, reuse=None, summarize=False, for_save=False
    ):
        c = self.copy()
        c.is_summary = summarize
        c.lhs = self.lhs.resolve_expression(
            query, allow_joins, reuse, summarize, for_save
        )
        if hasattr(self.rhs, "resolve_expression"):
            c.rhs = self.rhs.resolve_expression(
                query, allow_joins, reuse, summarize, for_save
            )
        return c

    def select_format(self, compiler, sql, params):
        # Wrap filters with a CASE WHEN expression if a database backend
        # (e.g. Oracle) doesn't support boolean expression in SELECT or GROUP
        # BY list.
        if not compiler.connection.features.supports_boolean_expr_in_select_clause:
            sql = f"CASE WHEN {sql} THEN 1 ELSE 0 END"
        return sql, params


class Transform(RegisterLookupMixin, Func):
    """
    RegisterLookupMixin() is first so that get_lookup() and get_transform()
    first examine self and then check output_field.
    """

    bilateral = False
    arity = 1

    @property
    def lhs(self):
        return self.get_source_expressions()[0]

    def get_bilateral_transforms(self):
        if hasattr(self.lhs, "get_bilateral_transforms"):
            bilateral_transforms = self.lhs.get_bilateral_transforms()
        else:
            bilateral_transforms = []
        if self.bilateral:
            bilateral_transforms.append(self.__class__)
        return bilateral_transforms


class BuiltinLookup(Lookup):
    def process_lhs(self, compiler, connection, lhs=None):
        lhs_sql, params = super().process_lhs(compiler, connection, lhs)
        field_internal_type = self.lhs.output_field.get_internal_type()
        db_type = self.lhs.output_field.db_type(connection=connection)
        lhs_sql = connection.ops.field_cast_sql(db_type, field_internal_type) % lhs_sql
        lhs_sql = (
            connection.ops.lookup_cast(self.lookup_name, field_internal_type) % lhs_sql
        )
        return lhs_sql, list(params)

    def as_sql(self, compiler, connection):
        lhs_sql, params = self.process_lhs(compiler, connection)
        rhs_sql, rhs_params = self.process_rhs(compiler, connection)
        params.extend(rhs_params)
        rhs_sql = self.get_rhs_op(connection, rhs_sql)
        return "%s %s" % (lhs_sql, rhs_sql), params

    def get_rhs_op(self, connection, rhs):
        return connection.operators[self.lookup_name] % rhs


class FieldGetDbPrepValueMixin:
    """
    Some lookups require Field.get_db_prep_value() to be called on their
    inputs.
    """

    get_db_prep_lookup_value_is_iterable = False

    def get_db_prep_lookup(self, value, connection):
        # For relational fields, use the 'target_field' attribute of the
        # output_field.
        field = getattr(self.lhs.output_field, "target_field", None)
        get_db_prep_value = (
            getattr(field, "get_db_prep_value", None)
            or self.lhs.output_field.get_db_prep_value
        )
        return (
            "%s",
            [get_db_prep_value(v, connection, prepared=True) for v in value]
            if self.get_db_prep_lookup_value_is_iterable
            else [get_db_prep_value(value, connection, prepared=True)],
        )


class FieldGetDbPrepValueIterableMixin(FieldGetDbPrepValueMixin):
    """
    Some lookups require Field.get_db_prep_value() to be called on each value
    in an iterable.
    """

    get_db_prep_lookup_value_is_iterable = True

    def get_prep_lookup(self):
        if hasattr(self.rhs, "resolve_expression"):
            return self.rhs
        prepared_values = []
        for rhs_value in self.rhs:
            if hasattr(rhs_value, "resolve_expression"):
                # An expression will be handled by the database but can coexist
                # alongside real values.
                pass
            elif self.prepare_rhs and hasattr(self.lhs.output_field, "get_prep_value"):
                rhs_value = self.lhs.output_field.get_prep_value(rhs_value)
            prepared_values.append(rhs_value)
        return prepared_values

    def process_rhs(self, compiler, connection):
        if self.rhs_is_direct_value():
            # rhs should be an iterable of values. Use batch_process_rhs()
            # to prepare/transform those values.
            return self.batch_process_rhs(compiler, connection)
        else:
            return super().process_rhs(compiler, connection)

    def resolve_expression_parameter(self, compiler, connection, sql, param):
        params = [param]
        if hasattr(param, "resolve_expression"):
            param = param.resolve_expression(compiler.query)
        if hasattr(param, "as_sql"):
            sql, params = compiler.compile(param)
        return sql, params

    def batch_process_rhs(self, compiler, connection, rhs=None):
        pre_processed = super().batch_process_rhs(compiler, connection, rhs)
        # The params list may contain expressions which compile to a
        # sql/param pair. Zip them to get sql and param pairs that refer to the
        # same argument and attempt to replace them with the result of
        # compiling the param step.
        sql, params = zip(
            *(
                self.resolve_expression_parameter(compiler, connection, sql, param)
                for sql, param in zip(*pre_processed)
            )
        )
        params = itertools.chain.from_iterable(params)
        return sql, tuple(params)


class PostgresOperatorLookup(Lookup):
    """Lookup defined by operators on PostgreSQL."""

    postgres_operator = None

    def as_postgresql(self, compiler, connection):
        lhs, lhs_params = self.process_lhs(compiler, connection)
        rhs, rhs_params = self.process_rhs(compiler, connection)
        params = tuple(lhs_params) + tuple(rhs_params)
        return "%s %s %s" % (lhs, self.postgres_operator, rhs), params


@Field.register_lookup
class Exact(FieldGetDbPrepValueMixin, BuiltinLookup):
    lookup_name = "exact"

    def get_prep_lookup(self):
        from django.db.models.sql.query import Query  # avoid circular import

        if isinstance(self.rhs, Query):
            if self.rhs.has_limit_one():
                if not self.rhs.has_select_fields:
                    self.rhs.clear_select_clause()
                    self.rhs.add_fields(["pk"])
            else:
                raise ValueError(
                    "The QuerySet value for an exact lookup must be limited to "
                    "one result using slicing."
                )
        return super().get_prep_lookup()

    def as_sql(self, compiler, connection):
        # Avoid comparison against direct rhs if lhs is a boolean value. That
        # turns "boolfield__exact=True" into "WHERE boolean_field" instead of
        # "WHERE boolean_field = True" when allowed.
        if (
            isinstance(self.rhs, bool)
            and getattr(self.lhs, "conditional", False)
            and connection.ops.conditional_expression_supported_in_where_clause(
                self.lhs
            )
        ):
            lhs_sql, params = self.process_lhs(compiler, connection)
            template = "%s" if self.rhs else "NOT %s"
            return template % lhs_sql, params
        return super().as_sql(compiler, connection)


@Field.register_lookup
class IExact(BuiltinLookup):
    lookup_name = "iexact"
    prepare_rhs = False

    def process_rhs(self, qn, connection):
        rhs, params = super().process_rhs(qn, connection)
        if params:
            params[0] = connection.ops.prep_for_iexact_query(params[0])
        return rhs, params


@Field.register_lookup
class GreaterThan(FieldGetDbPrepValueMixin, BuiltinLookup):
    lookup_name = "gt"


@Field.register_lookup
class GreaterThanOrEqual(FieldGetDbPrepValueMixin, BuiltinLookup):
    lookup_name = "gte"


@Field.register_lookup
class LessThan(FieldGetDbPrepValueMixin, BuiltinLookup):
    lookup_name = "lt"


@Field.register_lookup
class LessThanOrEqual(FieldGetDbPrepValueMixin, BuiltinLookup):
    lookup_name = "lte"


class IntegerFieldFloatRounding:
    """
    Allow floats to work as query values for IntegerField. Without this, the
    decimal portion of the float would always be discarded.
    """

    def get_prep_lookup(self):
        if isinstance(self.rhs, float):
            self.rhs = math.ceil(self.rhs)
        return super().get_prep_lookup()


@IntegerField.register_lookup
class IntegerGreaterThanOrEqual(IntegerFieldFloatRounding, GreaterThanOrEqual):
    pass


@IntegerField.register_lookup
class IntegerLessThan(IntegerFieldFloatRounding, LessThan):
    pass


@Field.register_lookup
class In(FieldGetDbPrepValueIterableMixin, BuiltinLookup):
    lookup_name = "in"

    def get_prep_lookup(self):
        from django.db.models.sql.query import Query  # avoid circular import

        if isinstance(self.rhs, Query):
            self.rhs.clear_ordering(clear_default=True)
            if not self.rhs.has_select_fields:
                self.rhs.clear_select_clause()
                self.rhs.add_fields(["pk"])
        return super().get_prep_lookup()

    def process_rhs(self, compiler, connection):
        db_rhs = getattr(self.rhs, "_db", None)
        if db_rhs is not None and db_rhs != connection.alias:
            raise ValueError(
                "Subqueries aren't allowed across different databases. Force "
                "the inner query to be evaluated using `list(inner_query)`."
            )

        if self.rhs_is_direct_value():
            # Remove None from the list as NULL is never equal to anything.
            try:
                rhs = OrderedSet(self.rhs)
                rhs.discard(None)
            except TypeError:  # Unhashable items in self.rhs
                rhs = [r for r in self.rhs if r is not None]

            if not rhs:
                raise EmptyResultSet

            # rhs should be an iterable; use batch_process_rhs() to
            # prepare/transform those values.
            sqls, sqls_params = self.batch_process_rhs(compiler, connection, rhs)
            placeholder = "(" + ", ".join(sqls) + ")"
            return (placeholder, sqls_params)
        return super().process_rhs(compiler, connection)

    def get_rhs_op(self, connection, rhs):
        return "IN %s" % rhs

    def as_sql(self, compiler, connection):
        max_in_list_size = connection.ops.max_in_list_size()
        if (
            self.rhs_is_direct_value()
            and max_in_list_size
            and len(self.rhs) > max_in_list_size
        ):
            return self.split_parameter_list_as_sql(compiler, connection)
        return super().as_sql(compiler, connection)

    def split_parameter_list_as_sql(self, compiler, connection):
        # This is a special case for databases which limit the number of
        # elements which can appear in an 'IN' clause.
        max_in_list_size = connection.ops.max_in_list_size()
        lhs, lhs_params = self.process_lhs(compiler, connection)
        rhs, rhs_params = self.batch_process_rhs(compiler, connection)
        in_clause_elements = ["("]
        params = []
        for offset in range(0, len(rhs_params), max_in_list_size):
            if offset > 0:
                in_clause_elements.append(" OR ")
            in_clause_elements.append("%s IN (" % lhs)
            params.extend(lhs_params)
            sqls = rhs[offset : offset + max_in_list_size]
            sqls_params = rhs_params[offset : offset + max_in_list_size]
            param_group = ", ".join(sqls)
            in_clause_elements.append(param_group)
            in_clause_elements.append(")")
            params.extend(sqls_params)
        in_clause_elements.append(")")
        return "".join(in_clause_elements), params


class PatternLookup(BuiltinLookup):
    param_pattern = "%%%s%%"
    prepare_rhs = False

    def get_rhs_op(self, connection, rhs):
        # Assume we are in startswith. We need to produce SQL like:
        #     col LIKE %s, ['thevalue%']
        # For python values we can (and should) do that directly in Python,
        # but if the value is for example reference to other column, then
        # we need to add the % pattern match to the lookup by something like
        #     col LIKE othercol || '%%'
        # So, for Python values we don't need any special pattern, but for
        # SQL reference values or SQL transformations we need the correct
        # pattern added.
        if hasattr(self.rhs, "as_sql") or self.bilateral_transforms:
            pattern = connection.pattern_ops[self.lookup_name].format(
                connection.pattern_esc
            )
            return pattern.format(rhs)
        else:
            return super().get_rhs_op(connection, rhs)

    def process_rhs(self, qn, connection):
        rhs, params = super().process_rhs(qn, connection)
        if self.rhs_is_direct_value() and params and not self.bilateral_transforms:
            params[0] = self.param_pattern % connection.ops.prep_for_like_query(
                params[0]
            )
        return rhs, params


@Field.register_lookup
class Contains(PatternLookup):
    lookup_name = "contains"


@Field.register_lookup
class IContains(Contains):
    lookup_name = "icontains"


@Field.register_lookup
class StartsWith(PatternLookup):
    lookup_name = "startswith"
    param_pattern = "%s%%"


@Field.register_lookup
class IStartsWith(StartsWith):
    lookup_name = "istartswith"


@Field.register_lookup
class EndsWith(PatternLookup):
    lookup_name = "endswith"
    param_pattern = "%%%s"


@Field.register_lookup
class IEndsWith(EndsWith):
    lookup_name = "iendswith"


@Field.register_lookup
class Range(FieldGetDbPrepValueIterableMixin, BuiltinLookup):
    lookup_name = "range"

    def get_rhs_op(self, connection, rhs):
        return "BETWEEN %s AND %s" % (rhs[0], rhs[1])


@Field.register_lookup
class IsNull(BuiltinLookup):
    lookup_name = "isnull"
    prepare_rhs = False

    def as_sql(self, compiler, connection):
        if not isinstance(self.rhs, bool):
            raise ValueError(
                "The QuerySet value for an isnull lookup must be True or False."
            )
        if isinstance(self.lhs, Value) and self.lhs.value is None:
            if self.rhs:
                raise FullResultSet
            else:
                raise EmptyResultSet
        sql, params = self.process_lhs(compiler, connection)
        if self.rhs:
            return "%s IS NULL" % sql, params
        else:
            return "%s IS NOT NULL" % sql, params


@Field.register_lookup
class Regex(BuiltinLookup):
    lookup_name = "regex"
    prepare_rhs = False

    def as_sql(self, compiler, connection):
        if self.lookup_name in connection.operators:
            return super().as_sql(compiler, connection)
        else:
            lhs, lhs_params = self.process_lhs(compiler, connection)
            rhs, rhs_params = self.process_rhs(compiler, connection)
            sql_template = connection.ops.regex_lookup(self.lookup_name)
            return sql_template % (lhs, rhs), lhs_params + rhs_params


@Field.register_lookup
class IRegex(Regex):
    lookup_name = "iregex"


class YearLookup(Lookup):
    def year_lookup_bounds(self, connection, year):
        from django.db.models.functions import ExtractIsoYear

        iso_year = isinstance(self.lhs, ExtractIsoYear)
        output_field = self.lhs.lhs.output_field
        if isinstance(output_field, DateTimeField):
            bounds = connection.ops.year_lookup_bounds_for_datetime_field(
                year,
                iso_year=iso_year,
            )
        else:
            bounds = connection.ops.year_lookup_bounds_for_date_field(
                year,
                iso_year=iso_year,
            )
        return bounds

    def as_sql(self, compiler, connection):
        # Avoid the extract operation if the rhs is a direct value to allow
        # indexes to be used.
        if self.rhs_is_direct_value():
            # Skip the extract part by directly using the originating field,
            # that is self.lhs.lhs.
            lhs_sql, params = self.process_lhs(compiler, connection, self.lhs.lhs)
            rhs_sql, _ = self.process_rhs(compiler, connection)
            rhs_sql = self.get_direct_rhs_sql(connection, rhs_sql)
            start, finish = self.year_lookup_bounds(connection, self.rhs)
            params.extend(self.get_bound_params(start, finish))
            return "%s %s" % (lhs_sql, rhs_sql), params
        return super().as_sql(compiler, connection)

    def get_direct_rhs_sql(self, connection, rhs):
        return connection.operators[self.lookup_name] % rhs

    def get_bound_params(self, start, finish):
        raise NotImplementedError(
            "subclasses of YearLookup must provide a get_bound_params() method"
        )


class YearExact(YearLookup, Exact):
    def get_direct_rhs_sql(self, connection, rhs):
        return "BETWEEN %s AND %s"

    def get_bound_params(self, start, finish):
        return (start, finish)


class YearGt(YearLookup, GreaterThan):
    def get_bound_params(self, start, finish):
        return (finish,)


class YearGte(YearLookup, GreaterThanOrEqual):
    def get_bound_params(self, start, finish):
        return (start,)


class YearLt(YearLookup, LessThan):
    def get_bound_params(self, start, finish):
        return (start,)


class YearLte(YearLookup, LessThanOrEqual):
    def get_bound_params(self, start, finish):
        return (finish,)


class UUIDTextMixin:
    """
    Strip hyphens from a value when filtering a UUIDField on backends without
    a native datatype for UUID.
    """

    def process_rhs(self, qn, connection):
        if not connection.features.has_native_uuid_field:
            from django.db.models.functions import Replace

            if self.rhs_is_direct_value():
                self.rhs = Value(self.rhs)
            self.rhs = Replace(
                self.rhs, Value("-"), Value(""), output_field=CharField()
            )
        rhs, params = super().process_rhs(qn, connection)
        return rhs, params


@UUIDField.register_lookup
class UUIDIExact(UUIDTextMixin, IExact):
    pass


@UUIDField.register_lookup
class UUIDContains(UUIDTextMixin, Contains):
    pass


@UUIDField.register_lookup
class UUIDIContains(UUIDTextMixin, IContains):
    pass


@UUIDField.register_lookup
class UUIDStartsWith(UUIDTextMixin, StartsWith):
    pass


@UUIDField.register_lookup
class UUIDIStartsWith(UUIDTextMixin, IStartsWith):
    pass


@UUIDField.register_lookup
class UUIDEndsWith(UUIDTextMixin, EndsWith):
    pass


@UUIDField.register_lookup
class UUIDIEndsWith(UUIDTextMixin, IEndsWith):
    pass

virt/lib/python3.9/site-packages/django/db/models/lookups 3.py
PrinceDisant/LostInNL
import itertools
import math

from django.core.exceptions import EmptyResultSet, FullResultSet
from django.db.models.expressions import Case, Expression, Func, Value, When
from django.db.models.fields import (
    BooleanField,
    CharField,
    DateTimeField,
    Field,
    IntegerField,
    UUIDField,
)
from django.db.models.query_utils import RegisterLookupMixin
from django.utils.datastructures import OrderedSet
from django.utils.functional import cached_property
from django.utils.hashable import make_hashable


class Lookup(Expression):
    lookup_name = None
    prepare_rhs = True
    can_use_none_as_rhs = False

    def __init__(self, lhs, rhs):
        self.lhs, self.rhs = lhs, rhs
        self.rhs = self.get_prep_lookup()
        self.lhs = self.get_prep_lhs()
        if hasattr(self.lhs, "get_bilateral_transforms"):
            bilateral_transforms = self.lhs.get_bilateral_transforms()
        else:
            bilateral_transforms = []
        if bilateral_transforms:
            # Warn the user as soon as possible if they are trying to apply
            # a bilateral transformation on a nested QuerySet: that won't work.
            from django.db.models.sql.query import Query  # avoid circular import

            if isinstance(rhs, Query):
                raise NotImplementedError(
                    "Bilateral transformations on nested querysets are not implemented."
                )
        self.bilateral_transforms = bilateral_transforms

    def apply_bilateral_transforms(self, value):
        for transform in self.bilateral_transforms:
            value = transform(value)
        return value

    def __repr__(self):
        return f"{self.__class__.__name__}({self.lhs!r}, {self.rhs!r})"

    def batch_process_rhs(self, compiler, connection, rhs=None):
        if rhs is None:
            rhs = self.rhs
        if self.bilateral_transforms:
            sqls, sqls_params = [], []
            for p in rhs:
                value = Value(p, output_field=self.lhs.output_field)
                value = self.apply_bilateral_transforms(value)
                value = value.resolve_expression(compiler.query)
                sql, sql_params = compiler.compile(value)
                sqls.append(sql)
                sqls_params.extend(sql_params)
        else:
            _, params = self.get_db_prep_lookup(rhs, connection)
            sqls, sqls_params = ["%s"] * len(params), params
        return sqls, sqls_params

    def get_source_expressions(self):
        if self.rhs_is_direct_value():
            return [self.lhs]
        return [self.lhs, self.rhs]

    def set_source_expressions(self, new_exprs):
        if len(new_exprs) == 1:
            self.lhs = new_exprs[0]
        else:
            self.lhs, self.rhs = new_exprs

    def get_prep_lookup(self):
        if not self.prepare_rhs or hasattr(self.rhs, "resolve_expression"):
            return self.rhs
        if hasattr(self.lhs, "output_field"):
            if hasattr(self.lhs.output_field, "get_prep_value"):
                return self.lhs.output_field.get_prep_value(self.rhs)
        elif self.rhs_is_direct_value():
            return Value(self.rhs)
        return self.rhs

    def get_prep_lhs(self):
        if hasattr(self.lhs, "resolve_expression"):
            return self.lhs
        return Value(self.lhs)

    def get_db_prep_lookup(self, value, connection):
        return ("%s", [value])

    def process_lhs(self, compiler, connection, lhs=None):
        lhs = lhs or self.lhs
        if hasattr(lhs, "resolve_expression"):
            lhs = lhs.resolve_expression(compiler.query)
        sql, params = compiler.compile(lhs)
        if isinstance(lhs, Lookup):
            # Wrapped in parentheses to respect operator precedence.
            sql = f"({sql})"
        return sql, params

    def process_rhs(self, compiler, connection):
        value = self.rhs
        if self.bilateral_transforms:
            if self.rhs_is_direct_value():
                # Do not call get_db_prep_lookup here as the value will be
                # transformed before being used for lookup
                value = Value(value, output_field=self.lhs.output_field)
            value = self.apply_bilateral_transforms(value)
            value = value.resolve_expression(compiler.query)
        if hasattr(value, "as_sql"):
            sql, params = compiler.compile(value)
            # Ensure expression is wrapped in parentheses to respect operator
            # precedence but avoid double wrapping as it can be misinterpreted
            # on some backends (e.g. subqueries on SQLite).
            if sql and sql[0] != "(":
                sql = "(%s)" % sql
            return sql, params
        else:
            return self.get_db_prep_lookup(value, connection)

    def rhs_is_direct_value(self):
        return not hasattr(self.rhs, "as_sql")

    def get_group_by_cols(self):
        cols = []
        for source in self.get_source_expressions():
            cols.extend(source.get_group_by_cols())
        return cols

    def as_oracle(self, compiler, connection):
        # Oracle doesn't allow EXISTS() and filters to be compared to another
        # expression unless they're wrapped in a CASE WHEN.
        wrapped = False
        exprs = []
        for expr in (self.lhs, self.rhs):
            if connection.ops.conditional_expression_supported_in_where_clause(expr):
                expr = Case(When(expr, then=True), default=False)
                wrapped = True
            exprs.append(expr)
        lookup = type(self)(*exprs) if wrapped else self
        return lookup.as_sql(compiler, connection)

    @cached_property
    def output_field(self):
        return BooleanField()

    @property
    def identity(self):
        return self.__class__, self.lhs, self.rhs

    def __eq__(self, other):
        if not isinstance(other, Lookup):
            return NotImplemented
        return self.identity == other.identity

    def __hash__(self):
        return hash(make_hashable(self.identity))

    def resolve_expression(
        self, query=None, allow_joins=True, reuse=None, summarize=False, for_save=False
    ):
        c = self.copy()
        c.is_summary = summarize
        c.lhs = self.lhs.resolve_expression(
            query, allow_joins, reuse, summarize, for_save
        )
        if hasattr(self.rhs, "resolve_expression"):
            c.rhs = self.rhs.resolve_expression(
                query, allow_joins, reuse, summarize, for_save
            )
        return c

    def select_format(self, compiler, sql, params):
        # Wrap filters with a CASE WHEN expression if a database backend
        # (e.g. Oracle) doesn't support boolean expression in SELECT or GROUP
        # BY list.
        if not compiler.connection.features.supports_boolean_expr_in_select_clause:
            sql = f"CASE WHEN {sql} THEN 1 ELSE 0 END"
        return sql, params


class Transform(RegisterLookupMixin, Func):
    """
    RegisterLookupMixin() is first so that get_lookup() and get_transform()
    first examine self and then check output_field.
    """

    bilateral = False
    arity = 1

    @property
    def lhs(self):
        return self.get_source_expressions()[0]

    def get_bilateral_transforms(self):
        if hasattr(self.lhs, "get_bilateral_transforms"):
            bilateral_transforms = self.lhs.get_bilateral_transforms()
        else:
            bilateral_transforms = []
        if self.bilateral:
            bilateral_transforms.append(self.__class__)
        return bilateral_transforms


class BuiltinLookup(Lookup):
    def process_lhs(self, compiler, connection, lhs=None):
        lhs_sql, params = super().process_lhs(compiler, connection, lhs)
        field_internal_type = self.lhs.output_field.get_internal_type()
        db_type = self.lhs.output_field.db_type(connection=connection)
        lhs_sql = connection.ops.field_cast_sql(db_type, field_internal_type) % lhs_sql
        lhs_sql = (
            connection.ops.lookup_cast(self.lookup_name, field_internal_type) % lhs_sql
        )
        return lhs_sql, list(params)

    def as_sql(self, compiler, connection):
        lhs_sql, params = self.process_lhs(compiler, connection)
        rhs_sql, rhs_params = self.process_rhs(compiler, connection)
        params.extend(rhs_params)
        rhs_sql = self.get_rhs_op(connection, rhs_sql)
        return "%s %s" % (lhs_sql, rhs_sql), params

    def get_rhs_op(self, connection, rhs):
        return connection.operators[self.lookup_name] % rhs


class FieldGetDbPrepValueMixin:
    """
    Some lookups require Field.get_db_prep_value() to be called on their
    inputs.
    """

    get_db_prep_lookup_value_is_iterable = False

    def get_db_prep_lookup(self, value, connection):
        # For relational fields, use the 'target_field' attribute of the
        # output_field.
        field = getattr(self.lhs.output_field, "target_field", None)
        get_db_prep_value = (
            getattr(field, "get_db_prep_value", None)
            or self.lhs.output_field.get_db_prep_value
        )
        return (
            "%s",
            [get_db_prep_value(v, connection, prepared=True) for v in value]
            if self.get_db_prep_lookup_value_is_iterable
            else [get_db_prep_value(value, connection, prepared=True)],
        )


class FieldGetDbPrepValueIterableMixin(FieldGetDbPrepValueMixin):
    """
    Some lookups require Field.get_db_prep_value() to be called on each value
    in an iterable.
    """

    get_db_prep_lookup_value_is_iterable = True

    def get_prep_lookup(self):
        if hasattr(self.rhs, "resolve_expression"):
            return self.rhs
        prepared_values = []
        for rhs_value in self.rhs:
            if hasattr(rhs_value, "resolve_expression"):
                # An expression will be handled by the database but can coexist
                # alongside real values.
                pass
            elif self.prepare_rhs and hasattr(self.lhs.output_field, "get_prep_value"):
                rhs_value = self.lhs.output_field.get_prep_value(rhs_value)
            prepared_values.append(rhs_value)
        return prepared_values

    def process_rhs(self, compiler, connection):
        if self.rhs_is_direct_value():
            # rhs should be an iterable of values. Use batch_process_rhs()
            # to prepare/transform those values.
            return self.batch_process_rhs(compiler, connection)
        else:
            return super().process_rhs(compiler, connection)

    def resolve_expression_parameter(self, compiler, connection, sql, param):
        params = [param]
        if hasattr(param, "resolve_expression"):
            param = param.resolve_expression(compiler.query)
        if hasattr(param, "as_sql"):
            sql, params = compiler.compile(param)
        return sql, params

    def batch_process_rhs(self, compiler, connection, rhs=None):
        pre_processed = super().batch_process_rhs(compiler, connection, rhs)
        # The params list may contain expressions which compile to a
        # sql/param pair. Zip them to get sql and param pairs that refer to the
        # same argument and attempt to replace them with the result of
        # compiling the param step.
        sql, params = zip(
            *(
                self.resolve_expression_parameter(compiler, connection, sql, param)
                for sql, param in zip(*pre_processed)
            )
        )
        params = itertools.chain.from_iterable(params)
        return sql, tuple(params)


class PostgresOperatorLookup(Lookup):
    """Lookup defined by operators on PostgreSQL."""

    postgres_operator = None

    def as_postgresql(self, compiler, connection):
        lhs, lhs_params = self.process_lhs(compiler, connection)
        rhs, rhs_params = self.process_rhs(compiler, connection)
        params = tuple(lhs_params) + tuple(rhs_params)
        return "%s %s %s" % (lhs, self.postgres_operator, rhs), params


@Field.register_lookup
class Exact(FieldGetDbPrepValueMixin, BuiltinLookup):
    lookup_name = "exact"

    def get_prep_lookup(self):
        from django.db.models.sql.query import Query  # avoid circular import

        if isinstance(self.rhs, Query):
            if self.rhs.has_limit_one():
                if not self.rhs.has_select_fields:
                    self.rhs.clear_select_clause()
                    self.rhs.add_fields(["pk"])
            else:
                raise ValueError(
                    "The QuerySet value for an exact lookup must be limited to "
                    "one result using slicing."
                )
        return super().get_prep_lookup()

    def as_sql(self, compiler, connection):
        # Avoid comparison against direct rhs if lhs is a boolean value. That
        # turns "boolfield__exact=True" into "WHERE boolean_field" instead of
        # "WHERE boolean_field = True" when allowed.
        if (
            isinstance(self.rhs, bool)
            and getattr(self.lhs, "conditional", False)
            and connection.ops.conditional_expression_supported_in_where_clause(
                self.lhs
            )
        ):
            lhs_sql, params = self.process_lhs(compiler, connection)
            template = "%s" if self.rhs else "NOT %s"
            return template % lhs_sql, params
        return super().as_sql(compiler, connection)


@Field.register_lookup
class IExact(BuiltinLookup):
    lookup_name = "iexact"
    prepare_rhs = False

    def process_rhs(self, qn, connection):
        rhs, params = super().process_rhs(qn, connection)
        if params:
            params[0] = connection.ops.prep_for_iexact_query(params[0])
        return rhs, params


@Field.register_lookup
class GreaterThan(FieldGetDbPrepValueMixin, BuiltinLookup):
    lookup_name = "gt"


@Field.register_lookup
class GreaterThanOrEqual(FieldGetDbPrepValueMixin, BuiltinLookup):
    lookup_name = "gte"


@Field.register_lookup
class LessThan(FieldGetDbPrepValueMixin, BuiltinLookup):
    lookup_name = "lt"


@Field.register_lookup
class LessThanOrEqual(FieldGetDbPrepValueMixin, BuiltinLookup):
    lookup_name = "lte"


class IntegerFieldFloatRounding:
    """
    Allow floats to work as query values for IntegerField. Without this, the
    decimal portion of the float would always be discarded.
    """

    def get_prep_lookup(self):
        if isinstance(self.rhs, float):
            self.rhs = math.ceil(self.rhs)
        return super().get_prep_lookup()


@IntegerField.register_lookup
class IntegerGreaterThanOrEqual(IntegerFieldFloatRounding, GreaterThanOrEqual):
    pass


@IntegerField.register_lookup
class IntegerLessThan(IntegerFieldFloatRounding, LessThan):
    pass


@Field.register_lookup
class In(FieldGetDbPrepValueIterableMixin, BuiltinLookup):
    lookup_name = "in"

    def get_prep_lookup(self):
        from django.db.models.sql.query import Query  # avoid circular import

        if isinstance(self.rhs, Query):
            self.rhs.clear_ordering(clear_default=True)
            if not self.rhs.has_select_fields:
                self.rhs.clear_select_clause()
                self.rhs.add_fields(["pk"])
        return super().get_prep_lookup()

    def process_rhs(self, compiler, connection):
        db_rhs = getattr(self.rhs, "_db", None)
        if db_rhs is not None and db_rhs != connection.alias:
            raise ValueError(
                "Subqueries aren't allowed across different databases. Force "
                "the inner query to be evaluated using `list(inner_query)`."
            )

        if self.rhs_is_direct_value():
            # Remove None from the list as NULL is never equal to anything.
            try:
                rhs = OrderedSet(self.rhs)
                rhs.discard(None)
            except TypeError:  # Unhashable items in self.rhs
                rhs = [r for r in self.rhs if r is not None]

            if not rhs:
                raise EmptyResultSet

            # rhs should be an iterable; use batch_process_rhs() to
            # prepare/transform those values.
            sqls, sqls_params = self.batch_process_rhs(compiler, connection, rhs)
            placeholder = "(" + ", ".join(sqls) + ")"
            return (placeholder, sqls_params)
        return super().process_rhs(compiler, connection)

    def get_rhs_op(self, connection, rhs):
        return "IN %s" % rhs

    def as_sql(self, compiler, connection):
        max_in_list_size = connection.ops.max_in_list_size()
        if (
            self.rhs_is_direct_value()
            and max_in_list_size
            and len(self.rhs) > max_in_list_size
        ):
            return self.split_parameter_list_as_sql(compiler, connection)
        return super().as_sql(compiler, connection)

    def split_parameter_list_as_sql(self, compiler, connection):
        # This is a special case for databases which limit the number of
        # elements which can appear in an 'IN' clause.
        max_in_list_size = connection.ops.max_in_list_size()
        lhs, lhs_params = self.process_lhs(compiler, connection)
        rhs, rhs_params = self.batch_process_rhs(compiler, connection)
        in_clause_elements = ["("]
        params = []
        for offset in range(0, len(rhs_params), max_in_list_size):
            if offset > 0:
                in_clause_elements.append(" OR ")
            in_clause_elements.append("%s IN (" % lhs)
            params.extend(lhs_params)
            sqls = rhs[offset : offset + max_in_list_size]
            sqls_params = rhs_params[offset : offset + max_in_list_size]
            param_group = ", ".join(sqls)
            in_clause_elements.append(param_group)
            in_clause_elements.append(")")
            params.extend(sqls_params)
        in_clause_elements.append(")")
        return "".join(in_clause_elements), params


class PatternLookup(BuiltinLookup):
    param_pattern = "%%%s%%"
    prepare_rhs = False

    def get_rhs_op(self, connection, rhs):
        # Assume we are in startswith. We need to produce SQL like:
        #     col LIKE %s, ['thevalue%']
        # For python values we can (and should) do that directly in Python,
        # but if the value is for example reference to other column, then
        # we need to add the % pattern match to the lookup by something like
        #     col LIKE othercol || '%%'
        # So, for Python values we don't need any special pattern, but for
        # SQL reference values or SQL transformations we need the correct
        # pattern added.
        if hasattr(self.rhs, "as_sql") or self.bilateral_transforms:
            pattern = connection.pattern_ops[self.lookup_name].format(
                connection.pattern_esc
            )
            return pattern.format(rhs)
        else:
            return super().get_rhs_op(connection, rhs)

    def process_rhs(self, qn, connection):
        rhs, params = super().process_rhs(qn, connection)
        if self.rhs_is_direct_value() and params and not self.bilateral_transforms:
            params[0] = self.param_pattern % connection.ops.prep_for_like_query(
                params[0]
            )
        return rhs, params


@Field.register_lookup
class Contains(PatternLookup):
    lookup_name = "contains"


@Field.register_lookup
class IContains(Contains):
    lookup_name = "icontains"


@Field.register_lookup
class StartsWith(PatternLookup):
    lookup_name = "startswith"
    param_pattern = "%s%%"


@Field.register_lookup
class IStartsWith(StartsWith):
    lookup_name = "istartswith"


@Field.register_lookup
class EndsWith(PatternLookup):
    lookup_name = "endswith"
    param_pattern = "%%%s"


@Field.register_lookup
class IEndsWith(EndsWith):
    lookup_name = "iendswith"


@Field.register_lookup
class Range(FieldGetDbPrepValueIterableMixin, BuiltinLookup):
    lookup_name = "range"

    def get_rhs_op(self, connection, rhs):
        return "BETWEEN %s AND %s" % (rhs[0], rhs[1])


@Field.register_lookup
class IsNull(BuiltinLookup):
    lookup_name = "isnull"
    prepare_rhs = False

    def as_sql(self, compiler, connection):
        if not isinstance(self.rhs, bool):
            raise ValueError(
                "The QuerySet value for an isnull lookup must be True or False."
            )
        if isinstance(self.lhs, Value) and self.lhs.value is None:
            if self.rhs:
                raise FullResultSet
            else:
                raise EmptyResultSet
        sql, params = self.process_lhs(compiler, connection)
        if self.rhs:
            return "%s IS NULL" % sql, params
        else:
            return "%s IS NOT NULL" % sql, params


@Field.register_lookup
class Regex(BuiltinLookup):
    lookup_name = "regex"
    prepare_rhs = False

    def as_sql(self, compiler, connection):
        if self.lookup_name in connection.operators:
            return super().as_sql(compiler, connection)
        else:
            lhs, lhs_params = self.process_lhs(compiler, connection)
            rhs, rhs_params = self.process_rhs(compiler, connection)
            sql_template = connection.ops.regex_lookup(self.lookup_name)
            return sql_template % (lhs, rhs), lhs_params + rhs_params


@Field.register_lookup
class IRegex(Regex):
    lookup_name = "iregex"


class YearLookup(Lookup):
    def year_lookup_bounds(self, connection, year):
        from django.db.models.functions import ExtractIsoYear

        iso_year = isinstance(self.lhs, ExtractIsoYear)
        output_field = self.lhs.lhs.output_field
        if isinstance(output_field, DateTimeField):
            bounds = connection.ops.year_lookup_bounds_for_datetime_field(
                year,
                iso_year=iso_year,
            )
        else:
            bounds = connection.ops.year_lookup_bounds_for_date_field(
                year,
                iso_year=iso_year,
            )
        return bounds

    def as_sql(self, compiler, connection):
        # Avoid the extract operation if the rhs is a direct value to allow
        # indexes to be used.
        if self.rhs_is_direct_value():
            # Skip the extract part by directly using the originating field,
            # that is self.lhs.lhs.
            lhs_sql, params = self.process_lhs(compiler, connection, self.lhs.lhs)
            rhs_sql, _ = self.process_rhs(compiler, connection)
            rhs_sql = self.get_direct_rhs_sql(connection, rhs_sql)
            start, finish = self.year_lookup_bounds(connection, self.rhs)
            params.extend(self.get_bound_params(start, finish))
            return "%s %s" % (lhs_sql, rhs_sql), params
        return super().as_sql(compiler, connection)

    def get_direct_rhs_sql(self, connection, rhs):
        return connection.operators[self.lookup_name] % rhs

    def get_bound_params(self, start, finish):
        raise NotImplementedError(
            "subclasses of YearLookup must provide a get_bound_params() method"
        )


class YearExact(YearLookup, Exact):
    def get_direct_rhs_sql(self, connection, rhs):
        return "BETWEEN %s AND %s"

    def get_bound_params(self, start, finish):
        return (start, finish)


class YearGt(YearLookup, GreaterThan):
    def get_bound_params(self, start, finish):
        return (finish,)


class YearGte(YearLookup, GreaterThanOrEqual):
    def get_bound_params(self, start, finish):
        return (start,)


class YearLt(YearLookup, LessThan):
    def get_bound_params(self, start, finish):
        return (start,)


class YearLte(YearLookup, LessThanOrEqual):
    def get_bound_params(self, start, finish):
        return (finish,)


class UUIDTextMixin:
    """
    Strip hyphens from a value when filtering a UUIDField on backends without
    a native datatype for UUID.
    """

    def process_rhs(self, qn, connection):
        if not connection.features.has_native_uuid_field:
            from django.db.models.functions import Replace

            if self.rhs_is_direct_value():
                self.rhs = Value(self.rhs)
            self.rhs = Replace(
                self.rhs, Value("-"), Value(""), output_field=CharField()
            )
        rhs, params = super().process_rhs(qn, connection)
        return rhs, params


@UUIDField.register_lookup
class UUIDIExact(UUIDTextMixin, IExact):
    pass


@UUIDField.register_lookup
class UUIDContains(UUIDTextMixin, Contains):
    pass


@UUIDField.register_lookup
class UUIDIContains(UUIDTextMixin, IContains):
    pass


@UUIDField.register_lookup
class UUIDStartsWith(UUIDTextMixin, StartsWith):
    pass


@UUIDField.register_lookup
class UUIDIStartsWith(UUIDTextMixin, IStartsWith):
    pass


@UUIDField.register_lookup
class UUIDEndsWith(UUIDTextMixin, EndsWith):
    pass


@UUIDField.register_lookup
class UUIDIEndsWith(UUIDTextMixin, IEndsWith):
    pass

virt/lib/python3.9/site-packages/django/db/models/lookups.py
PrinceDisant/LostInNL
"""
PHP date() style date formatting
See https://www.php.net/date for format strings

Usage:
>>> from datetime import datetime
>>> d = datetime.now()
>>> df = DateFormat(d)
>>> print(df.format('jS F Y H:i'))
7th October 2003 11:39
>>>
"""
import calendar
from datetime import date, datetime, time
from email.utils import format_datetime as format_datetime_rfc5322

from django.utils.dates import (
    MONTHS,
    MONTHS_3,
    MONTHS_ALT,
    MONTHS_AP,
    WEEKDAYS,
    WEEKDAYS_ABBR,
)
from django.utils.regex_helper import _lazy_re_compile
from django.utils.timezone import (
    _datetime_ambiguous_or_imaginary,
    get_default_timezone,
    is_naive,
    make_aware,
)
from django.utils.translation import gettext as _

re_formatchars = _lazy_re_compile(r"(?<!\\)([aAbcdDeEfFgGhHiIjlLmMnNoOPrsStTUuwWyYzZ])")
re_escaped = _lazy_re_compile(r"\\(.)")


class Formatter:
    def format(self, formatstr):
        pieces = []
        for i, piece in enumerate(re_formatchars.split(str(formatstr))):
            if i % 2:
                if type(self.data) is date and hasattr(TimeFormat, piece):
                    raise TypeError(
                        "The format for date objects may not contain "
                        "time-related format specifiers (found '%s')." % piece
                    )
                pieces.append(str(getattr(self, piece)()))
            elif piece:
                pieces.append(re_escaped.sub(r"\1", piece))
        return "".join(pieces)


class TimeFormat(Formatter):
    def __init__(self, obj):
        self.data = obj
        self.timezone = None

        if isinstance(obj, datetime):
            # Timezone is only supported when formatting datetime objects, not
            # date objects (timezone information not appropriate), or time
            # objects (against established django policy).
            if is_naive(obj):
                timezone = get_default_timezone()
            else:
                timezone = obj.tzinfo
            if not _datetime_ambiguous_or_imaginary(obj, timezone):
                self.timezone = timezone

    def a(self):
        "'a.m.' or 'p.m.'"
        if self.data.hour > 11:
            return _("p.m.")
        return _("a.m.")

    def A(self):
        "'AM' or 'PM'"
        if self.data.hour > 11:
            return _("PM")
        return _("AM")

    def e(self):
        """
        Timezone name.

        If timezone information is not available, return an empty string.
        """
        if not self.timezone:
            return ""

        try:
            if getattr(self.data, "tzinfo", None):
                return self.data.tzname() or ""
        except NotImplementedError:
            pass
        return ""

    def f(self):
        """
        Time, in 12-hour hours and minutes, with minutes left off if they're
        zero.
        Examples: '1', '1:30', '2:05', '2'
        Proprietary extension.
        """
        hour = self.data.hour % 12 or 12
        minute = self.data.minute
        return "%d:%02d" % (hour, minute) if minute else hour

    def g(self):
        "Hour, 12-hour format without leading zeros; i.e. '1' to '12'"
        return self.data.hour % 12 or 12

    def G(self):
        "Hour, 24-hour format without leading zeros; i.e. '0' to '23'"
        return self.data.hour

    def h(self):
        "Hour, 12-hour format; i.e. '01' to '12'"
        return "%02d" % (self.data.hour % 12 or 12)

    def H(self):
        "Hour, 24-hour format; i.e. '00' to '23'"
        return "%02d" % self.data.hour

    def i(self):
        "Minutes; i.e. '00' to '59'"
        return "%02d" % self.data.minute

    def O(self):  # NOQA: E743, E741
        """
        Difference to Greenwich time in hours; e.g. '+0200', '-0430'.

        If timezone information is not available, return an empty string.
        """
        if self.timezone is None:
            return ""

        offset = self.timezone.utcoffset(self.data)
        seconds = offset.days * 86400 + offset.seconds
        sign = "-" if seconds < 0 else "+"
        seconds = abs(seconds)
        return "%s%02d%02d" % (sign, seconds // 3600, (seconds // 60) % 60)

    def P(self):
        """
        Time, in 12-hour hours, minutes and 'a.m.'/'p.m.', with minutes left off
        if they're zero and the strings 'midnight' and 'noon' if appropriate.
        Examples: '1 a.m.', '1:30 p.m.', 'midnight', 'noon', '12:30 p.m.'
        Proprietary extension.
        """
        if self.data.minute == 0 and self.data.hour == 0:
            return _("midnight")
        if self.data.minute == 0 and self.data.hour == 12:
            return _("noon")
        return "%s %s" % (self.f(), self.a())

    def s(self):
        "Seconds; i.e. '00' to '59'"
        return "%02d" % self.data.second

    def T(self):
        """
        Time zone of this machine; e.g. 'EST' or 'MDT'.

        If timezone information is not available, return an empty string.
        """
        if self.timezone is None:
            return ""

        return str(self.timezone.tzname(self.data))

    def u(self):
        "Microseconds; i.e. '000000' to '999999'"
        return "%06d" % self.data.microsecond

    def Z(self):
        """
        Time zone offset in seconds (i.e. '-43200' to '43200'). The offset for
        timezones west of UTC is always negative, and for those east of UTC is
        always positive.

        If timezone information is not available, return an empty string.
        """
        if self.timezone is None:
            return ""

        offset = self.timezone.utcoffset(self.data)

        # `offset` is a datetime.timedelta. For negative values (to the west of
        # UTC) only days can be negative (days=-1) and seconds are always
        # positive. e.g. UTC-1 -> timedelta(days=-1, seconds=82800, microseconds=0)
        # Positive offsets have days=0
        return offset.days * 86400 + offset.seconds


class DateFormat(TimeFormat):
    def b(self):
        "Month, textual, 3 letters, lowercase; e.g. 'jan'"
        return MONTHS_3[self.data.month]

    def c(self):
        """
        ISO 8601 Format
        Example : '2008-01-02T10:30:00.000123'
        """
        return self.data.isoformat()

    def d(self):
        "Day of the month, 2 digits with leading zeros; i.e. '01' to '31'"
        return "%02d" % self.data.day

    def D(self):
        "Day of the week, textual, 3 letters; e.g. 'Fri'"
        return WEEKDAYS_ABBR[self.data.weekday()]

    def E(self):
        "Alternative month names as required by some locales. Proprietary extension."
        return MONTHS_ALT[self.data.month]

    def F(self):
        "Month, textual, long; e.g. 'January'"
        return MONTHS[self.data.month]

    def I(self):  # NOQA: E743, E741
        "'1' if daylight saving time, '0' otherwise."
        if self.timezone is None:
            return ""
        return "1" if self.timezone.dst(self.data) else "0"

    def j(self):
        "Day of the month without leading zeros; i.e. '1' to '31'"
        return self.data.day

    def l(self):  # NOQA: E743, E741
        "Day of the week, textual, long; e.g. 'Friday'"
        return WEEKDAYS[self.data.weekday()]

    def L(self):
        "Boolean for whether it is a leap year; i.e. True or False"
        return calendar.isleap(self.data.year)

    def m(self):
        "Month; i.e. '01' to '12'"
        return "%02d" % self.data.month

    def M(self):
        "Month, textual, 3 letters; e.g. 'Jan'"
        return MONTHS_3[self.data.month].title()

    def n(self):
        "Month without leading zeros; i.e. '1' to '12'"
        return self.data.month

    def N(self):
        "Month abbreviation in Associated Press style. Proprietary extension."
        return MONTHS_AP[self.data.month]

    def o(self):
        "ISO 8601 year number matching the ISO week number (W)"
        return self.data.isocalendar()[0]

    def r(self):
        "RFC 5322 formatted date; e.g. 'Thu, 21 Dec 2000 16:01:07 +0200'"
        value = self.data
        if not isinstance(value, datetime):
            # Assume midnight in default timezone if datetime.date provided.
            default_timezone = get_default_timezone()
            value = datetime.combine(value, time.min).replace(tzinfo=default_timezone)
        elif is_naive(value):
            value = make_aware(value, timezone=self.timezone)
        return format_datetime_rfc5322(value)

    def S(self):
        """
        English ordinal suffix for the day of the month, 2 characters; i.e.
        'st', 'nd', 'rd' or 'th'.
        """
        if self.data.day in (11, 12, 13):  # Special case
            return "th"
        last = self.data.day % 10
        if last == 1:
            return "st"
        if last == 2:
            return "nd"
        if last == 3:
            return "rd"
        return "th"

    def t(self):
        "Number of days in the given month; i.e. '28' to '31'"
        return calendar.monthrange(self.data.year, self.data.month)[1]

    def U(self):
        "Seconds since the Unix epoch (January 1 1970 00:00:00 GMT)"
        value = self.data
        if not isinstance(value, datetime):
            value = datetime.combine(value, time.min)
        return int(value.timestamp())

    def w(self):
        "Day of the week, numeric, i.e. '0' (Sunday) to '6' (Saturday)"
        return (self.data.weekday() + 1) % 7

    def W(self):
        "ISO-8601 week number of year, weeks starting on Monday"
        return self.data.isocalendar()[1]

    def y(self):
        """Year, 2 digits with leading zeros; e.g. '99'."""
        return "%02d" % (self.data.year % 100)

    def Y(self):
        """Year, 4 digits with leading zeros; e.g. '1999'."""
        return "%04d" % self.data.year

    def z(self):
        """Day of the year, i.e. 1 to 366."""
        return self.data.timetuple().tm_yday


def format(value, format_string):
    "Convenience function"
    df = DateFormat(value)
    return df.format(format_string)


def time_format(value, format_string):
    "Convenience function"
    tf = TimeFormat(value)
    return tf.format(format_string)

virt/lib/python3.9/site-packages/django/utils/dateformat.py
PrinceDisant/LostInNL
import codecs
import datetime
import locale
from decimal import Decimal
from urllib.parse import quote

from django.utils.functional import Promise


class DjangoUnicodeDecodeError(UnicodeDecodeError):
    def __init__(self, obj, *args):
        self.obj = obj
        super().__init__(*args)

    def __str__(self):
        return "%s. You passed in %r (%s)" % (
            super().__str__(),
            self.obj,
            type(self.obj),
        )


def smart_str(s, encoding="utf-8", strings_only=False, errors="strict"):
    """
    Return a string representing 's'. Treat bytestrings using the 'encoding'
    codec.

    If strings_only is True, don't convert (some) non-string-like objects.
    """
    if isinstance(s, Promise):
        # The input is the result of a gettext_lazy() call.
        return s
    return force_str(s, encoding, strings_only, errors)


_PROTECTED_TYPES = (
    type(None),
    int,
    float,
    Decimal,
    datetime.datetime,
    datetime.date,
    datetime.time,
)


def is_protected_type(obj):
    """Determine if the object instance is of a protected type.

    Objects of protected types are preserved as-is when passed to
    force_str(strings_only=True).
    """
    return isinstance(obj, _PROTECTED_TYPES)


def force_str(s, encoding="utf-8", strings_only=False, errors="strict"):
    """
    Similar to smart_str(), except that lazy instances are resolved to
    strings, rather than kept as lazy objects.

    If strings_only is True, don't convert (some) non-string-like objects.
    """
    # Handle the common case first for performance reasons.
    if issubclass(type(s), str):
        return s
    if strings_only and is_protected_type(s):
        return s
    try:
        if isinstance(s, bytes):
            s = str(s, encoding, errors)
        else:
            s = str(s)
    except UnicodeDecodeError as e:
        raise DjangoUnicodeDecodeError(s, *e.args)
    return s


def smart_bytes(s, encoding="utf-8", strings_only=False, errors="strict"):
    """
    Return a bytestring version of 's', encoded as specified in 'encoding'.

    If strings_only is True, don't convert (some) non-string-like objects.
    """
    if isinstance(s, Promise):
        # The input is the result of a gettext_lazy() call.
        return s
    return force_bytes(s, encoding, strings_only, errors)


def force_bytes(s, encoding="utf-8", strings_only=False, errors="strict"):
    """
    Similar to smart_bytes, except that lazy instances are resolved to
    strings, rather than kept as lazy objects.

    If strings_only is True, don't convert (some) non-string-like objects.
    """
    # Handle the common case first for performance reasons.
    if isinstance(s, bytes):
        if encoding == "utf-8":
            return s
        else:
            return s.decode("utf-8", errors).encode(encoding, errors)
    if strings_only and is_protected_type(s):
        return s
    if isinstance(s, memoryview):
        return bytes(s)
    return str(s).encode(encoding, errors)


def iri_to_uri(iri):
    """
    Convert an Internationalized Resource Identifier (IRI) portion to a URI
    portion that is suitable for inclusion in a URL.

    This is the algorithm from RFC 3987 Section 3.1, slightly simplified since
    the input is assumed to be a string rather than an arbitrary byte stream.

    Take an IRI (string or UTF-8 bytes, e.g. '/I ♥ Django/' or
    b'/I \xe2\x99\xa5 Django/') and return a string containing the encoded
    result with ASCII chars only (e.g. '/I%20%E2%99%A5%20Django/').
    """
    # The list of safe characters here is constructed from the "reserved" and
    # "unreserved" characters specified in RFC 3986 Sections 2.2 and 2.3:
    #     reserved    = gen-delims / sub-delims
    #     gen-delims  = ":" / "/" / "?" / "#" / "[" / "]" / "@"
    #     sub-delims  = "!" / "$" / "&" / "'" / "(" / ")"
    #                   / "*" / "+" / "," / ";" / "="
    #     unreserved  = ALPHA / DIGIT / "-" / "." / "_" / "~"
    # Of the unreserved characters, urllib.parse.quote() already considers all
    # but the ~ safe.
    # The % character is also added to the list of safe characters here, as the
    # end of RFC 3987 Section 3.1 specifically mentions that % must not be
    # converted.
    if iri is None:
        return iri
    elif isinstance(iri, Promise):
        iri = str(iri)
    return quote(iri, safe="/#%[]=:;$&()+,!?*@'~")


# List of byte values that uri_to_iri() decodes from percent encoding.
# First, the unreserved characters from RFC 3986:
_ascii_ranges = [[45, 46, 95, 126], range(65, 91), range(97, 123)]
_hextobyte = {
    (fmt % char).encode(): bytes((char,))
    for ascii_range in _ascii_ranges
    for char in ascii_range
    for fmt in ["%02x", "%02X"]
}
# And then everything above 128, because bytes ≥ 128 are part of multibyte
# Unicode characters.
_hexdig = "0123456789ABCDEFabcdef"
_hextobyte.update(
    {(a + b).encode(): bytes.fromhex(a + b) for a in _hexdig[8:] for b in _hexdig}
)


def uri_to_iri(uri):
    """
    Convert a Uniform Resource Identifier(URI) into an Internationalized
    Resource Identifier(IRI).

    This is the algorithm from RFC 3987 Section 3.2, excluding step 4.

    Take an URI in ASCII bytes (e.g. '/I%20%E2%99%A5%20Django/') and return
    a string containing the encoded result (e.g. '/I%20♥%20Django/').
    """
    if uri is None:
        return uri
    uri = force_bytes(uri)
    # Fast selective unquote: First, split on '%' and then starting with the
    # second block, decode the first 2 bytes if they represent a hex code to
    # decode. The rest of the block is the part after '%AB', not containing
    # any '%'. Add that to the output without further processing.
    bits = uri.split(b"%")
    if len(bits) == 1:
        iri = uri
    else:
        parts = [bits[0]]
        append = parts.append
        hextobyte = _hextobyte
        for item in bits[1:]:
            hex = item[:2]
            if hex in hextobyte:
                append(hextobyte[item[:2]])
                append(item[2:])
            else:
                append(b"%")
                append(item)
        iri = b"".join(parts)
    return repercent_broken_unicode(iri).decode()


def escape_uri_path(path):
    """
    Escape the unsafe characters from the path portion of a Uniform Resource
    Identifier (URI).
    """
    # These are the "reserved" and "unreserved" characters specified in RFC
    # 3986 Sections 2.2 and 2.3:
    #   reserved    = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" | "$" | ","
    #   unreserved  = alphanum | mark
    #   mark        = "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")"
    # The list of safe characters here is constructed subtracting ";", "=",
    # and "?" according to RFC 3986 Section 3.3.
    # The reason for not subtracting and escaping "/" is that we are escaping
    # the entire path, not a path segment.
    return quote(path, safe="/:@&+$,-_.!~*'()")


def punycode(domain):
    """Return the Punycode of the given domain if it's non-ASCII."""
    return domain.encode("idna").decode("ascii")


def repercent_broken_unicode(path):
    """
    As per RFC 3987 Section 3.2, step three of converting a URI into an IRI,
    repercent-encode any octet produced that is not part of a strictly legal
    UTF-8 octet sequence.
    """
    changed_parts = []
    while True:
        try:
            path.decode()
        except UnicodeDecodeError as e:
            # CVE-2019-14235: A recursion shouldn't be used since the exception
            # handling uses massive amounts of memory
            repercent = quote(path[e.start : e.end], safe=b"/#%[]=:;$&()+,!?*@'~")
            changed_parts.append(path[: e.start] + repercent.encode())
            path = path[e.end :]
        else:
            return b"".join(changed_parts) + path


def filepath_to_uri(path):
    """Convert a file system path to a URI portion that is suitable for
    inclusion in a URL.

    Encode certain chars that would normally be recognized as special chars
    for URIs. Do not encode the ' character, as it is a valid character
    within URIs. See the encodeURIComponent() JavaScript function for details.
    """
    if path is None:
        return path
    # I know about `os.sep` and `os.altsep` but I want to leave
    # some flexibility for hardcoding separators.
    return quote(str(path).replace("\\", "/"), safe="/~!*()'")


def get_system_encoding():
    """
    The encoding for the character type functions. Fallback to 'ascii' if the
    #encoding is unsupported by Python or could not be determined. See tickets
    #10335 and #5846.
    """
    try:
        encoding = locale.getlocale()[1] or "ascii"
        codecs.lookup(encoding)
    except Exception:
        encoding = "ascii"
    return encoding


DEFAULT_LOCALE_ENCODING = get_system_encoding()

virt/lib/python3.9/site-packages/django/utils/encoding.py
PrinceDisant/LostInNL
import datetime
import decimal
import functools
import re
import unicodedata
from importlib import import_module

from django.conf import settings
from django.utils import dateformat, numberformat
from django.utils.functional import lazy
from django.utils.translation import check_for_language, get_language, to_locale

# format_cache is a mapping from (format_type, lang) to the format string.
# By using the cache, it is possible to avoid running get_format_modules
# repeatedly.
_format_cache = {}
_format_modules_cache = {}

ISO_INPUT_FORMATS = {
    "DATE_INPUT_FORMATS": ["%Y-%m-%d"],
    "TIME_INPUT_FORMATS": ["%H:%M:%S", "%H:%M:%S.%f", "%H:%M"],
    "DATETIME_INPUT_FORMATS": [
        "%Y-%m-%d %H:%M:%S",
        "%Y-%m-%d %H:%M:%S.%f",
        "%Y-%m-%d %H:%M",
        "%Y-%m-%d",
    ],
}


FORMAT_SETTINGS = frozenset(
    [
        "DECIMAL_SEPARATOR",
        "THOUSAND_SEPARATOR",
        "NUMBER_GROUPING",
        "FIRST_DAY_OF_WEEK",
        "MONTH_DAY_FORMAT",
        "TIME_FORMAT",
        "DATE_FORMAT",
        "DATETIME_FORMAT",
        "SHORT_DATE_FORMAT",
        "SHORT_DATETIME_FORMAT",
        "YEAR_MONTH_FORMAT",
        "DATE_INPUT_FORMATS",
        "TIME_INPUT_FORMATS",
        "DATETIME_INPUT_FORMATS",
    ]
)


def reset_format_cache():
    """Clear any cached formats.

    This method is provided primarily for testing purposes,
    so that the effects of cached formats can be removed.
    """
    global _format_cache, _format_modules_cache
    _format_cache = {}
    _format_modules_cache = {}


def iter_format_modules(lang, format_module_path=None):
    """Find format modules."""
    if not check_for_language(lang):
        return

    if format_module_path is None:
        format_module_path = settings.FORMAT_MODULE_PATH

    format_locations = []
    if format_module_path:
        if isinstance(format_module_path, str):
            format_module_path = [format_module_path]
        for path in format_module_path:
            format_locations.append(path + ".%s")
    format_locations.append("django.conf.locale.%s")
    locale = to_locale(lang)
    locales = [locale]
    if "_" in locale:
        locales.append(locale.split("_")[0])
    for location in format_locations:
        for loc in locales:
            try:
                yield import_module("%s.formats" % (location % loc))
            except ImportError:
                pass


def get_format_modules(lang=None):
    """Return a list of the format modules found."""
    if lang is None:
        lang = get_language()
    if lang not in _format_modules_cache:
        _format_modules_cache[lang] = list(
            iter_format_modules(lang, settings.FORMAT_MODULE_PATH)
        )
    return _format_modules_cache[lang]


def get_format(format_type, lang=None, use_l10n=None):
    """
    For a specific format type, return the format for the current
    language (locale). Default to the format in the settings.
    format_type is the name of the format, e.g. 'DATE_FORMAT'.

    If use_l10n is provided and is not None, it forces the value to
    be localized (or not), overriding the value of settings.USE_L10N.
    """
    if use_l10n is None:
        try:
            use_l10n = settings._USE_L10N_INTERNAL
        except AttributeError:
            use_l10n = settings.USE_L10N
    if use_l10n and lang is None:
        lang = get_language()
    format_type = str(format_type)  # format_type may be lazy.
    cache_key = (format_type, lang)
    try:
        return _format_cache[cache_key]
    except KeyError:
        pass

    # The requested format_type has not been cached yet. Try to find it in any
    # of the format_modules for the given lang if l10n is enabled. If it's not
    # there or if l10n is disabled, fall back to the project settings.
    val = None
    if use_l10n:
        for module in get_format_modules(lang):
            val = getattr(module, format_type, None)
            if val is not None:
                break
    if val is None:
        if format_type not in FORMAT_SETTINGS:
            return format_type
        val = getattr(settings, format_type)
    elif format_type in ISO_INPUT_FORMATS:
        # If a list of input formats from one of the format_modules was
        # retrieved, make sure the ISO_INPUT_FORMATS are in this list.
        val = list(val)
        for iso_input in ISO_INPUT_FORMATS.get(format_type, ()):
            if iso_input not in val:
                val.append(iso_input)
    _format_cache[cache_key] = val
    return val


get_format_lazy = lazy(get_format, str, list, tuple)


def date_format(value, format=None, use_l10n=None):
    """
    Format a datetime.date or datetime.datetime object using a
    localizable format.

    If use_l10n is provided and is not None, that will force the value to
    be localized (or not), overriding the value of settings.USE_L10N.
    """
    return dateformat.format(
        value, get_format(format or "DATE_FORMAT", use_l10n=use_l10n)
    )


def time_format(value, format=None, use_l10n=None):
    """
    Format a datetime.time object using a localizable format.

    If use_l10n is provided and is not None, it forces the value to
    be localized (or not), overriding the value of settings.USE_L10N.
    """
    return dateformat.time_format(
        value, get_format(format or "TIME_FORMAT", use_l10n=use_l10n)
    )


def number_format(value, decimal_pos=None, use_l10n=None, force_grouping=False):
    """
    Format a numeric value using localization settings.

    If use_l10n is provided and is not None, it forces the value to
    be localized (or not), overriding the value of settings.USE_L10N.
    """
    if use_l10n is None:
        try:
            use_l10n = settings._USE_L10N_INTERNAL
        except AttributeError:
            use_l10n = settings.USE_L10N
    lang = get_language() if use_l10n else None
    return numberformat.format(
        value,
        get_format("DECIMAL_SEPARATOR", lang, use_l10n=use_l10n),
        decimal_pos,
        get_format("NUMBER_GROUPING", lang, use_l10n=use_l10n),
        get_format("THOUSAND_SEPARATOR", lang, use_l10n=use_l10n),
        force_grouping=force_grouping,
        use_l10n=use_l10n,
    )


def localize(value, use_l10n=None):
    """
    Check if value is a localizable type (date, number...) and return it
    formatted as a string using current locale format.

    If use_l10n is provided and is not None, it forces the value to
    be localized (or not), overriding the value of settings.USE_L10N.
    """
    if isinstance(value, str):  # Handle strings first for performance reasons.
        return value
    elif isinstance(value, bool):  # Make sure booleans don't get treated as numbers
        return str(value)
    elif isinstance(value, (decimal.Decimal, float, int)):
        if use_l10n is False:
            return str(value)
        return number_format(value, use_l10n=use_l10n)
    elif isinstance(value, datetime.datetime):
        return date_format(value, "DATETIME_FORMAT", use_l10n=use_l10n)
    elif isinstance(value, datetime.date):
        return date_format(value, use_l10n=use_l10n)
    elif isinstance(value, datetime.time):
        return time_format(value, use_l10n=use_l10n)
    return value


def localize_input(value, default=None):
    """
    Check if an input value is a localizable type and return it
    formatted with the appropriate formatting string of the current locale.
    """
    if isinstance(value, str):  # Handle strings first for performance reasons.
        return value
    elif isinstance(value, bool):  # Don't treat booleans as numbers.
        return str(value)
    elif isinstance(value, (decimal.Decimal, float, int)):
        return number_format(value)
    elif isinstance(value, datetime.datetime):
        format = default or get_format("DATETIME_INPUT_FORMATS")[0]
        format = sanitize_strftime_format(format)
        return value.strftime(format)
    elif isinstance(value, datetime.date):
        format = default or get_format("DATE_INPUT_FORMATS")[0]
        format = sanitize_strftime_format(format)
        return value.strftime(format)
    elif isinstance(value, datetime.time):
        format = default or get_format("TIME_INPUT_FORMATS")[0]
        return value.strftime(format)
    return value


@functools.lru_cache
def sanitize_strftime_format(fmt):
    """
    Ensure that certain specifiers are correctly padded with leading zeros.

    For years < 1000 specifiers %C, %F, %G, and %Y don't work as expected for
    strftime provided by glibc on Linux as they don't pad the year or century
    with leading zeros. Support for specifying the padding explicitly is
    available, however, which can be used to fix this issue.

    FreeBSD, macOS, and Windows do not support explicitly specifying the
    padding, but return four digit years (with leading zeros) as expected.

    This function checks whether the %Y produces a correctly padded string and,
    if not, makes the following substitutions:

    - %C → %02C
    - %F → %010F
    - %G → %04G
    - %Y → %04Y

    See https://bugs.python.org/issue13305 for more details.
    """
    if datetime.date(1, 1, 1).strftime("%Y") == "0001":
        return fmt
    mapping = {"C": 2, "F": 10, "G": 4, "Y": 4}
    return re.sub(
        r"((?:^|[^%])(?:%%)*)%([CFGY])",
        lambda m: r"%s%%0%s%s" % (m[1], mapping[m[2]], m[2]),
        fmt,
    )


def sanitize_separators(value):
    """
    Sanitize a value according to the current decimal and
    thousand separator setting. Used with form field input.
    """
    if isinstance(value, str):
        parts = []
        decimal_separator = get_format("DECIMAL_SEPARATOR")
        if decimal_separator in value:
            value, decimals = value.split(decimal_separator, 1)
            parts.append(decimals)
        if settings.USE_THOUSAND_SEPARATOR:
            thousand_sep = get_format("THOUSAND_SEPARATOR")
            if (
                thousand_sep == "."
                and value.count(".") == 1
                and len(value.split(".")[-1]) != 3
            ):
                # Special case where we suspect a dot meant decimal separator
                # (see #22171).
                pass
            else:
                for replacement in {
                    thousand_sep,
                    unicodedata.normalize("NFKD", thousand_sep),
                }:
                    value = value.replace(replacement, "")
        parts.append(value)
        value = ".".join(reversed(parts))
    return value

virt/lib/python3.9/site-packages/django/utils/formats.py
PrinceDisant/LostInNL
import io
import logging
import os
import shutil
import sys
import tempfile
from collections import OrderedDict
from contextlib import contextmanager
from typing import (IO, Dict, Iterable, Iterator, Mapping, Optional, Tuple,
                    Union)

from .parser import Binding, parse_stream
from .variables import parse_variables

# A type alias for a string path to be used for the paths in this file.
# These paths may flow to `open()` and `shutil.move()`; `shutil.move()`
# only accepts string paths, not byte paths or file descriptors. See
# https://github.com/python/typeshed/pull/6832.
StrPath = Union[str, 'os.PathLike[str]']

logger = logging.getLogger(__name__)


def with_warn_for_invalid_lines(mappings: Iterator[Binding]) -> Iterator[Binding]:
    for mapping in mappings:
        if mapping.error:
            logger.warning(
                "Python-dotenv could not parse statement starting at line %s",
                mapping.original.line,
            )
        yield mapping


class DotEnv:
    def __init__(
        self,
        dotenv_path: Optional[StrPath],
        stream: Optional[IO[str]] = None,
        verbose: bool = False,
        encoding: Optional[str] = None,
        interpolate: bool = True,
        override: bool = True,
    ) -> None:
        self.dotenv_path: Optional[StrPath] = dotenv_path
        self.stream: Optional[IO[str]] = stream
        self._dict: Optional[Dict[str, Optional[str]]] = None
        self.verbose: bool = verbose
        self.encoding: Optional[str] = encoding
        self.interpolate: bool = interpolate
        self.override: bool = override

    @contextmanager
    def _get_stream(self) -> Iterator[IO[str]]:
        if self.dotenv_path and os.path.isfile(self.dotenv_path):
            with open(self.dotenv_path, encoding=self.encoding) as stream:
                yield stream
        elif self.stream is not None:
            yield self.stream
        else:
            if self.verbose:
                logger.info(
                    "Python-dotenv could not find configuration file %s.",
                    self.dotenv_path or '.env',
                )
            yield io.StringIO('')

    def dict(self) -> Dict[str, Optional[str]]:
        """Return dotenv as dict"""
        if self._dict:
            return self._dict

        raw_values = self.parse()

        if self.interpolate:
            self._dict = OrderedDict(resolve_variables(raw_values, override=self.override))
        else:
            self._dict = OrderedDict(raw_values)

        return self._dict

    def parse(self) -> Iterator[Tuple[str, Optional[str]]]:
        with self._get_stream() as stream:
            for mapping in with_warn_for_invalid_lines(parse_stream(stream)):
                if mapping.key is not None:
                    yield mapping.key, mapping.value

    def set_as_environment_variables(self) -> bool:
        """
        Load the current dotenv as system environment variable.
        """
        if not self.dict():
            return False

        for k, v in self.dict().items():
            if k in os.environ and not self.override:
                continue
            if v is not None:
                os.environ[k] = v

        return True

    def get(self, key: str) -> Optional[str]:
        """
        """
        data = self.dict()

        if key in data:
            return data[key]

        if self.verbose:
            logger.warning("Key %s not found in %s.", key, self.dotenv_path)

        return None


def get_key(
    dotenv_path: StrPath,
    key_to_get: str,
    encoding: Optional[str] = "utf-8",
) -> Optional[str]:
    """
    Get the value of a given key from the given .env.

    Returns `None` if the key isn't found or doesn't have a value.
    """
    return DotEnv(dotenv_path, verbose=True, encoding=encoding).get(key_to_get)


@contextmanager
def rewrite(
    path: StrPath,
    encoding: Optional[str],
) -> Iterator[Tuple[IO[str], IO[str]]]:
    if not os.path.isfile(path):
        with open(path, mode="w", encoding=encoding) as source:
            source.write("")
    with tempfile.NamedTemporaryFile(mode="w", encoding=encoding, delete=False) as dest:
        try:
            with open(path, encoding=encoding) as source:
                yield (source, dest)
        except BaseException:
            os.unlink(dest.name)
            raise
    shutil.move(dest.name, path)


def set_key(
    dotenv_path: StrPath,
    key_to_set: str,
    value_to_set: str,
    quote_mode: str = "always",
    export: bool = False,
    encoding: Optional[str] = "utf-8",
) -> Tuple[Optional[bool], str, str]:
    """
    Adds or Updates a key/value to the given .env

    If the .env path given doesn't exist, fails instead of risking creating
    an orphan .env somewhere in the filesystem
    """
    if quote_mode not in ("always", "auto", "never"):
        raise ValueError(f"Unknown quote_mode: {quote_mode}")

    quote = (
        quote_mode == "always"
        or (quote_mode == "auto" and not value_to_set.isalnum())
    )

    if quote:
        value_out = "'{}'".format(value_to_set.replace("'", "\\'"))
    else:
        value_out = value_to_set
    if export:
        line_out = f'export {key_to_set}={value_out}\n'
    else:
        line_out = f"{key_to_set}={value_out}\n"

    with rewrite(dotenv_path, encoding=encoding) as (source, dest):
        replaced = False
        missing_newline = False
        for mapping in with_warn_for_invalid_lines(parse_stream(source)):
            if mapping.key == key_to_set:
                dest.write(line_out)
                replaced = True
            else:
                dest.write(mapping.original.string)
                missing_newline = not mapping.original.string.endswith("\n")
        if not replaced:
            if missing_newline:
                dest.write("\n")
            dest.write(line_out)

    return True, key_to_set, value_to_set


def unset_key(
    dotenv_path: StrPath,
    key_to_unset: str,
    quote_mode: str = "always",
    encoding: Optional[str] = "utf-8",
) -> Tuple[Optional[bool], str]:
    """
    Removes a given key from the given `.env` file.

    If the .env path given doesn't exist, fails.
    If the given key doesn't exist in the .env, fails.
    """
    if not os.path.exists(dotenv_path):
        logger.warning("Can't delete from %s - it doesn't exist.", dotenv_path)
        return None, key_to_unset

    removed = False
    with rewrite(dotenv_path, encoding=encoding) as (source, dest):
        for mapping in with_warn_for_invalid_lines(parse_stream(source)):
            if mapping.key == key_to_unset:
                removed = True
            else:
                dest.write(mapping.original.string)

    if not removed:
        logger.warning("Key %s not removed from %s - key doesn't exist.", key_to_unset, dotenv_path)
        return None, key_to_unset

    return removed, key_to_unset


def resolve_variables(
    values: Iterable[Tuple[str, Optional[str]]],
    override: bool,
) -> Mapping[str, Optional[str]]:
    new_values: Dict[str, Optional[str]] = {}

    for (name, value) in values:
        if value is None:
            result = None
        else:
            atoms = parse_variables(value)
            env: Dict[str, Optional[str]] = {}
            if override:
                env.update(os.environ)  # type: ignore
                env.update(new_values)
            else:
                env.update(new_values)
                env.update(os.environ)  # type: ignore
            result = "".join(atom.resolve(env) for atom in atoms)

        new_values[name] = result

    return new_values


def _walk_to_root(path: str) -> Iterator[str]:
    """
    Yield directories starting from the given directory up to the root
    """
    if not os.path.exists(path):
        raise IOError('Starting path not found')

    if os.path.isfile(path):
        path = os.path.dirname(path)

    last_dir = None
    current_dir = os.path.abspath(path)
    while last_dir != current_dir:
        yield current_dir
        parent_dir = os.path.abspath(os.path.join(current_dir, os.path.pardir))
        last_dir, current_dir = current_dir, parent_dir


def find_dotenv(
    filename: str = '.env',
    raise_error_if_not_found: bool = False,
    usecwd: bool = False,
) -> str:
    """
    Search in increasingly higher folders for the given file

    Returns path to the file if found, or an empty string otherwise
    """

    def _is_interactive():
        """ Decide whether this is running in a REPL or IPython notebook """
        main = __import__('__main__', None, None, fromlist=['__file__'])
        return not hasattr(main, '__file__')

    if usecwd or _is_interactive() or getattr(sys, 'frozen', False):
        # Should work without __file__, e.g. in REPL or IPython notebook.
        path = os.getcwd()
    else:
        # will work for .py files
        frame = sys._getframe()
        current_file = __file__

        while frame.f_code.co_filename == current_file:
            assert frame.f_back is not None
            frame = frame.f_back
        frame_filename = frame.f_code.co_filename
        path = os.path.dirname(os.path.abspath(frame_filename))

    for dirname in _walk_to_root(path):
        check_path = os.path.join(dirname, filename)
        if os.path.isfile(check_path):
            return check_path

    if raise_error_if_not_found:
        raise IOError('File not found')

    return ''


def load_dotenv(
    dotenv_path: Optional[StrPath] = None,
    stream: Optional[IO[str]] = None,
    verbose: bool = False,
    override: bool = False,
    interpolate: bool = True,
    encoding: Optional[str] = "utf-8",
) -> bool:
    """Parse a .env file and then load all the variables found as environment variables.

    Parameters:
        dotenv_path: Absolute or relative path to .env file.
        stream: Text stream (such as `io.StringIO`) with .env content, used if
            `dotenv_path` is `None`.
        verbose: Whether to output a warning the .env file is missing.
        override: Whether to override the system environment variables with the variables
            from the `.env` file.
        encoding: Encoding to be used to read the file.
    Returns:
        Bool: True if at least one environment variable is set else False

    If both `dotenv_path` and `stream` are `None`, `find_dotenv()` is used to find the
    .env file.
    """
    if dotenv_path is None and stream is None:
        dotenv_path = find_dotenv()

    dotenv = DotEnv(
        dotenv_path=dotenv_path,
        stream=stream,
        verbose=verbose,
        interpolate=interpolate,
        override=override,
        encoding=encoding,
    )
    return dotenv.set_as_environment_variables()


def dotenv_values(
    dotenv_path: Optional[StrPath] = None,
    stream: Optional[IO[str]] = None,
    verbose: bool = False,
    interpolate: bool = True,
    encoding: Optional[str] = "utf-8",
) -> Dict[str, Optional[str]]:
    """
    Parse a .env file and return its content as a dict.

    The returned dict will have `None` values for keys without values in the .env file.
    For example, `foo=bar` results in `{"foo": "bar"}` whereas `foo` alone results in
    `{"foo": None}`

    Parameters:
        dotenv_path: Absolute or relative path to the .env file.
        stream: `StringIO` object with .env content, used if `dotenv_path` is `None`.
        verbose: Whether to output a warning if the .env file is missing.
        encoding: Encoding to be used to read the file.

    If both `dotenv_path` and `stream` are `None`, `find_dotenv()` is used to find the
    .env file.
    """
    if dotenv_path is None and stream is None:
        dotenv_path = find_dotenv()

    return DotEnv(
        dotenv_path=dotenv_path,
        stream=stream,
        verbose=verbose,
        interpolate=interpolate,
        override=True,
        encoding=encoding,
    ).dict()

virt/lib/python3.9/site-packages/dotenv/main.py
PrinceDisant/LostInNL
# Copyright (c) 2009, 2022, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

"""MySQL Connector/Python - MySQL driver written in Python."""

try:
    from .connection_cext import CMySQLConnection
except ImportError:
    HAVE_CEXT = False
else:
    HAVE_CEXT = True


from . import version
from .connection import MySQLConnection
from .constants import CharacterSet, ClientFlag, FieldFlag, FieldType, RefreshOption
from .dbapi import (
    BINARY,
    DATETIME,
    NUMBER,
    ROWID,
    STRING,
    Binary,
    Date,
    DateFromTicks,
    Time,
    TimeFromTicks,
    Timestamp,
    TimestampFromTicks,
    apilevel,
    paramstyle,
    threadsafety,
)
from .errors import (  # pylint: disable=redefined-builtin
    DatabaseError,
    DataError,
    Error,
    IntegrityError,
    InterfaceError,
    InternalError,
    NotSupportedError,
    OperationalError,
    PoolError,
    ProgrammingError,
    Warning,
    custom_error_exception,
)
from .pooling import connect

Connect = connect

__version_info__ = version.VERSION
__version__ = version.VERSION_TEXT

__all__ = [
    "MySQLConnection",
    "Connect",
    "custom_error_exception",
    # Some useful constants
    "FieldType",
    "FieldFlag",
    "ClientFlag",
    "CharacterSet",
    "RefreshOption",
    "HAVE_CEXT",
    # Error handling
    "Error",
    "Warning",
    "InterfaceError",
    "DatabaseError",
    "NotSupportedError",
    "DataError",
    "IntegrityError",
    "PoolError",
    "ProgrammingError",
    "OperationalError",
    "InternalError",
    # DBAPI PEP 249 required exports
    "connect",
    "apilevel",
    "threadsafety",
    "paramstyle",
    "Date",
    "Time",
    "Timestamp",
    "Binary",
    "DateFromTicks",
    "DateFromTicks",
    "TimestampFromTicks",
    "TimeFromTicks",
    "STRING",
    "BINARY",
    "NUMBER",
    "DATETIME",
    "ROWID",
    # C Extension
    "CMySQLConnection",
]

virt/lib/python3.9/site-packages/mysql/connector/__init__.py
PrinceDisant/LostInNL
# Copyright (c) 2014, 2023, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

# mypy: disable-error-code="assignment,attr-defined"

"""Module gathering all abstract base classes."""

from __future__ import annotations

import importlib
import os
import re
import weakref

from abc import ABC, abstractmethod
from datetime import date, datetime, time, timedelta
from decimal import Decimal
from inspect import signature
from time import sleep
from types import TracebackType
from typing import (
    Any,
    BinaryIO,
    Callable,
    Dict,
    Generator,
    List,
    Mapping,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
)

TLS_V1_3_SUPPORTED = False
try:
    import ssl

    if hasattr(ssl, "HAS_TLSv1_3") and ssl.HAS_TLSv1_3:
        TLS_V1_3_SUPPORTED = True
except ImportError:
    # If import fails, we don't have SSL support.
    pass

from .constants import (
    CONN_ATTRS_DN,
    DEFAULT_CONFIGURATION,
    DEPRECATED_TLS_VERSIONS,
    OPENSSL_CS_NAMES,
    TLS_CIPHER_SUITES,
    TLS_VERSIONS,
    CharacterSet,
    ClientFlag,
)
from .conversion import MySQLConverter, MySQLConverterBase
from .errors import (
    Error,
    InterfaceError,
    NotSupportedError,
    OperationalError,
    ProgrammingError,
)
from .opentelemetry.constants import (
    CONNECTION_SPAN_NAME,
    OPTION_CNX_SPAN,
    OPTION_CNX_TRACER,
    OTEL_ENABLED,
)

if OTEL_ENABLED:
    from .opentelemetry.instrumentation import (
        end_span,
        record_exception_event,
        set_connection_span_attrs,
        trace,
    )

from .optionfiles import read_option_files
from .types import (
    ConnAttrsType,
    DescriptionType,
    HandShakeType,
    StrOrBytes,
    SupportedMysqlBinaryProtocolTypes,
    WarningType,
)

NAMED_TUPLE_CACHE: weakref.WeakValueDictionary[Any, Any] = weakref.WeakValueDictionary()

DUPLICATED_IN_LIST_ERROR = (
    "The '{list}' list must not contain repeated values, the value "
    "'{value}' is duplicated."
)

TLS_VERSION_ERROR = (
    "The given tls_version: '{}' is not recognized as a valid "
    "TLS protocol version (should be one of {})."
)

TLS_VERSION_DEPRECATED_ERROR = (
    "The given tls_version: '{}' are no longer allowed (should be one of {})."
)

TLS_VER_NO_SUPPORTED = (
    "No supported TLS protocol version found in the 'tls-versions' list '{}'. "
)

KRB_SERVICE_PINCIPAL_ERROR = (
    'Option "krb_service_principal" {error}, must be a string in the form '
    '"primary/instance@realm" e.g "ldap/ldapauth@MYSQL.COM" where "@realm" '
    "is optional and if it is not given will be assumed to belong to the "
    "default realm, as configured in the krb5.conf file."
)

MYSQL_PY_TYPES = (
    Decimal,
    bytes,
    date,
    datetime,
    float,
    int,
    str,
    time,
    timedelta,
)


class MySQLConnectionAbstract(ABC):
    """Abstract class for classes connecting to a MySQL server"""

    def __init__(self) -> None:
        """Initialize"""
        # opentelemetry related
        self._tracer: Any = None
        self._span: Any = None
        self.otel_context_propagation: bool = True

        self._client_flags: int = ClientFlag.get_default()
        self._charset_id: int = 45
        self._sql_mode: Optional[str] = None
        self._time_zone: Optional[str] = None
        self._autocommit: bool = False
        self._server_version: Optional[Tuple[int, ...]] = None
        self._handshake: Optional[HandShakeType] = None
        self._conn_attrs: ConnAttrsType = {}

        self._user: str = ""
        self._password: str = ""
        self._password1: str = ""
        self._password2: str = ""
        self._password3: str = ""
        self._database: str = ""
        self._host: str = "127.0.0.1"
        self._port: int = 3306
        self._unix_socket: Optional[str] = None
        self._client_host: str = ""
        self._client_port: int = 0
        self._ssl: Dict[str, Optional[Union[str, bool, List[str]]]] = {}
        self._ssl_disabled: bool = DEFAULT_CONFIGURATION["ssl_disabled"]
        self._force_ipv6: bool = False
        self._oci_config_file: Optional[str] = None
        self._oci_config_profile: Optional[str] = None
        self._fido_callback: Optional[Union[str, Callable]] = None
        self._krb_service_principal: Optional[str] = None

        self._use_unicode: bool = True
        self._get_warnings: bool = False
        self._raise_on_warnings: bool = False
        self._connection_timeout: Optional[int] = DEFAULT_CONFIGURATION[
            "connect_timeout"
        ]
        self._buffered: bool = False
        self._unread_result: bool = False
        self._have_next_result: bool = False
        self._raw: bool = False
        self._in_transaction: bool = False
        self._allow_local_infile: bool = DEFAULT_CONFIGURATION["allow_local_infile"]
        self._allow_local_infile_in_path: Optional[str] = DEFAULT_CONFIGURATION[
            "allow_local_infile_in_path"
        ]

        self._prepared_statements: Any = None
        self._query_attrs: Dict[str, Any] = {}

        self._ssl_active: bool = False
        self._auth_plugin: Optional[str] = None
        self._auth_plugin_class: Optional[str] = None
        self._pool_config_version: Any = None
        self.converter: Optional[MySQLConverter] = None
        self._converter_class: Optional[Type[MySQLConverter]] = None
        self._converter_str_fallback: bool = False
        self._compress: bool = False

        self._consume_results: bool = False
        self._init_command: Optional[str] = None

    def __enter__(self) -> MySQLConnectionAbstract:
        return self

    def __exit__(
        self,
        exc_type: Type[BaseException],
        exc_value: BaseException,
        traceback: TracebackType,
    ) -> None:
        self.close()

    def get_self(self) -> MySQLConnectionAbstract:
        """Return self for weakref.proxy

        This method is used when the original object is needed when using
        weakref.proxy.
        """
        return self

    @property
    def is_secure(self) -> bool:
        """Return True if is a secure connection."""
        return self._ssl_active or (
            self._unix_socket is not None and os.name == "posix"
        )

    @property
    def have_next_result(self) -> bool:
        """Return if have next result."""
        return self._have_next_result

    @property
    def query_attrs(self) -> List[Tuple[str, Any]]:
        """Return query attributes list."""
        return list(self._query_attrs.items())

    def query_attrs_append(
        self, value: Tuple[str, SupportedMysqlBinaryProtocolTypes]
    ) -> None:
        """Add element to the query attributes list.

        If an element in the query attributes list already matches
        the attribute name provided, the new element will NOT be added.
        """
        attr_name, attr_value = value
        if attr_name not in self._query_attrs:
            self._query_attrs[attr_name] = attr_value

    def query_attrs_remove(self, name: str) -> Any:
        """Remove element by name from the query attributes list.

        If no match, `None` is returned; else the corresponding value is returned.
        """
        return self._query_attrs.pop(name, None)

    def query_attrs_clear(self) -> None:
        """Clear query attributes list."""
        self._query_attrs = {}

    def _validate_tls_ciphersuites(self) -> None:
        """Validates the tls_ciphersuites option."""
        tls_ciphersuites = []
        tls_cs = self._ssl["tls_ciphersuites"]

        if isinstance(tls_cs, str):
            if not (tls_cs.startswith("[") and tls_cs.endswith("]")):
                raise AttributeError(
                    f"tls_ciphersuites must be a list, found: '{tls_cs}'"
                )
            tls_css = tls_cs[1:-1].split(",")
            if not tls_css:
                raise AttributeError(
                    "No valid cipher suite found in 'tls_ciphersuites' list"
                )
            for _tls_cs in tls_css:
                _tls_cs = tls_cs.strip().upper()
                if _tls_cs:
                    tls_ciphersuites.append(_tls_cs)

        elif isinstance(tls_cs, (list, set)):
            tls_ciphersuites = [tls_cs for tls_cs in tls_cs if tls_cs]
        else:
            raise AttributeError(
                "tls_ciphersuites should be a list with one or more "
                f"ciphersuites. Found: '{tls_cs}'"
            )

        tls_versions = (
            TLS_VERSIONS[:]
            if self._ssl.get("tls_versions", None) is None
            else self._ssl["tls_versions"][:]  # type: ignore[index]
        )

        # A newer TLS version can use a cipher introduced on
        # an older version.
        tls_versions.sort(reverse=True)  # type: ignore[union-attr]
        newer_tls_ver = tls_versions[0]
        # translated_names[0] belongs to TLSv1, TLSv1.1 and TLSv1.2
        # translated_names[1] are TLSv1.3 only
        translated_names: List[List[str]] = [[], []]
        iani_cipher_suites_names = {}
        ossl_cipher_suites_names: List[str] = []

        # Old ciphers can work with new TLS versions.
        # Find all the ciphers introduced on previous TLS versions.
        for tls_ver in TLS_VERSIONS[: TLS_VERSIONS.index(newer_tls_ver) + 1]:
            iani_cipher_suites_names.update(TLS_CIPHER_SUITES[tls_ver])
            ossl_cipher_suites_names.extend(OPENSSL_CS_NAMES[tls_ver])

        for name in tls_ciphersuites:
            if "-" in name and name in ossl_cipher_suites_names:
                if name in OPENSSL_CS_NAMES["TLSv1.3"]:
                    translated_names[1].append(name)
                else:
                    translated_names[0].append(name)
            elif name in iani_cipher_suites_names:
                translated_name = iani_cipher_suites_names[name]
                if translated_name in translated_names:
                    raise AttributeError(
                        DUPLICATED_IN_LIST_ERROR.format(
                            list="tls_ciphersuites", value=translated_name
                        )
                    )
                if name in TLS_CIPHER_SUITES["TLSv1.3"]:
                    translated_names[1].append(iani_cipher_suites_names[name])
                else:
                    translated_names[0].append(iani_cipher_suites_names[name])
            else:
                raise AttributeError(
                    f"The value '{name}' in tls_ciphersuites is not a valid "
                    "cipher suite"
                )
        if not translated_names[0] and not translated_names[1]:
            raise AttributeError(
                "No valid cipher suite found in the 'tls_ciphersuites' list"
            )

        self._ssl["tls_ciphersuites"] = [
            ":".join(translated_names[0]),
            ":".join(translated_names[1]),
        ]

    def _validate_tls_versions(self) -> None:
        """Validates the tls_versions option."""
        tls_versions = []
        tls_version = self._ssl["tls_versions"]

        if isinstance(tls_version, str):
            if not (tls_version.startswith("[") and tls_version.endswith("]")):
                raise AttributeError(
                    f"tls_versions must be a list, found: '{tls_version}'"
                )
            tls_vers = tls_version[1:-1].split(",")
            for tls_ver in tls_vers:
                tls_version = tls_ver.strip()
                if tls_version == "":
                    continue
                if tls_version in tls_versions:
                    raise AttributeError(
                        DUPLICATED_IN_LIST_ERROR.format(
                            list="tls_versions", value=tls_version
                        )
                    )
                tls_versions.append(tls_version)
            if tls_vers == ["TLSv1.3"] and not TLS_V1_3_SUPPORTED:
                raise AttributeError(
                    TLS_VER_NO_SUPPORTED.format(tls_version, TLS_VERSIONS)
                )
        elif isinstance(tls_version, list):
            if not tls_version:
                raise AttributeError(
                    "At least one TLS protocol version must be specified in "
                    "'tls_versions' list"
                )
            for tls_ver in tls_version:
                if tls_ver in tls_versions:
                    raise AttributeError(
                        DUPLICATED_IN_LIST_ERROR.format(
                            list="tls_versions", value=tls_ver
                        )
                    )
                tls_versions.append(tls_ver)
        elif isinstance(tls_version, set):
            for tls_ver in tls_version:
                tls_versions.append(tls_ver)
        else:
            raise AttributeError(
                "tls_versions should be a list with one or more of versions "
                f"in {', '.join(TLS_VERSIONS)}. found: '{tls_versions}'"
            )

        if not tls_versions:
            raise AttributeError(
                "At least one TLS protocol version must be specified "
                "in 'tls_versions' list when this option is given"
            )

        use_tls_versions = []
        deprecated_tls_versions = []
        invalid_tls_versions = []
        for tls_ver in tls_versions:
            if tls_ver in TLS_VERSIONS:
                use_tls_versions.append(tls_ver)
            if tls_ver in DEPRECATED_TLS_VERSIONS:
                deprecated_tls_versions.append(tls_ver)
            else:
                invalid_tls_versions.append(tls_ver)

        if use_tls_versions:
            if use_tls_versions == ["TLSv1.3"] and not TLS_V1_3_SUPPORTED:
                raise NotSupportedError(
                    TLS_VER_NO_SUPPORTED.format(tls_version, TLS_VERSIONS)
                )
            use_tls_versions.sort()
            self._ssl["tls_versions"] = use_tls_versions
        elif deprecated_tls_versions:
            raise NotSupportedError(
                TLS_VERSION_DEPRECATED_ERROR.format(
                    deprecated_tls_versions, TLS_VERSIONS
                )
            )
        elif invalid_tls_versions:
            raise AttributeError(TLS_VERSION_ERROR.format(tls_ver, TLS_VERSIONS))

    @property
    def user(self) -> str:
        """User used while connecting to MySQL"""
        return self._user

    @property
    def server_host(self) -> str:
        """MySQL server IP address or name"""
        return self._host

    @property
    def server_port(self) -> int:
        "MySQL server TCP/IP port"
        return self._port

    @property
    def unix_socket(self) -> Optional[str]:
        "MySQL Unix socket file location"
        return self._unix_socket

    @property
    @abstractmethod
    def database(self) -> str:
        """Get the current database"""

    @database.setter
    def database(self, value: str) -> None:
        """Set the current database"""
        self.cmd_query(f"USE {value}")

    @property
    def can_consume_results(self) -> bool:
        """Returns whether to consume results"""
        return self._consume_results

    @can_consume_results.setter
    def can_consume_results(self, value: bool) -> None:
        """Set if can consume results."""
        assert isinstance(value, bool)
        self._consume_results = value

    @property
    def pool_config_version(self) -> Any:
        """Return the pool configuration version"""
        return self._pool_config_version

    @pool_config_version.setter
    def pool_config_version(self, value: Any) -> None:
        """Set the pool configuration version"""
        self._pool_config_version = value

    def config(self, **kwargs: Any) -> None:
        """Configure the MySQL Connection

        This method allows you to configure the MySQLConnection instance.

        Raises on errors.
        """
        # opentelemetry related
        self._span = kwargs.pop(OPTION_CNX_SPAN, None)
        self._tracer = kwargs.pop(OPTION_CNX_TRACER, None)

        config = kwargs.copy()
        if "dsn" in config:
            raise NotSupportedError("Data source name is not supported")

        # Read option files
        config = read_option_files(**config)

        # Configure how we handle MySQL warnings
        try:
            self.get_warnings = config["get_warnings"]
            del config["get_warnings"]
        except KeyError:
            pass  # Leave what was set or default
        try:
            self.raise_on_warnings = config["raise_on_warnings"]
            del config["raise_on_warnings"]
        except KeyError:
            pass  # Leave what was set or default

        # Configure client flags
        try:
            default = ClientFlag.get_default()
            self.set_client_flags(config["client_flags"] or default)
            del config["client_flags"]
        except KeyError:
            pass  # Missing client_flags-argument is OK

        try:
            if config["compress"]:
                self._compress = True
                self.set_client_flags([ClientFlag.COMPRESS])
        except KeyError:
            pass  # Missing compress argument is OK

        self._allow_local_infile = config.get(
            "allow_local_infile", DEFAULT_CONFIGURATION["allow_local_infile"]
        )
        self._allow_local_infile_in_path = config.get(
            "allow_local_infile_in_path",
            DEFAULT_CONFIGURATION["allow_local_infile_in_path"],
        )
        infile_in_path = None
        if self._allow_local_infile_in_path:
            infile_in_path = os.path.abspath(self._allow_local_infile_in_path)
            if (
                infile_in_path
                and os.path.exists(infile_in_path)
                and not os.path.isdir(infile_in_path)
                or os.path.islink(infile_in_path)
            ):
                raise AttributeError("allow_local_infile_in_path must be a directory")
        if self._allow_local_infile or self._allow_local_infile_in_path:
            self.set_client_flags([ClientFlag.LOCAL_FILES])
        else:
            self.set_client_flags([-ClientFlag.LOCAL_FILES])

        try:
            if not config["consume_results"]:
                self._consume_results = False
            else:
                self._consume_results = True
        except KeyError:
            self._consume_results = False

        # Configure auth_plugin
        try:
            self._auth_plugin = config["auth_plugin"]
            del config["auth_plugin"]
        except KeyError:
            self._auth_plugin = ""

        # Configure character set and collation
        if "charset" in config or "collation" in config:
            try:
                charset = config["charset"]
                del config["charset"]
            except KeyError:
                charset = None
            try:
                collation = config["collation"]
                del config["collation"]
            except KeyError:
                collation = None
            self._charset_id = CharacterSet.get_charset_info(charset, collation)[0]

        # Set converter class
        try:
            self.set_converter_class(config["converter_class"])
        except KeyError:
            pass  # Using default converter class
        except TypeError as err:
            raise AttributeError(
                "Converter class should be a subclass of "
                "conversion.MySQLConverterBase"
            ) from err

        # Compatible configuration with other drivers
        compat_map = [
            # (<other driver argument>,<translates to>)
            ("db", "database"),
            ("username", "user"),
            ("passwd", "password"),
            ("connect_timeout", "connection_timeout"),
            ("read_default_file", "option_files"),
        ]
        for compat, translate in compat_map:
            try:
                if translate not in config:
                    config[translate] = config[compat]
                del config[compat]
            except KeyError:
                pass  # Missing compat argument is OK

        # Configure login information
        if "user" in config or "password" in config:
            try:
                user = config["user"]
                del config["user"]
            except KeyError:
                user = self._user
            try:
                password = config["password"]
                del config["password"]
            except KeyError:
                password = self._password
            self.set_login(user, password)

        # Configure host information
        if "host" in config and config["host"]:
            self._host = config["host"]

        # Check network locations
        try:
            self._port = int(config["port"])
            del config["port"]
        except KeyError:
            pass  # Missing port argument is OK
        except ValueError as err:
            raise InterfaceError("TCP/IP port number should be an integer") from err

        if "ssl_disabled" in config:
            self._ssl_disabled = config.pop("ssl_disabled")

        # If an init_command is set, keep it, so we can execute it in _post_connection
        if "init_command" in config:
            self._init_command = config["init_command"]
            del config["init_command"]

        # Other configuration
        set_ssl_flag = False
        for key, value in config.items():
            try:
                DEFAULT_CONFIGURATION[key]
            except KeyError:
                raise AttributeError(f"Unsupported argument '{key}'") from None
            # SSL Configuration
            if key.startswith("ssl_"):
                set_ssl_flag = True
                self._ssl.update({key.replace("ssl_", ""): value})
            elif key.startswith("tls_"):
                set_ssl_flag = True
                self._ssl.update({key: value})
            else:
                attribute = "_" + key
                try:
                    setattr(self, attribute, value.strip())
                except AttributeError:
                    setattr(self, attribute, value)

        # Disable SSL for unix socket connections
        if self._unix_socket and os.name == "posix":
            self._ssl_disabled = True

        if self._ssl_disabled and self._auth_plugin == "mysql_clear_password":
            raise InterfaceError(
                "Clear password authentication is not supported over insecure channels"
            )

        if set_ssl_flag:
            if "verify_cert" not in self._ssl:
                self._ssl["verify_cert"] = DEFAULT_CONFIGURATION["ssl_verify_cert"]
            if "verify_identity" not in self._ssl:
                self._ssl["verify_identity"] = DEFAULT_CONFIGURATION[
                    "ssl_verify_identity"
                ]
            # Make sure both ssl_key/ssl_cert are set, or neither (XOR)
            if "ca" not in self._ssl or self._ssl["ca"] is None:
                self._ssl["ca"] = ""
            if bool("key" in self._ssl) != bool("cert" in self._ssl):
                raise AttributeError(
                    "ssl_key and ssl_cert need to be both specified, or neither"
                )
            # Make sure key/cert are set to None
            if not set(("key", "cert")) <= set(self._ssl):
                self._ssl["key"] = None
                self._ssl["cert"] = None
            elif (self._ssl["key"] is None) != (self._ssl["cert"] is None):
                raise AttributeError(
                    "ssl_key and ssl_cert need to be both set, or neither"
                )
            if "tls_versions" in self._ssl and self._ssl["tls_versions"] is not None:
                self._validate_tls_versions()

            if (
                "tls_ciphersuites" in self._ssl
                and self._ssl["tls_ciphersuites"] is not None
            ):
                self._validate_tls_ciphersuites()

        if self._conn_attrs is None:
            self._conn_attrs = {}
        elif not isinstance(self._conn_attrs, dict):
            raise InterfaceError("conn_attrs must be of type dict")
        else:
            for attr_name, attr_value in self._conn_attrs.items():
                if attr_name in CONN_ATTRS_DN:
                    continue
                # Validate name type
                if not isinstance(attr_name, str):
                    raise InterfaceError(
                        "Attribute name should be a string, found: "
                        f"'{attr_name}' in '{self._conn_attrs}'"
                    )
                # Validate attribute name limit 32 characters
                if len(attr_name) > 32:
                    raise InterfaceError(
                        f"Attribute name '{attr_name}' exceeds 32 characters limit size"
                    )
                # Validate names in connection attributes cannot start with "_"
                if attr_name.startswith("_"):
                    raise InterfaceError(
                        "Key names in connection attributes cannot start with "
                        "'_', found: '{attr_name}'"
                    )
                # Validate value type
                if not isinstance(attr_value, str):
                    raise InterfaceError(
                        f"Attribute '{attr_name}' value: '{attr_value}' must "
                        "be a string type"
                    )
                # Validate attribute value limit 1024 characters
                if len(attr_value) > 1024:
                    raise InterfaceError(
                        f"Attribute '{attr_name}' value: '{attr_value}' "
                        "exceeds 1024 characters limit size"
                    )

        if self._client_flags & ClientFlag.CONNECT_ARGS:
            self._add_default_conn_attrs()

        if "kerberos_auth_mode" in config and config["kerberos_auth_mode"] is not None:
            if not isinstance(config["kerberos_auth_mode"], str):
                raise InterfaceError("'kerberos_auth_mode' must be of type str")
            kerberos_auth_mode = config["kerberos_auth_mode"].lower()
            if kerberos_auth_mode == "sspi":
                if os.name != "nt":
                    raise InterfaceError(
                        "'kerberos_auth_mode=SSPI' is only available on Windows"
                    )
                self._auth_plugin_class = "MySQLSSPIKerberosAuthPlugin"
            elif kerberos_auth_mode == "gssapi":
                self._auth_plugin_class = "MySQLKerberosAuthPlugin"
            else:
                raise InterfaceError(
                    "Invalid 'kerberos_auth_mode' mode. Please use 'SSPI' or 'GSSAPI'"
                )

        if (
            "krb_service_principal" in config
            and config["krb_service_principal"] is not None
        ):
            self._krb_service_principal = config["krb_service_principal"]
            if not isinstance(self._krb_service_principal, str):
                raise InterfaceError(
                    KRB_SERVICE_PINCIPAL_ERROR.format(error="is not a string")
                )
            if self._krb_service_principal == "":
                raise InterfaceError(
                    KRB_SERVICE_PINCIPAL_ERROR.format(
                        error="can not be an empty string"
                    )
                )
            if "/" not in self._krb_service_principal:
                raise InterfaceError(
                    KRB_SERVICE_PINCIPAL_ERROR.format(error="is incorrectly formatted")
                )

        if self._fido_callback:
            # Import the callable if it's a str
            if isinstance(self._fido_callback, str):
                try:
                    module, callback = self._fido_callback.rsplit(".", 1)
                except ValueError:
                    raise ProgrammingError(
                        f"No callable named '{self._fido_callback}'"
                    ) from None
                try:
                    module = importlib.import_module(module)
                    self._fido_callback = getattr(module, callback)
                except (AttributeError, ModuleNotFoundError) as err:
                    raise ProgrammingError(f"{err}") from err
            # Check if it's a callable
            if not callable(self._fido_callback):
                raise ProgrammingError("Expected a callable for 'fido_callback'")
            # Check the callable signature if has only 1 positional argument
            params = len(signature(self._fido_callback).parameters)
            if params != 1:
                raise ProgrammingError(
                    "'fido_callback' requires 1 positional argument, but the "
                    f"callback provided has {params}"
                )

    def _add_default_conn_attrs(self) -> Any:
        """Add the default connection attributes."""

    @staticmethod
    def _check_server_version(server_version: StrOrBytes) -> Tuple[int, ...]:
        """Check the MySQL version

        This method will check the MySQL version and raise an InterfaceError
        when it is not supported or invalid. It will return the version
        as a tuple with major, minor and patch.

        Raises InterfaceError if invalid server version.

        Returns tuple
        """
        if isinstance(server_version, (bytearray, bytes)):
            server_version = server_version.decode()

        regex_ver = re.compile(r"^(\d{1,2})\.(\d{1,2})\.(\d{1,3})(.*)")
        match = regex_ver.match(server_version)
        if not match:
            raise InterfaceError("Failed parsing MySQL version")

        version = tuple(int(v) for v in match.groups()[0:3])
        if version < (4, 1):
            raise InterfaceError(f"MySQL Version '{server_version}' is not supported")

        return version

    def get_server_version(self) -> Tuple[int, ...]:
        """Get the MySQL version

        This method returns the MySQL server version as a tuple. If not
        previously connected, it will return None.

        Returns a tuple or None.
        """
        return self._server_version

    def get_server_info(self) -> Optional[str]:
        """Get the original MySQL version information

        This method returns the original MySQL server as text. If not
        previously connected, it will return None.

        Returns a string or None.
        """
        try:
            return self._handshake["server_version_original"]  # type: ignore[return-value]
        except (TypeError, KeyError):
            return None

    @property
    @abstractmethod
    def in_transaction(self) -> Any:
        """MySQL session has started a transaction"""

    def set_client_flags(self, flags: Union[int, Sequence[int]]) -> int:
        """Set the client flags

        The flags-argument can be either an int or a list (or tuple) of
        ClientFlag-values. If it is an integer, it will set client_flags
        to flags as is.
        If flags is a list (or tuple), each flag will be set or unset
        when it's negative.

        set_client_flags([ClientFlag.FOUND_ROWS,-ClientFlag.LONG_FLAG])

        Raises ProgrammingError when the flags argument is not a set or
        an integer bigger than 0.

        Returns self.client_flags
        """
        if isinstance(flags, int) and flags > 0:
            self._client_flags = flags
        elif isinstance(flags, (tuple, list)):
            for flag in flags:
                if flag < 0:
                    self._client_flags &= ~abs(flag)
                else:
                    self._client_flags |= flag
        else:
            raise ProgrammingError("set_client_flags expect integer (>0) or set")
        return self._client_flags

    def isset_client_flag(self, flag: int) -> bool:
        """Check if a client flag is set"""
        if (self._client_flags & flag) > 0:
            return True
        return False

    @property
    def time_zone(self) -> str:
        """Get the current time zone"""
        return self.info_query("SELECT @@session.time_zone")[0]

    @time_zone.setter
    def time_zone(self, value: str) -> None:
        """Set the time zone"""
        self.cmd_query(f"SET @@session.time_zone = '{value}'")
        self._time_zone = value

    @property
    def sql_mode(self) -> str:
        """Get the SQL mode"""
        if self._sql_mode is None:
            self._sql_mode = self.info_query("SELECT @@session.sql_mode")[0]
        return self._sql_mode

    @sql_mode.setter
    def sql_mode(self, value: Union[str, Sequence[int]]) -> None:
        """Set the SQL mode

        This method sets the SQL Mode for the current connection. The value
        argument can be either a string with comma separate mode names, or
        a sequence of mode names.

        It is good practice to use the constants class SQLMode:
          from mysql.connector.constants import SQLMode
          cnx.sql_mode = [SQLMode.NO_ZERO_DATE, SQLMode.REAL_AS_FLOAT]
        """
        if isinstance(value, (list, tuple)):
            value = ",".join(value)
        self.cmd_query(f"SET @@session.sql_mode = '{value}'")
        self._sql_mode = value

    @abstractmethod
    def info_query(self, query: Any) -> Any:
        """Send a query which only returns 1 row"""

    def set_login(
        self, username: Optional[str] = None, password: Optional[str] = None
    ) -> None:
        """Set login information for MySQL

        Set the username and/or password for the user connecting to
        the MySQL Server.
        """
        if username is not None:
            self._user = username.strip()
        else:
            self._user = ""
        if password is not None:
            self._password = password
        else:
            self._password = ""

    def set_unicode(self, value: bool = True) -> None:
        """Toggle unicode mode

        Set whether we return string fields as unicode or not.
        Default is True.
        """
        self._use_unicode = value
        if self.converter:
            self.converter.set_unicode(value)

    @property
    def autocommit(self) -> bool:
        """Get whether autocommit is on or off"""
        value = self.info_query("SELECT @@session.autocommit")[0]
        return value == 1

    @autocommit.setter
    def autocommit(self, value: bool) -> None:
        """Toggle autocommit"""
        switch = "ON" if value else "OFF"
        self.cmd_query(f"SET @@session.autocommit = {switch}")
        self._autocommit = value

    @property
    def get_warnings(self) -> bool:
        """Get whether this connection retrieves warnings automatically

        This method returns whether this connection retrieves warnings
        automatically.

        Returns True, or False when warnings are not retrieved.
        """
        return self._get_warnings

    @get_warnings.setter
    def get_warnings(self, value: bool) -> None:
        """Set whether warnings should be automatically retrieved

        The toggle-argument must be a boolean. When True, cursors for this
        connection will retrieve information about warnings (if any).

        Raises ValueError on error.
        """
        if not isinstance(value, bool):
            raise ValueError("Expected a boolean type")
        self._get_warnings = value

    @property
    def raise_on_warnings(self) -> bool:
        """Get whether this connection raises an error on warnings

        This method returns whether this connection will raise errors when
        MySQL reports warnings.

        Returns True or False.
        """
        return self._raise_on_warnings

    @raise_on_warnings.setter
    def raise_on_warnings(self, value: bool) -> None:
        """Set whether warnings raise an error

        The toggle-argument must be a boolean. When True, cursors for this
        connection will raise an error when MySQL reports warnings.

        Raising on warnings implies retrieving warnings automatically. In
        other words: warnings will be set to True. If set to False, warnings
        will be also set to False.

        Raises ValueError on error.
        """
        if not isinstance(value, bool):
            raise ValueError("Expected a boolean type")
        self._raise_on_warnings = value
        # Don't disable warning retrieval if raising explicitly disabled
        if value:
            self._get_warnings = value

    @property
    def unread_result(self) -> bool:
        """Get whether there is an unread result

        This method is used by cursors to check whether another cursor still
        needs to retrieve its result set.

        Returns True, or False when there is no unread result.
        """
        return self._unread_result

    @unread_result.setter
    def unread_result(self, value: bool) -> None:
        """Set whether there is an unread result

        This method is used by cursors to let other cursors know there is
        still a result set that needs to be retrieved.

        Raises ValueError on errors.
        """
        if not isinstance(value, bool):
            raise ValueError("Expected a boolean type")
        self._unread_result = value

    @property
    def charset(self) -> str:
        """Returns the character set for current connection

        This property returns the character set name of the current connection.
        The server is queried when the connection is active. If not connected,
        the configured character set name is returned.

        Returns a string.
        """
        return CharacterSet.get_info(self._charset_id)[0]

    @property
    def python_charset(self) -> str:
        """Returns the Python character set for current connection

        This property returns the character set name of the current connection.
        Note that, unlike property charset, this checks if the previously set
        character set is supported by Python and if not, it returns the
        equivalent character set that Python supports.

        Returns a string.
        """
        encoding = CharacterSet.get_info(self._charset_id)[0]
        if encoding in ("utf8mb4", "utf8mb3", "binary"):
            return "utf8"
        return encoding

    def set_charset_collation(
        self, charset: Optional[Union[int, str]] = None, collation: Optional[str] = None
    ) -> None:
        """Sets the character set and collation for the current connection

        This method sets the character set and collation to be used for
        the current connection. The charset argument can be either the
        name of a character set as a string, or the numerical equivalent
        as defined in constants.CharacterSet.

        When the collation is not given, the default will be looked up and
        used.

        For example, the following will set the collation for the latin1
        character set to latin1_general_ci:

           set_charset('latin1','latin1_general_ci')

        """
        err_msg = "{} should be either integer, string or None"
        if not isinstance(charset, (int, str)) and charset is not None:
            raise ValueError(err_msg.format("charset"))
        if not isinstance(collation, str) and collation is not None:
            raise ValueError("collation should be either string or None")

        if charset:
            if isinstance(charset, int):
                (
                    self._charset_id,
                    charset_name,
                    collation_name,
                ) = CharacterSet.get_charset_info(charset)
            elif isinstance(charset, str):
                (
                    self._charset_id,
                    charset_name,
                    collation_name,
                ) = CharacterSet.get_charset_info(charset, collation)
            else:
                raise ValueError(err_msg.format("charset"))
        elif collation:
            (
                self._charset_id,
                charset_name,
                collation_name,
            ) = CharacterSet.get_charset_info(collation=collation)
        else:
            charset = DEFAULT_CONFIGURATION["charset"]
            (
                self._charset_id,
                charset_name,
                collation_name,
            ) = CharacterSet.get_charset_info(charset, collation=None)

        self._execute_query(f"SET NAMES '{charset_name}' COLLATE '{collation_name}'")

        try:
            # Required for C Extension
            self.set_character_set_name(charset_name)
        except AttributeError:
            # Not required for pure Python connection
            pass

        if self.converter:
            self.converter.set_charset(charset_name)

    @property
    def collation(self) -> str:
        """Returns the collation for current connection

        This property returns the collation name of the current connection.
        The server is queried when the connection is active. If not connected,
        the configured collation name is returned.

        Returns a string.
        """
        return CharacterSet.get_charset_info(self._charset_id)[2]

    @abstractmethod
    def _do_handshake(self) -> Any:
        """Gather information of the MySQL server before authentication"""

    @abstractmethod
    def _open_connection(self) -> Any:
        """Open the connection to the MySQL server"""

    def _post_connection(self) -> None:
        """Executes commands after connection has been established

        This method executes commands after the connection has been
        established. Some setting like autocommit, character set, and SQL mode
        are set using this method.
        """
        self.set_charset_collation(self._charset_id)
        self.autocommit = self._autocommit
        if self._time_zone:
            self.time_zone = self._time_zone
        if self._sql_mode:
            self.sql_mode = self._sql_mode
        if self._init_command:
            self._execute_query(self._init_command)

    @abstractmethod
    def disconnect(self) -> Any:
        """Disconnect from the MySQL server"""

    close: Callable[[], Any] = disconnect

    def connect(self, **kwargs: Any) -> None:
        """Connect to the MySQL server

        This method sets up the connection to the MySQL server. If no
        arguments are given, it will use the already configured or default
        values.
        """
        if kwargs:
            self.config(**kwargs)

        self.disconnect()
        self._open_connection()
        # Server does not allow to run any other statement different from ALTER
        # when user's password has been expired.
        if not self._client_flags & ClientFlag.CAN_HANDLE_EXPIRED_PASSWORDS:
            self._post_connection()

    def reconnect(self, attempts: int = 1, delay: int = 0) -> None:
        """Attempt to reconnect to the MySQL server

        The argument attempts should be the number of times a reconnect
        is tried. The delay argument is the number of seconds to wait between
        each retry.

        You may want to set the number of attempts higher and use delay when
        you expect the MySQL server to be down for maintenance or when you
        expect the network to be temporary unavailable.

        Raises InterfaceError on errors.
        """
        counter = 0
        span = None

        if self._tracer:
            span = self._tracer.start_span(
                name=CONNECTION_SPAN_NAME, kind=trace.SpanKind.CLIENT
            )

        try:
            while counter != attempts:
                counter = counter + 1
                try:
                    self.disconnect()
                    self.connect()
                    if self.is_connected():
                        break
                except (Error, IOError) as err:
                    if counter == attempts:
                        msg = (
                            f"Can not reconnect to MySQL after {attempts} "
                            f"attempt(s): {err}"
                        )
                        raise InterfaceError(msg) from err
                if delay > 0:
                    sleep(delay)
        except InterfaceError as interface_err:
            if OTEL_ENABLED:
                set_connection_span_attrs(self, span)
                record_exception_event(span, interface_err)
                end_span(span)
            raise

        self._span = span
        if OTEL_ENABLED:
            set_connection_span_attrs(self, self._span)

    @abstractmethod
    def is_connected(self) -> Any:
        """Reports whether the connection to MySQL Server is available"""

    @abstractmethod
    def ping(self, reconnect: bool = False, attempts: int = 1, delay: int = 0) -> Any:
        """Check availability of the MySQL server"""

    @abstractmethod
    def commit(self) -> Any:
        """Commit current transaction"""

    @abstractmethod
    def cursor(
        self,
        buffered: Optional[bool] = None,
        raw: Optional[bool] = None,
        prepared: Optional[bool] = None,
        cursor_class: Optional[type] = None,
        dictionary: Optional[bool] = None,
        named_tuple: Optional[bool] = None,
    ) -> "MySQLCursorAbstract":
        """Instantiates and returns a cursor"""

    @abstractmethod
    def _execute_query(self, query: Any) -> Any:
        """Execute a query"""

    @abstractmethod
    def rollback(self) -> Any:
        """Rollback current transaction"""

    def start_transaction(
        self,
        consistent_snapshot: bool = False,
        isolation_level: Optional[str] = None,
        readonly: Optional[bool] = None,
    ) -> None:
        """Start a transaction

        This method explicitly starts a transaction sending the
        START TRANSACTION statement to the MySQL server. You can optionally
        set whether there should be a consistent snapshot, which
        isolation level you need or which access mode i.e. READ ONLY or
        READ WRITE.

        For example, to start a transaction with isolation level SERIALIZABLE,
        you would do the following:
            >>> cnx = mysql.connector.connect(..)
            >>> cnx.start_transaction(isolation_level='SERIALIZABLE')

        Raises ProgrammingError when a transaction is already in progress
        and when ValueError when isolation_level specifies an Unknown
        level.
        """
        if self.in_transaction:
            raise ProgrammingError("Transaction already in progress")

        if isolation_level:
            level = isolation_level.strip().replace("-", " ").upper()
            levels = [
                "READ UNCOMMITTED",
                "READ COMMITTED",
                "REPEATABLE READ",
                "SERIALIZABLE",
            ]

            if level not in levels:
                raise ValueError(f'Unknown isolation level "{isolation_level}"')

            self._execute_query(f"SET TRANSACTION ISOLATION LEVEL {level}")

        if readonly is not None:
            if self._server_version < (5, 6, 5):
                raise ValueError(
                    f"MySQL server version {self._server_version} does not "
                    "support this feature"
                )

            if readonly:
                access_mode = "READ ONLY"
            else:
                access_mode = "READ WRITE"
            self._execute_query(f"SET TRANSACTION {access_mode}")

        query = "START TRANSACTION"
        if consistent_snapshot:
            query += " WITH CONSISTENT SNAPSHOT"
        self.cmd_query(query)

    def reset_session(
        self,
        user_variables: Optional[Dict[str, Any]] = None,
        session_variables: Optional[Dict[str, Any]] = None,
    ) -> None:
        """Clears the current active session

        This method resets the session state, if the MySQL server is 5.7.3
        or later active session will be reset without re-authenticating.
        For other server versions session will be reset by re-authenticating.

        It is possible to provide a sequence of variables and their values to
        be set after clearing the session. This is possible for both user
        defined variables and session variables.
        This method takes two arguments user_variables and session_variables
        which are dictionaries.

        Raises OperationalError if not connected, InternalError if there are
        unread results and InterfaceError on errors.
        """
        if not self.is_connected():
            raise OperationalError("MySQL Connection not available")

        try:
            self.cmd_reset_connection()
        except (NotSupportedError, NotImplementedError):
            if self._compress:
                raise NotSupportedError(
                    "Reset session is not supported with compression for "
                    "MySQL server version 5.7.2 or earlier"
                ) from None
            self.cmd_change_user(
                self._user,
                self._password,
                self._database,
                self._charset_id,
            )

        if user_variables or session_variables:
            cur = self.cursor()
            if user_variables:
                for key, value in user_variables.items():
                    cur.execute(f"SET @`{key}` = {value}")
            if session_variables:
                for key, value in session_variables.items():
                    cur.execute(f"SET SESSION `{key}` = {value}")
            cur.close()

    def set_converter_class(self, convclass: Optional[Type[MySQLConverter]]) -> None:
        """
        Set the converter class to be used. This should be a class overloading
        methods and members of conversion.MySQLConverter.
        """
        if convclass and issubclass(convclass, MySQLConverterBase):
            charset_name = CharacterSet.get_info(self._charset_id)[0]
            self._converter_class = convclass
            self.converter = convclass(charset_name, self._use_unicode)
            self.converter.str_fallback = self._converter_str_fallback
        else:
            raise TypeError(
                "Converter class should be a subclass of conversion.MySQLConverterBase."
            )

    @abstractmethod
    def get_rows(
        self,
        count: Optional[int] = None,
        binary: bool = False,
        columns: Optional[List[DescriptionType]] = None,
        raw: Optional[bool] = None,
        prep_stmt: Any = None,
    ) -> Tuple[List[Any], Optional[Mapping[str, Any]]]:
        """Get all rows returned by the MySQL server"""

    def cmd_init_db(self, database: str) -> Optional[Mapping[str, Any]]:
        """Change the current database"""
        raise NotImplementedError

    def cmd_query(
        self,
        query: Any,
        raw: bool = False,
        buffered: bool = False,
        raw_as_string: bool = False,
    ) -> Optional[Mapping[str, Any]]:
        """Send a query to the MySQL server"""
        raise NotImplementedError

    def cmd_query_iter(
        self, statements: Any
    ) -> Generator[Mapping[str, Any], None, None]:
        """Send one or more statements to the MySQL server"""
        raise NotImplementedError

    def cmd_refresh(self, options: int) -> Optional[Mapping[str, Any]]:
        """Send the Refresh command to the MySQL server"""
        raise NotImplementedError

    def cmd_quit(self) -> Any:
        """Close the current connection with the server"""
        raise NotImplementedError

    def cmd_shutdown(
        self, shutdown_type: Optional[int] = None
    ) -> Optional[Mapping[str, Any]]:
        """Shut down the MySQL Server"""
        raise NotImplementedError

    def cmd_statistics(self) -> Optional[Mapping[str, Any]]:
        """Send the statistics command to the MySQL Server"""
        raise NotImplementedError

    @staticmethod
    def cmd_process_info() -> Any:
        """Get the process list of the MySQL Server

        This method is a placeholder to notify that the PROCESS_INFO command
        is not supported by raising the NotSupportedError. The command
        "SHOW PROCESSLIST" should be send using the cmd_query()-method or
        using the INFORMATION_SCHEMA database.

        Raises NotSupportedError exception
        """
        raise NotSupportedError(
            "Not implemented. Use SHOW PROCESSLIST or INFORMATION_SCHEMA"
        )

    def cmd_process_kill(self, mysql_pid: int) -> Optional[Mapping[str, Any]]:
        """Kill a MySQL process"""
        raise NotImplementedError

    def cmd_debug(self) -> Optional[Mapping[str, Any]]:
        """Send the DEBUG command"""
        raise NotImplementedError

    def cmd_ping(self) -> Optional[Mapping[str, Any]]:
        """Send the PING command"""
        raise NotImplementedError

    def cmd_change_user(
        self,
        username: str = "",
        password: str = "",
        database: str = "",
        charset: int = 45,
        password1: str = "",
        password2: str = "",
        password3: str = "",
        oci_config_file: str = "",
    ) -> Optional[Mapping[str, Any]]:
        """Change the current logged in user"""
        raise NotImplementedError

    def cmd_stmt_prepare(self, statement: Any) -> Optional[Mapping[str, Any]]:
        """Prepare a MySQL statement"""
        raise NotImplementedError

    def cmd_stmt_execute(
        self,
        statement_id: Any,
        data: Sequence[Any] = (),
        parameters: Sequence[Any] = (),
        flags: int = 0,
    ) -> Any:
        """Execute a prepared MySQL statement"""
        raise NotImplementedError

    def cmd_stmt_close(self, statement_id: Any) -> Any:
        """Deallocate a prepared MySQL statement"""
        raise NotImplementedError

    def cmd_stmt_send_long_data(
        self, statement_id: Any, param_id: int, data: BinaryIO
    ) -> Any:
        """Send data for a column"""
        raise NotImplementedError

    def cmd_stmt_reset(self, statement_id: Any) -> Any:
        """Reset data for prepared statement sent as long data"""
        raise NotImplementedError

    def cmd_reset_connection(self) -> Any:
        """Resets the session state without re-authenticating"""
        raise NotImplementedError


class MySQLCursorAbstract(ABC):
    """Abstract cursor class

    Abstract class defining cursor class with method and members
    required by the Python Database API Specification v2.0.
    """

    def __init__(self) -> None:
        """Initialization"""
        self._description: Optional[List[DescriptionType]] = None
        self._rowcount: int = -1
        self._last_insert_id: Optional[int] = None
        self._warnings: Optional[List[WarningType]] = None
        self._warning_count: int = 0
        self._executed: Optional[StrOrBytes] = None
        self._executed_list: List[StrOrBytes] = []
        self._stored_results: List[Any] = []
        self.arraysize: int = 1

    def __enter__(self) -> MySQLCursorAbstract:
        return self

    def __exit__(
        self,
        exc_type: Type[BaseException],
        exc_value: BaseException,
        traceback: TracebackType,
    ) -> None:
        self.close()

    @abstractmethod
    def callproc(self, procname: str, args: Sequence[Any] = ()) -> Any:
        """Calls a stored procedure with the given arguments

        The arguments will be set during this session, meaning
        they will be called like  _<procname>__arg<nr> where
        <nr> is an enumeration (+1) of the arguments.

        Coding Example:
          1) Defining the Stored Routine in MySQL:
          CREATE PROCEDURE multiply(IN pFac1 INT, IN pFac2 INT, OUT pProd INT)
          BEGIN
            SET pProd := pFac1 * pFac2;
          END

          2) Executing in Python:
          args = (5,5,0) # 0 is to hold pprod
          cursor.callproc('multiply', args)
          print(cursor.fetchone())

        Does not return a value, but a result set will be
        available when the CALL-statement execute successfully.
        Raises exceptions when something is wrong.
        """

    @abstractmethod
    def close(self) -> Any:
        """Close the cursor."""

    @abstractmethod
    def execute(
        self,
        operation: str,
        params: Union[Sequence[Any], Dict[str, Any]] = (),
        multi: bool = False,
    ) -> Any:
        """Executes the given operation

        Executes the given operation substituting any markers with
        the given parameters.

        For example, getting all rows where id is 5:
          cursor.execute("SELECT * FROM t1 WHERE id = %s", (5,))

        The multi argument should be set to True when executing multiple
        statements in one operation. If not set and multiple results are
        found, an InterfaceError will be raised.

        If warnings where generated, and connection.get_warnings is True, then
        self._warnings will be a list containing these warnings.

        Returns an iterator when multi is True, otherwise None.
        """

    @abstractmethod
    def executemany(
        self, operation: str, seq_params: Sequence[Union[Sequence[Any], Dict[str, Any]]]
    ) -> Any:
        """Execute the given operation multiple times

        The executemany() method will execute the operation iterating
        over the list of parameters in seq_params.

        Example: Inserting 3 new employees and their phone number

        data = [
            ('Jane','555-001'),
            ('Joe', '555-001'),
            ('John', '555-003')
            ]
        stmt = "INSERT INTO employees (name, phone) VALUES ('%s','%s')"
        cursor.executemany(stmt, data)

        INSERT statements are optimized by batching the data, that is
        using the MySQL multiple rows syntax.

        Results are discarded. If they are needed, consider looping over
        data using the execute() method.
        """

    @abstractmethod
    def fetchone(self) -> Optional[Sequence[Any]]:
        """Returns next row of a query result set

        Returns a tuple or None.
        """

    @abstractmethod
    def fetchmany(self, size: int = 1) -> List[Sequence[Any]]:
        """Returns the next set of rows of a query result, returning a
        list of tuples. When no more rows are available, it returns an
        empty list.

        The number of rows returned can be specified using the size argument,
        which defaults to one
        """

    @abstractmethod
    def fetchall(self) -> Sequence[Any]:
        """Returns all rows of a query result set

        Returns a list of tuples.
        """

    def nextset(self) -> Any:
        """Not Implemented."""

    def setinputsizes(self, sizes: Any) -> Any:
        """Not Implemented."""

    def setoutputsize(self, size: Any, column: Any = None) -> Any:
        """Not Implemented."""

    def reset(self, free: bool = True) -> Any:
        """Reset the cursor to default"""

    @property
    @abstractmethod
    def description(
        self,
    ) -> Optional[List[DescriptionType]]:
        """Returns description of columns in a result

        This property returns a list of tuples describing the columns in
        in a result set. A tuple is described as follows::

                (column_name,
                 type,
                 None,
                 None,
                 None,
                 None,
                 null_ok,
                 column_flags)  # Addition to PEP-249 specs

        Returns a list of tuples.
        """
        return self._description

    @property
    @abstractmethod
    def rowcount(self) -> int:
        """Returns the number of rows produced or affected

        This property returns the number of rows produced by queries
        such as a SELECT, or affected rows when executing DML statements
        like INSERT or UPDATE.

        Note that for non-buffered cursors it is impossible to know the
        number of rows produced before having fetched them all. For those,
        the number of rows will be -1 right after execution, and
        incremented when fetching rows.

        Returns an integer.
        """
        return self._rowcount

    @property
    def lastrowid(self) -> Optional[int]:
        """Returns the value generated for an AUTO_INCREMENT column

        Returns the value generated for an AUTO_INCREMENT column by
        the previous INSERT or UPDATE statement or None when there is
        no such value available.

        Returns a long value or None.
        """
        return self._last_insert_id

    @property
    def warnings(self) -> Optional[List[WarningType]]:
        """Return warnings."""
        return self._warnings

    @property
    def warning_count(self) -> int:
        """Returns the number of warnings

        This property returns the number of warnings generated by the
        previously executed operation.

        Returns an integer value.
        """
        return self._warning_count

    def fetchwarnings(self) -> Optional[List[WarningType]]:
        """Returns Warnings."""
        return self._warnings

    def get_attributes(self) -> Optional[List[Tuple[str, Any]]]:
        """Get the added query attributes so far."""
        if hasattr(self, "_cnx"):
            return self._cnx.query_attrs
        if hasattr(self, "_connection"):
            return self._connection.query_attrs
        return None

    def add_attribute(self, name: str, value: Any) -> None:
        """Add a query attribute and his value."""
        if not isinstance(name, str):
            raise ProgrammingError("Parameter `name` must be a string type")
        if value is not None and not isinstance(value, MYSQL_PY_TYPES):
            raise ProgrammingError(
                f"Object {value} cannot be converted to a MySQL type"
            )
        if hasattr(self, "_cnx"):
            self._cnx.query_attrs_append((name, value))
        elif hasattr(self, "_connection"):
            self._connection.query_attrs_append((name, value))

    def remove_attribute(self, name: str) -> Any:
        """Remove a query attribute by name.

        If no match, `None` is returned; else the corresponding value is returned.
        """
        if not isinstance(name, str):
            raise ProgrammingError("Parameter `name` must be a string type")
        if hasattr(self, "_cnx"):
            return self._cnx.query_attrs_remove(name)
        if hasattr(self, "_connection"):
            return self._connection.query_attrs_remove(name)
        return None

    def clear_attributes(self) -> None:
        """Remove all the query attributes."""
        if hasattr(self, "_cnx"):
            self._cnx.query_attrs_clear()
        elif hasattr(self, "_connection"):
            self._connection.query_attrs_clear()

virt/lib/python3.9/site-packages/mysql/connector/abstracts 2.py
PrinceDisant/LostInNL
# Copyright (c) 2014, 2023, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

# mypy: disable-error-code="assignment,attr-defined"

"""Module gathering all abstract base classes."""

from __future__ import annotations

import importlib
import os
import re
import weakref

from abc import ABC, abstractmethod
from datetime import date, datetime, time, timedelta
from decimal import Decimal
from inspect import signature
from time import sleep
from types import TracebackType
from typing import (
    Any,
    BinaryIO,
    Callable,
    Dict,
    Generator,
    List,
    Mapping,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
)

TLS_V1_3_SUPPORTED = False
try:
    import ssl

    if hasattr(ssl, "HAS_TLSv1_3") and ssl.HAS_TLSv1_3:
        TLS_V1_3_SUPPORTED = True
except ImportError:
    # If import fails, we don't have SSL support.
    pass

from .constants import (
    CONN_ATTRS_DN,
    DEFAULT_CONFIGURATION,
    DEPRECATED_TLS_VERSIONS,
    OPENSSL_CS_NAMES,
    TLS_CIPHER_SUITES,
    TLS_VERSIONS,
    CharacterSet,
    ClientFlag,
)
from .conversion import MySQLConverter, MySQLConverterBase
from .errors import (
    Error,
    InterfaceError,
    NotSupportedError,
    OperationalError,
    ProgrammingError,
)
from .opentelemetry.constants import (
    CONNECTION_SPAN_NAME,
    OPTION_CNX_SPAN,
    OPTION_CNX_TRACER,
    OTEL_ENABLED,
)

if OTEL_ENABLED:
    from .opentelemetry.instrumentation import (
        end_span,
        record_exception_event,
        set_connection_span_attrs,
        trace,
    )

from .optionfiles import read_option_files
from .types import (
    ConnAttrsType,
    DescriptionType,
    HandShakeType,
    StrOrBytes,
    SupportedMysqlBinaryProtocolTypes,
    WarningType,
)

NAMED_TUPLE_CACHE: weakref.WeakValueDictionary[Any, Any] = weakref.WeakValueDictionary()

DUPLICATED_IN_LIST_ERROR = (
    "The '{list}' list must not contain repeated values, the value "
    "'{value}' is duplicated."
)

TLS_VERSION_ERROR = (
    "The given tls_version: '{}' is not recognized as a valid "
    "TLS protocol version (should be one of {})."
)

TLS_VERSION_DEPRECATED_ERROR = (
    "The given tls_version: '{}' are no longer allowed (should be one of {})."
)

TLS_VER_NO_SUPPORTED = (
    "No supported TLS protocol version found in the 'tls-versions' list '{}'. "
)

KRB_SERVICE_PINCIPAL_ERROR = (
    'Option "krb_service_principal" {error}, must be a string in the form '
    '"primary/instance@realm" e.g "ldap/ldapauth@MYSQL.COM" where "@realm" '
    "is optional and if it is not given will be assumed to belong to the "
    "default realm, as configured in the krb5.conf file."
)

MYSQL_PY_TYPES = (
    Decimal,
    bytes,
    date,
    datetime,
    float,
    int,
    str,
    time,
    timedelta,
)


class MySQLConnectionAbstract(ABC):
    """Abstract class for classes connecting to a MySQL server"""

    def __init__(self) -> None:
        """Initialize"""
        # opentelemetry related
        self._tracer: Any = None
        self._span: Any = None
        self.otel_context_propagation: bool = True

        self._client_flags: int = ClientFlag.get_default()
        self._charset_id: int = 45
        self._sql_mode: Optional[str] = None
        self._time_zone: Optional[str] = None
        self._autocommit: bool = False
        self._server_version: Optional[Tuple[int, ...]] = None
        self._handshake: Optional[HandShakeType] = None
        self._conn_attrs: ConnAttrsType = {}

        self._user: str = ""
        self._password: str = ""
        self._password1: str = ""
        self._password2: str = ""
        self._password3: str = ""
        self._database: str = ""
        self._host: str = "127.0.0.1"
        self._port: int = 3306
        self._unix_socket: Optional[str] = None
        self._client_host: str = ""
        self._client_port: int = 0
        self._ssl: Dict[str, Optional[Union[str, bool, List[str]]]] = {}
        self._ssl_disabled: bool = DEFAULT_CONFIGURATION["ssl_disabled"]
        self._force_ipv6: bool = False
        self._oci_config_file: Optional[str] = None
        self._oci_config_profile: Optional[str] = None
        self._fido_callback: Optional[Union[str, Callable]] = None
        self._krb_service_principal: Optional[str] = None

        self._use_unicode: bool = True
        self._get_warnings: bool = False
        self._raise_on_warnings: bool = False
        self._connection_timeout: Optional[int] = DEFAULT_CONFIGURATION[
            "connect_timeout"
        ]
        self._buffered: bool = False
        self._unread_result: bool = False
        self._have_next_result: bool = False
        self._raw: bool = False
        self._in_transaction: bool = False
        self._allow_local_infile: bool = DEFAULT_CONFIGURATION["allow_local_infile"]
        self._allow_local_infile_in_path: Optional[str] = DEFAULT_CONFIGURATION[
            "allow_local_infile_in_path"
        ]

        self._prepared_statements: Any = None
        self._query_attrs: Dict[str, Any] = {}

        self._ssl_active: bool = False
        self._auth_plugin: Optional[str] = None
        self._auth_plugin_class: Optional[str] = None
        self._pool_config_version: Any = None
        self.converter: Optional[MySQLConverter] = None
        self._converter_class: Optional[Type[MySQLConverter]] = None
        self._converter_str_fallback: bool = False
        self._compress: bool = False

        self._consume_results: bool = False
        self._init_command: Optional[str] = None

    def __enter__(self) -> MySQLConnectionAbstract:
        return self

    def __exit__(
        self,
        exc_type: Type[BaseException],
        exc_value: BaseException,
        traceback: TracebackType,
    ) -> None:
        self.close()

    def get_self(self) -> MySQLConnectionAbstract:
        """Return self for weakref.proxy

        This method is used when the original object is needed when using
        weakref.proxy.
        """
        return self

    @property
    def is_secure(self) -> bool:
        """Return True if is a secure connection."""
        return self._ssl_active or (
            self._unix_socket is not None and os.name == "posix"
        )

    @property
    def have_next_result(self) -> bool:
        """Return if have next result."""
        return self._have_next_result

    @property
    def query_attrs(self) -> List[Tuple[str, Any]]:
        """Return query attributes list."""
        return list(self._query_attrs.items())

    def query_attrs_append(
        self, value: Tuple[str, SupportedMysqlBinaryProtocolTypes]
    ) -> None:
        """Add element to the query attributes list.

        If an element in the query attributes list already matches
        the attribute name provided, the new element will NOT be added.
        """
        attr_name, attr_value = value
        if attr_name not in self._query_attrs:
            self._query_attrs[attr_name] = attr_value

    def query_attrs_remove(self, name: str) -> Any:
        """Remove element by name from the query attributes list.

        If no match, `None` is returned; else the corresponding value is returned.
        """
        return self._query_attrs.pop(name, None)

    def query_attrs_clear(self) -> None:
        """Clear query attributes list."""
        self._query_attrs = {}

    def _validate_tls_ciphersuites(self) -> None:
        """Validates the tls_ciphersuites option."""
        tls_ciphersuites = []
        tls_cs = self._ssl["tls_ciphersuites"]

        if isinstance(tls_cs, str):
            if not (tls_cs.startswith("[") and tls_cs.endswith("]")):
                raise AttributeError(
                    f"tls_ciphersuites must be a list, found: '{tls_cs}'"
                )
            tls_css = tls_cs[1:-1].split(",")
            if not tls_css:
                raise AttributeError(
                    "No valid cipher suite found in 'tls_ciphersuites' list"
                )
            for _tls_cs in tls_css:
                _tls_cs = tls_cs.strip().upper()
                if _tls_cs:
                    tls_ciphersuites.append(_tls_cs)

        elif isinstance(tls_cs, (list, set)):
            tls_ciphersuites = [tls_cs for tls_cs in tls_cs if tls_cs]
        else:
            raise AttributeError(
                "tls_ciphersuites should be a list with one or more "
                f"ciphersuites. Found: '{tls_cs}'"
            )

        tls_versions = (
            TLS_VERSIONS[:]
            if self._ssl.get("tls_versions", None) is None
            else self._ssl["tls_versions"][:]  # type: ignore[index]
        )

        # A newer TLS version can use a cipher introduced on
        # an older version.
        tls_versions.sort(reverse=True)  # type: ignore[union-attr]
        newer_tls_ver = tls_versions[0]
        # translated_names[0] belongs to TLSv1, TLSv1.1 and TLSv1.2
        # translated_names[1] are TLSv1.3 only
        translated_names: List[List[str]] = [[], []]
        iani_cipher_suites_names = {}
        ossl_cipher_suites_names: List[str] = []

        # Old ciphers can work with new TLS versions.
        # Find all the ciphers introduced on previous TLS versions.
        for tls_ver in TLS_VERSIONS[: TLS_VERSIONS.index(newer_tls_ver) + 1]:
            iani_cipher_suites_names.update(TLS_CIPHER_SUITES[tls_ver])
            ossl_cipher_suites_names.extend(OPENSSL_CS_NAMES[tls_ver])

        for name in tls_ciphersuites:
            if "-" in name and name in ossl_cipher_suites_names:
                if name in OPENSSL_CS_NAMES["TLSv1.3"]:
                    translated_names[1].append(name)
                else:
                    translated_names[0].append(name)
            elif name in iani_cipher_suites_names:
                translated_name = iani_cipher_suites_names[name]
                if translated_name in translated_names:
                    raise AttributeError(
                        DUPLICATED_IN_LIST_ERROR.format(
                            list="tls_ciphersuites", value=translated_name
                        )
                    )
                if name in TLS_CIPHER_SUITES["TLSv1.3"]:
                    translated_names[1].append(iani_cipher_suites_names[name])
                else:
                    translated_names[0].append(iani_cipher_suites_names[name])
            else:
                raise AttributeError(
                    f"The value '{name}' in tls_ciphersuites is not a valid "
                    "cipher suite"
                )
        if not translated_names[0] and not translated_names[1]:
            raise AttributeError(
                "No valid cipher suite found in the 'tls_ciphersuites' list"
            )

        self._ssl["tls_ciphersuites"] = [
            ":".join(translated_names[0]),
            ":".join(translated_names[1]),
        ]

    def _validate_tls_versions(self) -> None:
        """Validates the tls_versions option."""
        tls_versions = []
        tls_version = self._ssl["tls_versions"]

        if isinstance(tls_version, str):
            if not (tls_version.startswith("[") and tls_version.endswith("]")):
                raise AttributeError(
                    f"tls_versions must be a list, found: '{tls_version}'"
                )
            tls_vers = tls_version[1:-1].split(",")
            for tls_ver in tls_vers:
                tls_version = tls_ver.strip()
                if tls_version == "":
                    continue
                if tls_version in tls_versions:
                    raise AttributeError(
                        DUPLICATED_IN_LIST_ERROR.format(
                            list="tls_versions", value=tls_version
                        )
                    )
                tls_versions.append(tls_version)
            if tls_vers == ["TLSv1.3"] and not TLS_V1_3_SUPPORTED:
                raise AttributeError(
                    TLS_VER_NO_SUPPORTED.format(tls_version, TLS_VERSIONS)
                )
        elif isinstance(tls_version, list):
            if not tls_version:
                raise AttributeError(
                    "At least one TLS protocol version must be specified in "
                    "'tls_versions' list"
                )
            for tls_ver in tls_version:
                if tls_ver in tls_versions:
                    raise AttributeError(
                        DUPLICATED_IN_LIST_ERROR.format(
                            list="tls_versions", value=tls_ver
                        )
                    )
                tls_versions.append(tls_ver)
        elif isinstance(tls_version, set):
            for tls_ver in tls_version:
                tls_versions.append(tls_ver)
        else:
            raise AttributeError(
                "tls_versions should be a list with one or more of versions "
                f"in {', '.join(TLS_VERSIONS)}. found: '{tls_versions}'"
            )

        if not tls_versions:
            raise AttributeError(
                "At least one TLS protocol version must be specified "
                "in 'tls_versions' list when this option is given"
            )

        use_tls_versions = []
        deprecated_tls_versions = []
        invalid_tls_versions = []
        for tls_ver in tls_versions:
            if tls_ver in TLS_VERSIONS:
                use_tls_versions.append(tls_ver)
            if tls_ver in DEPRECATED_TLS_VERSIONS:
                deprecated_tls_versions.append(tls_ver)
            else:
                invalid_tls_versions.append(tls_ver)

        if use_tls_versions:
            if use_tls_versions == ["TLSv1.3"] and not TLS_V1_3_SUPPORTED:
                raise NotSupportedError(
                    TLS_VER_NO_SUPPORTED.format(tls_version, TLS_VERSIONS)
                )
            use_tls_versions.sort()
            self._ssl["tls_versions"] = use_tls_versions
        elif deprecated_tls_versions:
            raise NotSupportedError(
                TLS_VERSION_DEPRECATED_ERROR.format(
                    deprecated_tls_versions, TLS_VERSIONS
                )
            )
        elif invalid_tls_versions:
            raise AttributeError(TLS_VERSION_ERROR.format(tls_ver, TLS_VERSIONS))

    @property
    def user(self) -> str:
        """User used while connecting to MySQL"""
        return self._user

    @property
    def server_host(self) -> str:
        """MySQL server IP address or name"""
        return self._host

    @property
    def server_port(self) -> int:
        "MySQL server TCP/IP port"
        return self._port

    @property
    def unix_socket(self) -> Optional[str]:
        "MySQL Unix socket file location"
        return self._unix_socket

    @property
    @abstractmethod
    def database(self) -> str:
        """Get the current database"""

    @database.setter
    def database(self, value: str) -> None:
        """Set the current database"""
        self.cmd_query(f"USE {value}")

    @property
    def can_consume_results(self) -> bool:
        """Returns whether to consume results"""
        return self._consume_results

    @can_consume_results.setter
    def can_consume_results(self, value: bool) -> None:
        """Set if can consume results."""
        assert isinstance(value, bool)
        self._consume_results = value

    @property
    def pool_config_version(self) -> Any:
        """Return the pool configuration version"""
        return self._pool_config_version

    @pool_config_version.setter
    def pool_config_version(self, value: Any) -> None:
        """Set the pool configuration version"""
        self._pool_config_version = value

    def config(self, **kwargs: Any) -> None:
        """Configure the MySQL Connection

        This method allows you to configure the MySQLConnection instance.

        Raises on errors.
        """
        # opentelemetry related
        self._span = kwargs.pop(OPTION_CNX_SPAN, None)
        self._tracer = kwargs.pop(OPTION_CNX_TRACER, None)

        config = kwargs.copy()
        if "dsn" in config:
            raise NotSupportedError("Data source name is not supported")

        # Read option files
        config = read_option_files(**config)

        # Configure how we handle MySQL warnings
        try:
            self.get_warnings = config["get_warnings"]
            del config["get_warnings"]
        except KeyError:
            pass  # Leave what was set or default
        try:
            self.raise_on_warnings = config["raise_on_warnings"]
            del config["raise_on_warnings"]
        except KeyError:
            pass  # Leave what was set or default

        # Configure client flags
        try:
            default = ClientFlag.get_default()
            self.set_client_flags(config["client_flags"] or default)
            del config["client_flags"]
        except KeyError:
            pass  # Missing client_flags-argument is OK

        try:
            if config["compress"]:
                self._compress = True
                self.set_client_flags([ClientFlag.COMPRESS])
        except KeyError:
            pass  # Missing compress argument is OK

        self._allow_local_infile = config.get(
            "allow_local_infile", DEFAULT_CONFIGURATION["allow_local_infile"]
        )
        self._allow_local_infile_in_path = config.get(
            "allow_local_infile_in_path",
            DEFAULT_CONFIGURATION["allow_local_infile_in_path"],
        )
        infile_in_path = None
        if self._allow_local_infile_in_path:
            infile_in_path = os.path.abspath(self._allow_local_infile_in_path)
            if (
                infile_in_path
                and os.path.exists(infile_in_path)
                and not os.path.isdir(infile_in_path)
                or os.path.islink(infile_in_path)
            ):
                raise AttributeError("allow_local_infile_in_path must be a directory")
        if self._allow_local_infile or self._allow_local_infile_in_path:
            self.set_client_flags([ClientFlag.LOCAL_FILES])
        else:
            self.set_client_flags([-ClientFlag.LOCAL_FILES])

        try:
            if not config["consume_results"]:
                self._consume_results = False
            else:
                self._consume_results = True
        except KeyError:
            self._consume_results = False

        # Configure auth_plugin
        try:
            self._auth_plugin = config["auth_plugin"]
            del config["auth_plugin"]
        except KeyError:
            self._auth_plugin = ""

        # Configure character set and collation
        if "charset" in config or "collation" in config:
            try:
                charset = config["charset"]
                del config["charset"]
            except KeyError:
                charset = None
            try:
                collation = config["collation"]
                del config["collation"]
            except KeyError:
                collation = None
            self._charset_id = CharacterSet.get_charset_info(charset, collation)[0]

        # Set converter class
        try:
            self.set_converter_class(config["converter_class"])
        except KeyError:
            pass  # Using default converter class
        except TypeError as err:
            raise AttributeError(
                "Converter class should be a subclass of "
                "conversion.MySQLConverterBase"
            ) from err

        # Compatible configuration with other drivers
        compat_map = [
            # (<other driver argument>,<translates to>)
            ("db", "database"),
            ("username", "user"),
            ("passwd", "password"),
            ("connect_timeout", "connection_timeout"),
            ("read_default_file", "option_files"),
        ]
        for compat, translate in compat_map:
            try:
                if translate not in config:
                    config[translate] = config[compat]
                del config[compat]
            except KeyError:
                pass  # Missing compat argument is OK

        # Configure login information
        if "user" in config or "password" in config:
            try:
                user = config["user"]
                del config["user"]
            except KeyError:
                user = self._user
            try:
                password = config["password"]
                del config["password"]
            except KeyError:
                password = self._password
            self.set_login(user, password)

        # Configure host information
        if "host" in config and config["host"]:
            self._host = config["host"]

        # Check network locations
        try:
            self._port = int(config["port"])
            del config["port"]
        except KeyError:
            pass  # Missing port argument is OK
        except ValueError as err:
            raise InterfaceError("TCP/IP port number should be an integer") from err

        if "ssl_disabled" in config:
            self._ssl_disabled = config.pop("ssl_disabled")

        # If an init_command is set, keep it, so we can execute it in _post_connection
        if "init_command" in config:
            self._init_command = config["init_command"]
            del config["init_command"]

        # Other configuration
        set_ssl_flag = False
        for key, value in config.items():
            try:
                DEFAULT_CONFIGURATION[key]
            except KeyError:
                raise AttributeError(f"Unsupported argument '{key}'") from None
            # SSL Configuration
            if key.startswith("ssl_"):
                set_ssl_flag = True
                self._ssl.update({key.replace("ssl_", ""): value})
            elif key.startswith("tls_"):
                set_ssl_flag = True
                self._ssl.update({key: value})
            else:
                attribute = "_" + key
                try:
                    setattr(self, attribute, value.strip())
                except AttributeError:
                    setattr(self, attribute, value)

        # Disable SSL for unix socket connections
        if self._unix_socket and os.name == "posix":
            self._ssl_disabled = True

        if self._ssl_disabled and self._auth_plugin == "mysql_clear_password":
            raise InterfaceError(
                "Clear password authentication is not supported over insecure channels"
            )

        if set_ssl_flag:
            if "verify_cert" not in self._ssl:
                self._ssl["verify_cert"] = DEFAULT_CONFIGURATION["ssl_verify_cert"]
            if "verify_identity" not in self._ssl:
                self._ssl["verify_identity"] = DEFAULT_CONFIGURATION[
                    "ssl_verify_identity"
                ]
            # Make sure both ssl_key/ssl_cert are set, or neither (XOR)
            if "ca" not in self._ssl or self._ssl["ca"] is None:
                self._ssl["ca"] = ""
            if bool("key" in self._ssl) != bool("cert" in self._ssl):
                raise AttributeError(
                    "ssl_key and ssl_cert need to be both specified, or neither"
                )
            # Make sure key/cert are set to None
            if not set(("key", "cert")) <= set(self._ssl):
                self._ssl["key"] = None
                self._ssl["cert"] = None
            elif (self._ssl["key"] is None) != (self._ssl["cert"] is None):
                raise AttributeError(
                    "ssl_key and ssl_cert need to be both set, or neither"
                )
            if "tls_versions" in self._ssl and self._ssl["tls_versions"] is not None:
                self._validate_tls_versions()

            if (
                "tls_ciphersuites" in self._ssl
                and self._ssl["tls_ciphersuites"] is not None
            ):
                self._validate_tls_ciphersuites()

        if self._conn_attrs is None:
            self._conn_attrs = {}
        elif not isinstance(self._conn_attrs, dict):
            raise InterfaceError("conn_attrs must be of type dict")
        else:
            for attr_name, attr_value in self._conn_attrs.items():
                if attr_name in CONN_ATTRS_DN:
                    continue
                # Validate name type
                if not isinstance(attr_name, str):
                    raise InterfaceError(
                        "Attribute name should be a string, found: "
                        f"'{attr_name}' in '{self._conn_attrs}'"
                    )
                # Validate attribute name limit 32 characters
                if len(attr_name) > 32:
                    raise InterfaceError(
                        f"Attribute name '{attr_name}' exceeds 32 characters limit size"
                    )
                # Validate names in connection attributes cannot start with "_"
                if attr_name.startswith("_"):
                    raise InterfaceError(
                        "Key names in connection attributes cannot start with "
                        "'_', found: '{attr_name}'"
                    )
                # Validate value type
                if not isinstance(attr_value, str):
                    raise InterfaceError(
                        f"Attribute '{attr_name}' value: '{attr_value}' must "
                        "be a string type"
                    )
                # Validate attribute value limit 1024 characters
                if len(attr_value) > 1024:
                    raise InterfaceError(
                        f"Attribute '{attr_name}' value: '{attr_value}' "
                        "exceeds 1024 characters limit size"
                    )

        if self._client_flags & ClientFlag.CONNECT_ARGS:
            self._add_default_conn_attrs()

        if "kerberos_auth_mode" in config and config["kerberos_auth_mode"] is not None:
            if not isinstance(config["kerberos_auth_mode"], str):
                raise InterfaceError("'kerberos_auth_mode' must be of type str")
            kerberos_auth_mode = config["kerberos_auth_mode"].lower()
            if kerberos_auth_mode == "sspi":
                if os.name != "nt":
                    raise InterfaceError(
                        "'kerberos_auth_mode=SSPI' is only available on Windows"
                    )
                self._auth_plugin_class = "MySQLSSPIKerberosAuthPlugin"
            elif kerberos_auth_mode == "gssapi":
                self._auth_plugin_class = "MySQLKerberosAuthPlugin"
            else:
                raise InterfaceError(
                    "Invalid 'kerberos_auth_mode' mode. Please use 'SSPI' or 'GSSAPI'"
                )

        if (
            "krb_service_principal" in config
            and config["krb_service_principal"] is not None
        ):
            self._krb_service_principal = config["krb_service_principal"]
            if not isinstance(self._krb_service_principal, str):
                raise InterfaceError(
                    KRB_SERVICE_PINCIPAL_ERROR.format(error="is not a string")
                )
            if self._krb_service_principal == "":
                raise InterfaceError(
                    KRB_SERVICE_PINCIPAL_ERROR.format(
                        error="can not be an empty string"
                    )
                )
            if "/" not in self._krb_service_principal:
                raise InterfaceError(
                    KRB_SERVICE_PINCIPAL_ERROR.format(error="is incorrectly formatted")
                )

        if self._fido_callback:
            # Import the callable if it's a str
            if isinstance(self._fido_callback, str):
                try:
                    module, callback = self._fido_callback.rsplit(".", 1)
                except ValueError:
                    raise ProgrammingError(
                        f"No callable named '{self._fido_callback}'"
                    ) from None
                try:
                    module = importlib.import_module(module)
                    self._fido_callback = getattr(module, callback)
                except (AttributeError, ModuleNotFoundError) as err:
                    raise ProgrammingError(f"{err}") from err
            # Check if it's a callable
            if not callable(self._fido_callback):
                raise ProgrammingError("Expected a callable for 'fido_callback'")
            # Check the callable signature if has only 1 positional argument
            params = len(signature(self._fido_callback).parameters)
            if params != 1:
                raise ProgrammingError(
                    "'fido_callback' requires 1 positional argument, but the "
                    f"callback provided has {params}"
                )

    def _add_default_conn_attrs(self) -> Any:
        """Add the default connection attributes."""

    @staticmethod
    def _check_server_version(server_version: StrOrBytes) -> Tuple[int, ...]:
        """Check the MySQL version

        This method will check the MySQL version and raise an InterfaceError
        when it is not supported or invalid. It will return the version
        as a tuple with major, minor and patch.

        Raises InterfaceError if invalid server version.

        Returns tuple
        """
        if isinstance(server_version, (bytearray, bytes)):
            server_version = server_version.decode()

        regex_ver = re.compile(r"^(\d{1,2})\.(\d{1,2})\.(\d{1,3})(.*)")
        match = regex_ver.match(server_version)
        if not match:
            raise InterfaceError("Failed parsing MySQL version")

        version = tuple(int(v) for v in match.groups()[0:3])
        if version < (4, 1):
            raise InterfaceError(f"MySQL Version '{server_version}' is not supported")

        return version

    def get_server_version(self) -> Tuple[int, ...]:
        """Get the MySQL version

        This method returns the MySQL server version as a tuple. If not
        previously connected, it will return None.

        Returns a tuple or None.
        """
        return self._server_version

    def get_server_info(self) -> Optional[str]:
        """Get the original MySQL version information

        This method returns the original MySQL server as text. If not
        previously connected, it will return None.

        Returns a string or None.
        """
        try:
            return self._handshake["server_version_original"]  # type: ignore[return-value]
        except (TypeError, KeyError):
            return None

    @property
    @abstractmethod
    def in_transaction(self) -> Any:
        """MySQL session has started a transaction"""

    def set_client_flags(self, flags: Union[int, Sequence[int]]) -> int:
        """Set the client flags

        The flags-argument can be either an int or a list (or tuple) of
        ClientFlag-values. If it is an integer, it will set client_flags
        to flags as is.
        If flags is a list (or tuple), each flag will be set or unset
        when it's negative.

        set_client_flags([ClientFlag.FOUND_ROWS,-ClientFlag.LONG_FLAG])

        Raises ProgrammingError when the flags argument is not a set or
        an integer bigger than 0.

        Returns self.client_flags
        """
        if isinstance(flags, int) and flags > 0:
            self._client_flags = flags
        elif isinstance(flags, (tuple, list)):
            for flag in flags:
                if flag < 0:
                    self._client_flags &= ~abs(flag)
                else:
                    self._client_flags |= flag
        else:
            raise ProgrammingError("set_client_flags expect integer (>0) or set")
        return self._client_flags

    def isset_client_flag(self, flag: int) -> bool:
        """Check if a client flag is set"""
        if (self._client_flags & flag) > 0:
            return True
        return False

    @property
    def time_zone(self) -> str:
        """Get the current time zone"""
        return self.info_query("SELECT @@session.time_zone")[0]

    @time_zone.setter
    def time_zone(self, value: str) -> None:
        """Set the time zone"""
        self.cmd_query(f"SET @@session.time_zone = '{value}'")
        self._time_zone = value

    @property
    def sql_mode(self) -> str:
        """Get the SQL mode"""
        if self._sql_mode is None:
            self._sql_mode = self.info_query("SELECT @@session.sql_mode")[0]
        return self._sql_mode

    @sql_mode.setter
    def sql_mode(self, value: Union[str, Sequence[int]]) -> None:
        """Set the SQL mode

        This method sets the SQL Mode for the current connection. The value
        argument can be either a string with comma separate mode names, or
        a sequence of mode names.

        It is good practice to use the constants class SQLMode:
          from mysql.connector.constants import SQLMode
          cnx.sql_mode = [SQLMode.NO_ZERO_DATE, SQLMode.REAL_AS_FLOAT]
        """
        if isinstance(value, (list, tuple)):
            value = ",".join(value)
        self.cmd_query(f"SET @@session.sql_mode = '{value}'")
        self._sql_mode = value

    @abstractmethod
    def info_query(self, query: Any) -> Any:
        """Send a query which only returns 1 row"""

    def set_login(
        self, username: Optional[str] = None, password: Optional[str] = None
    ) -> None:
        """Set login information for MySQL

        Set the username and/or password for the user connecting to
        the MySQL Server.
        """
        if username is not None:
            self._user = username.strip()
        else:
            self._user = ""
        if password is not None:
            self._password = password
        else:
            self._password = ""

    def set_unicode(self, value: bool = True) -> None:
        """Toggle unicode mode

        Set whether we return string fields as unicode or not.
        Default is True.
        """
        self._use_unicode = value
        if self.converter:
            self.converter.set_unicode(value)

    @property
    def autocommit(self) -> bool:
        """Get whether autocommit is on or off"""
        value = self.info_query("SELECT @@session.autocommit")[0]
        return value == 1

    @autocommit.setter
    def autocommit(self, value: bool) -> None:
        """Toggle autocommit"""
        switch = "ON" if value else "OFF"
        self.cmd_query(f"SET @@session.autocommit = {switch}")
        self._autocommit = value

    @property
    def get_warnings(self) -> bool:
        """Get whether this connection retrieves warnings automatically

        This method returns whether this connection retrieves warnings
        automatically.

        Returns True, or False when warnings are not retrieved.
        """
        return self._get_warnings

    @get_warnings.setter
    def get_warnings(self, value: bool) -> None:
        """Set whether warnings should be automatically retrieved

        The toggle-argument must be a boolean. When True, cursors for this
        connection will retrieve information about warnings (if any).

        Raises ValueError on error.
        """
        if not isinstance(value, bool):
            raise ValueError("Expected a boolean type")
        self._get_warnings = value

    @property
    def raise_on_warnings(self) -> bool:
        """Get whether this connection raises an error on warnings

        This method returns whether this connection will raise errors when
        MySQL reports warnings.

        Returns True or False.
        """
        return self._raise_on_warnings

    @raise_on_warnings.setter
    def raise_on_warnings(self, value: bool) -> None:
        """Set whether warnings raise an error

        The toggle-argument must be a boolean. When True, cursors for this
        connection will raise an error when MySQL reports warnings.

        Raising on warnings implies retrieving warnings automatically. In
        other words: warnings will be set to True. If set to False, warnings
        will be also set to False.

        Raises ValueError on error.
        """
        if not isinstance(value, bool):
            raise ValueError("Expected a boolean type")
        self._raise_on_warnings = value
        # Don't disable warning retrieval if raising explicitly disabled
        if value:
            self._get_warnings = value

    @property
    def unread_result(self) -> bool:
        """Get whether there is an unread result

        This method is used by cursors to check whether another cursor still
        needs to retrieve its result set.

        Returns True, or False when there is no unread result.
        """
        return self._unread_result

    @unread_result.setter
    def unread_result(self, value: bool) -> None:
        """Set whether there is an unread result

        This method is used by cursors to let other cursors know there is
        still a result set that needs to be retrieved.

        Raises ValueError on errors.
        """
        if not isinstance(value, bool):
            raise ValueError("Expected a boolean type")
        self._unread_result = value

    @property
    def charset(self) -> str:
        """Returns the character set for current connection

        This property returns the character set name of the current connection.
        The server is queried when the connection is active. If not connected,
        the configured character set name is returned.

        Returns a string.
        """
        return CharacterSet.get_info(self._charset_id)[0]

    @property
    def python_charset(self) -> str:
        """Returns the Python character set for current connection

        This property returns the character set name of the current connection.
        Note that, unlike property charset, this checks if the previously set
        character set is supported by Python and if not, it returns the
        equivalent character set that Python supports.

        Returns a string.
        """
        encoding = CharacterSet.get_info(self._charset_id)[0]
        if encoding in ("utf8mb4", "utf8mb3", "binary"):
            return "utf8"
        return encoding

    def set_charset_collation(
        self, charset: Optional[Union[int, str]] = None, collation: Optional[str] = None
    ) -> None:
        """Sets the character set and collation for the current connection

        This method sets the character set and collation to be used for
        the current connection. The charset argument can be either the
        name of a character set as a string, or the numerical equivalent
        as defined in constants.CharacterSet.

        When the collation is not given, the default will be looked up and
        used.

        For example, the following will set the collation for the latin1
        character set to latin1_general_ci:

           set_charset('latin1','latin1_general_ci')

        """
        err_msg = "{} should be either integer, string or None"
        if not isinstance(charset, (int, str)) and charset is not None:
            raise ValueError(err_msg.format("charset"))
        if not isinstance(collation, str) and collation is not None:
            raise ValueError("collation should be either string or None")

        if charset:
            if isinstance(charset, int):
                (
                    self._charset_id,
                    charset_name,
                    collation_name,
                ) = CharacterSet.get_charset_info(charset)
            elif isinstance(charset, str):
                (
                    self._charset_id,
                    charset_name,
                    collation_name,
                ) = CharacterSet.get_charset_info(charset, collation)
            else:
                raise ValueError(err_msg.format("charset"))
        elif collation:
            (
                self._charset_id,
                charset_name,
                collation_name,
            ) = CharacterSet.get_charset_info(collation=collation)
        else:
            charset = DEFAULT_CONFIGURATION["charset"]
            (
                self._charset_id,
                charset_name,
                collation_name,
            ) = CharacterSet.get_charset_info(charset, collation=None)

        self._execute_query(f"SET NAMES '{charset_name}' COLLATE '{collation_name}'")

        try:
            # Required for C Extension
            self.set_character_set_name(charset_name)
        except AttributeError:
            # Not required for pure Python connection
            pass

        if self.converter:
            self.converter.set_charset(charset_name)

    @property
    def collation(self) -> str:
        """Returns the collation for current connection

        This property returns the collation name of the current connection.
        The server is queried when the connection is active. If not connected,
        the configured collation name is returned.

        Returns a string.
        """
        return CharacterSet.get_charset_info(self._charset_id)[2]

    @abstractmethod
    def _do_handshake(self) -> Any:
        """Gather information of the MySQL server before authentication"""

    @abstractmethod
    def _open_connection(self) -> Any:
        """Open the connection to the MySQL server"""

    def _post_connection(self) -> None:
        """Executes commands after connection has been established

        This method executes commands after the connection has been
        established. Some setting like autocommit, character set, and SQL mode
        are set using this method.
        """
        self.set_charset_collation(self._charset_id)
        self.autocommit = self._autocommit
        if self._time_zone:
            self.time_zone = self._time_zone
        if self._sql_mode:
            self.sql_mode = self._sql_mode
        if self._init_command:
            self._execute_query(self._init_command)

    @abstractmethod
    def disconnect(self) -> Any:
        """Disconnect from the MySQL server"""

    close: Callable[[], Any] = disconnect

    def connect(self, **kwargs: Any) -> None:
        """Connect to the MySQL server

        This method sets up the connection to the MySQL server. If no
        arguments are given, it will use the already configured or default
        values.
        """
        if kwargs:
            self.config(**kwargs)

        self.disconnect()
        self._open_connection()
        # Server does not allow to run any other statement different from ALTER
        # when user's password has been expired.
        if not self._client_flags & ClientFlag.CAN_HANDLE_EXPIRED_PASSWORDS:
            self._post_connection()

    def reconnect(self, attempts: int = 1, delay: int = 0) -> None:
        """Attempt to reconnect to the MySQL server

        The argument attempts should be the number of times a reconnect
        is tried. The delay argument is the number of seconds to wait between
        each retry.

        You may want to set the number of attempts higher and use delay when
        you expect the MySQL server to be down for maintenance or when you
        expect the network to be temporary unavailable.

        Raises InterfaceError on errors.
        """
        counter = 0
        span = None

        if self._tracer:
            span = self._tracer.start_span(
                name=CONNECTION_SPAN_NAME, kind=trace.SpanKind.CLIENT
            )

        try:
            while counter != attempts:
                counter = counter + 1
                try:
                    self.disconnect()
                    self.connect()
                    if self.is_connected():
                        break
                except (Error, IOError) as err:
                    if counter == attempts:
                        msg = (
                            f"Can not reconnect to MySQL after {attempts} "
                            f"attempt(s): {err}"
                        )
                        raise InterfaceError(msg) from err
                if delay > 0:
                    sleep(delay)
        except InterfaceError as interface_err:
            if OTEL_ENABLED:
                set_connection_span_attrs(self, span)
                record_exception_event(span, interface_err)
                end_span(span)
            raise

        self._span = span
        if OTEL_ENABLED:
            set_connection_span_attrs(self, self._span)

    @abstractmethod
    def is_connected(self) -> Any:
        """Reports whether the connection to MySQL Server is available"""

    @abstractmethod
    def ping(self, reconnect: bool = False, attempts: int = 1, delay: int = 0) -> Any:
        """Check availability of the MySQL server"""

    @abstractmethod
    def commit(self) -> Any:
        """Commit current transaction"""

    @abstractmethod
    def cursor(
        self,
        buffered: Optional[bool] = None,
        raw: Optional[bool] = None,
        prepared: Optional[bool] = None,
        cursor_class: Optional[type] = None,
        dictionary: Optional[bool] = None,
        named_tuple: Optional[bool] = None,
    ) -> "MySQLCursorAbstract":
        """Instantiates and returns a cursor"""

    @abstractmethod
    def _execute_query(self, query: Any) -> Any:
        """Execute a query"""

    @abstractmethod
    def rollback(self) -> Any:
        """Rollback current transaction"""

    def start_transaction(
        self,
        consistent_snapshot: bool = False,
        isolation_level: Optional[str] = None,
        readonly: Optional[bool] = None,
    ) -> None:
        """Start a transaction

        This method explicitly starts a transaction sending the
        START TRANSACTION statement to the MySQL server. You can optionally
        set whether there should be a consistent snapshot, which
        isolation level you need or which access mode i.e. READ ONLY or
        READ WRITE.

        For example, to start a transaction with isolation level SERIALIZABLE,
        you would do the following:
            >>> cnx = mysql.connector.connect(..)
            >>> cnx.start_transaction(isolation_level='SERIALIZABLE')

        Raises ProgrammingError when a transaction is already in progress
        and when ValueError when isolation_level specifies an Unknown
        level.
        """
        if self.in_transaction:
            raise ProgrammingError("Transaction already in progress")

        if isolation_level:
            level = isolation_level.strip().replace("-", " ").upper()
            levels = [
                "READ UNCOMMITTED",
                "READ COMMITTED",
                "REPEATABLE READ",
                "SERIALIZABLE",
            ]

            if level not in levels:
                raise ValueError(f'Unknown isolation level "{isolation_level}"')

            self._execute_query(f"SET TRANSACTION ISOLATION LEVEL {level}")

        if readonly is not None:
            if self._server_version < (5, 6, 5):
                raise ValueError(
                    f"MySQL server version {self._server_version} does not "
                    "support this feature"
                )

            if readonly:
                access_mode = "READ ONLY"
            else:
                access_mode = "READ WRITE"
            self._execute_query(f"SET TRANSACTION {access_mode}")

        query = "START TRANSACTION"
        if consistent_snapshot:
            query += " WITH CONSISTENT SNAPSHOT"
        self.cmd_query(query)

    def reset_session(
        self,
        user_variables: Optional[Dict[str, Any]] = None,
        session_variables: Optional[Dict[str, Any]] = None,
    ) -> None:
        """Clears the current active session

        This method resets the session state, if the MySQL server is 5.7.3
        or later active session will be reset without re-authenticating.
        For other server versions session will be reset by re-authenticating.

        It is possible to provide a sequence of variables and their values to
        be set after clearing the session. This is possible for both user
        defined variables and session variables.
        This method takes two arguments user_variables and session_variables
        which are dictionaries.

        Raises OperationalError if not connected, InternalError if there are
        unread results and InterfaceError on errors.
        """
        if not self.is_connected():
            raise OperationalError("MySQL Connection not available")

        try:
            self.cmd_reset_connection()
        except (NotSupportedError, NotImplementedError):
            if self._compress:
                raise NotSupportedError(
                    "Reset session is not supported with compression for "
                    "MySQL server version 5.7.2 or earlier"
                ) from None
            self.cmd_change_user(
                self._user,
                self._password,
                self._database,
                self._charset_id,
            )

        if user_variables or session_variables:
            cur = self.cursor()
            if user_variables:
                for key, value in user_variables.items():
                    cur.execute(f"SET @`{key}` = {value}")
            if session_variables:
                for key, value in session_variables.items():
                    cur.execute(f"SET SESSION `{key}` = {value}")
            cur.close()

    def set_converter_class(self, convclass: Optional[Type[MySQLConverter]]) -> None:
        """
        Set the converter class to be used. This should be a class overloading
        methods and members of conversion.MySQLConverter.
        """
        if convclass and issubclass(convclass, MySQLConverterBase):
            charset_name = CharacterSet.get_info(self._charset_id)[0]
            self._converter_class = convclass
            self.converter = convclass(charset_name, self._use_unicode)
            self.converter.str_fallback = self._converter_str_fallback
        else:
            raise TypeError(
                "Converter class should be a subclass of conversion.MySQLConverterBase."
            )

    @abstractmethod
    def get_rows(
        self,
        count: Optional[int] = None,
        binary: bool = False,
        columns: Optional[List[DescriptionType]] = None,
        raw: Optional[bool] = None,
        prep_stmt: Any = None,
    ) -> Tuple[List[Any], Optional[Mapping[str, Any]]]:
        """Get all rows returned by the MySQL server"""

    def cmd_init_db(self, database: str) -> Optional[Mapping[str, Any]]:
        """Change the current database"""
        raise NotImplementedError

    def cmd_query(
        self,
        query: Any,
        raw: bool = False,
        buffered: bool = False,
        raw_as_string: bool = False,
    ) -> Optional[Mapping[str, Any]]:
        """Send a query to the MySQL server"""
        raise NotImplementedError

    def cmd_query_iter(
        self, statements: Any
    ) -> Generator[Mapping[str, Any], None, None]:
        """Send one or more statements to the MySQL server"""
        raise NotImplementedError

    def cmd_refresh(self, options: int) -> Optional[Mapping[str, Any]]:
        """Send the Refresh command to the MySQL server"""
        raise NotImplementedError

    def cmd_quit(self) -> Any:
        """Close the current connection with the server"""
        raise NotImplementedError

    def cmd_shutdown(
        self, shutdown_type: Optional[int] = None
    ) -> Optional[Mapping[str, Any]]:
        """Shut down the MySQL Server"""
        raise NotImplementedError

    def cmd_statistics(self) -> Optional[Mapping[str, Any]]:
        """Send the statistics command to the MySQL Server"""
        raise NotImplementedError

    @staticmethod
    def cmd_process_info() -> Any:
        """Get the process list of the MySQL Server

        This method is a placeholder to notify that the PROCESS_INFO command
        is not supported by raising the NotSupportedError. The command
        "SHOW PROCESSLIST" should be send using the cmd_query()-method or
        using the INFORMATION_SCHEMA database.

        Raises NotSupportedError exception
        """
        raise NotSupportedError(
            "Not implemented. Use SHOW PROCESSLIST or INFORMATION_SCHEMA"
        )

    def cmd_process_kill(self, mysql_pid: int) -> Optional[Mapping[str, Any]]:
        """Kill a MySQL process"""
        raise NotImplementedError

    def cmd_debug(self) -> Optional[Mapping[str, Any]]:
        """Send the DEBUG command"""
        raise NotImplementedError

    def cmd_ping(self) -> Optional[Mapping[str, Any]]:
        """Send the PING command"""
        raise NotImplementedError

    def cmd_change_user(
        self,
        username: str = "",
        password: str = "",
        database: str = "",
        charset: int = 45,
        password1: str = "",
        password2: str = "",
        password3: str = "",
        oci_config_file: str = "",
    ) -> Optional[Mapping[str, Any]]:
        """Change the current logged in user"""
        raise NotImplementedError

    def cmd_stmt_prepare(self, statement: Any) -> Optional[Mapping[str, Any]]:
        """Prepare a MySQL statement"""
        raise NotImplementedError

    def cmd_stmt_execute(
        self,
        statement_id: Any,
        data: Sequence[Any] = (),
        parameters: Sequence[Any] = (),
        flags: int = 0,
    ) -> Any:
        """Execute a prepared MySQL statement"""
        raise NotImplementedError

    def cmd_stmt_close(self, statement_id: Any) -> Any:
        """Deallocate a prepared MySQL statement"""
        raise NotImplementedError

    def cmd_stmt_send_long_data(
        self, statement_id: Any, param_id: int, data: BinaryIO
    ) -> Any:
        """Send data for a column"""
        raise NotImplementedError

    def cmd_stmt_reset(self, statement_id: Any) -> Any:
        """Reset data for prepared statement sent as long data"""
        raise NotImplementedError

    def cmd_reset_connection(self) -> Any:
        """Resets the session state without re-authenticating"""
        raise NotImplementedError


class MySQLCursorAbstract(ABC):
    """Abstract cursor class

    Abstract class defining cursor class with method and members
    required by the Python Database API Specification v2.0.
    """

    def __init__(self) -> None:
        """Initialization"""
        self._description: Optional[List[DescriptionType]] = None
        self._rowcount: int = -1
        self._last_insert_id: Optional[int] = None
        self._warnings: Optional[List[WarningType]] = None
        self._warning_count: int = 0
        self._executed: Optional[StrOrBytes] = None
        self._executed_list: List[StrOrBytes] = []
        self._stored_results: List[Any] = []
        self.arraysize: int = 1

    def __enter__(self) -> MySQLCursorAbstract:
        return self

    def __exit__(
        self,
        exc_type: Type[BaseException],
        exc_value: BaseException,
        traceback: TracebackType,
    ) -> None:
        self.close()

    @abstractmethod
    def callproc(self, procname: str, args: Sequence[Any] = ()) -> Any:
        """Calls a stored procedure with the given arguments

        The arguments will be set during this session, meaning
        they will be called like  _<procname>__arg<nr> where
        <nr> is an enumeration (+1) of the arguments.

        Coding Example:
          1) Defining the Stored Routine in MySQL:
          CREATE PROCEDURE multiply(IN pFac1 INT, IN pFac2 INT, OUT pProd INT)
          BEGIN
            SET pProd := pFac1 * pFac2;
          END

          2) Executing in Python:
          args = (5,5,0) # 0 is to hold pprod
          cursor.callproc('multiply', args)
          print(cursor.fetchone())

        Does not return a value, but a result set will be
        available when the CALL-statement execute successfully.
        Raises exceptions when something is wrong.
        """

    @abstractmethod
    def close(self) -> Any:
        """Close the cursor."""

    @abstractmethod
    def execute(
        self,
        operation: str,
        params: Union[Sequence[Any], Dict[str, Any]] = (),
        multi: bool = False,
    ) -> Any:
        """Executes the given operation

        Executes the given operation substituting any markers with
        the given parameters.

        For example, getting all rows where id is 5:
          cursor.execute("SELECT * FROM t1 WHERE id = %s", (5,))

        The multi argument should be set to True when executing multiple
        statements in one operation. If not set and multiple results are
        found, an InterfaceError will be raised.

        If warnings where generated, and connection.get_warnings is True, then
        self._warnings will be a list containing these warnings.

        Returns an iterator when multi is True, otherwise None.
        """

    @abstractmethod
    def executemany(
        self, operation: str, seq_params: Sequence[Union[Sequence[Any], Dict[str, Any]]]
    ) -> Any:
        """Execute the given operation multiple times

        The executemany() method will execute the operation iterating
        over the list of parameters in seq_params.

        Example: Inserting 3 new employees and their phone number

        data = [
            ('Jane','555-001'),
            ('Joe', '555-001'),
            ('John', '555-003')
            ]
        stmt = "INSERT INTO employees (name, phone) VALUES ('%s','%s')"
        cursor.executemany(stmt, data)

        INSERT statements are optimized by batching the data, that is
        using the MySQL multiple rows syntax.

        Results are discarded. If they are needed, consider looping over
        data using the execute() method.
        """

    @abstractmethod
    def fetchone(self) -> Optional[Sequence[Any]]:
        """Returns next row of a query result set

        Returns a tuple or None.
        """

    @abstractmethod
    def fetchmany(self, size: int = 1) -> List[Sequence[Any]]:
        """Returns the next set of rows of a query result, returning a
        list of tuples. When no more rows are available, it returns an
        empty list.

        The number of rows returned can be specified using the size argument,
        which defaults to one
        """

    @abstractmethod
    def fetchall(self) -> Sequence[Any]:
        """Returns all rows of a query result set

        Returns a list of tuples.
        """

    def nextset(self) -> Any:
        """Not Implemented."""

    def setinputsizes(self, sizes: Any) -> Any:
        """Not Implemented."""

    def setoutputsize(self, size: Any, column: Any = None) -> Any:
        """Not Implemented."""

    def reset(self, free: bool = True) -> Any:
        """Reset the cursor to default"""

    @property
    @abstractmethod
    def description(
        self,
    ) -> Optional[List[DescriptionType]]:
        """Returns description of columns in a result

        This property returns a list of tuples describing the columns in
        in a result set. A tuple is described as follows::

                (column_name,
                 type,
                 None,
                 None,
                 None,
                 None,
                 null_ok,
                 column_flags)  # Addition to PEP-249 specs

        Returns a list of tuples.
        """
        return self._description

    @property
    @abstractmethod
    def rowcount(self) -> int:
        """Returns the number of rows produced or affected

        This property returns the number of rows produced by queries
        such as a SELECT, or affected rows when executing DML statements
        like INSERT or UPDATE.

        Note that for non-buffered cursors it is impossible to know the
        number of rows produced before having fetched them all. For those,
        the number of rows will be -1 right after execution, and
        incremented when fetching rows.

        Returns an integer.
        """
        return self._rowcount

    @property
    def lastrowid(self) -> Optional[int]:
        """Returns the value generated for an AUTO_INCREMENT column

        Returns the value generated for an AUTO_INCREMENT column by
        the previous INSERT or UPDATE statement or None when there is
        no such value available.

        Returns a long value or None.
        """
        return self._last_insert_id

    @property
    def warnings(self) -> Optional[List[WarningType]]:
        """Return warnings."""
        return self._warnings

    @property
    def warning_count(self) -> int:
        """Returns the number of warnings

        This property returns the number of warnings generated by the
        previously executed operation.

        Returns an integer value.
        """
        return self._warning_count

    def fetchwarnings(self) -> Optional[List[WarningType]]:
        """Returns Warnings."""
        return self._warnings

    def get_attributes(self) -> Optional[List[Tuple[str, Any]]]:
        """Get the added query attributes so far."""
        if hasattr(self, "_cnx"):
            return self._cnx.query_attrs
        if hasattr(self, "_connection"):
            return self._connection.query_attrs
        return None

    def add_attribute(self, name: str, value: Any) -> None:
        """Add a query attribute and his value."""
        if not isinstance(name, str):
            raise ProgrammingError("Parameter `name` must be a string type")
        if value is not None and not isinstance(value, MYSQL_PY_TYPES):
            raise ProgrammingError(
                f"Object {value} cannot be converted to a MySQL type"
            )
        if hasattr(self, "_cnx"):
            self._cnx.query_attrs_append((name, value))
        elif hasattr(self, "_connection"):
            self._connection.query_attrs_append((name, value))

    def remove_attribute(self, name: str) -> Any:
        """Remove a query attribute by name.

        If no match, `None` is returned; else the corresponding value is returned.
        """
        if not isinstance(name, str):
            raise ProgrammingError("Parameter `name` must be a string type")
        if hasattr(self, "_cnx"):
            return self._cnx.query_attrs_remove(name)
        if hasattr(self, "_connection"):
            return self._connection.query_attrs_remove(name)
        return None

    def clear_attributes(self) -> None:
        """Remove all the query attributes."""
        if hasattr(self, "_cnx"):
            self._cnx.query_attrs_clear()
        elif hasattr(self, "_connection"):
            self._connection.query_attrs_clear()

virt/lib/python3.9/site-packages/mysql/connector/abstracts 3.py
PrinceDisant/LostInNL
# Copyright (c) 2014, 2023, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

# mypy: disable-error-code="assignment,attr-defined"

"""Module gathering all abstract base classes."""

from __future__ import annotations

import importlib
import os
import re
import weakref

from abc import ABC, abstractmethod
from datetime import date, datetime, time, timedelta
from decimal import Decimal
from inspect import signature
from time import sleep
from types import TracebackType
from typing import (
    Any,
    BinaryIO,
    Callable,
    Dict,
    Generator,
    List,
    Mapping,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
)

TLS_V1_3_SUPPORTED = False
try:
    import ssl

    if hasattr(ssl, "HAS_TLSv1_3") and ssl.HAS_TLSv1_3:
        TLS_V1_3_SUPPORTED = True
except ImportError:
    # If import fails, we don't have SSL support.
    pass

from .constants import (
    CONN_ATTRS_DN,
    DEFAULT_CONFIGURATION,
    DEPRECATED_TLS_VERSIONS,
    OPENSSL_CS_NAMES,
    TLS_CIPHER_SUITES,
    TLS_VERSIONS,
    CharacterSet,
    ClientFlag,
)
from .conversion import MySQLConverter, MySQLConverterBase
from .errors import (
    Error,
    InterfaceError,
    NotSupportedError,
    OperationalError,
    ProgrammingError,
)
from .opentelemetry.constants import (
    CONNECTION_SPAN_NAME,
    OPTION_CNX_SPAN,
    OPTION_CNX_TRACER,
    OTEL_ENABLED,
)

if OTEL_ENABLED:
    from .opentelemetry.instrumentation import (
        end_span,
        record_exception_event,
        set_connection_span_attrs,
        trace,
    )

from .optionfiles import read_option_files
from .types import (
    ConnAttrsType,
    DescriptionType,
    HandShakeType,
    StrOrBytes,
    SupportedMysqlBinaryProtocolTypes,
    WarningType,
)

NAMED_TUPLE_CACHE: weakref.WeakValueDictionary[Any, Any] = weakref.WeakValueDictionary()

DUPLICATED_IN_LIST_ERROR = (
    "The '{list}' list must not contain repeated values, the value "
    "'{value}' is duplicated."
)

TLS_VERSION_ERROR = (
    "The given tls_version: '{}' is not recognized as a valid "
    "TLS protocol version (should be one of {})."
)

TLS_VERSION_DEPRECATED_ERROR = (
    "The given tls_version: '{}' are no longer allowed (should be one of {})."
)

TLS_VER_NO_SUPPORTED = (
    "No supported TLS protocol version found in the 'tls-versions' list '{}'. "
)

KRB_SERVICE_PINCIPAL_ERROR = (
    'Option "krb_service_principal" {error}, must be a string in the form '
    '"primary/instance@realm" e.g "ldap/ldapauth@MYSQL.COM" where "@realm" '
    "is optional and if it is not given will be assumed to belong to the "
    "default realm, as configured in the krb5.conf file."
)

MYSQL_PY_TYPES = (
    Decimal,
    bytes,
    date,
    datetime,
    float,
    int,
    str,
    time,
    timedelta,
)


class MySQLConnectionAbstract(ABC):
    """Abstract class for classes connecting to a MySQL server"""

    def __init__(self) -> None:
        """Initialize"""
        # opentelemetry related
        self._tracer: Any = None
        self._span: Any = None
        self.otel_context_propagation: bool = True

        self._client_flags: int = ClientFlag.get_default()
        self._charset_id: int = 45
        self._sql_mode: Optional[str] = None
        self._time_zone: Optional[str] = None
        self._autocommit: bool = False
        self._server_version: Optional[Tuple[int, ...]] = None
        self._handshake: Optional[HandShakeType] = None
        self._conn_attrs: ConnAttrsType = {}

        self._user: str = ""
        self._password: str = ""
        self._password1: str = ""
        self._password2: str = ""
        self._password3: str = ""
        self._database: str = ""
        self._host: str = "127.0.0.1"
        self._port: int = 3306
        self._unix_socket: Optional[str] = None
        self._client_host: str = ""
        self._client_port: int = 0
        self._ssl: Dict[str, Optional[Union[str, bool, List[str]]]] = {}
        self._ssl_disabled: bool = DEFAULT_CONFIGURATION["ssl_disabled"]
        self._force_ipv6: bool = False
        self._oci_config_file: Optional[str] = None
        self._oci_config_profile: Optional[str] = None
        self._fido_callback: Optional[Union[str, Callable]] = None
        self._krb_service_principal: Optional[str] = None

        self._use_unicode: bool = True
        self._get_warnings: bool = False
        self._raise_on_warnings: bool = False
        self._connection_timeout: Optional[int] = DEFAULT_CONFIGURATION[
            "connect_timeout"
        ]
        self._buffered: bool = False
        self._unread_result: bool = False
        self._have_next_result: bool = False
        self._raw: bool = False
        self._in_transaction: bool = False
        self._allow_local_infile: bool = DEFAULT_CONFIGURATION["allow_local_infile"]
        self._allow_local_infile_in_path: Optional[str] = DEFAULT_CONFIGURATION[
            "allow_local_infile_in_path"
        ]

        self._prepared_statements: Any = None
        self._query_attrs: Dict[str, Any] = {}

        self._ssl_active: bool = False
        self._auth_plugin: Optional[str] = None
        self._auth_plugin_class: Optional[str] = None
        self._pool_config_version: Any = None
        self.converter: Optional[MySQLConverter] = None
        self._converter_class: Optional[Type[MySQLConverter]] = None
        self._converter_str_fallback: bool = False
        self._compress: bool = False

        self._consume_results: bool = False
        self._init_command: Optional[str] = None

    def __enter__(self) -> MySQLConnectionAbstract:
        return self

    def __exit__(
        self,
        exc_type: Type[BaseException],
        exc_value: BaseException,
        traceback: TracebackType,
    ) -> None:
        self.close()

    def get_self(self) -> MySQLConnectionAbstract:
        """Return self for weakref.proxy

        This method is used when the original object is needed when using
        weakref.proxy.
        """
        return self

    @property
    def is_secure(self) -> bool:
        """Return True if is a secure connection."""
        return self._ssl_active or (
            self._unix_socket is not None and os.name == "posix"
        )

    @property
    def have_next_result(self) -> bool:
        """Return if have next result."""
        return self._have_next_result

    @property
    def query_attrs(self) -> List[Tuple[str, Any]]:
        """Return query attributes list."""
        return list(self._query_attrs.items())

    def query_attrs_append(
        self, value: Tuple[str, SupportedMysqlBinaryProtocolTypes]
    ) -> None:
        """Add element to the query attributes list.

        If an element in the query attributes list already matches
        the attribute name provided, the new element will NOT be added.
        """
        attr_name, attr_value = value
        if attr_name not in self._query_attrs:
            self._query_attrs[attr_name] = attr_value

    def query_attrs_remove(self, name: str) -> Any:
        """Remove element by name from the query attributes list.

        If no match, `None` is returned; else the corresponding value is returned.
        """
        return self._query_attrs.pop(name, None)

    def query_attrs_clear(self) -> None:
        """Clear query attributes list."""
        self._query_attrs = {}

    def _validate_tls_ciphersuites(self) -> None:
        """Validates the tls_ciphersuites option."""
        tls_ciphersuites = []
        tls_cs = self._ssl["tls_ciphersuites"]

        if isinstance(tls_cs, str):
            if not (tls_cs.startswith("[") and tls_cs.endswith("]")):
                raise AttributeError(
                    f"tls_ciphersuites must be a list, found: '{tls_cs}'"
                )
            tls_css = tls_cs[1:-1].split(",")
            if not tls_css:
                raise AttributeError(
                    "No valid cipher suite found in 'tls_ciphersuites' list"
                )
            for _tls_cs in tls_css:
                _tls_cs = tls_cs.strip().upper()
                if _tls_cs:
                    tls_ciphersuites.append(_tls_cs)

        elif isinstance(tls_cs, (list, set)):
            tls_ciphersuites = [tls_cs for tls_cs in tls_cs if tls_cs]
        else:
            raise AttributeError(
                "tls_ciphersuites should be a list with one or more "
                f"ciphersuites. Found: '{tls_cs}'"
            )

        tls_versions = (
            TLS_VERSIONS[:]
            if self._ssl.get("tls_versions", None) is None
            else self._ssl["tls_versions"][:]  # type: ignore[index]
        )

        # A newer TLS version can use a cipher introduced on
        # an older version.
        tls_versions.sort(reverse=True)  # type: ignore[union-attr]
        newer_tls_ver = tls_versions[0]
        # translated_names[0] belongs to TLSv1, TLSv1.1 and TLSv1.2
        # translated_names[1] are TLSv1.3 only
        translated_names: List[List[str]] = [[], []]
        iani_cipher_suites_names = {}
        ossl_cipher_suites_names: List[str] = []

        # Old ciphers can work with new TLS versions.
        # Find all the ciphers introduced on previous TLS versions.
        for tls_ver in TLS_VERSIONS[: TLS_VERSIONS.index(newer_tls_ver) + 1]:
            iani_cipher_suites_names.update(TLS_CIPHER_SUITES[tls_ver])
            ossl_cipher_suites_names.extend(OPENSSL_CS_NAMES[tls_ver])

        for name in tls_ciphersuites:
            if "-" in name and name in ossl_cipher_suites_names:
                if name in OPENSSL_CS_NAMES["TLSv1.3"]:
                    translated_names[1].append(name)
                else:
                    translated_names[0].append(name)
            elif name in iani_cipher_suites_names:
                translated_name = iani_cipher_suites_names[name]
                if translated_name in translated_names:
                    raise AttributeError(
                        DUPLICATED_IN_LIST_ERROR.format(
                            list="tls_ciphersuites", value=translated_name
                        )
                    )
                if name in TLS_CIPHER_SUITES["TLSv1.3"]:
                    translated_names[1].append(iani_cipher_suites_names[name])
                else:
                    translated_names[0].append(iani_cipher_suites_names[name])
            else:
                raise AttributeError(
                    f"The value '{name}' in tls_ciphersuites is not a valid "
                    "cipher suite"
                )
        if not translated_names[0] and not translated_names[1]:
            raise AttributeError(
                "No valid cipher suite found in the 'tls_ciphersuites' list"
            )

        self._ssl["tls_ciphersuites"] = [
            ":".join(translated_names[0]),
            ":".join(translated_names[1]),
        ]

    def _validate_tls_versions(self) -> None:
        """Validates the tls_versions option."""
        tls_versions = []
        tls_version = self._ssl["tls_versions"]

        if isinstance(tls_version, str):
            if not (tls_version.startswith("[") and tls_version.endswith("]")):
                raise AttributeError(
                    f"tls_versions must be a list, found: '{tls_version}'"
                )
            tls_vers = tls_version[1:-1].split(",")
            for tls_ver in tls_vers:
                tls_version = tls_ver.strip()
                if tls_version == "":
                    continue
                if tls_version in tls_versions:
                    raise AttributeError(
                        DUPLICATED_IN_LIST_ERROR.format(
                            list="tls_versions", value=tls_version
                        )
                    )
                tls_versions.append(tls_version)
            if tls_vers == ["TLSv1.3"] and not TLS_V1_3_SUPPORTED:
                raise AttributeError(
                    TLS_VER_NO_SUPPORTED.format(tls_version, TLS_VERSIONS)
                )
        elif isinstance(tls_version, list):
            if not tls_version:
                raise AttributeError(
                    "At least one TLS protocol version must be specified in "
                    "'tls_versions' list"
                )
            for tls_ver in tls_version:
                if tls_ver in tls_versions:
                    raise AttributeError(
                        DUPLICATED_IN_LIST_ERROR.format(
                            list="tls_versions", value=tls_ver
                        )
                    )
                tls_versions.append(tls_ver)
        elif isinstance(tls_version, set):
            for tls_ver in tls_version:
                tls_versions.append(tls_ver)
        else:
            raise AttributeError(
                "tls_versions should be a list with one or more of versions "
                f"in {', '.join(TLS_VERSIONS)}. found: '{tls_versions}'"
            )

        if not tls_versions:
            raise AttributeError(
                "At least one TLS protocol version must be specified "
                "in 'tls_versions' list when this option is given"
            )

        use_tls_versions = []
        deprecated_tls_versions = []
        invalid_tls_versions = []
        for tls_ver in tls_versions:
            if tls_ver in TLS_VERSIONS:
                use_tls_versions.append(tls_ver)
            if tls_ver in DEPRECATED_TLS_VERSIONS:
                deprecated_tls_versions.append(tls_ver)
            else:
                invalid_tls_versions.append(tls_ver)

        if use_tls_versions:
            if use_tls_versions == ["TLSv1.3"] and not TLS_V1_3_SUPPORTED:
                raise NotSupportedError(
                    TLS_VER_NO_SUPPORTED.format(tls_version, TLS_VERSIONS)
                )
            use_tls_versions.sort()
            self._ssl["tls_versions"] = use_tls_versions
        elif deprecated_tls_versions:
            raise NotSupportedError(
                TLS_VERSION_DEPRECATED_ERROR.format(
                    deprecated_tls_versions, TLS_VERSIONS
                )
            )
        elif invalid_tls_versions:
            raise AttributeError(TLS_VERSION_ERROR.format(tls_ver, TLS_VERSIONS))

    @property
    def user(self) -> str:
        """User used while connecting to MySQL"""
        return self._user

    @property
    def server_host(self) -> str:
        """MySQL server IP address or name"""
        return self._host

    @property
    def server_port(self) -> int:
        "MySQL server TCP/IP port"
        return self._port

    @property
    def unix_socket(self) -> Optional[str]:
        "MySQL Unix socket file location"
        return self._unix_socket

    @property
    @abstractmethod
    def database(self) -> str:
        """Get the current database"""

    @database.setter
    def database(self, value: str) -> None:
        """Set the current database"""
        self.cmd_query(f"USE {value}")

    @property
    def can_consume_results(self) -> bool:
        """Returns whether to consume results"""
        return self._consume_results

    @can_consume_results.setter
    def can_consume_results(self, value: bool) -> None:
        """Set if can consume results."""
        assert isinstance(value, bool)
        self._consume_results = value

    @property
    def pool_config_version(self) -> Any:
        """Return the pool configuration version"""
        return self._pool_config_version

    @pool_config_version.setter
    def pool_config_version(self, value: Any) -> None:
        """Set the pool configuration version"""
        self._pool_config_version = value

    def config(self, **kwargs: Any) -> None:
        """Configure the MySQL Connection

        This method allows you to configure the MySQLConnection instance.

        Raises on errors.
        """
        # opentelemetry related
        self._span = kwargs.pop(OPTION_CNX_SPAN, None)
        self._tracer = kwargs.pop(OPTION_CNX_TRACER, None)

        config = kwargs.copy()
        if "dsn" in config:
            raise NotSupportedError("Data source name is not supported")

        # Read option files
        config = read_option_files(**config)

        # Configure how we handle MySQL warnings
        try:
            self.get_warnings = config["get_warnings"]
            del config["get_warnings"]
        except KeyError:
            pass  # Leave what was set or default
        try:
            self.raise_on_warnings = config["raise_on_warnings"]
            del config["raise_on_warnings"]
        except KeyError:
            pass  # Leave what was set or default

        # Configure client flags
        try:
            default = ClientFlag.get_default()
            self.set_client_flags(config["client_flags"] or default)
            del config["client_flags"]
        except KeyError:
            pass  # Missing client_flags-argument is OK

        try:
            if config["compress"]:
                self._compress = True
                self.set_client_flags([ClientFlag.COMPRESS])
        except KeyError:
            pass  # Missing compress argument is OK

        self._allow_local_infile = config.get(
            "allow_local_infile", DEFAULT_CONFIGURATION["allow_local_infile"]
        )
        self._allow_local_infile_in_path = config.get(
            "allow_local_infile_in_path",
            DEFAULT_CONFIGURATION["allow_local_infile_in_path"],
        )
        infile_in_path = None
        if self._allow_local_infile_in_path:
            infile_in_path = os.path.abspath(self._allow_local_infile_in_path)
            if (
                infile_in_path
                and os.path.exists(infile_in_path)
                and not os.path.isdir(infile_in_path)
                or os.path.islink(infile_in_path)
            ):
                raise AttributeError("allow_local_infile_in_path must be a directory")
        if self._allow_local_infile or self._allow_local_infile_in_path:
            self.set_client_flags([ClientFlag.LOCAL_FILES])
        else:
            self.set_client_flags([-ClientFlag.LOCAL_FILES])

        try:
            if not config["consume_results"]:
                self._consume_results = False
            else:
                self._consume_results = True
        except KeyError:
            self._consume_results = False

        # Configure auth_plugin
        try:
            self._auth_plugin = config["auth_plugin"]
            del config["auth_plugin"]
        except KeyError:
            self._auth_plugin = ""

        # Configure character set and collation
        if "charset" in config or "collation" in config:
            try:
                charset = config["charset"]
                del config["charset"]
            except KeyError:
                charset = None
            try:
                collation = config["collation"]
                del config["collation"]
            except KeyError:
                collation = None
            self._charset_id = CharacterSet.get_charset_info(charset, collation)[0]

        # Set converter class
        try:
            self.set_converter_class(config["converter_class"])
        except KeyError:
            pass  # Using default converter class
        except TypeError as err:
            raise AttributeError(
                "Converter class should be a subclass of "
                "conversion.MySQLConverterBase"
            ) from err

        # Compatible configuration with other drivers
        compat_map = [
            # (<other driver argument>,<translates to>)
            ("db", "database"),
            ("username", "user"),
            ("passwd", "password"),
            ("connect_timeout", "connection_timeout"),
            ("read_default_file", "option_files"),
        ]
        for compat, translate in compat_map:
            try:
                if translate not in config:
                    config[translate] = config[compat]
                del config[compat]
            except KeyError:
                pass  # Missing compat argument is OK

        # Configure login information
        if "user" in config or "password" in config:
            try:
                user = config["user"]
                del config["user"]
            except KeyError:
                user = self._user
            try:
                password = config["password"]
                del config["password"]
            except KeyError:
                password = self._password
            self.set_login(user, password)

        # Configure host information
        if "host" in config and config["host"]:
            self._host = config["host"]

        # Check network locations
        try:
            self._port = int(config["port"])
            del config["port"]
        except KeyError:
            pass  # Missing port argument is OK
        except ValueError as err:
            raise InterfaceError("TCP/IP port number should be an integer") from err

        if "ssl_disabled" in config:
            self._ssl_disabled = config.pop("ssl_disabled")

        # If an init_command is set, keep it, so we can execute it in _post_connection
        if "init_command" in config:
            self._init_command = config["init_command"]
            del config["init_command"]

        # Other configuration
        set_ssl_flag = False
        for key, value in config.items():
            try:
                DEFAULT_CONFIGURATION[key]
            except KeyError:
                raise AttributeError(f"Unsupported argument '{key}'") from None
            # SSL Configuration
            if key.startswith("ssl_"):
                set_ssl_flag = True
                self._ssl.update({key.replace("ssl_", ""): value})
            elif key.startswith("tls_"):
                set_ssl_flag = True
                self._ssl.update({key: value})
            else:
                attribute = "_" + key
                try:
                    setattr(self, attribute, value.strip())
                except AttributeError:
                    setattr(self, attribute, value)

        # Disable SSL for unix socket connections
        if self._unix_socket and os.name == "posix":
            self._ssl_disabled = True

        if self._ssl_disabled and self._auth_plugin == "mysql_clear_password":
            raise InterfaceError(
                "Clear password authentication is not supported over insecure channels"
            )

        if set_ssl_flag:
            if "verify_cert" not in self._ssl:
                self._ssl["verify_cert"] = DEFAULT_CONFIGURATION["ssl_verify_cert"]
            if "verify_identity" not in self._ssl:
                self._ssl["verify_identity"] = DEFAULT_CONFIGURATION[
                    "ssl_verify_identity"
                ]
            # Make sure both ssl_key/ssl_cert are set, or neither (XOR)
            if "ca" not in self._ssl or self._ssl["ca"] is None:
                self._ssl["ca"] = ""
            if bool("key" in self._ssl) != bool("cert" in self._ssl):
                raise AttributeError(
                    "ssl_key and ssl_cert need to be both specified, or neither"
                )
            # Make sure key/cert are set to None
            if not set(("key", "cert")) <= set(self._ssl):
                self._ssl["key"] = None
                self._ssl["cert"] = None
            elif (self._ssl["key"] is None) != (self._ssl["cert"] is None):
                raise AttributeError(
                    "ssl_key and ssl_cert need to be both set, or neither"
                )
            if "tls_versions" in self._ssl and self._ssl["tls_versions"] is not None:
                self._validate_tls_versions()

            if (
                "tls_ciphersuites" in self._ssl
                and self._ssl["tls_ciphersuites"] is not None
            ):
                self._validate_tls_ciphersuites()

        if self._conn_attrs is None:
            self._conn_attrs = {}
        elif not isinstance(self._conn_attrs, dict):
            raise InterfaceError("conn_attrs must be of type dict")
        else:
            for attr_name, attr_value in self._conn_attrs.items():
                if attr_name in CONN_ATTRS_DN:
                    continue
                # Validate name type
                if not isinstance(attr_name, str):
                    raise InterfaceError(
                        "Attribute name should be a string, found: "
                        f"'{attr_name}' in '{self._conn_attrs}'"
                    )
                # Validate attribute name limit 32 characters
                if len(attr_name) > 32:
                    raise InterfaceError(
                        f"Attribute name '{attr_name}' exceeds 32 characters limit size"
                    )
                # Validate names in connection attributes cannot start with "_"
                if attr_name.startswith("_"):
                    raise InterfaceError(
                        "Key names in connection attributes cannot start with "
                        "'_', found: '{attr_name}'"
                    )
                # Validate value type
                if not isinstance(attr_value, str):
                    raise InterfaceError(
                        f"Attribute '{attr_name}' value: '{attr_value}' must "
                        "be a string type"
                    )
                # Validate attribute value limit 1024 characters
                if len(attr_value) > 1024:
                    raise InterfaceError(
                        f"Attribute '{attr_name}' value: '{attr_value}' "
                        "exceeds 1024 characters limit size"
                    )

        if self._client_flags & ClientFlag.CONNECT_ARGS:
            self._add_default_conn_attrs()

        if "kerberos_auth_mode" in config and config["kerberos_auth_mode"] is not None:
            if not isinstance(config["kerberos_auth_mode"], str):
                raise InterfaceError("'kerberos_auth_mode' must be of type str")
            kerberos_auth_mode = config["kerberos_auth_mode"].lower()
            if kerberos_auth_mode == "sspi":
                if os.name != "nt":
                    raise InterfaceError(
                        "'kerberos_auth_mode=SSPI' is only available on Windows"
                    )
                self._auth_plugin_class = "MySQLSSPIKerberosAuthPlugin"
            elif kerberos_auth_mode == "gssapi":
                self._auth_plugin_class = "MySQLKerberosAuthPlugin"
            else:
                raise InterfaceError(
                    "Invalid 'kerberos_auth_mode' mode. Please use 'SSPI' or 'GSSAPI'"
                )

        if (
            "krb_service_principal" in config
            and config["krb_service_principal"] is not None
        ):
            self._krb_service_principal = config["krb_service_principal"]
            if not isinstance(self._krb_service_principal, str):
                raise InterfaceError(
                    KRB_SERVICE_PINCIPAL_ERROR.format(error="is not a string")
                )
            if self._krb_service_principal == "":
                raise InterfaceError(
                    KRB_SERVICE_PINCIPAL_ERROR.format(
                        error="can not be an empty string"
                    )
                )
            if "/" not in self._krb_service_principal:
                raise InterfaceError(
                    KRB_SERVICE_PINCIPAL_ERROR.format(error="is incorrectly formatted")
                )

        if self._fido_callback:
            # Import the callable if it's a str
            if isinstance(self._fido_callback, str):
                try:
                    module, callback = self._fido_callback.rsplit(".", 1)
                except ValueError:
                    raise ProgrammingError(
                        f"No callable named '{self._fido_callback}'"
                    ) from None
                try:
                    module = importlib.import_module(module)
                    self._fido_callback = getattr(module, callback)
                except (AttributeError, ModuleNotFoundError) as err:
                    raise ProgrammingError(f"{err}") from err
            # Check if it's a callable
            if not callable(self._fido_callback):
                raise ProgrammingError("Expected a callable for 'fido_callback'")
            # Check the callable signature if has only 1 positional argument
            params = len(signature(self._fido_callback).parameters)
            if params != 1:
                raise ProgrammingError(
                    "'fido_callback' requires 1 positional argument, but the "
                    f"callback provided has {params}"
                )

    def _add_default_conn_attrs(self) -> Any:
        """Add the default connection attributes."""

    @staticmethod
    def _check_server_version(server_version: StrOrBytes) -> Tuple[int, ...]:
        """Check the MySQL version

        This method will check the MySQL version and raise an InterfaceError
        when it is not supported or invalid. It will return the version
        as a tuple with major, minor and patch.

        Raises InterfaceError if invalid server version.

        Returns tuple
        """
        if isinstance(server_version, (bytearray, bytes)):
            server_version = server_version.decode()

        regex_ver = re.compile(r"^(\d{1,2})\.(\d{1,2})\.(\d{1,3})(.*)")
        match = regex_ver.match(server_version)
        if not match:
            raise InterfaceError("Failed parsing MySQL version")

        version = tuple(int(v) for v in match.groups()[0:3])
        if version < (4, 1):
            raise InterfaceError(f"MySQL Version '{server_version}' is not supported")

        return version

    def get_server_version(self) -> Tuple[int, ...]:
        """Get the MySQL version

        This method returns the MySQL server version as a tuple. If not
        previously connected, it will return None.

        Returns a tuple or None.
        """
        return self._server_version

    def get_server_info(self) -> Optional[str]:
        """Get the original MySQL version information

        This method returns the original MySQL server as text. If not
        previously connected, it will return None.

        Returns a string or None.
        """
        try:
            return self._handshake["server_version_original"]  # type: ignore[return-value]
        except (TypeError, KeyError):
            return None

    @property
    @abstractmethod
    def in_transaction(self) -> Any:
        """MySQL session has started a transaction"""

    def set_client_flags(self, flags: Union[int, Sequence[int]]) -> int:
        """Set the client flags

        The flags-argument can be either an int or a list (or tuple) of
        ClientFlag-values. If it is an integer, it will set client_flags
        to flags as is.
        If flags is a list (or tuple), each flag will be set or unset
        when it's negative.

        set_client_flags([ClientFlag.FOUND_ROWS,-ClientFlag.LONG_FLAG])

        Raises ProgrammingError when the flags argument is not a set or
        an integer bigger than 0.

        Returns self.client_flags
        """
        if isinstance(flags, int) and flags > 0:
            self._client_flags = flags
        elif isinstance(flags, (tuple, list)):
            for flag in flags:
                if flag < 0:
                    self._client_flags &= ~abs(flag)
                else:
                    self._client_flags |= flag
        else:
            raise ProgrammingError("set_client_flags expect integer (>0) or set")
        return self._client_flags

    def isset_client_flag(self, flag: int) -> bool:
        """Check if a client flag is set"""
        if (self._client_flags & flag) > 0:
            return True
        return False

    @property
    def time_zone(self) -> str:
        """Get the current time zone"""
        return self.info_query("SELECT @@session.time_zone")[0]

    @time_zone.setter
    def time_zone(self, value: str) -> None:
        """Set the time zone"""
        self.cmd_query(f"SET @@session.time_zone = '{value}'")
        self._time_zone = value

    @property
    def sql_mode(self) -> str:
        """Get the SQL mode"""
        if self._sql_mode is None:
            self._sql_mode = self.info_query("SELECT @@session.sql_mode")[0]
        return self._sql_mode

    @sql_mode.setter
    def sql_mode(self, value: Union[str, Sequence[int]]) -> None:
        """Set the SQL mode

        This method sets the SQL Mode for the current connection. The value
        argument can be either a string with comma separate mode names, or
        a sequence of mode names.

        It is good practice to use the constants class SQLMode:
          from mysql.connector.constants import SQLMode
          cnx.sql_mode = [SQLMode.NO_ZERO_DATE, SQLMode.REAL_AS_FLOAT]
        """
        if isinstance(value, (list, tuple)):
            value = ",".join(value)
        self.cmd_query(f"SET @@session.sql_mode = '{value}'")
        self._sql_mode = value

    @abstractmethod
    def info_query(self, query: Any) -> Any:
        """Send a query which only returns 1 row"""

    def set_login(
        self, username: Optional[str] = None, password: Optional[str] = None
    ) -> None:
        """Set login information for MySQL

        Set the username and/or password for the user connecting to
        the MySQL Server.
        """
        if username is not None:
            self._user = username.strip()
        else:
            self._user = ""
        if password is not None:
            self._password = password
        else:
            self._password = ""

    def set_unicode(self, value: bool = True) -> None:
        """Toggle unicode mode

        Set whether we return string fields as unicode or not.
        Default is True.
        """
        self._use_unicode = value
        if self.converter:
            self.converter.set_unicode(value)

    @property
    def autocommit(self) -> bool:
        """Get whether autocommit is on or off"""
        value = self.info_query("SELECT @@session.autocommit")[0]
        return value == 1

    @autocommit.setter
    def autocommit(self, value: bool) -> None:
        """Toggle autocommit"""
        switch = "ON" if value else "OFF"
        self.cmd_query(f"SET @@session.autocommit = {switch}")
        self._autocommit = value

    @property
    def get_warnings(self) -> bool:
        """Get whether this connection retrieves warnings automatically

        This method returns whether this connection retrieves warnings
        automatically.

        Returns True, or False when warnings are not retrieved.
        """
        return self._get_warnings

    @get_warnings.setter
    def get_warnings(self, value: bool) -> None:
        """Set whether warnings should be automatically retrieved

        The toggle-argument must be a boolean. When True, cursors for this
        connection will retrieve information about warnings (if any).

        Raises ValueError on error.
        """
        if not isinstance(value, bool):
            raise ValueError("Expected a boolean type")
        self._get_warnings = value

    @property
    def raise_on_warnings(self) -> bool:
        """Get whether this connection raises an error on warnings

        This method returns whether this connection will raise errors when
        MySQL reports warnings.

        Returns True or False.
        """
        return self._raise_on_warnings

    @raise_on_warnings.setter
    def raise_on_warnings(self, value: bool) -> None:
        """Set whether warnings raise an error

        The toggle-argument must be a boolean. When True, cursors for this
        connection will raise an error when MySQL reports warnings.

        Raising on warnings implies retrieving warnings automatically. In
        other words: warnings will be set to True. If set to False, warnings
        will be also set to False.

        Raises ValueError on error.
        """
        if not isinstance(value, bool):
            raise ValueError("Expected a boolean type")
        self._raise_on_warnings = value
        # Don't disable warning retrieval if raising explicitly disabled
        if value:
            self._get_warnings = value

    @property
    def unread_result(self) -> bool:
        """Get whether there is an unread result

        This method is used by cursors to check whether another cursor still
        needs to retrieve its result set.

        Returns True, or False when there is no unread result.
        """
        return self._unread_result

    @unread_result.setter
    def unread_result(self, value: bool) -> None:
        """Set whether there is an unread result

        This method is used by cursors to let other cursors know there is
        still a result set that needs to be retrieved.

        Raises ValueError on errors.
        """
        if not isinstance(value, bool):
            raise ValueError("Expected a boolean type")
        self._unread_result = value

    @property
    def charset(self) -> str:
        """Returns the character set for current connection

        This property returns the character set name of the current connection.
        The server is queried when the connection is active. If not connected,
        the configured character set name is returned.

        Returns a string.
        """
        return CharacterSet.get_info(self._charset_id)[0]

    @property
    def python_charset(self) -> str:
        """Returns the Python character set for current connection

        This property returns the character set name of the current connection.
        Note that, unlike property charset, this checks if the previously set
        character set is supported by Python and if not, it returns the
        equivalent character set that Python supports.

        Returns a string.
        """
        encoding = CharacterSet.get_info(self._charset_id)[0]
        if encoding in ("utf8mb4", "utf8mb3", "binary"):
            return "utf8"
        return encoding

    def set_charset_collation(
        self, charset: Optional[Union[int, str]] = None, collation: Optional[str] = None
    ) -> None:
        """Sets the character set and collation for the current connection

        This method sets the character set and collation to be used for
        the current connection. The charset argument can be either the
        name of a character set as a string, or the numerical equivalent
        as defined in constants.CharacterSet.

        When the collation is not given, the default will be looked up and
        used.

        For example, the following will set the collation for the latin1
        character set to latin1_general_ci:

           set_charset('latin1','latin1_general_ci')

        """
        err_msg = "{} should be either integer, string or None"
        if not isinstance(charset, (int, str)) and charset is not None:
            raise ValueError(err_msg.format("charset"))
        if not isinstance(collation, str) and collation is not None:
            raise ValueError("collation should be either string or None")

        if charset:
            if isinstance(charset, int):
                (
                    self._charset_id,
                    charset_name,
                    collation_name,
                ) = CharacterSet.get_charset_info(charset)
            elif isinstance(charset, str):
                (
                    self._charset_id,
                    charset_name,
                    collation_name,
                ) = CharacterSet.get_charset_info(charset, collation)
            else:
                raise ValueError(err_msg.format("charset"))
        elif collation:
            (
                self._charset_id,
                charset_name,
                collation_name,
            ) = CharacterSet.get_charset_info(collation=collation)
        else:
            charset = DEFAULT_CONFIGURATION["charset"]
            (
                self._charset_id,
                charset_name,
                collation_name,
            ) = CharacterSet.get_charset_info(charset, collation=None)

        self._execute_query(f"SET NAMES '{charset_name}' COLLATE '{collation_name}'")

        try:
            # Required for C Extension
            self.set_character_set_name(charset_name)
        except AttributeError:
            # Not required for pure Python connection
            pass

        if self.converter:
            self.converter.set_charset(charset_name)

    @property
    def collation(self) -> str:
        """Returns the collation for current connection

        This property returns the collation name of the current connection.
        The server is queried when the connection is active. If not connected,
        the configured collation name is returned.

        Returns a string.
        """
        return CharacterSet.get_charset_info(self._charset_id)[2]

    @abstractmethod
    def _do_handshake(self) -> Any:
        """Gather information of the MySQL server before authentication"""

    @abstractmethod
    def _open_connection(self) -> Any:
        """Open the connection to the MySQL server"""

    def _post_connection(self) -> None:
        """Executes commands after connection has been established

        This method executes commands after the connection has been
        established. Some setting like autocommit, character set, and SQL mode
        are set using this method.
        """
        self.set_charset_collation(self._charset_id)
        self.autocommit = self._autocommit
        if self._time_zone:
            self.time_zone = self._time_zone
        if self._sql_mode:
            self.sql_mode = self._sql_mode
        if self._init_command:
            self._execute_query(self._init_command)

    @abstractmethod
    def disconnect(self) -> Any:
        """Disconnect from the MySQL server"""

    close: Callable[[], Any] = disconnect

    def connect(self, **kwargs: Any) -> None:
        """Connect to the MySQL server

        This method sets up the connection to the MySQL server. If no
        arguments are given, it will use the already configured or default
        values.
        """
        if kwargs:
            self.config(**kwargs)

        self.disconnect()
        self._open_connection()
        # Server does not allow to run any other statement different from ALTER
        # when user's password has been expired.
        if not self._client_flags & ClientFlag.CAN_HANDLE_EXPIRED_PASSWORDS:
            self._post_connection()

    def reconnect(self, attempts: int = 1, delay: int = 0) -> None:
        """Attempt to reconnect to the MySQL server

        The argument attempts should be the number of times a reconnect
        is tried. The delay argument is the number of seconds to wait between
        each retry.

        You may want to set the number of attempts higher and use delay when
        you expect the MySQL server to be down for maintenance or when you
        expect the network to be temporary unavailable.

        Raises InterfaceError on errors.
        """
        counter = 0
        span = None

        if self._tracer:
            span = self._tracer.start_span(
                name=CONNECTION_SPAN_NAME, kind=trace.SpanKind.CLIENT
            )

        try:
            while counter != attempts:
                counter = counter + 1
                try:
                    self.disconnect()
                    self.connect()
                    if self.is_connected():
                        break
                except (Error, IOError) as err:
                    if counter == attempts:
                        msg = (
                            f"Can not reconnect to MySQL after {attempts} "
                            f"attempt(s): {err}"
                        )
                        raise InterfaceError(msg) from err
                if delay > 0:
                    sleep(delay)
        except InterfaceError as interface_err:
            if OTEL_ENABLED:
                set_connection_span_attrs(self, span)
                record_exception_event(span, interface_err)
                end_span(span)
            raise

        self._span = span
        if OTEL_ENABLED:
            set_connection_span_attrs(self, self._span)

    @abstractmethod
    def is_connected(self) -> Any:
        """Reports whether the connection to MySQL Server is available"""

    @abstractmethod
    def ping(self, reconnect: bool = False, attempts: int = 1, delay: int = 0) -> Any:
        """Check availability of the MySQL server"""

    @abstractmethod
    def commit(self) -> Any:
        """Commit current transaction"""

    @abstractmethod
    def cursor(
        self,
        buffered: Optional[bool] = None,
        raw: Optional[bool] = None,
        prepared: Optional[bool] = None,
        cursor_class: Optional[type] = None,
        dictionary: Optional[bool] = None,
        named_tuple: Optional[bool] = None,
    ) -> "MySQLCursorAbstract":
        """Instantiates and returns a cursor"""

    @abstractmethod
    def _execute_query(self, query: Any) -> Any:
        """Execute a query"""

    @abstractmethod
    def rollback(self) -> Any:
        """Rollback current transaction"""

    def start_transaction(
        self,
        consistent_snapshot: bool = False,
        isolation_level: Optional[str] = None,
        readonly: Optional[bool] = None,
    ) -> None:
        """Start a transaction

        This method explicitly starts a transaction sending the
        START TRANSACTION statement to the MySQL server. You can optionally
        set whether there should be a consistent snapshot, which
        isolation level you need or which access mode i.e. READ ONLY or
        READ WRITE.

        For example, to start a transaction with isolation level SERIALIZABLE,
        you would do the following:
            >>> cnx = mysql.connector.connect(..)
            >>> cnx.start_transaction(isolation_level='SERIALIZABLE')

        Raises ProgrammingError when a transaction is already in progress
        and when ValueError when isolation_level specifies an Unknown
        level.
        """
        if self.in_transaction:
            raise ProgrammingError("Transaction already in progress")

        if isolation_level:
            level = isolation_level.strip().replace("-", " ").upper()
            levels = [
                "READ UNCOMMITTED",
                "READ COMMITTED",
                "REPEATABLE READ",
                "SERIALIZABLE",
            ]

            if level not in levels:
                raise ValueError(f'Unknown isolation level "{isolation_level}"')

            self._execute_query(f"SET TRANSACTION ISOLATION LEVEL {level}")

        if readonly is not None:
            if self._server_version < (5, 6, 5):
                raise ValueError(
                    f"MySQL server version {self._server_version} does not "
                    "support this feature"
                )

            if readonly:
                access_mode = "READ ONLY"
            else:
                access_mode = "READ WRITE"
            self._execute_query(f"SET TRANSACTION {access_mode}")

        query = "START TRANSACTION"
        if consistent_snapshot:
            query += " WITH CONSISTENT SNAPSHOT"
        self.cmd_query(query)

    def reset_session(
        self,
        user_variables: Optional[Dict[str, Any]] = None,
        session_variables: Optional[Dict[str, Any]] = None,
    ) -> None:
        """Clears the current active session

        This method resets the session state, if the MySQL server is 5.7.3
        or later active session will be reset without re-authenticating.
        For other server versions session will be reset by re-authenticating.

        It is possible to provide a sequence of variables and their values to
        be set after clearing the session. This is possible for both user
        defined variables and session variables.
        This method takes two arguments user_variables and session_variables
        which are dictionaries.

        Raises OperationalError if not connected, InternalError if there are
        unread results and InterfaceError on errors.
        """
        if not self.is_connected():
            raise OperationalError("MySQL Connection not available")

        try:
            self.cmd_reset_connection()
        except (NotSupportedError, NotImplementedError):
            if self._compress:
                raise NotSupportedError(
                    "Reset session is not supported with compression for "
                    "MySQL server version 5.7.2 or earlier"
                ) from None
            self.cmd_change_user(
                self._user,
                self._password,
                self._database,
                self._charset_id,
            )

        if user_variables or session_variables:
            cur = self.cursor()
            if user_variables:
                for key, value in user_variables.items():
                    cur.execute(f"SET @`{key}` = {value}")
            if session_variables:
                for key, value in session_variables.items():
                    cur.execute(f"SET SESSION `{key}` = {value}")
            cur.close()

    def set_converter_class(self, convclass: Optional[Type[MySQLConverter]]) -> None:
        """
        Set the converter class to be used. This should be a class overloading
        methods and members of conversion.MySQLConverter.
        """
        if convclass and issubclass(convclass, MySQLConverterBase):
            charset_name = CharacterSet.get_info(self._charset_id)[0]
            self._converter_class = convclass
            self.converter = convclass(charset_name, self._use_unicode)
            self.converter.str_fallback = self._converter_str_fallback
        else:
            raise TypeError(
                "Converter class should be a subclass of conversion.MySQLConverterBase."
            )

    @abstractmethod
    def get_rows(
        self,
        count: Optional[int] = None,
        binary: bool = False,
        columns: Optional[List[DescriptionType]] = None,
        raw: Optional[bool] = None,
        prep_stmt: Any = None,
    ) -> Tuple[List[Any], Optional[Mapping[str, Any]]]:
        """Get all rows returned by the MySQL server"""

    def cmd_init_db(self, database: str) -> Optional[Mapping[str, Any]]:
        """Change the current database"""
        raise NotImplementedError

    def cmd_query(
        self,
        query: Any,
        raw: bool = False,
        buffered: bool = False,
        raw_as_string: bool = False,
    ) -> Optional[Mapping[str, Any]]:
        """Send a query to the MySQL server"""
        raise NotImplementedError

    def cmd_query_iter(
        self, statements: Any
    ) -> Generator[Mapping[str, Any], None, None]:
        """Send one or more statements to the MySQL server"""
        raise NotImplementedError

    def cmd_refresh(self, options: int) -> Optional[Mapping[str, Any]]:
        """Send the Refresh command to the MySQL server"""
        raise NotImplementedError

    def cmd_quit(self) -> Any:
        """Close the current connection with the server"""
        raise NotImplementedError

    def cmd_shutdown(
        self, shutdown_type: Optional[int] = None
    ) -> Optional[Mapping[str, Any]]:
        """Shut down the MySQL Server"""
        raise NotImplementedError

    def cmd_statistics(self) -> Optional[Mapping[str, Any]]:
        """Send the statistics command to the MySQL Server"""
        raise NotImplementedError

    @staticmethod
    def cmd_process_info() -> Any:
        """Get the process list of the MySQL Server

        This method is a placeholder to notify that the PROCESS_INFO command
        is not supported by raising the NotSupportedError. The command
        "SHOW PROCESSLIST" should be send using the cmd_query()-method or
        using the INFORMATION_SCHEMA database.

        Raises NotSupportedError exception
        """
        raise NotSupportedError(
            "Not implemented. Use SHOW PROCESSLIST or INFORMATION_SCHEMA"
        )

    def cmd_process_kill(self, mysql_pid: int) -> Optional[Mapping[str, Any]]:
        """Kill a MySQL process"""
        raise NotImplementedError

    def cmd_debug(self) -> Optional[Mapping[str, Any]]:
        """Send the DEBUG command"""
        raise NotImplementedError

    def cmd_ping(self) -> Optional[Mapping[str, Any]]:
        """Send the PING command"""
        raise NotImplementedError

    def cmd_change_user(
        self,
        username: str = "",
        password: str = "",
        database: str = "",
        charset: int = 45,
        password1: str = "",
        password2: str = "",
        password3: str = "",
        oci_config_file: str = "",
    ) -> Optional[Mapping[str, Any]]:
        """Change the current logged in user"""
        raise NotImplementedError

    def cmd_stmt_prepare(self, statement: Any) -> Optional[Mapping[str, Any]]:
        """Prepare a MySQL statement"""
        raise NotImplementedError

    def cmd_stmt_execute(
        self,
        statement_id: Any,
        data: Sequence[Any] = (),
        parameters: Sequence[Any] = (),
        flags: int = 0,
    ) -> Any:
        """Execute a prepared MySQL statement"""
        raise NotImplementedError

    def cmd_stmt_close(self, statement_id: Any) -> Any:
        """Deallocate a prepared MySQL statement"""
        raise NotImplementedError

    def cmd_stmt_send_long_data(
        self, statement_id: Any, param_id: int, data: BinaryIO
    ) -> Any:
        """Send data for a column"""
        raise NotImplementedError

    def cmd_stmt_reset(self, statement_id: Any) -> Any:
        """Reset data for prepared statement sent as long data"""
        raise NotImplementedError

    def cmd_reset_connection(self) -> Any:
        """Resets the session state without re-authenticating"""
        raise NotImplementedError


class MySQLCursorAbstract(ABC):
    """Abstract cursor class

    Abstract class defining cursor class with method and members
    required by the Python Database API Specification v2.0.
    """

    def __init__(self) -> None:
        """Initialization"""
        self._description: Optional[List[DescriptionType]] = None
        self._rowcount: int = -1
        self._last_insert_id: Optional[int] = None
        self._warnings: Optional[List[WarningType]] = None
        self._warning_count: int = 0
        self._executed: Optional[StrOrBytes] = None
        self._executed_list: List[StrOrBytes] = []
        self._stored_results: List[Any] = []
        self.arraysize: int = 1

    def __enter__(self) -> MySQLCursorAbstract:
        return self

    def __exit__(
        self,
        exc_type: Type[BaseException],
        exc_value: BaseException,
        traceback: TracebackType,
    ) -> None:
        self.close()

    @abstractmethod
    def callproc(self, procname: str, args: Sequence[Any] = ()) -> Any:
        """Calls a stored procedure with the given arguments

        The arguments will be set during this session, meaning
        they will be called like  _<procname>__arg<nr> where
        <nr> is an enumeration (+1) of the arguments.

        Coding Example:
          1) Defining the Stored Routine in MySQL:
          CREATE PROCEDURE multiply(IN pFac1 INT, IN pFac2 INT, OUT pProd INT)
          BEGIN
            SET pProd := pFac1 * pFac2;
          END

          2) Executing in Python:
          args = (5,5,0) # 0 is to hold pprod
          cursor.callproc('multiply', args)
          print(cursor.fetchone())

        Does not return a value, but a result set will be
        available when the CALL-statement execute successfully.
        Raises exceptions when something is wrong.
        """

    @abstractmethod
    def close(self) -> Any:
        """Close the cursor."""

    @abstractmethod
    def execute(
        self,
        operation: str,
        params: Union[Sequence[Any], Dict[str, Any]] = (),
        multi: bool = False,
    ) -> Any:
        """Executes the given operation

        Executes the given operation substituting any markers with
        the given parameters.

        For example, getting all rows where id is 5:
          cursor.execute("SELECT * FROM t1 WHERE id = %s", (5,))

        The multi argument should be set to True when executing multiple
        statements in one operation. If not set and multiple results are
        found, an InterfaceError will be raised.

        If warnings where generated, and connection.get_warnings is True, then
        self._warnings will be a list containing these warnings.

        Returns an iterator when multi is True, otherwise None.
        """

    @abstractmethod
    def executemany(
        self, operation: str, seq_params: Sequence[Union[Sequence[Any], Dict[str, Any]]]
    ) -> Any:
        """Execute the given operation multiple times

        The executemany() method will execute the operation iterating
        over the list of parameters in seq_params.

        Example: Inserting 3 new employees and their phone number

        data = [
            ('Jane','555-001'),
            ('Joe', '555-001'),
            ('John', '555-003')
            ]
        stmt = "INSERT INTO employees (name, phone) VALUES ('%s','%s')"
        cursor.executemany(stmt, data)

        INSERT statements are optimized by batching the data, that is
        using the MySQL multiple rows syntax.

        Results are discarded. If they are needed, consider looping over
        data using the execute() method.
        """

    @abstractmethod
    def fetchone(self) -> Optional[Sequence[Any]]:
        """Returns next row of a query result set

        Returns a tuple or None.
        """

    @abstractmethod
    def fetchmany(self, size: int = 1) -> List[Sequence[Any]]:
        """Returns the next set of rows of a query result, returning a
        list of tuples. When no more rows are available, it returns an
        empty list.

        The number of rows returned can be specified using the size argument,
        which defaults to one
        """

    @abstractmethod
    def fetchall(self) -> Sequence[Any]:
        """Returns all rows of a query result set

        Returns a list of tuples.
        """

    def nextset(self) -> Any:
        """Not Implemented."""

    def setinputsizes(self, sizes: Any) -> Any:
        """Not Implemented."""

    def setoutputsize(self, size: Any, column: Any = None) -> Any:
        """Not Implemented."""

    def reset(self, free: bool = True) -> Any:
        """Reset the cursor to default"""

    @property
    @abstractmethod
    def description(
        self,
    ) -> Optional[List[DescriptionType]]:
        """Returns description of columns in a result

        This property returns a list of tuples describing the columns in
        in a result set. A tuple is described as follows::

                (column_name,
                 type,
                 None,
                 None,
                 None,
                 None,
                 null_ok,
                 column_flags)  # Addition to PEP-249 specs

        Returns a list of tuples.
        """
        return self._description

    @property
    @abstractmethod
    def rowcount(self) -> int:
        """Returns the number of rows produced or affected

        This property returns the number of rows produced by queries
        such as a SELECT, or affected rows when executing DML statements
        like INSERT or UPDATE.

        Note that for non-buffered cursors it is impossible to know the
        number of rows produced before having fetched them all. For those,
        the number of rows will be -1 right after execution, and
        incremented when fetching rows.

        Returns an integer.
        """
        return self._rowcount

    @property
    def lastrowid(self) -> Optional[int]:
        """Returns the value generated for an AUTO_INCREMENT column

        Returns the value generated for an AUTO_INCREMENT column by
        the previous INSERT or UPDATE statement or None when there is
        no such value available.

        Returns a long value or None.
        """
        return self._last_insert_id

    @property
    def warnings(self) -> Optional[List[WarningType]]:
        """Return warnings."""
        return self._warnings

    @property
    def warning_count(self) -> int:
        """Returns the number of warnings

        This property returns the number of warnings generated by the
        previously executed operation.

        Returns an integer value.
        """
        return self._warning_count

    def fetchwarnings(self) -> Optional[List[WarningType]]:
        """Returns Warnings."""
        return self._warnings

    def get_attributes(self) -> Optional[List[Tuple[str, Any]]]:
        """Get the added query attributes so far."""
        if hasattr(self, "_cnx"):
            return self._cnx.query_attrs
        if hasattr(self, "_connection"):
            return self._connection.query_attrs
        return None

    def add_attribute(self, name: str, value: Any) -> None:
        """Add a query attribute and his value."""
        if not isinstance(name, str):
            raise ProgrammingError("Parameter `name` must be a string type")
        if value is not None and not isinstance(value, MYSQL_PY_TYPES):
            raise ProgrammingError(
                f"Object {value} cannot be converted to a MySQL type"
            )
        if hasattr(self, "_cnx"):
            self._cnx.query_attrs_append((name, value))
        elif hasattr(self, "_connection"):
            self._connection.query_attrs_append((name, value))

    def remove_attribute(self, name: str) -> Any:
        """Remove a query attribute by name.

        If no match, `None` is returned; else the corresponding value is returned.
        """
        if not isinstance(name, str):
            raise ProgrammingError("Parameter `name` must be a string type")
        if hasattr(self, "_cnx"):
            return self._cnx.query_attrs_remove(name)
        if hasattr(self, "_connection"):
            return self._connection.query_attrs_remove(name)
        return None

    def clear_attributes(self) -> None:
        """Remove all the query attributes."""
        if hasattr(self, "_cnx"):
            self._cnx.query_attrs_clear()
        elif hasattr(self, "_connection"):
            self._connection.query_attrs_clear()

virt/lib/python3.9/site-packages/mysql/connector/abstracts.py
PrinceDisant/LostInNL
# Copyright (c) 2014, 2022, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

"""Implementing support for MySQL Authentication Plugins"""

import importlib

from functools import lru_cache
from typing import Optional, Type

from .errors import NotSupportedError, ProgrammingError
from .logger import logger
from .plugins import BaseAuthPlugin

DEFAULT_PLUGINS_PKG = "mysql.connector.plugins"


@lru_cache(maxsize=10, typed=False)
def get_auth_plugin(
    plugin_name: str,
    auth_plugin_class: Optional[str] = None,
) -> Type[BaseAuthPlugin]:  # AUTH_PLUGIN_CLASS_TYPES:
    """Return authentication class based on plugin name

    This function returns the class for the authentication plugin plugin_name.
    The returned class is a subclass of BaseAuthPlugin.

    Args:
        plugin_name (str): Authentication plugin name.
        auth_plugin_class (str): Authentication plugin class name.

    Raises:
        NotSupportedError: When plugin_name is not supported.

    Returns:
        Subclass of `BaseAuthPlugin`.
    """
    package = DEFAULT_PLUGINS_PKG
    if plugin_name:
        try:
            logger.info("package: %s", package)
            logger.info("plugin_name: %s", plugin_name)
            plugin_module = importlib.import_module(f".{plugin_name}", package)
            if not auth_plugin_class or not hasattr(plugin_module, auth_plugin_class):
                auth_plugin_class = plugin_module.AUTHENTICATION_PLUGIN_CLASS
            logger.info("AUTHENTICATION_PLUGIN_CLASS: %s", auth_plugin_class)
            return getattr(plugin_module, auth_plugin_class)
        except ModuleNotFoundError as err:
            logger.warning("Requested Module was not found: %s", err)
        except ValueError as err:
            raise ProgrammingError(f"Invalid module name: {err}") from err
    raise NotSupportedError(f"Authentication plugin '{plugin_name}' is not supported")

virt/lib/python3.9/site-packages/mysql/connector/authentication.py
PrinceDisant/LostInNL
# -*- coding: utf-8 -*- # pylint: disable=missing-module-docstring

# Copyright (c) 2013, 2022, Oracle and/or its affiliates. All rights reserved.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

from typing import List, Optional, Tuple

"""This module contains the MySQL Server Character Sets."""  # pylint: disable=pointless-string-statement

# This file was auto-generated.
_GENERATED_ON: str = "2022-05-09"
_MYSQL_VERSION: Tuple[int, int, int] = (8, 0, 30)

MYSQL_CHARACTER_SETS: List[Optional[Tuple[str, str, bool]]] = [
    # (character set name, collation, default)
    None,
    ("big5", "big5_chinese_ci", True),  # 1
    ("latin2", "latin2_czech_cs", False),  # 2
    ("dec8", "dec8_swedish_ci", True),  # 3
    ("cp850", "cp850_general_ci", True),  # 4
    ("latin1", "latin1_german1_ci", False),  # 5
    ("hp8", "hp8_english_ci", True),  # 6
    ("koi8r", "koi8r_general_ci", True),  # 7
    ("latin1", "latin1_swedish_ci", True),  # 8
    ("latin2", "latin2_general_ci", True),  # 9
    ("swe7", "swe7_swedish_ci", True),  # 10
    ("ascii", "ascii_general_ci", True),  # 11
    ("ujis", "ujis_japanese_ci", True),  # 12
    ("sjis", "sjis_japanese_ci", True),  # 13
    ("cp1251", "cp1251_bulgarian_ci", False),  # 14
    ("latin1", "latin1_danish_ci", False),  # 15
    ("hebrew", "hebrew_general_ci", True),  # 16
    None,
    ("tis620", "tis620_thai_ci", True),  # 18
    ("euckr", "euckr_korean_ci", True),  # 19
    ("latin7", "latin7_estonian_cs", False),  # 20
    ("latin2", "latin2_hungarian_ci", False),  # 21
    ("koi8u", "koi8u_general_ci", True),  # 22
    ("cp1251", "cp1251_ukrainian_ci", False),  # 23
    ("gb2312", "gb2312_chinese_ci", True),  # 24
    ("greek", "greek_general_ci", True),  # 25
    ("cp1250", "cp1250_general_ci", True),  # 26
    ("latin2", "latin2_croatian_ci", False),  # 27
    ("gbk", "gbk_chinese_ci", True),  # 28
    ("cp1257", "cp1257_lithuanian_ci", False),  # 29
    ("latin5", "latin5_turkish_ci", True),  # 30
    ("latin1", "latin1_german2_ci", False),  # 31
    ("armscii8", "armscii8_general_ci", True),  # 32
    ("utf8mb3", "utf8mb3_general_ci", True),  # 33
    ("cp1250", "cp1250_czech_cs", False),  # 34
    ("ucs2", "ucs2_general_ci", True),  # 35
    ("cp866", "cp866_general_ci", True),  # 36
    ("keybcs2", "keybcs2_general_ci", True),  # 37
    ("macce", "macce_general_ci", True),  # 38
    ("macroman", "macroman_general_ci", True),  # 39
    ("cp852", "cp852_general_ci", True),  # 40
    ("latin7", "latin7_general_ci", True),  # 41
    ("latin7", "latin7_general_cs", False),  # 42
    ("macce", "macce_bin", False),  # 43
    ("cp1250", "cp1250_croatian_ci", False),  # 44
    ("utf8mb4", "utf8mb4_general_ci", False),  # 45
    ("utf8mb4", "utf8mb4_bin", False),  # 46
    ("latin1", "latin1_bin", False),  # 47
    ("latin1", "latin1_general_ci", False),  # 48
    ("latin1", "latin1_general_cs", False),  # 49
    ("cp1251", "cp1251_bin", False),  # 50
    ("cp1251", "cp1251_general_ci", True),  # 51
    ("cp1251", "cp1251_general_cs", False),  # 52
    ("macroman", "macroman_bin", False),  # 53
    ("utf16", "utf16_general_ci", True),  # 54
    ("utf16", "utf16_bin", False),  # 55
    ("utf16le", "utf16le_general_ci", True),  # 56
    ("cp1256", "cp1256_general_ci", True),  # 57
    ("cp1257", "cp1257_bin", False),  # 58
    ("cp1257", "cp1257_general_ci", True),  # 59
    ("utf32", "utf32_general_ci", True),  # 60
    ("utf32", "utf32_bin", False),  # 61
    ("utf16le", "utf16le_bin", False),  # 62
    ("binary", "binary", True),  # 63
    ("armscii8", "armscii8_bin", False),  # 64
    ("ascii", "ascii_bin", False),  # 65
    ("cp1250", "cp1250_bin", False),  # 66
    ("cp1256", "cp1256_bin", False),  # 67
    ("cp866", "cp866_bin", False),  # 68
    ("dec8", "dec8_bin", False),  # 69
    ("greek", "greek_bin", False),  # 70
    ("hebrew", "hebrew_bin", False),  # 71
    ("hp8", "hp8_bin", False),  # 72
    ("keybcs2", "keybcs2_bin", False),  # 73
    ("koi8r", "koi8r_bin", False),  # 74
    ("koi8u", "koi8u_bin", False),  # 75
    ("utf8mb3", "utf8mb3_tolower_ci", False),  # 76
    ("latin2", "latin2_bin", False),  # 77
    ("latin5", "latin5_bin", False),  # 78
    ("latin7", "latin7_bin", False),  # 79
    ("cp850", "cp850_bin", False),  # 80
    ("cp852", "cp852_bin", False),  # 81
    ("swe7", "swe7_bin", False),  # 82
    ("utf8mb3", "utf8mb3_bin", False),  # 83
    ("big5", "big5_bin", False),  # 84
    ("euckr", "euckr_bin", False),  # 85
    ("gb2312", "gb2312_bin", False),  # 86
    ("gbk", "gbk_bin", False),  # 87
    ("sjis", "sjis_bin", False),  # 88
    ("tis620", "tis620_bin", False),  # 89
    ("ucs2", "ucs2_bin", False),  # 90
    ("ujis", "ujis_bin", False),  # 91
    ("geostd8", "geostd8_general_ci", True),  # 92
    ("geostd8", "geostd8_bin", False),  # 93
    ("latin1", "latin1_spanish_ci", False),  # 94
    ("cp932", "cp932_japanese_ci", True),  # 95
    ("cp932", "cp932_bin", False),  # 96
    ("eucjpms", "eucjpms_japanese_ci", True),  # 97
    ("eucjpms", "eucjpms_bin", False),  # 98
    ("cp1250", "cp1250_polish_ci", False),  # 99
    None,
    ("utf16", "utf16_unicode_ci", False),  # 101
    ("utf16", "utf16_icelandic_ci", False),  # 102
    ("utf16", "utf16_latvian_ci", False),  # 103
    ("utf16", "utf16_romanian_ci", False),  # 104
    ("utf16", "utf16_slovenian_ci", False),  # 105
    ("utf16", "utf16_polish_ci", False),  # 106
    ("utf16", "utf16_estonian_ci", False),  # 107
    ("utf16", "utf16_spanish_ci", False),  # 108
    ("utf16", "utf16_swedish_ci", False),  # 109
    ("utf16", "utf16_turkish_ci", False),  # 110
    ("utf16", "utf16_czech_ci", False),  # 111
    ("utf16", "utf16_danish_ci", False),  # 112
    ("utf16", "utf16_lithuanian_ci", False),  # 113
    ("utf16", "utf16_slovak_ci", False),  # 114
    ("utf16", "utf16_spanish2_ci", False),  # 115
    ("utf16", "utf16_roman_ci", False),  # 116
    ("utf16", "utf16_persian_ci", False),  # 117
    ("utf16", "utf16_esperanto_ci", False),  # 118
    ("utf16", "utf16_hungarian_ci", False),  # 119
    ("utf16", "utf16_sinhala_ci", False),  # 120
    ("utf16", "utf16_german2_ci", False),  # 121
    ("utf16", "utf16_croatian_ci", False),  # 122
    ("utf16", "utf16_unicode_520_ci", False),  # 123
    ("utf16", "utf16_vietnamese_ci", False),  # 124
    None,
    None,
    None,
    ("ucs2", "ucs2_unicode_ci", False),  # 128
    ("ucs2", "ucs2_icelandic_ci", False),  # 129
    ("ucs2", "ucs2_latvian_ci", False),  # 130
    ("ucs2", "ucs2_romanian_ci", False),  # 131
    ("ucs2", "ucs2_slovenian_ci", False),  # 132
    ("ucs2", "ucs2_polish_ci", False),  # 133
    ("ucs2", "ucs2_estonian_ci", False),  # 134
    ("ucs2", "ucs2_spanish_ci", False),  # 135
    ("ucs2", "ucs2_swedish_ci", False),  # 136
    ("ucs2", "ucs2_turkish_ci", False),  # 137
    ("ucs2", "ucs2_czech_ci", False),  # 138
    ("ucs2", "ucs2_danish_ci", False),  # 139
    ("ucs2", "ucs2_lithuanian_ci", False),  # 140
    ("ucs2", "ucs2_slovak_ci", False),  # 141
    ("ucs2", "ucs2_spanish2_ci", False),  # 142
    ("ucs2", "ucs2_roman_ci", False),  # 143
    ("ucs2", "ucs2_persian_ci", False),  # 144
    ("ucs2", "ucs2_esperanto_ci", False),  # 145
    ("ucs2", "ucs2_hungarian_ci", False),  # 146
    ("ucs2", "ucs2_sinhala_ci", False),  # 147
    ("ucs2", "ucs2_german2_ci", False),  # 148
    ("ucs2", "ucs2_croatian_ci", False),  # 149
    ("ucs2", "ucs2_unicode_520_ci", False),  # 150
    ("ucs2", "ucs2_vietnamese_ci", False),  # 151
    None,
    None,
    None,
    None,
    None,
    None,
    None,
    ("ucs2", "ucs2_general_mysql500_ci", False),  # 159
    ("utf32", "utf32_unicode_ci", False),  # 160
    ("utf32", "utf32_icelandic_ci", False),  # 161
    ("utf32", "utf32_latvian_ci", False),  # 162
    ("utf32", "utf32_romanian_ci", False),  # 163
    ("utf32", "utf32_slovenian_ci", False),  # 164
    ("utf32", "utf32_polish_ci", False),  # 165
    ("utf32", "utf32_estonian_ci", False),  # 166
    ("utf32", "utf32_spanish_ci", False),  # 167
    ("utf32", "utf32_swedish_ci", False),  # 168
    ("utf32", "utf32_turkish_ci", False),  # 169
    ("utf32", "utf32_czech_ci", False),  # 170
    ("utf32", "utf32_danish_ci", False),  # 171
    ("utf32", "utf32_lithuanian_ci", False),  # 172
    ("utf32", "utf32_slovak_ci", False),  # 173
    ("utf32", "utf32_spanish2_ci", False),  # 174
    ("utf32", "utf32_roman_ci", False),  # 175
    ("utf32", "utf32_persian_ci", False),  # 176
    ("utf32", "utf32_esperanto_ci", False),  # 177
    ("utf32", "utf32_hungarian_ci", False),  # 178
    ("utf32", "utf32_sinhala_ci", False),  # 179
    ("utf32", "utf32_german2_ci", False),  # 180
    ("utf32", "utf32_croatian_ci", False),  # 181
    ("utf32", "utf32_unicode_520_ci", False),  # 182
    ("utf32", "utf32_vietnamese_ci", False),  # 183
    None,
    None,
    None,
    None,
    None,
    None,
    None,
    None,
    ("utf8mb3", "utf8mb3_unicode_ci", False),  # 192
    ("utf8mb3", "utf8mb3_icelandic_ci", False),  # 193
    ("utf8mb3", "utf8mb3_latvian_ci", False),  # 194
    ("utf8mb3", "utf8mb3_romanian_ci", False),  # 195
    ("utf8mb3", "utf8mb3_slovenian_ci", False),  # 196
    ("utf8mb3", "utf8mb3_polish_ci", False),  # 197
    ("utf8mb3", "utf8mb3_estonian_ci", False),  # 198
    ("utf8mb3", "utf8mb3_spanish_ci", False),  # 199
    ("utf8mb3", "utf8mb3_swedish_ci", False),  # 200
    ("utf8mb3", "utf8mb3_turkish_ci", False),  # 201
    ("utf8mb3", "utf8mb3_czech_ci", False),  # 202
    ("utf8mb3", "utf8mb3_danish_ci", False),  # 203
    ("utf8mb3", "utf8mb3_lithuanian_ci", False),  # 204
    ("utf8mb3", "utf8mb3_slovak_ci", False),  # 205
    ("utf8mb3", "utf8mb3_spanish2_ci", False),  # 206
    ("utf8mb3", "utf8mb3_roman_ci", False),  # 207
    ("utf8mb3", "utf8mb3_persian_ci", False),  # 208
    ("utf8mb3", "utf8mb3_esperanto_ci", False),  # 209
    ("utf8mb3", "utf8mb3_hungarian_ci", False),  # 210
    ("utf8mb3", "utf8mb3_sinhala_ci", False),  # 211
    ("utf8mb3", "utf8mb3_german2_ci", False),  # 212
    ("utf8mb3", "utf8mb3_croatian_ci", False),  # 213
    ("utf8mb3", "utf8mb3_unicode_520_ci", False),  # 214
    ("utf8mb3", "utf8mb3_vietnamese_ci", False),  # 215
    None,
    None,
    None,
    None,
    None,
    None,
    None,
    ("utf8mb3", "utf8mb3_general_mysql500_ci", False),  # 223
    ("utf8mb4", "utf8mb4_unicode_ci", False),  # 224
    ("utf8mb4", "utf8mb4_icelandic_ci", False),  # 225
    ("utf8mb4", "utf8mb4_latvian_ci", False),  # 226
    ("utf8mb4", "utf8mb4_romanian_ci", False),  # 227
    ("utf8mb4", "utf8mb4_slovenian_ci", False),  # 228
    ("utf8mb4", "utf8mb4_polish_ci", False),  # 229
    ("utf8mb4", "utf8mb4_estonian_ci", False),  # 230
    ("utf8mb4", "utf8mb4_spanish_ci", False),  # 231
    ("utf8mb4", "utf8mb4_swedish_ci", False),  # 232
    ("utf8mb4", "utf8mb4_turkish_ci", False),  # 233
    ("utf8mb4", "utf8mb4_czech_ci", False),  # 234
    ("utf8mb4", "utf8mb4_danish_ci", False),  # 235
    ("utf8mb4", "utf8mb4_lithuanian_ci", False),  # 236
    ("utf8mb4", "utf8mb4_slovak_ci", False),  # 237
    ("utf8mb4", "utf8mb4_spanish2_ci", False),  # 238
    ("utf8mb4", "utf8mb4_roman_ci", False),  # 239
    ("utf8mb4", "utf8mb4_persian_ci", False),  # 240
    ("utf8mb4", "utf8mb4_esperanto_ci", False),  # 241
    ("utf8mb4", "utf8mb4_hungarian_ci", False),  # 242
    ("utf8mb4", "utf8mb4_sinhala_ci", False),  # 243
    ("utf8mb4", "utf8mb4_german2_ci", False),  # 244
    ("utf8mb4", "utf8mb4_croatian_ci", False),  # 245
    ("utf8mb4", "utf8mb4_unicode_520_ci", False),  # 246
    ("utf8mb4", "utf8mb4_vietnamese_ci", False),  # 247
    ("gb18030", "gb18030_chinese_ci", True),  # 248
    ("gb18030", "gb18030_bin", False),  # 249
    ("gb18030", "gb18030_unicode_520_ci", False),  # 250
    None,
    None,
    None,
    None,
    ("utf8mb4", "utf8mb4_0900_ai_ci", True),  # 255
    ("utf8mb4", "utf8mb4_de_pb_0900_ai_ci", False),  # 256
    ("utf8mb4", "utf8mb4_is_0900_ai_ci", False),  # 257
    ("utf8mb4", "utf8mb4_lv_0900_ai_ci", False),  # 258
    ("utf8mb4", "utf8mb4_ro_0900_ai_ci", False),  # 259
    ("utf8mb4", "utf8mb4_sl_0900_ai_ci", False),  # 260
    ("utf8mb4", "utf8mb4_pl_0900_ai_ci", False),  # 261
    ("utf8mb4", "utf8mb4_et_0900_ai_ci", False),  # 262
    ("utf8mb4", "utf8mb4_es_0900_ai_ci", False),  # 263
    ("utf8mb4", "utf8mb4_sv_0900_ai_ci", False),  # 264
    ("utf8mb4", "utf8mb4_tr_0900_ai_ci", False),  # 265
    ("utf8mb4", "utf8mb4_cs_0900_ai_ci", False),  # 266
    ("utf8mb4", "utf8mb4_da_0900_ai_ci", False),  # 267
    ("utf8mb4", "utf8mb4_lt_0900_ai_ci", False),  # 268
    ("utf8mb4", "utf8mb4_sk_0900_ai_ci", False),  # 269
    ("utf8mb4", "utf8mb4_es_trad_0900_ai_ci", False),  # 270
    ("utf8mb4", "utf8mb4_la_0900_ai_ci", False),  # 271
    None,
    ("utf8mb4", "utf8mb4_eo_0900_ai_ci", False),  # 273
    ("utf8mb4", "utf8mb4_hu_0900_ai_ci", False),  # 274
    ("utf8mb4", "utf8mb4_hr_0900_ai_ci", False),  # 275
    None,
    ("utf8mb4", "utf8mb4_vi_0900_ai_ci", False),  # 277
    ("utf8mb4", "utf8mb4_0900_as_cs", False),  # 278
    ("utf8mb4", "utf8mb4_de_pb_0900_as_cs", False),  # 279
    ("utf8mb4", "utf8mb4_is_0900_as_cs", False),  # 280
    ("utf8mb4", "utf8mb4_lv_0900_as_cs", False),  # 281
    ("utf8mb4", "utf8mb4_ro_0900_as_cs", False),  # 282
    ("utf8mb4", "utf8mb4_sl_0900_as_cs", False),  # 283
    ("utf8mb4", "utf8mb4_pl_0900_as_cs", False),  # 284
    ("utf8mb4", "utf8mb4_et_0900_as_cs", False),  # 285
    ("utf8mb4", "utf8mb4_es_0900_as_cs", False),  # 286
    ("utf8mb4", "utf8mb4_sv_0900_as_cs", False),  # 287
    ("utf8mb4", "utf8mb4_tr_0900_as_cs", False),  # 288
    ("utf8mb4", "utf8mb4_cs_0900_as_cs", False),  # 289
    ("utf8mb4", "utf8mb4_da_0900_as_cs", False),  # 290
    ("utf8mb4", "utf8mb4_lt_0900_as_cs", False),  # 291
    ("utf8mb4", "utf8mb4_sk_0900_as_cs", False),  # 292
    ("utf8mb4", "utf8mb4_es_trad_0900_as_cs", False),  # 293
    ("utf8mb4", "utf8mb4_la_0900_as_cs", False),  # 294
    None,
    ("utf8mb4", "utf8mb4_eo_0900_as_cs", False),  # 296
    ("utf8mb4", "utf8mb4_hu_0900_as_cs", False),  # 297
    ("utf8mb4", "utf8mb4_hr_0900_as_cs", False),  # 298
    None,
    ("utf8mb4", "utf8mb4_vi_0900_as_cs", False),  # 300
    None,
    None,
    ("utf8mb4", "utf8mb4_ja_0900_as_cs", False),  # 303
    ("utf8mb4", "utf8mb4_ja_0900_as_cs_ks", False),  # 304
    ("utf8mb4", "utf8mb4_0900_as_ci", False),  # 305
    ("utf8mb4", "utf8mb4_ru_0900_ai_ci", False),  # 306
    ("utf8mb4", "utf8mb4_ru_0900_as_cs", False),  # 307
    ("utf8mb4", "utf8mb4_zh_0900_as_cs", False),  # 308
    ("utf8mb4", "utf8mb4_0900_bin", False),  # 309
    ("utf8mb4", "utf8mb4_nb_0900_ai_ci", False),  # 310
    ("utf8mb4", "utf8mb4_nb_0900_as_cs", False),  # 311
    ("utf8mb4", "utf8mb4_nn_0900_ai_ci", False),  # 312
    ("utf8mb4", "utf8mb4_nn_0900_as_cs", False),  # 313
    ("utf8mb4", "utf8mb4_sr_latn_0900_ai_ci", False),  # 314
    ("utf8mb4", "utf8mb4_sr_latn_0900_as_cs", False),  # 315
    ("utf8mb4", "utf8mb4_bs_0900_ai_ci", False),  # 316
    ("utf8mb4", "utf8mb4_bs_0900_as_cs", False),  # 317
    ("utf8mb4", "utf8mb4_bg_0900_ai_ci", False),  # 318
    ("utf8mb4", "utf8mb4_bg_0900_as_cs", False),  # 319
    ("utf8mb4", "utf8mb4_gl_0900_ai_ci", False),  # 320
    ("utf8mb4", "utf8mb4_gl_0900_as_cs", False),  # 321
    ("utf8mb4", "utf8mb4_mn_cyrl_0900_ai_ci", False),  # 322
    ("utf8mb4", "utf8mb4_mn_cyrl_0900_as_cs", False),  # 323
]

MYSQL_CHARACTER_SETS_57: List[Optional[Tuple[str, str, bool]]] = [
    # (character set name, collation, default)
    None,
    ("big5", "big5_chinese_ci", True),  # 1
    ("latin2", "latin2_czech_cs", False),  # 2
    ("dec8", "dec8_swedish_ci", True),  # 3
    ("cp850", "cp850_general_ci", True),  # 4
    ("latin1", "latin1_german1_ci", False),  # 5
    ("hp8", "hp8_english_ci", True),  # 6
    ("koi8r", "koi8r_general_ci", True),  # 7
    ("latin1", "latin1_swedish_ci", True),  # 8
    ("latin2", "latin2_general_ci", True),  # 9
    ("swe7", "swe7_swedish_ci", True),  # 10
    ("ascii", "ascii_general_ci", True),  # 11
    ("ujis", "ujis_japanese_ci", True),  # 12
    ("sjis", "sjis_japanese_ci", True),  # 13
    ("cp1251", "cp1251_bulgarian_ci", False),  # 14
    ("latin1", "latin1_danish_ci", False),  # 15
    ("hebrew", "hebrew_general_ci", True),  # 16
    None,
    ("tis620", "tis620_thai_ci", True),  # 18
    ("euckr", "euckr_korean_ci", True),  # 19
    ("latin7", "latin7_estonian_cs", False),  # 20
    ("latin2", "latin2_hungarian_ci", False),  # 21
    ("koi8u", "koi8u_general_ci", True),  # 22
    ("cp1251", "cp1251_ukrainian_ci", False),  # 23
    ("gb2312", "gb2312_chinese_ci", True),  # 24
    ("greek", "greek_general_ci", True),  # 25
    ("cp1250", "cp1250_general_ci", True),  # 26
    ("latin2", "latin2_croatian_ci", False),  # 27
    ("gbk", "gbk_chinese_ci", True),  # 28
    ("cp1257", "cp1257_lithuanian_ci", False),  # 29
    ("latin5", "latin5_turkish_ci", True),  # 30
    ("latin1", "latin1_german2_ci", False),  # 31
    ("armscii8", "armscii8_general_ci", True),  # 32
    ("utf8", "utf8_general_ci", True),  # 33
    ("cp1250", "cp1250_czech_cs", False),  # 34
    ("ucs2", "ucs2_general_ci", True),  # 35
    ("cp866", "cp866_general_ci", True),  # 36
    ("keybcs2", "keybcs2_general_ci", True),  # 37
    ("macce", "macce_general_ci", True),  # 38
    ("macroman", "macroman_general_ci", True),  # 39
    ("cp852", "cp852_general_ci", True),  # 40
    ("latin7", "latin7_general_ci", True),  # 41
    ("latin7", "latin7_general_cs", False),  # 42
    ("macce", "macce_bin", False),  # 43
    ("cp1250", "cp1250_croatian_ci", False),  # 44
    ("utf8mb4", "utf8mb4_general_ci", True),  # 45
    ("utf8mb4", "utf8mb4_bin", False),  # 46
    ("latin1", "latin1_bin", False),  # 47
    ("latin1", "latin1_general_ci", False),  # 48
    ("latin1", "latin1_general_cs", False),  # 49
    ("cp1251", "cp1251_bin", False),  # 50
    ("cp1251", "cp1251_general_ci", True),  # 51
    ("cp1251", "cp1251_general_cs", False),  # 52
    ("macroman", "macroman_bin", False),  # 53
    ("utf16", "utf16_general_ci", True),  # 54
    ("utf16", "utf16_bin", False),  # 55
    ("utf16le", "utf16le_general_ci", True),  # 56
    ("cp1256", "cp1256_general_ci", True),  # 57
    ("cp1257", "cp1257_bin", False),  # 58
    ("cp1257", "cp1257_general_ci", True),  # 59
    ("utf32", "utf32_general_ci", True),  # 60
    ("utf32", "utf32_bin", False),  # 61
    ("utf16le", "utf16le_bin", False),  # 62
    ("binary", "binary", True),  # 63
    ("armscii8", "armscii8_bin", False),  # 64
    ("ascii", "ascii_bin", False),  # 65
    ("cp1250", "cp1250_bin", False),  # 66
    ("cp1256", "cp1256_bin", False),  # 67
    ("cp866", "cp866_bin", False),  # 68
    ("dec8", "dec8_bin", False),  # 69
    ("greek", "greek_bin", False),  # 70
    ("hebrew", "hebrew_bin", False),  # 71
    ("hp8", "hp8_bin", False),  # 72
    ("keybcs2", "keybcs2_bin", False),  # 73
    ("koi8r", "koi8r_bin", False),  # 74
    ("koi8u", "koi8u_bin", False),  # 75
    None,
    ("latin2", "latin2_bin", False),  # 77
    ("latin5", "latin5_bin", False),  # 78
    ("latin7", "latin7_bin", False),  # 79
    ("cp850", "cp850_bin", False),  # 80
    ("cp852", "cp852_bin", False),  # 81
    ("swe7", "swe7_bin", False),  # 82
    ("utf8", "utf8_bin", False),  # 83
    ("big5", "big5_bin", False),  # 84
    ("euckr", "euckr_bin", False),  # 85
    ("gb2312", "gb2312_bin", False),  # 86
    ("gbk", "gbk_bin", False),  # 87
    ("sjis", "sjis_bin", False),  # 88
    ("tis620", "tis620_bin", False),  # 89
    ("ucs2", "ucs2_bin", False),  # 90
    ("ujis", "ujis_bin", False),  # 91
    ("geostd8", "geostd8_general_ci", True),  # 92
    ("geostd8", "geostd8_bin", False),  # 93
    ("latin1", "latin1_spanish_ci", False),  # 94
    ("cp932", "cp932_japanese_ci", True),  # 95
    ("cp932", "cp932_bin", False),  # 96
    ("eucjpms", "eucjpms_japanese_ci", True),  # 97
    ("eucjpms", "eucjpms_bin", False),  # 98
    ("cp1250", "cp1250_polish_ci", False),  # 99
    None,
    ("utf16", "utf16_unicode_ci", False),  # 101
    ("utf16", "utf16_icelandic_ci", False),  # 102
    ("utf16", "utf16_latvian_ci", False),  # 103
    ("utf16", "utf16_romanian_ci", False),  # 104
    ("utf16", "utf16_slovenian_ci", False),  # 105
    ("utf16", "utf16_polish_ci", False),  # 106
    ("utf16", "utf16_estonian_ci", False),  # 107
    ("utf16", "utf16_spanish_ci", False),  # 108
    ("utf16", "utf16_swedish_ci", False),  # 109
    ("utf16", "utf16_turkish_ci", False),  # 110
    ("utf16", "utf16_czech_ci", False),  # 111
    ("utf16", "utf16_danish_ci", False),  # 112
    ("utf16", "utf16_lithuanian_ci", False),  # 113
    ("utf16", "utf16_slovak_ci", False),  # 114
    ("utf16", "utf16_spanish2_ci", False),  # 115
    ("utf16", "utf16_roman_ci", False),  # 116
    ("utf16", "utf16_persian_ci", False),  # 117
    ("utf16", "utf16_esperanto_ci", False),  # 118
    ("utf16", "utf16_hungarian_ci", False),  # 119
    ("utf16", "utf16_sinhala_ci", False),  # 120
    ("utf16", "utf16_german2_ci", False),  # 121
    ("utf16", "utf16_croatian_ci", False),  # 122
    ("utf16", "utf16_unicode_520_ci", False),  # 123
    ("utf16", "utf16_vietnamese_ci", False),  # 124
    None,
    None,
    None,
    ("ucs2", "ucs2_unicode_ci", False),  # 128
    ("ucs2", "ucs2_icelandic_ci", False),  # 129
    ("ucs2", "ucs2_latvian_ci", False),  # 130
    ("ucs2", "ucs2_romanian_ci", False),  # 131
    ("ucs2", "ucs2_slovenian_ci", False),  # 132
    ("ucs2", "ucs2_polish_ci", False),  # 133
    ("ucs2", "ucs2_estonian_ci", False),  # 134
    ("ucs2", "ucs2_spanish_ci", False),  # 135
    ("ucs2", "ucs2_swedish_ci", False),  # 136
    ("ucs2", "ucs2_turkish_ci", False),  # 137
    ("ucs2", "ucs2_czech_ci", False),  # 138
    ("ucs2", "ucs2_danish_ci", False),  # 139
    ("ucs2", "ucs2_lithuanian_ci", False),  # 140
    ("ucs2", "ucs2_slovak_ci", False),  # 141
    ("ucs2", "ucs2_spanish2_ci", False),  # 142
    ("ucs2", "ucs2_roman_ci", False),  # 143
    ("ucs2", "ucs2_persian_ci", False),  # 144
    ("ucs2", "ucs2_esperanto_ci", False),  # 145
    ("ucs2", "ucs2_hungarian_ci", False),  # 146
    ("ucs2", "ucs2_sinhala_ci", False),  # 147
    ("ucs2", "ucs2_german2_ci", False),  # 148
    ("ucs2", "ucs2_croatian_ci", False),  # 149
    ("ucs2", "ucs2_unicode_520_ci", False),  # 150
    ("ucs2", "ucs2_vietnamese_ci", False),  # 151
    None,
    None,
    None,
    None,
    None,
    None,
    None,
    ("ucs2", "ucs2_general_mysql500_ci", False),  # 159
    ("utf32", "utf32_unicode_ci", False),  # 160
    ("utf32", "utf32_icelandic_ci", False),  # 161
    ("utf32", "utf32_latvian_ci", False),  # 162
    ("utf32", "utf32_romanian_ci", False),  # 163
    ("utf32", "utf32_slovenian_ci", False),  # 164
    ("utf32", "utf32_polish_ci", False),  # 165
    ("utf32", "utf32_estonian_ci", False),  # 166
    ("utf32", "utf32_spanish_ci", False),  # 167
    ("utf32", "utf32_swedish_ci", False),  # 168
    ("utf32", "utf32_turkish_ci", False),  # 169
    ("utf32", "utf32_czech_ci", False),  # 170
    ("utf32", "utf32_danish_ci", False),  # 171
    ("utf32", "utf32_lithuanian_ci", False),  # 172
    ("utf32", "utf32_slovak_ci", False),  # 173
    ("utf32", "utf32_spanish2_ci", False),  # 174
    ("utf32", "utf32_roman_ci", False),  # 175
    ("utf32", "utf32_persian_ci", False),  # 176
    ("utf32", "utf32_esperanto_ci", False),  # 177
    ("utf32", "utf32_hungarian_ci", False),  # 178
    ("utf32", "utf32_sinhala_ci", False),  # 179
    ("utf32", "utf32_german2_ci", False),  # 180
    ("utf32", "utf32_croatian_ci", False),  # 181
    ("utf32", "utf32_unicode_520_ci", False),  # 182
    ("utf32", "utf32_vietnamese_ci", False),  # 183
    None,
    None,
    None,
    None,
    None,
    None,
    None,
    None,
    ("utf8", "utf8_unicode_ci", False),  # 192
    ("utf8", "utf8_icelandic_ci", False),  # 193
    ("utf8", "utf8_latvian_ci", False),  # 194
    ("utf8", "utf8_romanian_ci", False),  # 195
    ("utf8", "utf8_slovenian_ci", False),  # 196
    ("utf8", "utf8_polish_ci", False),  # 197
    ("utf8", "utf8_estonian_ci", False),  # 198
    ("utf8", "utf8_spanish_ci", False),  # 199
    ("utf8", "utf8_swedish_ci", False),  # 200
    ("utf8", "utf8_turkish_ci", False),  # 201
    ("utf8", "utf8_czech_ci", False),  # 202
    ("utf8", "utf8_danish_ci", False),  # 203
    ("utf8", "utf8_lithuanian_ci", False),  # 204
    ("utf8", "utf8_slovak_ci", False),  # 205
    ("utf8", "utf8_spanish2_ci", False),  # 206
    ("utf8", "utf8_roman_ci", False),  # 207
    ("utf8", "utf8_persian_ci", False),  # 208
    ("utf8", "utf8_esperanto_ci", False),  # 209
    ("utf8", "utf8_hungarian_ci", False),  # 210
    ("utf8", "utf8_sinhala_ci", False),  # 211
    ("utf8", "utf8_german2_ci", False),  # 212
    ("utf8", "utf8_croatian_ci", False),  # 213
    ("utf8", "utf8_unicode_520_ci", False),  # 214
    ("utf8", "utf8_vietnamese_ci", False),  # 215
    None,
    None,
    None,
    None,
    None,
    None,
    None,
    ("utf8", "utf8_general_mysql500_ci", False),  # 223
    ("utf8mb4", "utf8mb4_unicode_ci", False),  # 224
    ("utf8mb4", "utf8mb4_icelandic_ci", False),  # 225
    ("utf8mb4", "utf8mb4_latvian_ci", False),  # 226
    ("utf8mb4", "utf8mb4_romanian_ci", False),  # 227
    ("utf8mb4", "utf8mb4_slovenian_ci", False),  # 228
    ("utf8mb4", "utf8mb4_polish_ci", False),  # 229
    ("utf8mb4", "utf8mb4_estonian_ci", False),  # 230
    ("utf8mb4", "utf8mb4_spanish_ci", False),  # 231
    ("utf8mb4", "utf8mb4_swedish_ci", False),  # 232
    ("utf8mb4", "utf8mb4_turkish_ci", False),  # 233
    ("utf8mb4", "utf8mb4_czech_ci", False),  # 234
    ("utf8mb4", "utf8mb4_danish_ci", False),  # 235
    ("utf8mb4", "utf8mb4_lithuanian_ci", False),  # 236
    ("utf8mb4", "utf8mb4_slovak_ci", False),  # 237
    ("utf8mb4", "utf8mb4_spanish2_ci", False),  # 238
    ("utf8mb4", "utf8mb4_roman_ci", False),  # 239
    ("utf8mb4", "utf8mb4_persian_ci", False),  # 240
    ("utf8mb4", "utf8mb4_esperanto_ci", False),  # 241
    ("utf8mb4", "utf8mb4_hungarian_ci", False),  # 242
    ("utf8mb4", "utf8mb4_sinhala_ci", False),  # 243
    ("utf8mb4", "utf8mb4_german2_ci", False),  # 244
    ("utf8mb4", "utf8mb4_croatian_ci", False),  # 245
    ("utf8mb4", "utf8mb4_unicode_520_ci", False),  # 246
    ("utf8mb4", "utf8mb4_vietnamese_ci", False),  # 247
    ("gb18030", "gb18030_chinese_ci", True),  # 248
    ("gb18030", "gb18030_bin", False),  # 249
    ("gb18030", "gb18030_unicode_520_ci", False),  # 250
]

virt/lib/python3.9/site-packages/mysql/connector/charsets 2.py
PrinceDisant/LostInNL
# -*- coding: utf-8 -*- # pylint: disable=missing-module-docstring

# Copyright (c) 2013, 2022, Oracle and/or its affiliates. All rights reserved.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

from typing import List, Optional, Tuple

"""This module contains the MySQL Server Character Sets."""  # pylint: disable=pointless-string-statement

# This file was auto-generated.
_GENERATED_ON: str = "2022-05-09"
_MYSQL_VERSION: Tuple[int, int, int] = (8, 0, 30)

MYSQL_CHARACTER_SETS: List[Optional[Tuple[str, str, bool]]] = [
    # (character set name, collation, default)
    None,
    ("big5", "big5_chinese_ci", True),  # 1
    ("latin2", "latin2_czech_cs", False),  # 2
    ("dec8", "dec8_swedish_ci", True),  # 3
    ("cp850", "cp850_general_ci", True),  # 4
    ("latin1", "latin1_german1_ci", False),  # 5
    ("hp8", "hp8_english_ci", True),  # 6
    ("koi8r", "koi8r_general_ci", True),  # 7
    ("latin1", "latin1_swedish_ci", True),  # 8
    ("latin2", "latin2_general_ci", True),  # 9
    ("swe7", "swe7_swedish_ci", True),  # 10
    ("ascii", "ascii_general_ci", True),  # 11
    ("ujis", "ujis_japanese_ci", True),  # 12
    ("sjis", "sjis_japanese_ci", True),  # 13
    ("cp1251", "cp1251_bulgarian_ci", False),  # 14
    ("latin1", "latin1_danish_ci", False),  # 15
    ("hebrew", "hebrew_general_ci", True),  # 16
    None,
    ("tis620", "tis620_thai_ci", True),  # 18
    ("euckr", "euckr_korean_ci", True),  # 19
    ("latin7", "latin7_estonian_cs", False),  # 20
    ("latin2", "latin2_hungarian_ci", False),  # 21
    ("koi8u", "koi8u_general_ci", True),  # 22
    ("cp1251", "cp1251_ukrainian_ci", False),  # 23
    ("gb2312", "gb2312_chinese_ci", True),  # 24
    ("greek", "greek_general_ci", True),  # 25
    ("cp1250", "cp1250_general_ci", True),  # 26
    ("latin2", "latin2_croatian_ci", False),  # 27
    ("gbk", "gbk_chinese_ci", True),  # 28
    ("cp1257", "cp1257_lithuanian_ci", False),  # 29
    ("latin5", "latin5_turkish_ci", True),  # 30
    ("latin1", "latin1_german2_ci", False),  # 31
    ("armscii8", "armscii8_general_ci", True),  # 32
    ("utf8mb3", "utf8mb3_general_ci", True),  # 33
    ("cp1250", "cp1250_czech_cs", False),  # 34
    ("ucs2", "ucs2_general_ci", True),  # 35
    ("cp866", "cp866_general_ci", True),  # 36
    ("keybcs2", "keybcs2_general_ci", True),  # 37
    ("macce", "macce_general_ci", True),  # 38
    ("macroman", "macroman_general_ci", True),  # 39
    ("cp852", "cp852_general_ci", True),  # 40
    ("latin7", "latin7_general_ci", True),  # 41
    ("latin7", "latin7_general_cs", False),  # 42
    ("macce", "macce_bin", False),  # 43
    ("cp1250", "cp1250_croatian_ci", False),  # 44
    ("utf8mb4", "utf8mb4_general_ci", False),  # 45
    ("utf8mb4", "utf8mb4_bin", False),  # 46
    ("latin1", "latin1_bin", False),  # 47
    ("latin1", "latin1_general_ci", False),  # 48
    ("latin1", "latin1_general_cs", False),  # 49
    ("cp1251", "cp1251_bin", False),  # 50
    ("cp1251", "cp1251_general_ci", True),  # 51
    ("cp1251", "cp1251_general_cs", False),  # 52
    ("macroman", "macroman_bin", False),  # 53
    ("utf16", "utf16_general_ci", True),  # 54
    ("utf16", "utf16_bin", False),  # 55
    ("utf16le", "utf16le_general_ci", True),  # 56
    ("cp1256", "cp1256_general_ci", True),  # 57
    ("cp1257", "cp1257_bin", False),  # 58
    ("cp1257", "cp1257_general_ci", True),  # 59
    ("utf32", "utf32_general_ci", True),  # 60
    ("utf32", "utf32_bin", False),  # 61
    ("utf16le", "utf16le_bin", False),  # 62
    ("binary", "binary", True),  # 63
    ("armscii8", "armscii8_bin", False),  # 64
    ("ascii", "ascii_bin", False),  # 65
    ("cp1250", "cp1250_bin", False),  # 66
    ("cp1256", "cp1256_bin", False),  # 67
    ("cp866", "cp866_bin", False),  # 68
    ("dec8", "dec8_bin", False),  # 69
    ("greek", "greek_bin", False),  # 70
    ("hebrew", "hebrew_bin", False),  # 71
    ("hp8", "hp8_bin", False),  # 72
    ("keybcs2", "keybcs2_bin", False),  # 73
    ("koi8r", "koi8r_bin", False),  # 74
    ("koi8u", "koi8u_bin", False),  # 75
    ("utf8mb3", "utf8mb3_tolower_ci", False),  # 76
    ("latin2", "latin2_bin", False),  # 77
    ("latin5", "latin5_bin", False),  # 78
    ("latin7", "latin7_bin", False),  # 79
    ("cp850", "cp850_bin", False),  # 80
    ("cp852", "cp852_bin", False),  # 81
    ("swe7", "swe7_bin", False),  # 82
    ("utf8mb3", "utf8mb3_bin", False),  # 83
    ("big5", "big5_bin", False),  # 84
    ("euckr", "euckr_bin", False),  # 85
    ("gb2312", "gb2312_bin", False),  # 86
    ("gbk", "gbk_bin", False),  # 87
    ("sjis", "sjis_bin", False),  # 88
    ("tis620", "tis620_bin", False),  # 89
    ("ucs2", "ucs2_bin", False),  # 90
    ("ujis", "ujis_bin", False),  # 91
    ("geostd8", "geostd8_general_ci", True),  # 92
    ("geostd8", "geostd8_bin", False),  # 93
    ("latin1", "latin1_spanish_ci", False),  # 94
    ("cp932", "cp932_japanese_ci", True),  # 95
    ("cp932", "cp932_bin", False),  # 96
    ("eucjpms", "eucjpms_japanese_ci", True),  # 97
    ("eucjpms", "eucjpms_bin", False),  # 98
    ("cp1250", "cp1250_polish_ci", False),  # 99
    None,
    ("utf16", "utf16_unicode_ci", False),  # 101
    ("utf16", "utf16_icelandic_ci", False),  # 102
    ("utf16", "utf16_latvian_ci", False),  # 103
    ("utf16", "utf16_romanian_ci", False),  # 104
    ("utf16", "utf16_slovenian_ci", False),  # 105
    ("utf16", "utf16_polish_ci", False),  # 106
    ("utf16", "utf16_estonian_ci", False),  # 107
    ("utf16", "utf16_spanish_ci", False),  # 108
    ("utf16", "utf16_swedish_ci", False),  # 109
    ("utf16", "utf16_turkish_ci", False),  # 110
    ("utf16", "utf16_czech_ci", False),  # 111
    ("utf16", "utf16_danish_ci", False),  # 112
    ("utf16", "utf16_lithuanian_ci", False),  # 113
    ("utf16", "utf16_slovak_ci", False),  # 114
    ("utf16", "utf16_spanish2_ci", False),  # 115
    ("utf16", "utf16_roman_ci", False),  # 116
    ("utf16", "utf16_persian_ci", False),  # 117
    ("utf16", "utf16_esperanto_ci", False),  # 118
    ("utf16", "utf16_hungarian_ci", False),  # 119
    ("utf16", "utf16_sinhala_ci", False),  # 120
    ("utf16", "utf16_german2_ci", False),  # 121
    ("utf16", "utf16_croatian_ci", False),  # 122
    ("utf16", "utf16_unicode_520_ci", False),  # 123
    ("utf16", "utf16_vietnamese_ci", False),  # 124
    None,
    None,
    None,
    ("ucs2", "ucs2_unicode_ci", False),  # 128
    ("ucs2", "ucs2_icelandic_ci", False),  # 129
    ("ucs2", "ucs2_latvian_ci", False),  # 130
    ("ucs2", "ucs2_romanian_ci", False),  # 131
    ("ucs2", "ucs2_slovenian_ci", False),  # 132
    ("ucs2", "ucs2_polish_ci", False),  # 133
    ("ucs2", "ucs2_estonian_ci", False),  # 134
    ("ucs2", "ucs2_spanish_ci", False),  # 135
    ("ucs2", "ucs2_swedish_ci", False),  # 136
    ("ucs2", "ucs2_turkish_ci", False),  # 137
    ("ucs2", "ucs2_czech_ci", False),  # 138
    ("ucs2", "ucs2_danish_ci", False),  # 139
    ("ucs2", "ucs2_lithuanian_ci", False),  # 140
    ("ucs2", "ucs2_slovak_ci", False),  # 141
    ("ucs2", "ucs2_spanish2_ci", False),  # 142
    ("ucs2", "ucs2_roman_ci", False),  # 143
    ("ucs2", "ucs2_persian_ci", False),  # 144
    ("ucs2", "ucs2_esperanto_ci", False),  # 145
    ("ucs2", "ucs2_hungarian_ci", False),  # 146
    ("ucs2", "ucs2_sinhala_ci", False),  # 147
    ("ucs2", "ucs2_german2_ci", False),  # 148
    ("ucs2", "ucs2_croatian_ci", False),  # 149
    ("ucs2", "ucs2_unicode_520_ci", False),  # 150
    ("ucs2", "ucs2_vietnamese_ci", False),  # 151
    None,
    None,
    None,
    None,
    None,
    None,
    None,
    ("ucs2", "ucs2_general_mysql500_ci", False),  # 159
    ("utf32", "utf32_unicode_ci", False),  # 160
    ("utf32", "utf32_icelandic_ci", False),  # 161
    ("utf32", "utf32_latvian_ci", False),  # 162
    ("utf32", "utf32_romanian_ci", False),  # 163
    ("utf32", "utf32_slovenian_ci", False),  # 164
    ("utf32", "utf32_polish_ci", False),  # 165
    ("utf32", "utf32_estonian_ci", False),  # 166
    ("utf32", "utf32_spanish_ci", False),  # 167
    ("utf32", "utf32_swedish_ci", False),  # 168
    ("utf32", "utf32_turkish_ci", False),  # 169
    ("utf32", "utf32_czech_ci", False),  # 170
    ("utf32", "utf32_danish_ci", False),  # 171
    ("utf32", "utf32_lithuanian_ci", False),  # 172
    ("utf32", "utf32_slovak_ci", False),  # 173
    ("utf32", "utf32_spanish2_ci", False),  # 174
    ("utf32", "utf32_roman_ci", False),  # 175
    ("utf32", "utf32_persian_ci", False),  # 176
    ("utf32", "utf32_esperanto_ci", False),  # 177
    ("utf32", "utf32_hungarian_ci", False),  # 178
    ("utf32", "utf32_sinhala_ci", False),  # 179
    ("utf32", "utf32_german2_ci", False),  # 180
    ("utf32", "utf32_croatian_ci", False),  # 181
    ("utf32", "utf32_unicode_520_ci", False),  # 182
    ("utf32", "utf32_vietnamese_ci", False),  # 183
    None,
    None,
    None,
    None,
    None,
    None,
    None,
    None,
    ("utf8mb3", "utf8mb3_unicode_ci", False),  # 192
    ("utf8mb3", "utf8mb3_icelandic_ci", False),  # 193
    ("utf8mb3", "utf8mb3_latvian_ci", False),  # 194
    ("utf8mb3", "utf8mb3_romanian_ci", False),  # 195
    ("utf8mb3", "utf8mb3_slovenian_ci", False),  # 196
    ("utf8mb3", "utf8mb3_polish_ci", False),  # 197
    ("utf8mb3", "utf8mb3_estonian_ci", False),  # 198
    ("utf8mb3", "utf8mb3_spanish_ci", False),  # 199
    ("utf8mb3", "utf8mb3_swedish_ci", False),  # 200
    ("utf8mb3", "utf8mb3_turkish_ci", False),  # 201
    ("utf8mb3", "utf8mb3_czech_ci", False),  # 202
    ("utf8mb3", "utf8mb3_danish_ci", False),  # 203
    ("utf8mb3", "utf8mb3_lithuanian_ci", False),  # 204
    ("utf8mb3", "utf8mb3_slovak_ci", False),  # 205
    ("utf8mb3", "utf8mb3_spanish2_ci", False),  # 206
    ("utf8mb3", "utf8mb3_roman_ci", False),  # 207
    ("utf8mb3", "utf8mb3_persian_ci", False),  # 208
    ("utf8mb3", "utf8mb3_esperanto_ci", False),  # 209
    ("utf8mb3", "utf8mb3_hungarian_ci", False),  # 210
    ("utf8mb3", "utf8mb3_sinhala_ci", False),  # 211
    ("utf8mb3", "utf8mb3_german2_ci", False),  # 212
    ("utf8mb3", "utf8mb3_croatian_ci", False),  # 213
    ("utf8mb3", "utf8mb3_unicode_520_ci", False),  # 214
    ("utf8mb3", "utf8mb3_vietnamese_ci", False),  # 215
    None,
    None,
    None,
    None,
    None,
    None,
    None,
    ("utf8mb3", "utf8mb3_general_mysql500_ci", False),  # 223
    ("utf8mb4", "utf8mb4_unicode_ci", False),  # 224
    ("utf8mb4", "utf8mb4_icelandic_ci", False),  # 225
    ("utf8mb4", "utf8mb4_latvian_ci", False),  # 226
    ("utf8mb4", "utf8mb4_romanian_ci", False),  # 227
    ("utf8mb4", "utf8mb4_slovenian_ci", False),  # 228
    ("utf8mb4", "utf8mb4_polish_ci", False),  # 229
    ("utf8mb4", "utf8mb4_estonian_ci", False),  # 230
    ("utf8mb4", "utf8mb4_spanish_ci", False),  # 231
    ("utf8mb4", "utf8mb4_swedish_ci", False),  # 232
    ("utf8mb4", "utf8mb4_turkish_ci", False),  # 233
    ("utf8mb4", "utf8mb4_czech_ci", False),  # 234
    ("utf8mb4", "utf8mb4_danish_ci", False),  # 235
    ("utf8mb4", "utf8mb4_lithuanian_ci", False),  # 236
    ("utf8mb4", "utf8mb4_slovak_ci", False),  # 237
    ("utf8mb4", "utf8mb4_spanish2_ci", False),  # 238
    ("utf8mb4", "utf8mb4_roman_ci", False),  # 239
    ("utf8mb4", "utf8mb4_persian_ci", False),  # 240
    ("utf8mb4", "utf8mb4_esperanto_ci", False),  # 241
    ("utf8mb4", "utf8mb4_hungarian_ci", False),  # 242
    ("utf8mb4", "utf8mb4_sinhala_ci", False),  # 243
    ("utf8mb4", "utf8mb4_german2_ci", False),  # 244
    ("utf8mb4", "utf8mb4_croatian_ci", False),  # 245
    ("utf8mb4", "utf8mb4_unicode_520_ci", False),  # 246
    ("utf8mb4", "utf8mb4_vietnamese_ci", False),  # 247
    ("gb18030", "gb18030_chinese_ci", True),  # 248
    ("gb18030", "gb18030_bin", False),  # 249
    ("gb18030", "gb18030_unicode_520_ci", False),  # 250
    None,
    None,
    None,
    None,
    ("utf8mb4", "utf8mb4_0900_ai_ci", True),  # 255
    ("utf8mb4", "utf8mb4_de_pb_0900_ai_ci", False),  # 256
    ("utf8mb4", "utf8mb4_is_0900_ai_ci", False),  # 257
    ("utf8mb4", "utf8mb4_lv_0900_ai_ci", False),  # 258
    ("utf8mb4", "utf8mb4_ro_0900_ai_ci", False),  # 259
    ("utf8mb4", "utf8mb4_sl_0900_ai_ci", False),  # 260
    ("utf8mb4", "utf8mb4_pl_0900_ai_ci", False),  # 261
    ("utf8mb4", "utf8mb4_et_0900_ai_ci", False),  # 262
    ("utf8mb4", "utf8mb4_es_0900_ai_ci", False),  # 263
    ("utf8mb4", "utf8mb4_sv_0900_ai_ci", False),  # 264
    ("utf8mb4", "utf8mb4_tr_0900_ai_ci", False),  # 265
    ("utf8mb4", "utf8mb4_cs_0900_ai_ci", False),  # 266
    ("utf8mb4", "utf8mb4_da_0900_ai_ci", False),  # 267
    ("utf8mb4", "utf8mb4_lt_0900_ai_ci", False),  # 268
    ("utf8mb4", "utf8mb4_sk_0900_ai_ci", False),  # 269
    ("utf8mb4", "utf8mb4_es_trad_0900_ai_ci", False),  # 270
    ("utf8mb4", "utf8mb4_la_0900_ai_ci", False),  # 271
    None,
    ("utf8mb4", "utf8mb4_eo_0900_ai_ci", False),  # 273
    ("utf8mb4", "utf8mb4_hu_0900_ai_ci", False),  # 274
    ("utf8mb4", "utf8mb4_hr_0900_ai_ci", False),  # 275
    None,
    ("utf8mb4", "utf8mb4_vi_0900_ai_ci", False),  # 277
    ("utf8mb4", "utf8mb4_0900_as_cs", False),  # 278
    ("utf8mb4", "utf8mb4_de_pb_0900_as_cs", False),  # 279
    ("utf8mb4", "utf8mb4_is_0900_as_cs", False),  # 280
    ("utf8mb4", "utf8mb4_lv_0900_as_cs", False),  # 281
    ("utf8mb4", "utf8mb4_ro_0900_as_cs", False),  # 282
    ("utf8mb4", "utf8mb4_sl_0900_as_cs", False),  # 283
    ("utf8mb4", "utf8mb4_pl_0900_as_cs", False),  # 284
    ("utf8mb4", "utf8mb4_et_0900_as_cs", False),  # 285
    ("utf8mb4", "utf8mb4_es_0900_as_cs", False),  # 286
    ("utf8mb4", "utf8mb4_sv_0900_as_cs", False),  # 287
    ("utf8mb4", "utf8mb4_tr_0900_as_cs", False),  # 288
    ("utf8mb4", "utf8mb4_cs_0900_as_cs", False),  # 289
    ("utf8mb4", "utf8mb4_da_0900_as_cs", False),  # 290
    ("utf8mb4", "utf8mb4_lt_0900_as_cs", False),  # 291
    ("utf8mb4", "utf8mb4_sk_0900_as_cs", False),  # 292
    ("utf8mb4", "utf8mb4_es_trad_0900_as_cs", False),  # 293
    ("utf8mb4", "utf8mb4_la_0900_as_cs", False),  # 294
    None,
    ("utf8mb4", "utf8mb4_eo_0900_as_cs", False),  # 296
    ("utf8mb4", "utf8mb4_hu_0900_as_cs", False),  # 297
    ("utf8mb4", "utf8mb4_hr_0900_as_cs", False),  # 298
    None,
    ("utf8mb4", "utf8mb4_vi_0900_as_cs", False),  # 300
    None,
    None,
    ("utf8mb4", "utf8mb4_ja_0900_as_cs", False),  # 303
    ("utf8mb4", "utf8mb4_ja_0900_as_cs_ks", False),  # 304
    ("utf8mb4", "utf8mb4_0900_as_ci", False),  # 305
    ("utf8mb4", "utf8mb4_ru_0900_ai_ci", False),  # 306
    ("utf8mb4", "utf8mb4_ru_0900_as_cs", False),  # 307
    ("utf8mb4", "utf8mb4_zh_0900_as_cs", False),  # 308
    ("utf8mb4", "utf8mb4_0900_bin", False),  # 309
    ("utf8mb4", "utf8mb4_nb_0900_ai_ci", False),  # 310
    ("utf8mb4", "utf8mb4_nb_0900_as_cs", False),  # 311
    ("utf8mb4", "utf8mb4_nn_0900_ai_ci", False),  # 312
    ("utf8mb4", "utf8mb4_nn_0900_as_cs", False),  # 313
    ("utf8mb4", "utf8mb4_sr_latn_0900_ai_ci", False),  # 314
    ("utf8mb4", "utf8mb4_sr_latn_0900_as_cs", False),  # 315
    ("utf8mb4", "utf8mb4_bs_0900_ai_ci", False),  # 316
    ("utf8mb4", "utf8mb4_bs_0900_as_cs", False),  # 317
    ("utf8mb4", "utf8mb4_bg_0900_ai_ci", False),  # 318
    ("utf8mb4", "utf8mb4_bg_0900_as_cs", False),  # 319
    ("utf8mb4", "utf8mb4_gl_0900_ai_ci", False),  # 320
    ("utf8mb4", "utf8mb4_gl_0900_as_cs", False),  # 321
    ("utf8mb4", "utf8mb4_mn_cyrl_0900_ai_ci", False),  # 322
    ("utf8mb4", "utf8mb4_mn_cyrl_0900_as_cs", False),  # 323
]

MYSQL_CHARACTER_SETS_57: List[Optional[Tuple[str, str, bool]]] = [
    # (character set name, collation, default)
    None,
    ("big5", "big5_chinese_ci", True),  # 1
    ("latin2", "latin2_czech_cs", False),  # 2
    ("dec8", "dec8_swedish_ci", True),  # 3
    ("cp850", "cp850_general_ci", True),  # 4
    ("latin1", "latin1_german1_ci", False),  # 5
    ("hp8", "hp8_english_ci", True),  # 6
    ("koi8r", "koi8r_general_ci", True),  # 7
    ("latin1", "latin1_swedish_ci", True),  # 8
    ("latin2", "latin2_general_ci", True),  # 9
    ("swe7", "swe7_swedish_ci", True),  # 10
    ("ascii", "ascii_general_ci", True),  # 11
    ("ujis", "ujis_japanese_ci", True),  # 12
    ("sjis", "sjis_japanese_ci", True),  # 13
    ("cp1251", "cp1251_bulgarian_ci", False),  # 14
    ("latin1", "latin1_danish_ci", False),  # 15
    ("hebrew", "hebrew_general_ci", True),  # 16
    None,
    ("tis620", "tis620_thai_ci", True),  # 18
    ("euckr", "euckr_korean_ci", True),  # 19
    ("latin7", "latin7_estonian_cs", False),  # 20
    ("latin2", "latin2_hungarian_ci", False),  # 21
    ("koi8u", "koi8u_general_ci", True),  # 22
    ("cp1251", "cp1251_ukrainian_ci", False),  # 23
    ("gb2312", "gb2312_chinese_ci", True),  # 24
    ("greek", "greek_general_ci", True),  # 25
    ("cp1250", "cp1250_general_ci", True),  # 26
    ("latin2", "latin2_croatian_ci", False),  # 27
    ("gbk", "gbk_chinese_ci", True),  # 28
    ("cp1257", "cp1257_lithuanian_ci", False),  # 29
    ("latin5", "latin5_turkish_ci", True),  # 30
    ("latin1", "latin1_german2_ci", False),  # 31
    ("armscii8", "armscii8_general_ci", True),  # 32
    ("utf8", "utf8_general_ci", True),  # 33
    ("cp1250", "cp1250_czech_cs", False),  # 34
    ("ucs2", "ucs2_general_ci", True),  # 35
    ("cp866", "cp866_general_ci", True),  # 36
    ("keybcs2", "keybcs2_general_ci", True),  # 37
    ("macce", "macce_general_ci", True),  # 38
    ("macroman", "macroman_general_ci", True),  # 39
    ("cp852", "cp852_general_ci", True),  # 40
    ("latin7", "latin7_general_ci", True),  # 41
    ("latin7", "latin7_general_cs", False),  # 42
    ("macce", "macce_bin", False),  # 43
    ("cp1250", "cp1250_croatian_ci", False),  # 44
    ("utf8mb4", "utf8mb4_general_ci", True),  # 45
    ("utf8mb4", "utf8mb4_bin", False),  # 46
    ("latin1", "latin1_bin", False),  # 47
    ("latin1", "latin1_general_ci", False),  # 48
    ("latin1", "latin1_general_cs", False),  # 49
    ("cp1251", "cp1251_bin", False),  # 50
    ("cp1251", "cp1251_general_ci", True),  # 51
    ("cp1251", "cp1251_general_cs", False),  # 52
    ("macroman", "macroman_bin", False),  # 53
    ("utf16", "utf16_general_ci", True),  # 54
    ("utf16", "utf16_bin", False),  # 55
    ("utf16le", "utf16le_general_ci", True),  # 56
    ("cp1256", "cp1256_general_ci", True),  # 57
    ("cp1257", "cp1257_bin", False),  # 58
    ("cp1257", "cp1257_general_ci", True),  # 59
    ("utf32", "utf32_general_ci", True),  # 60
    ("utf32", "utf32_bin", False),  # 61
    ("utf16le", "utf16le_bin", False),  # 62
    ("binary", "binary", True),  # 63
    ("armscii8", "armscii8_bin", False),  # 64
    ("ascii", "ascii_bin", False),  # 65
    ("cp1250", "cp1250_bin", False),  # 66
    ("cp1256", "cp1256_bin", False),  # 67
    ("cp866", "cp866_bin", False),  # 68
    ("dec8", "dec8_bin", False),  # 69
    ("greek", "greek_bin", False),  # 70
    ("hebrew", "hebrew_bin", False),  # 71
    ("hp8", "hp8_bin", False),  # 72
    ("keybcs2", "keybcs2_bin", False),  # 73
    ("koi8r", "koi8r_bin", False),  # 74
    ("koi8u", "koi8u_bin", False),  # 75
    None,
    ("latin2", "latin2_bin", False),  # 77
    ("latin5", "latin5_bin", False),  # 78
    ("latin7", "latin7_bin", False),  # 79
    ("cp850", "cp850_bin", False),  # 80
    ("cp852", "cp852_bin", False),  # 81
    ("swe7", "swe7_bin", False),  # 82
    ("utf8", "utf8_bin", False),  # 83
    ("big5", "big5_bin", False),  # 84
    ("euckr", "euckr_bin", False),  # 85
    ("gb2312", "gb2312_bin", False),  # 86
    ("gbk", "gbk_bin", False),  # 87
    ("sjis", "sjis_bin", False),  # 88
    ("tis620", "tis620_bin", False),  # 89
    ("ucs2", "ucs2_bin", False),  # 90
    ("ujis", "ujis_bin", False),  # 91
    ("geostd8", "geostd8_general_ci", True),  # 92
    ("geostd8", "geostd8_bin", False),  # 93
    ("latin1", "latin1_spanish_ci", False),  # 94
    ("cp932", "cp932_japanese_ci", True),  # 95
    ("cp932", "cp932_bin", False),  # 96
    ("eucjpms", "eucjpms_japanese_ci", True),  # 97
    ("eucjpms", "eucjpms_bin", False),  # 98
    ("cp1250", "cp1250_polish_ci", False),  # 99
    None,
    ("utf16", "utf16_unicode_ci", False),  # 101
    ("utf16", "utf16_icelandic_ci", False),  # 102
    ("utf16", "utf16_latvian_ci", False),  # 103
    ("utf16", "utf16_romanian_ci", False),  # 104
    ("utf16", "utf16_slovenian_ci", False),  # 105
    ("utf16", "utf16_polish_ci", False),  # 106
    ("utf16", "utf16_estonian_ci", False),  # 107
    ("utf16", "utf16_spanish_ci", False),  # 108
    ("utf16", "utf16_swedish_ci", False),  # 109
    ("utf16", "utf16_turkish_ci", False),  # 110
    ("utf16", "utf16_czech_ci", False),  # 111
    ("utf16", "utf16_danish_ci", False),  # 112
    ("utf16", "utf16_lithuanian_ci", False),  # 113
    ("utf16", "utf16_slovak_ci", False),  # 114
    ("utf16", "utf16_spanish2_ci", False),  # 115
    ("utf16", "utf16_roman_ci", False),  # 116
    ("utf16", "utf16_persian_ci", False),  # 117
    ("utf16", "utf16_esperanto_ci", False),  # 118
    ("utf16", "utf16_hungarian_ci", False),  # 119
    ("utf16", "utf16_sinhala_ci", False),  # 120
    ("utf16", "utf16_german2_ci", False),  # 121
    ("utf16", "utf16_croatian_ci", False),  # 122
    ("utf16", "utf16_unicode_520_ci", False),  # 123
    ("utf16", "utf16_vietnamese_ci", False),  # 124
    None,
    None,
    None,
    ("ucs2", "ucs2_unicode_ci", False),  # 128
    ("ucs2", "ucs2_icelandic_ci", False),  # 129
    ("ucs2", "ucs2_latvian_ci", False),  # 130
    ("ucs2", "ucs2_romanian_ci", False),  # 131
    ("ucs2", "ucs2_slovenian_ci", False),  # 132
    ("ucs2", "ucs2_polish_ci", False),  # 133
    ("ucs2", "ucs2_estonian_ci", False),  # 134
    ("ucs2", "ucs2_spanish_ci", False),  # 135
    ("ucs2", "ucs2_swedish_ci", False),  # 136
    ("ucs2", "ucs2_turkish_ci", False),  # 137
    ("ucs2", "ucs2_czech_ci", False),  # 138
    ("ucs2", "ucs2_danish_ci", False),  # 139
    ("ucs2", "ucs2_lithuanian_ci", False),  # 140
    ("ucs2", "ucs2_slovak_ci", False),  # 141
    ("ucs2", "ucs2_spanish2_ci", False),  # 142
    ("ucs2", "ucs2_roman_ci", False),  # 143
    ("ucs2", "ucs2_persian_ci", False),  # 144
    ("ucs2", "ucs2_esperanto_ci", False),  # 145
    ("ucs2", "ucs2_hungarian_ci", False),  # 146
    ("ucs2", "ucs2_sinhala_ci", False),  # 147
    ("ucs2", "ucs2_german2_ci", False),  # 148
    ("ucs2", "ucs2_croatian_ci", False),  # 149
    ("ucs2", "ucs2_unicode_520_ci", False),  # 150
    ("ucs2", "ucs2_vietnamese_ci", False),  # 151
    None,
    None,
    None,
    None,
    None,
    None,
    None,
    ("ucs2", "ucs2_general_mysql500_ci", False),  # 159
    ("utf32", "utf32_unicode_ci", False),  # 160
    ("utf32", "utf32_icelandic_ci", False),  # 161
    ("utf32", "utf32_latvian_ci", False),  # 162
    ("utf32", "utf32_romanian_ci", False),  # 163
    ("utf32", "utf32_slovenian_ci", False),  # 164
    ("utf32", "utf32_polish_ci", False),  # 165
    ("utf32", "utf32_estonian_ci", False),  # 166
    ("utf32", "utf32_spanish_ci", False),  # 167
    ("utf32", "utf32_swedish_ci", False),  # 168
    ("utf32", "utf32_turkish_ci", False),  # 169
    ("utf32", "utf32_czech_ci", False),  # 170
    ("utf32", "utf32_danish_ci", False),  # 171
    ("utf32", "utf32_lithuanian_ci", False),  # 172
    ("utf32", "utf32_slovak_ci", False),  # 173
    ("utf32", "utf32_spanish2_ci", False),  # 174
    ("utf32", "utf32_roman_ci", False),  # 175
    ("utf32", "utf32_persian_ci", False),  # 176
    ("utf32", "utf32_esperanto_ci", False),  # 177
    ("utf32", "utf32_hungarian_ci", False),  # 178
    ("utf32", "utf32_sinhala_ci", False),  # 179
    ("utf32", "utf32_german2_ci", False),  # 180
    ("utf32", "utf32_croatian_ci", False),  # 181
    ("utf32", "utf32_unicode_520_ci", False),  # 182
    ("utf32", "utf32_vietnamese_ci", False),  # 183
    None,
    None,
    None,
    None,
    None,
    None,
    None,
    None,
    ("utf8", "utf8_unicode_ci", False),  # 192
    ("utf8", "utf8_icelandic_ci", False),  # 193
    ("utf8", "utf8_latvian_ci", False),  # 194
    ("utf8", "utf8_romanian_ci", False),  # 195
    ("utf8", "utf8_slovenian_ci", False),  # 196
    ("utf8", "utf8_polish_ci", False),  # 197
    ("utf8", "utf8_estonian_ci", False),  # 198
    ("utf8", "utf8_spanish_ci", False),  # 199
    ("utf8", "utf8_swedish_ci", False),  # 200
    ("utf8", "utf8_turkish_ci", False),  # 201
    ("utf8", "utf8_czech_ci", False),  # 202
    ("utf8", "utf8_danish_ci", False),  # 203
    ("utf8", "utf8_lithuanian_ci", False),  # 204
    ("utf8", "utf8_slovak_ci", False),  # 205
    ("utf8", "utf8_spanish2_ci", False),  # 206
    ("utf8", "utf8_roman_ci", False),  # 207
    ("utf8", "utf8_persian_ci", False),  # 208
    ("utf8", "utf8_esperanto_ci", False),  # 209
    ("utf8", "utf8_hungarian_ci", False),  # 210
    ("utf8", "utf8_sinhala_ci", False),  # 211
    ("utf8", "utf8_german2_ci", False),  # 212
    ("utf8", "utf8_croatian_ci", False),  # 213
    ("utf8", "utf8_unicode_520_ci", False),  # 214
    ("utf8", "utf8_vietnamese_ci", False),  # 215
    None,
    None,
    None,
    None,
    None,
    None,
    None,
    ("utf8", "utf8_general_mysql500_ci", False),  # 223
    ("utf8mb4", "utf8mb4_unicode_ci", False),  # 224
    ("utf8mb4", "utf8mb4_icelandic_ci", False),  # 225
    ("utf8mb4", "utf8mb4_latvian_ci", False),  # 226
    ("utf8mb4", "utf8mb4_romanian_ci", False),  # 227
    ("utf8mb4", "utf8mb4_slovenian_ci", False),  # 228
    ("utf8mb4", "utf8mb4_polish_ci", False),  # 229
    ("utf8mb4", "utf8mb4_estonian_ci", False),  # 230
    ("utf8mb4", "utf8mb4_spanish_ci", False),  # 231
    ("utf8mb4", "utf8mb4_swedish_ci", False),  # 232
    ("utf8mb4", "utf8mb4_turkish_ci", False),  # 233
    ("utf8mb4", "utf8mb4_czech_ci", False),  # 234
    ("utf8mb4", "utf8mb4_danish_ci", False),  # 235
    ("utf8mb4", "utf8mb4_lithuanian_ci", False),  # 236
    ("utf8mb4", "utf8mb4_slovak_ci", False),  # 237
    ("utf8mb4", "utf8mb4_spanish2_ci", False),  # 238
    ("utf8mb4", "utf8mb4_roman_ci", False),  # 239
    ("utf8mb4", "utf8mb4_persian_ci", False),  # 240
    ("utf8mb4", "utf8mb4_esperanto_ci", False),  # 241
    ("utf8mb4", "utf8mb4_hungarian_ci", False),  # 242
    ("utf8mb4", "utf8mb4_sinhala_ci", False),  # 243
    ("utf8mb4", "utf8mb4_german2_ci", False),  # 244
    ("utf8mb4", "utf8mb4_croatian_ci", False),  # 245
    ("utf8mb4", "utf8mb4_unicode_520_ci", False),  # 246
    ("utf8mb4", "utf8mb4_vietnamese_ci", False),  # 247
    ("gb18030", "gb18030_chinese_ci", True),  # 248
    ("gb18030", "gb18030_bin", False),  # 249
    ("gb18030", "gb18030_unicode_520_ci", False),  # 250
]

virt/lib/python3.9/site-packages/mysql/connector/charsets 3.py
PrinceDisant/LostInNL
# -*- coding: utf-8 -*- # pylint: disable=missing-module-docstring

# Copyright (c) 2013, 2022, Oracle and/or its affiliates. All rights reserved.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

from typing import List, Optional, Tuple

"""This module contains the MySQL Server Character Sets."""  # pylint: disable=pointless-string-statement

# This file was auto-generated.
_GENERATED_ON: str = "2022-05-09"
_MYSQL_VERSION: Tuple[int, int, int] = (8, 0, 30)

MYSQL_CHARACTER_SETS: List[Optional[Tuple[str, str, bool]]] = [
    # (character set name, collation, default)
    None,
    ("big5", "big5_chinese_ci", True),  # 1
    ("latin2", "latin2_czech_cs", False),  # 2
    ("dec8", "dec8_swedish_ci", True),  # 3
    ("cp850", "cp850_general_ci", True),  # 4
    ("latin1", "latin1_german1_ci", False),  # 5
    ("hp8", "hp8_english_ci", True),  # 6
    ("koi8r", "koi8r_general_ci", True),  # 7
    ("latin1", "latin1_swedish_ci", True),  # 8
    ("latin2", "latin2_general_ci", True),  # 9
    ("swe7", "swe7_swedish_ci", True),  # 10
    ("ascii", "ascii_general_ci", True),  # 11
    ("ujis", "ujis_japanese_ci", True),  # 12
    ("sjis", "sjis_japanese_ci", True),  # 13
    ("cp1251", "cp1251_bulgarian_ci", False),  # 14
    ("latin1", "latin1_danish_ci", False),  # 15
    ("hebrew", "hebrew_general_ci", True),  # 16
    None,
    ("tis620", "tis620_thai_ci", True),  # 18
    ("euckr", "euckr_korean_ci", True),  # 19
    ("latin7", "latin7_estonian_cs", False),  # 20
    ("latin2", "latin2_hungarian_ci", False),  # 21
    ("koi8u", "koi8u_general_ci", True),  # 22
    ("cp1251", "cp1251_ukrainian_ci", False),  # 23
    ("gb2312", "gb2312_chinese_ci", True),  # 24
    ("greek", "greek_general_ci", True),  # 25
    ("cp1250", "cp1250_general_ci", True),  # 26
    ("latin2", "latin2_croatian_ci", False),  # 27
    ("gbk", "gbk_chinese_ci", True),  # 28
    ("cp1257", "cp1257_lithuanian_ci", False),  # 29
    ("latin5", "latin5_turkish_ci", True),  # 30
    ("latin1", "latin1_german2_ci", False),  # 31
    ("armscii8", "armscii8_general_ci", True),  # 32
    ("utf8mb3", "utf8mb3_general_ci", True),  # 33
    ("cp1250", "cp1250_czech_cs", False),  # 34
    ("ucs2", "ucs2_general_ci", True),  # 35
    ("cp866", "cp866_general_ci", True),  # 36
    ("keybcs2", "keybcs2_general_ci", True),  # 37
    ("macce", "macce_general_ci", True),  # 38
    ("macroman", "macroman_general_ci", True),  # 39
    ("cp852", "cp852_general_ci", True),  # 40
    ("latin7", "latin7_general_ci", True),  # 41
    ("latin7", "latin7_general_cs", False),  # 42
    ("macce", "macce_bin", False),  # 43
    ("cp1250", "cp1250_croatian_ci", False),  # 44
    ("utf8mb4", "utf8mb4_general_ci", False),  # 45
    ("utf8mb4", "utf8mb4_bin", False),  # 46
    ("latin1", "latin1_bin", False),  # 47
    ("latin1", "latin1_general_ci", False),  # 48
    ("latin1", "latin1_general_cs", False),  # 49
    ("cp1251", "cp1251_bin", False),  # 50
    ("cp1251", "cp1251_general_ci", True),  # 51
    ("cp1251", "cp1251_general_cs", False),  # 52
    ("macroman", "macroman_bin", False),  # 53
    ("utf16", "utf16_general_ci", True),  # 54
    ("utf16", "utf16_bin", False),  # 55
    ("utf16le", "utf16le_general_ci", True),  # 56
    ("cp1256", "cp1256_general_ci", True),  # 57
    ("cp1257", "cp1257_bin", False),  # 58
    ("cp1257", "cp1257_general_ci", True),  # 59
    ("utf32", "utf32_general_ci", True),  # 60
    ("utf32", "utf32_bin", False),  # 61
    ("utf16le", "utf16le_bin", False),  # 62
    ("binary", "binary", True),  # 63
    ("armscii8", "armscii8_bin", False),  # 64
    ("ascii", "ascii_bin", False),  # 65
    ("cp1250", "cp1250_bin", False),  # 66
    ("cp1256", "cp1256_bin", False),  # 67
    ("cp866", "cp866_bin", False),  # 68
    ("dec8", "dec8_bin", False),  # 69
    ("greek", "greek_bin", False),  # 70
    ("hebrew", "hebrew_bin", False),  # 71
    ("hp8", "hp8_bin", False),  # 72
    ("keybcs2", "keybcs2_bin", False),  # 73
    ("koi8r", "koi8r_bin", False),  # 74
    ("koi8u", "koi8u_bin", False),  # 75
    ("utf8mb3", "utf8mb3_tolower_ci", False),  # 76
    ("latin2", "latin2_bin", False),  # 77
    ("latin5", "latin5_bin", False),  # 78
    ("latin7", "latin7_bin", False),  # 79
    ("cp850", "cp850_bin", False),  # 80
    ("cp852", "cp852_bin", False),  # 81
    ("swe7", "swe7_bin", False),  # 82
    ("utf8mb3", "utf8mb3_bin", False),  # 83
    ("big5", "big5_bin", False),  # 84
    ("euckr", "euckr_bin", False),  # 85
    ("gb2312", "gb2312_bin", False),  # 86
    ("gbk", "gbk_bin", False),  # 87
    ("sjis", "sjis_bin", False),  # 88
    ("tis620", "tis620_bin", False),  # 89
    ("ucs2", "ucs2_bin", False),  # 90
    ("ujis", "ujis_bin", False),  # 91
    ("geostd8", "geostd8_general_ci", True),  # 92
    ("geostd8", "geostd8_bin", False),  # 93
    ("latin1", "latin1_spanish_ci", False),  # 94
    ("cp932", "cp932_japanese_ci", True),  # 95
    ("cp932", "cp932_bin", False),  # 96
    ("eucjpms", "eucjpms_japanese_ci", True),  # 97
    ("eucjpms", "eucjpms_bin", False),  # 98
    ("cp1250", "cp1250_polish_ci", False),  # 99
    None,
    ("utf16", "utf16_unicode_ci", False),  # 101
    ("utf16", "utf16_icelandic_ci", False),  # 102
    ("utf16", "utf16_latvian_ci", False),  # 103
    ("utf16", "utf16_romanian_ci", False),  # 104
    ("utf16", "utf16_slovenian_ci", False),  # 105
    ("utf16", "utf16_polish_ci", False),  # 106
    ("utf16", "utf16_estonian_ci", False),  # 107
    ("utf16", "utf16_spanish_ci", False),  # 108
    ("utf16", "utf16_swedish_ci", False),  # 109
    ("utf16", "utf16_turkish_ci", False),  # 110
    ("utf16", "utf16_czech_ci", False),  # 111
    ("utf16", "utf16_danish_ci", False),  # 112
    ("utf16", "utf16_lithuanian_ci", False),  # 113
    ("utf16", "utf16_slovak_ci", False),  # 114
    ("utf16", "utf16_spanish2_ci", False),  # 115
    ("utf16", "utf16_roman_ci", False),  # 116
    ("utf16", "utf16_persian_ci", False),  # 117
    ("utf16", "utf16_esperanto_ci", False),  # 118
    ("utf16", "utf16_hungarian_ci", False),  # 119
    ("utf16", "utf16_sinhala_ci", False),  # 120
    ("utf16", "utf16_german2_ci", False),  # 121
    ("utf16", "utf16_croatian_ci", False),  # 122
    ("utf16", "utf16_unicode_520_ci", False),  # 123
    ("utf16", "utf16_vietnamese_ci", False),  # 124
    None,
    None,
    None,
    ("ucs2", "ucs2_unicode_ci", False),  # 128
    ("ucs2", "ucs2_icelandic_ci", False),  # 129
    ("ucs2", "ucs2_latvian_ci", False),  # 130
    ("ucs2", "ucs2_romanian_ci", False),  # 131
    ("ucs2", "ucs2_slovenian_ci", False),  # 132
    ("ucs2", "ucs2_polish_ci", False),  # 133
    ("ucs2", "ucs2_estonian_ci", False),  # 134
    ("ucs2", "ucs2_spanish_ci", False),  # 135
    ("ucs2", "ucs2_swedish_ci", False),  # 136
    ("ucs2", "ucs2_turkish_ci", False),  # 137
    ("ucs2", "ucs2_czech_ci", False),  # 138
    ("ucs2", "ucs2_danish_ci", False),  # 139
    ("ucs2", "ucs2_lithuanian_ci", False),  # 140
    ("ucs2", "ucs2_slovak_ci", False),  # 141
    ("ucs2", "ucs2_spanish2_ci", False),  # 142
    ("ucs2", "ucs2_roman_ci", False),  # 143
    ("ucs2", "ucs2_persian_ci", False),  # 144
    ("ucs2", "ucs2_esperanto_ci", False),  # 145
    ("ucs2", "ucs2_hungarian_ci", False),  # 146
    ("ucs2", "ucs2_sinhala_ci", False),  # 147
    ("ucs2", "ucs2_german2_ci", False),  # 148
    ("ucs2", "ucs2_croatian_ci", False),  # 149
    ("ucs2", "ucs2_unicode_520_ci", False),  # 150
    ("ucs2", "ucs2_vietnamese_ci", False),  # 151
    None,
    None,
    None,
    None,
    None,
    None,
    None,
    ("ucs2", "ucs2_general_mysql500_ci", False),  # 159
    ("utf32", "utf32_unicode_ci", False),  # 160
    ("utf32", "utf32_icelandic_ci", False),  # 161
    ("utf32", "utf32_latvian_ci", False),  # 162
    ("utf32", "utf32_romanian_ci", False),  # 163
    ("utf32", "utf32_slovenian_ci", False),  # 164
    ("utf32", "utf32_polish_ci", False),  # 165
    ("utf32", "utf32_estonian_ci", False),  # 166
    ("utf32", "utf32_spanish_ci", False),  # 167
    ("utf32", "utf32_swedish_ci", False),  # 168
    ("utf32", "utf32_turkish_ci", False),  # 169
    ("utf32", "utf32_czech_ci", False),  # 170
    ("utf32", "utf32_danish_ci", False),  # 171
    ("utf32", "utf32_lithuanian_ci", False),  # 172
    ("utf32", "utf32_slovak_ci", False),  # 173
    ("utf32", "utf32_spanish2_ci", False),  # 174
    ("utf32", "utf32_roman_ci", False),  # 175
    ("utf32", "utf32_persian_ci", False),  # 176
    ("utf32", "utf32_esperanto_ci", False),  # 177
    ("utf32", "utf32_hungarian_ci", False),  # 178
    ("utf32", "utf32_sinhala_ci", False),  # 179
    ("utf32", "utf32_german2_ci", False),  # 180
    ("utf32", "utf32_croatian_ci", False),  # 181
    ("utf32", "utf32_unicode_520_ci", False),  # 182
    ("utf32", "utf32_vietnamese_ci", False),  # 183
    None,
    None,
    None,
    None,
    None,
    None,
    None,
    None,
    ("utf8mb3", "utf8mb3_unicode_ci", False),  # 192
    ("utf8mb3", "utf8mb3_icelandic_ci", False),  # 193
    ("utf8mb3", "utf8mb3_latvian_ci", False),  # 194
    ("utf8mb3", "utf8mb3_romanian_ci", False),  # 195
    ("utf8mb3", "utf8mb3_slovenian_ci", False),  # 196
    ("utf8mb3", "utf8mb3_polish_ci", False),  # 197
    ("utf8mb3", "utf8mb3_estonian_ci", False),  # 198
    ("utf8mb3", "utf8mb3_spanish_ci", False),  # 199
    ("utf8mb3", "utf8mb3_swedish_ci", False),  # 200
    ("utf8mb3", "utf8mb3_turkish_ci", False),  # 201
    ("utf8mb3", "utf8mb3_czech_ci", False),  # 202
    ("utf8mb3", "utf8mb3_danish_ci", False),  # 203
    ("utf8mb3", "utf8mb3_lithuanian_ci", False),  # 204
    ("utf8mb3", "utf8mb3_slovak_ci", False),  # 205
    ("utf8mb3", "utf8mb3_spanish2_ci", False),  # 206
    ("utf8mb3", "utf8mb3_roman_ci", False),  # 207
    ("utf8mb3", "utf8mb3_persian_ci", False),  # 208
    ("utf8mb3", "utf8mb3_esperanto_ci", False),  # 209
    ("utf8mb3", "utf8mb3_hungarian_ci", False),  # 210
    ("utf8mb3", "utf8mb3_sinhala_ci", False),  # 211
    ("utf8mb3", "utf8mb3_german2_ci", False),  # 212
    ("utf8mb3", "utf8mb3_croatian_ci", False),  # 213
    ("utf8mb3", "utf8mb3_unicode_520_ci", False),  # 214
    ("utf8mb3", "utf8mb3_vietnamese_ci", False),  # 215
    None,
    None,
    None,
    None,
    None,
    None,
    None,
    ("utf8mb3", "utf8mb3_general_mysql500_ci", False),  # 223
    ("utf8mb4", "utf8mb4_unicode_ci", False),  # 224
    ("utf8mb4", "utf8mb4_icelandic_ci", False),  # 225
    ("utf8mb4", "utf8mb4_latvian_ci", False),  # 226
    ("utf8mb4", "utf8mb4_romanian_ci", False),  # 227
    ("utf8mb4", "utf8mb4_slovenian_ci", False),  # 228
    ("utf8mb4", "utf8mb4_polish_ci", False),  # 229
    ("utf8mb4", "utf8mb4_estonian_ci", False),  # 230
    ("utf8mb4", "utf8mb4_spanish_ci", False),  # 231
    ("utf8mb4", "utf8mb4_swedish_ci", False),  # 232
    ("utf8mb4", "utf8mb4_turkish_ci", False),  # 233
    ("utf8mb4", "utf8mb4_czech_ci", False),  # 234
    ("utf8mb4", "utf8mb4_danish_ci", False),  # 235
    ("utf8mb4", "utf8mb4_lithuanian_ci", False),  # 236
    ("utf8mb4", "utf8mb4_slovak_ci", False),  # 237
    ("utf8mb4", "utf8mb4_spanish2_ci", False),  # 238
    ("utf8mb4", "utf8mb4_roman_ci", False),  # 239
    ("utf8mb4", "utf8mb4_persian_ci", False),  # 240
    ("utf8mb4", "utf8mb4_esperanto_ci", False),  # 241
    ("utf8mb4", "utf8mb4_hungarian_ci", False),  # 242
    ("utf8mb4", "utf8mb4_sinhala_ci", False),  # 243
    ("utf8mb4", "utf8mb4_german2_ci", False),  # 244
    ("utf8mb4", "utf8mb4_croatian_ci", False),  # 245
    ("utf8mb4", "utf8mb4_unicode_520_ci", False),  # 246
    ("utf8mb4", "utf8mb4_vietnamese_ci", False),  # 247
    ("gb18030", "gb18030_chinese_ci", True),  # 248
    ("gb18030", "gb18030_bin", False),  # 249
    ("gb18030", "gb18030_unicode_520_ci", False),  # 250
    None,
    None,
    None,
    None,
    ("utf8mb4", "utf8mb4_0900_ai_ci", True),  # 255
    ("utf8mb4", "utf8mb4_de_pb_0900_ai_ci", False),  # 256
    ("utf8mb4", "utf8mb4_is_0900_ai_ci", False),  # 257
    ("utf8mb4", "utf8mb4_lv_0900_ai_ci", False),  # 258
    ("utf8mb4", "utf8mb4_ro_0900_ai_ci", False),  # 259
    ("utf8mb4", "utf8mb4_sl_0900_ai_ci", False),  # 260
    ("utf8mb4", "utf8mb4_pl_0900_ai_ci", False),  # 261
    ("utf8mb4", "utf8mb4_et_0900_ai_ci", False),  # 262
    ("utf8mb4", "utf8mb4_es_0900_ai_ci", False),  # 263
    ("utf8mb4", "utf8mb4_sv_0900_ai_ci", False),  # 264
    ("utf8mb4", "utf8mb4_tr_0900_ai_ci", False),  # 265
    ("utf8mb4", "utf8mb4_cs_0900_ai_ci", False),  # 266
    ("utf8mb4", "utf8mb4_da_0900_ai_ci", False),  # 267
    ("utf8mb4", "utf8mb4_lt_0900_ai_ci", False),  # 268
    ("utf8mb4", "utf8mb4_sk_0900_ai_ci", False),  # 269
    ("utf8mb4", "utf8mb4_es_trad_0900_ai_ci", False),  # 270
    ("utf8mb4", "utf8mb4_la_0900_ai_ci", False),  # 271
    None,
    ("utf8mb4", "utf8mb4_eo_0900_ai_ci", False),  # 273
    ("utf8mb4", "utf8mb4_hu_0900_ai_ci", False),  # 274
    ("utf8mb4", "utf8mb4_hr_0900_ai_ci", False),  # 275
    None,
    ("utf8mb4", "utf8mb4_vi_0900_ai_ci", False),  # 277
    ("utf8mb4", "utf8mb4_0900_as_cs", False),  # 278
    ("utf8mb4", "utf8mb4_de_pb_0900_as_cs", False),  # 279
    ("utf8mb4", "utf8mb4_is_0900_as_cs", False),  # 280
    ("utf8mb4", "utf8mb4_lv_0900_as_cs", False),  # 281
    ("utf8mb4", "utf8mb4_ro_0900_as_cs", False),  # 282
    ("utf8mb4", "utf8mb4_sl_0900_as_cs", False),  # 283
    ("utf8mb4", "utf8mb4_pl_0900_as_cs", False),  # 284
    ("utf8mb4", "utf8mb4_et_0900_as_cs", False),  # 285
    ("utf8mb4", "utf8mb4_es_0900_as_cs", False),  # 286
    ("utf8mb4", "utf8mb4_sv_0900_as_cs", False),  # 287
    ("utf8mb4", "utf8mb4_tr_0900_as_cs", False),  # 288
    ("utf8mb4", "utf8mb4_cs_0900_as_cs", False),  # 289
    ("utf8mb4", "utf8mb4_da_0900_as_cs", False),  # 290
    ("utf8mb4", "utf8mb4_lt_0900_as_cs", False),  # 291
    ("utf8mb4", "utf8mb4_sk_0900_as_cs", False),  # 292
    ("utf8mb4", "utf8mb4_es_trad_0900_as_cs", False),  # 293
    ("utf8mb4", "utf8mb4_la_0900_as_cs", False),  # 294
    None,
    ("utf8mb4", "utf8mb4_eo_0900_as_cs", False),  # 296
    ("utf8mb4", "utf8mb4_hu_0900_as_cs", False),  # 297
    ("utf8mb4", "utf8mb4_hr_0900_as_cs", False),  # 298
    None,
    ("utf8mb4", "utf8mb4_vi_0900_as_cs", False),  # 300
    None,
    None,
    ("utf8mb4", "utf8mb4_ja_0900_as_cs", False),  # 303
    ("utf8mb4", "utf8mb4_ja_0900_as_cs_ks", False),  # 304
    ("utf8mb4", "utf8mb4_0900_as_ci", False),  # 305
    ("utf8mb4", "utf8mb4_ru_0900_ai_ci", False),  # 306
    ("utf8mb4", "utf8mb4_ru_0900_as_cs", False),  # 307
    ("utf8mb4", "utf8mb4_zh_0900_as_cs", False),  # 308
    ("utf8mb4", "utf8mb4_0900_bin", False),  # 309
    ("utf8mb4", "utf8mb4_nb_0900_ai_ci", False),  # 310
    ("utf8mb4", "utf8mb4_nb_0900_as_cs", False),  # 311
    ("utf8mb4", "utf8mb4_nn_0900_ai_ci", False),  # 312
    ("utf8mb4", "utf8mb4_nn_0900_as_cs", False),  # 313
    ("utf8mb4", "utf8mb4_sr_latn_0900_ai_ci", False),  # 314
    ("utf8mb4", "utf8mb4_sr_latn_0900_as_cs", False),  # 315
    ("utf8mb4", "utf8mb4_bs_0900_ai_ci", False),  # 316
    ("utf8mb4", "utf8mb4_bs_0900_as_cs", False),  # 317
    ("utf8mb4", "utf8mb4_bg_0900_ai_ci", False),  # 318
    ("utf8mb4", "utf8mb4_bg_0900_as_cs", False),  # 319
    ("utf8mb4", "utf8mb4_gl_0900_ai_ci", False),  # 320
    ("utf8mb4", "utf8mb4_gl_0900_as_cs", False),  # 321
    ("utf8mb4", "utf8mb4_mn_cyrl_0900_ai_ci", False),  # 322
    ("utf8mb4", "utf8mb4_mn_cyrl_0900_as_cs", False),  # 323
]

MYSQL_CHARACTER_SETS_57: List[Optional[Tuple[str, str, bool]]] = [
    # (character set name, collation, default)
    None,
    ("big5", "big5_chinese_ci", True),  # 1
    ("latin2", "latin2_czech_cs", False),  # 2
    ("dec8", "dec8_swedish_ci", True),  # 3
    ("cp850", "cp850_general_ci", True),  # 4
    ("latin1", "latin1_german1_ci", False),  # 5
    ("hp8", "hp8_english_ci", True),  # 6
    ("koi8r", "koi8r_general_ci", True),  # 7
    ("latin1", "latin1_swedish_ci", True),  # 8
    ("latin2", "latin2_general_ci", True),  # 9
    ("swe7", "swe7_swedish_ci", True),  # 10
    ("ascii", "ascii_general_ci", True),  # 11
    ("ujis", "ujis_japanese_ci", True),  # 12
    ("sjis", "sjis_japanese_ci", True),  # 13
    ("cp1251", "cp1251_bulgarian_ci", False),  # 14
    ("latin1", "latin1_danish_ci", False),  # 15
    ("hebrew", "hebrew_general_ci", True),  # 16
    None,
    ("tis620", "tis620_thai_ci", True),  # 18
    ("euckr", "euckr_korean_ci", True),  # 19
    ("latin7", "latin7_estonian_cs", False),  # 20
    ("latin2", "latin2_hungarian_ci", False),  # 21
    ("koi8u", "koi8u_general_ci", True),  # 22
    ("cp1251", "cp1251_ukrainian_ci", False),  # 23
    ("gb2312", "gb2312_chinese_ci", True),  # 24
    ("greek", "greek_general_ci", True),  # 25
    ("cp1250", "cp1250_general_ci", True),  # 26
    ("latin2", "latin2_croatian_ci", False),  # 27
    ("gbk", "gbk_chinese_ci", True),  # 28
    ("cp1257", "cp1257_lithuanian_ci", False),  # 29
    ("latin5", "latin5_turkish_ci", True),  # 30
    ("latin1", "latin1_german2_ci", False),  # 31
    ("armscii8", "armscii8_general_ci", True),  # 32
    ("utf8", "utf8_general_ci", True),  # 33
    ("cp1250", "cp1250_czech_cs", False),  # 34
    ("ucs2", "ucs2_general_ci", True),  # 35
    ("cp866", "cp866_general_ci", True),  # 36
    ("keybcs2", "keybcs2_general_ci", True),  # 37
    ("macce", "macce_general_ci", True),  # 38
    ("macroman", "macroman_general_ci", True),  # 39
    ("cp852", "cp852_general_ci", True),  # 40
    ("latin7", "latin7_general_ci", True),  # 41
    ("latin7", "latin7_general_cs", False),  # 42
    ("macce", "macce_bin", False),  # 43
    ("cp1250", "cp1250_croatian_ci", False),  # 44
    ("utf8mb4", "utf8mb4_general_ci", True),  # 45
    ("utf8mb4", "utf8mb4_bin", False),  # 46
    ("latin1", "latin1_bin", False),  # 47
    ("latin1", "latin1_general_ci", False),  # 48
    ("latin1", "latin1_general_cs", False),  # 49
    ("cp1251", "cp1251_bin", False),  # 50
    ("cp1251", "cp1251_general_ci", True),  # 51
    ("cp1251", "cp1251_general_cs", False),  # 52
    ("macroman", "macroman_bin", False),  # 53
    ("utf16", "utf16_general_ci", True),  # 54
    ("utf16", "utf16_bin", False),  # 55
    ("utf16le", "utf16le_general_ci", True),  # 56
    ("cp1256", "cp1256_general_ci", True),  # 57
    ("cp1257", "cp1257_bin", False),  # 58
    ("cp1257", "cp1257_general_ci", True),  # 59
    ("utf32", "utf32_general_ci", True),  # 60
    ("utf32", "utf32_bin", False),  # 61
    ("utf16le", "utf16le_bin", False),  # 62
    ("binary", "binary", True),  # 63
    ("armscii8", "armscii8_bin", False),  # 64
    ("ascii", "ascii_bin", False),  # 65
    ("cp1250", "cp1250_bin", False),  # 66
    ("cp1256", "cp1256_bin", False),  # 67
    ("cp866", "cp866_bin", False),  # 68
    ("dec8", "dec8_bin", False),  # 69
    ("greek", "greek_bin", False),  # 70
    ("hebrew", "hebrew_bin", False),  # 71
    ("hp8", "hp8_bin", False),  # 72
    ("keybcs2", "keybcs2_bin", False),  # 73
    ("koi8r", "koi8r_bin", False),  # 74
    ("koi8u", "koi8u_bin", False),  # 75
    None,
    ("latin2", "latin2_bin", False),  # 77
    ("latin5", "latin5_bin", False),  # 78
    ("latin7", "latin7_bin", False),  # 79
    ("cp850", "cp850_bin", False),  # 80
    ("cp852", "cp852_bin", False),  # 81
    ("swe7", "swe7_bin", False),  # 82
    ("utf8", "utf8_bin", False),  # 83
    ("big5", "big5_bin", False),  # 84
    ("euckr", "euckr_bin", False),  # 85
    ("gb2312", "gb2312_bin", False),  # 86
    ("gbk", "gbk_bin", False),  # 87
    ("sjis", "sjis_bin", False),  # 88
    ("tis620", "tis620_bin", False),  # 89
    ("ucs2", "ucs2_bin", False),  # 90
    ("ujis", "ujis_bin", False),  # 91
    ("geostd8", "geostd8_general_ci", True),  # 92
    ("geostd8", "geostd8_bin", False),  # 93
    ("latin1", "latin1_spanish_ci", False),  # 94
    ("cp932", "cp932_japanese_ci", True),  # 95
    ("cp932", "cp932_bin", False),  # 96
    ("eucjpms", "eucjpms_japanese_ci", True),  # 97
    ("eucjpms", "eucjpms_bin", False),  # 98
    ("cp1250", "cp1250_polish_ci", False),  # 99
    None,
    ("utf16", "utf16_unicode_ci", False),  # 101
    ("utf16", "utf16_icelandic_ci", False),  # 102
    ("utf16", "utf16_latvian_ci", False),  # 103
    ("utf16", "utf16_romanian_ci", False),  # 104
    ("utf16", "utf16_slovenian_ci", False),  # 105
    ("utf16", "utf16_polish_ci", False),  # 106
    ("utf16", "utf16_estonian_ci", False),  # 107
    ("utf16", "utf16_spanish_ci", False),  # 108
    ("utf16", "utf16_swedish_ci", False),  # 109
    ("utf16", "utf16_turkish_ci", False),  # 110
    ("utf16", "utf16_czech_ci", False),  # 111
    ("utf16", "utf16_danish_ci", False),  # 112
    ("utf16", "utf16_lithuanian_ci", False),  # 113
    ("utf16", "utf16_slovak_ci", False),  # 114
    ("utf16", "utf16_spanish2_ci", False),  # 115
    ("utf16", "utf16_roman_ci", False),  # 116
    ("utf16", "utf16_persian_ci", False),  # 117
    ("utf16", "utf16_esperanto_ci", False),  # 118
    ("utf16", "utf16_hungarian_ci", False),  # 119
    ("utf16", "utf16_sinhala_ci", False),  # 120
    ("utf16", "utf16_german2_ci", False),  # 121
    ("utf16", "utf16_croatian_ci", False),  # 122
    ("utf16", "utf16_unicode_520_ci", False),  # 123
    ("utf16", "utf16_vietnamese_ci", False),  # 124
    None,
    None,
    None,
    ("ucs2", "ucs2_unicode_ci", False),  # 128
    ("ucs2", "ucs2_icelandic_ci", False),  # 129
    ("ucs2", "ucs2_latvian_ci", False),  # 130
    ("ucs2", "ucs2_romanian_ci", False),  # 131
    ("ucs2", "ucs2_slovenian_ci", False),  # 132
    ("ucs2", "ucs2_polish_ci", False),  # 133
    ("ucs2", "ucs2_estonian_ci", False),  # 134
    ("ucs2", "ucs2_spanish_ci", False),  # 135
    ("ucs2", "ucs2_swedish_ci", False),  # 136
    ("ucs2", "ucs2_turkish_ci", False),  # 137
    ("ucs2", "ucs2_czech_ci", False),  # 138
    ("ucs2", "ucs2_danish_ci", False),  # 139
    ("ucs2", "ucs2_lithuanian_ci", False),  # 140
    ("ucs2", "ucs2_slovak_ci", False),  # 141
    ("ucs2", "ucs2_spanish2_ci", False),  # 142
    ("ucs2", "ucs2_roman_ci", False),  # 143
    ("ucs2", "ucs2_persian_ci", False),  # 144
    ("ucs2", "ucs2_esperanto_ci", False),  # 145
    ("ucs2", "ucs2_hungarian_ci", False),  # 146
    ("ucs2", "ucs2_sinhala_ci", False),  # 147
    ("ucs2", "ucs2_german2_ci", False),  # 148
    ("ucs2", "ucs2_croatian_ci", False),  # 149
    ("ucs2", "ucs2_unicode_520_ci", False),  # 150
    ("ucs2", "ucs2_vietnamese_ci", False),  # 151
    None,
    None,
    None,
    None,
    None,
    None,
    None,
    ("ucs2", "ucs2_general_mysql500_ci", False),  # 159
    ("utf32", "utf32_unicode_ci", False),  # 160
    ("utf32", "utf32_icelandic_ci", False),  # 161
    ("utf32", "utf32_latvian_ci", False),  # 162
    ("utf32", "utf32_romanian_ci", False),  # 163
    ("utf32", "utf32_slovenian_ci", False),  # 164
    ("utf32", "utf32_polish_ci", False),  # 165
    ("utf32", "utf32_estonian_ci", False),  # 166
    ("utf32", "utf32_spanish_ci", False),  # 167
    ("utf32", "utf32_swedish_ci", False),  # 168
    ("utf32", "utf32_turkish_ci", False),  # 169
    ("utf32", "utf32_czech_ci", False),  # 170
    ("utf32", "utf32_danish_ci", False),  # 171
    ("utf32", "utf32_lithuanian_ci", False),  # 172
    ("utf32", "utf32_slovak_ci", False),  # 173
    ("utf32", "utf32_spanish2_ci", False),  # 174
    ("utf32", "utf32_roman_ci", False),  # 175
    ("utf32", "utf32_persian_ci", False),  # 176
    ("utf32", "utf32_esperanto_ci", False),  # 177
    ("utf32", "utf32_hungarian_ci", False),  # 178
    ("utf32", "utf32_sinhala_ci", False),  # 179
    ("utf32", "utf32_german2_ci", False),  # 180
    ("utf32", "utf32_croatian_ci", False),  # 181
    ("utf32", "utf32_unicode_520_ci", False),  # 182
    ("utf32", "utf32_vietnamese_ci", False),  # 183
    None,
    None,
    None,
    None,
    None,
    None,
    None,
    None,
    ("utf8", "utf8_unicode_ci", False),  # 192
    ("utf8", "utf8_icelandic_ci", False),  # 193
    ("utf8", "utf8_latvian_ci", False),  # 194
    ("utf8", "utf8_romanian_ci", False),  # 195
    ("utf8", "utf8_slovenian_ci", False),  # 196
    ("utf8", "utf8_polish_ci", False),  # 197
    ("utf8", "utf8_estonian_ci", False),  # 198
    ("utf8", "utf8_spanish_ci", False),  # 199
    ("utf8", "utf8_swedish_ci", False),  # 200
    ("utf8", "utf8_turkish_ci", False),  # 201
    ("utf8", "utf8_czech_ci", False),  # 202
    ("utf8", "utf8_danish_ci", False),  # 203
    ("utf8", "utf8_lithuanian_ci", False),  # 204
    ("utf8", "utf8_slovak_ci", False),  # 205
    ("utf8", "utf8_spanish2_ci", False),  # 206
    ("utf8", "utf8_roman_ci", False),  # 207
    ("utf8", "utf8_persian_ci", False),  # 208
    ("utf8", "utf8_esperanto_ci", False),  # 209
    ("utf8", "utf8_hungarian_ci", False),  # 210
    ("utf8", "utf8_sinhala_ci", False),  # 211
    ("utf8", "utf8_german2_ci", False),  # 212
    ("utf8", "utf8_croatian_ci", False),  # 213
    ("utf8", "utf8_unicode_520_ci", False),  # 214
    ("utf8", "utf8_vietnamese_ci", False),  # 215
    None,
    None,
    None,
    None,
    None,
    None,
    None,
    ("utf8", "utf8_general_mysql500_ci", False),  # 223
    ("utf8mb4", "utf8mb4_unicode_ci", False),  # 224
    ("utf8mb4", "utf8mb4_icelandic_ci", False),  # 225
    ("utf8mb4", "utf8mb4_latvian_ci", False),  # 226
    ("utf8mb4", "utf8mb4_romanian_ci", False),  # 227
    ("utf8mb4", "utf8mb4_slovenian_ci", False),  # 228
    ("utf8mb4", "utf8mb4_polish_ci", False),  # 229
    ("utf8mb4", "utf8mb4_estonian_ci", False),  # 230
    ("utf8mb4", "utf8mb4_spanish_ci", False),  # 231
    ("utf8mb4", "utf8mb4_swedish_ci", False),  # 232
    ("utf8mb4", "utf8mb4_turkish_ci", False),  # 233
    ("utf8mb4", "utf8mb4_czech_ci", False),  # 234
    ("utf8mb4", "utf8mb4_danish_ci", False),  # 235
    ("utf8mb4", "utf8mb4_lithuanian_ci", False),  # 236
    ("utf8mb4", "utf8mb4_slovak_ci", False),  # 237
    ("utf8mb4", "utf8mb4_spanish2_ci", False),  # 238
    ("utf8mb4", "utf8mb4_roman_ci", False),  # 239
    ("utf8mb4", "utf8mb4_persian_ci", False),  # 240
    ("utf8mb4", "utf8mb4_esperanto_ci", False),  # 241
    ("utf8mb4", "utf8mb4_hungarian_ci", False),  # 242
    ("utf8mb4", "utf8mb4_sinhala_ci", False),  # 243
    ("utf8mb4", "utf8mb4_german2_ci", False),  # 244
    ("utf8mb4", "utf8mb4_croatian_ci", False),  # 245
    ("utf8mb4", "utf8mb4_unicode_520_ci", False),  # 246
    ("utf8mb4", "utf8mb4_vietnamese_ci", False),  # 247
    ("gb18030", "gb18030_chinese_ci", True),  # 248
    ("gb18030", "gb18030_bin", False),  # 249
    ("gb18030", "gb18030_unicode_520_ci", False),  # 250
]

virt/lib/python3.9/site-packages/mysql/connector/charsets.py
PrinceDisant/LostInNL
# Copyright (c) 2009, 2023, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

# mypy: disable-error-code="arg-type,operator,attr-defined,assignment"

"""Implementing communication with MySQL servers."""

import datetime
import getpass
import os
import socket
import struct
import sys
import warnings

from decimal import Decimal
from io import IOBase
from typing import (
    Any,
    BinaryIO,
    Dict,
    Generator,
    List,
    Mapping,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
)

from . import version
from .abstracts import MySQLConnectionAbstract
from .authentication import get_auth_plugin
from .constants import (
    CharacterSet,
    ClientFlag,
    FieldType,
    ServerCmd,
    ServerFlag,
    ShutdownType,
    flag_is_set,
)
from .conversion import MySQLConverter
from .cursor import (
    CursorBase,
    MySQLCursor,
    MySQLCursorBuffered,
    MySQLCursorBufferedDict,
    MySQLCursorBufferedNamedTuple,
    MySQLCursorBufferedRaw,
    MySQLCursorDict,
    MySQLCursorNamedTuple,
    MySQLCursorPrepared,
    MySQLCursorPreparedDict,
    MySQLCursorPreparedNamedTuple,
    MySQLCursorPreparedRaw,
    MySQLCursorRaw,
)
from .errors import (
    DatabaseError,
    Error,
    InterfaceError,
    InternalError,
    NotSupportedError,
    OperationalError,
    ProgrammingError,
    get_exception,
)
from .logger import logger
from .network import MySQLSocket, MySQLTCPSocket, MySQLUnixSocket
from .opentelemetry.constants import OTEL_ENABLED
from .opentelemetry.context_propagation import with_context_propagation
from .plugins import BaseAuthPlugin
from .protocol import MySQLProtocol
from .types import (
    ConnAttrsType,
    DescriptionType,
    EofPacketType,
    HandShakeType,
    OkPacketType,
    ResultType,
    RowType,
    StatsPacketType,
    StrOrBytes,
    SupportedMysqlBinaryProtocolTypes,
)
from .utils import get_platform, int1store, int4store, lc_int

if OTEL_ENABLED:
    from .opentelemetry.instrumentation import end_span, record_exception_event


class MySQLConnection(MySQLConnectionAbstract):
    """Connection to a MySQL Server"""

    def __init__(self, **kwargs: Any) -> None:
        self._protocol: Optional[MySQLProtocol] = None
        self._socket: Optional[MySQLSocket] = None
        self._handshake: Optional[HandShakeType] = None
        super().__init__()

        self._converter_class: Type[MySQLConverter] = MySQLConverter

        self._client_flags: int = ClientFlag.get_default()
        self._charset_id: int = 45
        self._sql_mode: Optional[str] = None
        self._time_zone: Optional[str] = None
        self._autocommit: bool = False

        self._user: str = ""
        self._password: str = ""
        self._database: str = ""
        self._host: str = "127.0.0.1"
        self._port: int = 3306
        self._unix_socket: Optional[str] = None
        self._client_host: str = ""
        self._client_port: int = 0
        self._ssl: Dict[str, Optional[Union[str, bool, List[str]]]] = {}
        self._force_ipv6: bool = False

        self._use_unicode: bool = True
        self._get_warnings: bool = False
        self._raise_on_warnings: bool = False
        self._buffered: bool = False
        self._unread_result: bool = False
        self._have_next_result: bool = False
        self._raw: bool = False
        self._in_transaction: bool = False

        self._prepared_statements: Any = None

        self._ssl_active: bool = False
        self._auth_plugin: Optional[str] = None
        self._krb_service_principal: Optional[str] = None
        self._pool_config_version: Any = None
        self._query_attrs_supported: int = False

        self._columns_desc: List[DescriptionType] = []
        self._mfa_nfactor: int = 1

        if kwargs:
            try:
                self.connect(**kwargs)
            except Exception:
                # Tidy-up underlying socket on failure
                self.close()
                self._socket = None
                raise

    def _add_default_conn_attrs(self) -> None:
        """Add the default connection attributes."""
        platform = get_platform()
        license_chunks = version.LICENSE.split(" ")
        if license_chunks[0] == "GPLv2":
            client_license = "GPL-2.0"
        else:
            client_license = "Commercial"
        default_conn_attrs = {
            "_pid": str(os.getpid()),
            "_platform": platform["arch"],
            "_source_host": socket.gethostname(),
            "_client_name": "mysql-connector-python",
            "_client_license": client_license,
            "_client_version": ".".join([str(x) for x in version.VERSION[0:3]]),
            "_os": platform["version"],
        }

        self._conn_attrs.update((default_conn_attrs))

    def _do_handshake(self) -> None:
        """Get the handshake from the MySQL server"""
        packet = self._socket.recv()
        if packet[4] == 255:
            raise get_exception(packet)

        self._handshake = None
        handshake = self._protocol.parse_handshake(packet)

        server_version = handshake["server_version_original"]

        self._server_version = self._check_server_version(
            server_version
            if isinstance(server_version, (str, bytes, bytearray))
            else "Unknown"
        )
        CharacterSet.set_mysql_version(self._server_version)

        if not handshake["capabilities"] & ClientFlag.SSL:
            if self._auth_plugin == "mysql_clear_password" and not self.is_secure:
                raise InterfaceError(
                    "Clear password authentication is not supported over "
                    "insecure channels"
                )
            if self._ssl.get("verify_cert"):
                raise InterfaceError(
                    "SSL is required but the server doesn't support it",
                    errno=2026,
                )
            self._client_flags &= ~ClientFlag.SSL
        elif not self._ssl_disabled:
            self._client_flags |= ClientFlag.SSL

        if handshake["capabilities"] & ClientFlag.PLUGIN_AUTH:
            self.set_client_flags([ClientFlag.PLUGIN_AUTH])

        if handshake["capabilities"] & ClientFlag.CLIENT_QUERY_ATTRIBUTES:
            self._query_attrs_supported = True
            self.set_client_flags([ClientFlag.CLIENT_QUERY_ATTRIBUTES])

        if handshake["capabilities"] & ClientFlag.MULTI_FACTOR_AUTHENTICATION:
            self.set_client_flags([ClientFlag.MULTI_FACTOR_AUTHENTICATION])

        self._handshake = handshake

    def _do_auth(
        self,
        username: Optional[str] = None,
        password: Optional[str] = None,
        database: Optional[str] = None,
        client_flags: int = 0,
        charset: int = 45,
        ssl_options: Optional[Dict[str, Optional[Union[str, bool, List[str]]]]] = None,
        conn_attrs: Optional[ConnAttrsType] = None,
    ) -> bool:
        """Authenticate with the MySQL server

        Authentication happens in two parts. We first send a response to the
        handshake. The MySQL server will then send either an AuthSwitchRequest
        or an error packet.

        Raises NotSupportedError when we get the old, insecure password
        reply back. Raises any error coming from MySQL.
        """
        self._ssl_active = False
        if ssl_options is None:
            ssl_options = {}
        if not self._ssl_disabled and (client_flags & ClientFlag.SSL):
            packet = self._protocol.make_auth_ssl(
                charset=charset, client_flags=client_flags
            )
            self._socket.send(packet)
            if ssl_options.get("tls_ciphersuites") is not None:
                tls_ciphersuites = ":".join(ssl_options.get("tls_ciphersuites"))
            else:
                tls_ciphersuites = ""
            self._socket.switch_to_ssl(
                ssl_options.get("ca"),
                ssl_options.get("cert"),
                ssl_options.get("key"),
                ssl_options.get("verify_cert") or False,
                ssl_options.get("verify_identity") or False,
                tls_ciphersuites,
                ssl_options.get("tls_versions"),
            )
            self._ssl_active = True

        if self._password1 and password != self._password1:
            password = self._password1

        logger.debug("# _do_auth(): self._auth_plugin: %s", self._auth_plugin)
        if (
            self._auth_plugin.startswith("authentication_oci")
            or (
                self._auth_plugin.startswith("authentication_kerberos")
                and os.name == "nt"
            )
        ) and not username:
            username = getpass.getuser()
            logger.debug(
                "MySQL user is empty, OS user: %s will be used for %s",
                username,
                self._auth_plugin,
            )

        packet = self._protocol.make_auth(
            handshake=self._handshake,
            username=username,
            password=password,
            database=database,
            charset=charset,
            client_flags=client_flags,
            ssl_enabled=self._ssl_active,
            auth_plugin=self._auth_plugin,
            conn_attrs=conn_attrs,
            auth_plugin_class=self._auth_plugin_class,
        )
        self._socket.send(packet)
        self._auth_switch_request(username, password)

        if not (client_flags & ClientFlag.CONNECT_WITH_DB) and database:
            self.cmd_init_db(database)

        return True

    def _auth_switch_request(
        self, username: Optional[str] = None, password: Optional[str] = None
    ) -> Optional[OkPacketType]:
        """Handle second part of authentication

        Raises NotSupportedError when we get the old, insecure password
        reply back. Raises any error coming from MySQL.
        """
        auth = None
        new_auth_plugin: Optional[str] = (
            self._auth_plugin or self._handshake["auth_plugin"]
        )
        logger.debug("new_auth_plugin: %s", new_auth_plugin)
        packet = self._socket.recv()
        if packet[4] == 254 and len(packet) == 5:
            raise NotSupportedError(
                "Authentication with old (insecure) passwords "
                "is not supported. For more information, lookup "
                "Password Hashing in the latest MySQL manual"
            )
        if packet[4] == 254:
            # AuthSwitchRequest
            (
                new_auth_plugin,
                auth_data,
            ) = self._protocol.parse_auth_switch_request(packet)
            auth = get_auth_plugin(new_auth_plugin, self._auth_plugin_class)(
                auth_data,
                username=username or self._user,
                password=password,
                ssl_enabled=self.is_secure,
            )
            packet = self._auth_continue(auth, new_auth_plugin, auth_data)

        if packet[4] == 1:
            auth_data = self._protocol.parse_auth_more_data(packet)
            auth = get_auth_plugin(new_auth_plugin, self._auth_plugin_class)(
                auth_data, password=password, ssl_enabled=self.is_secure
            )
            if new_auth_plugin == "caching_sha2_password":
                response = auth.auth_response()
                if response:
                    self._socket.send(response)
                packet = self._socket.recv()

        if packet[4] == 0:
            return self._handle_ok(packet)
        if packet[4] == 2:
            return self._handle_mfa(packet)
        if packet[4] == 255:
            raise get_exception(packet)
        return None

    def _handle_mfa(self, packet: bytes) -> Optional[OkPacketType]:
        """Handle Multi Factor Authentication."""
        self._mfa_nfactor += 1
        if self._mfa_nfactor == 2:
            password = self._password2
        elif self._mfa_nfactor == 3:
            password = self._password3
        else:
            raise InterfaceError(
                "Failed Multi Factor Authentication (invalid N factor)"
            )

        logger.debug("# MFA N Factor #%d", self._mfa_nfactor)

        packet, auth_plugin = self._protocol.parse_auth_next_factor(packet[4:])
        auth = get_auth_plugin(auth_plugin, self._auth_plugin_class)(
            None,
            username=self._user,
            password=password,
            ssl_enabled=self.is_secure,
        )
        packet = self._auth_continue(auth, auth_plugin, packet)

        if packet[4] == 1:
            auth_data = self._protocol.parse_auth_more_data(packet)
            auth = get_auth_plugin(auth_plugin, self._auth_plugin_class)(
                auth_data, password=password, ssl_enabled=self.is_secure
            )
            if auth_plugin == "caching_sha2_password":
                response = auth.auth_response()
                if response:
                    self._socket.send(response)
                packet = self._socket.recv()

        if packet[4] == 0:
            return self._handle_ok(packet)
        if packet[4] == 2:
            return self._handle_mfa(packet)
        if packet[4] == 255:
            raise get_exception(packet)
        return None

    def _auth_continue(
        self, auth: BaseAuthPlugin, auth_plugin: str, auth_data: bytes
    ) -> bytes:
        """Continue with the authentication."""
        if auth_plugin == "authentication_ldap_sasl_client":
            logger.debug("# auth_data: %s", auth_data)
            response = auth.auth_response(self._krb_service_principal)
        elif auth_plugin == "authentication_kerberos_client":
            logger.debug("# auth_data: %s", auth_data)
            response = auth.auth_response(auth_data)
        elif auth_plugin == "authentication_oci_client":
            logger.debug("# oci configuration file path: %s", self._oci_config_file)
            auth.oci_config_file = self._oci_config_file
            auth.oci_config_profile = self._oci_config_profile
            response = auth.auth_response()
        else:
            response = auth.auth_response()

        logger.debug("# request: %s size: %s", response, len(response))
        self._socket.send(response)
        packet = self._socket.recv()
        logger.debug("# server response packet: %s", packet)
        if (
            auth_plugin == "authentication_ldap_sasl_client"
            and len(packet) >= 6
            and packet[5] == 114
            and packet[6] == 61
        ):  # 'r' and '='
            # Continue with sasl authentication
            dec_response = packet[5:]
            cresponse = auth.auth_continue(dec_response)
            self._socket.send(cresponse)
            packet = self._socket.recv()
            if packet[5] == 118 and packet[6] == 61:  # 'v' and '='
                if auth.auth_finalize(packet[5:]):
                    # receive packed OK
                    packet = self._socket.recv()
        elif (
            auth_plugin == "authentication_ldap_sasl_client"
            and auth_data == b"GSSAPI"
            and packet[4] != 255
        ):
            rcode_size = 5  # header size for the response status code.
            logger.debug("# Continue with sasl GSSAPI authentication")
            logger.debug("# response header: %s", packet[: rcode_size + 1])
            logger.debug("# response size: %s", len(packet))

            logger.debug("# Negotiate a service request")
            complete = False
            tries = 0  # To avoid a infinite loop attempt no more than feedback messages
            while not complete and tries < 5:
                logger.debug("%s Attempt %s %s", "-" * 20, tries + 1, "-" * 20)
                logger.debug("<< server response: %s", packet)
                logger.debug("# response code: %s", packet[: rcode_size + 1])
                step, complete = auth.auth_continue_krb(packet[rcode_size:])
                logger.debug(" >> response to server: %s", step)
                self._socket.send(step or b"")
                packet = self._socket.recv()
                tries += 1
            if not complete:
                raise InterfaceError(
                    f"Unable to fulfill server request after {tries} "
                    f"attempts. Last server response: {packet}"
                )
            logger.debug(
                " last GSSAPI response from server: %s length: %d",
                packet,
                len(packet),
            )
            last_step = auth.auth_accept_close_handshake(packet[rcode_size:])
            logger.debug(
                " >> last response to server: %s length: %d",
                last_step,
                len(last_step),
            )
            self._socket.send(last_step)
            # Receive final handshake from server
            packet = self._socket.recv()
            logger.debug("<< final handshake from server: %s", packet)

            # receive OK packet from server.
            packet = self._socket.recv()
            logger.debug("<< ok packet from server: %s", packet)
        elif auth_plugin == "authentication_kerberos_client" and packet[4] != 255:
            rcode_size = 5  # Reader size for the response status code
            logger.debug("# Continue with GSSAPI authentication")
            logger.debug("# Response header: %s", packet[: rcode_size + 1])
            logger.debug("# Response size: %s", len(packet))
            logger.debug("# Negotiate a service request")
            complete = False
            tries = 0

            while not complete and tries < 5:
                logger.debug("%s Attempt %s %s", "-" * 20, tries + 1, "-" * 20)
                logger.debug("<< Server response: %s", packet)
                logger.debug("# Response code: %s", packet[: rcode_size + 1])
                token, complete = auth.auth_continue(packet[rcode_size:])
                if token:
                    self._socket.send(token)
                if complete:
                    break
                packet = self._socket.recv()

                logger.debug(">> Response to server: %s", token)
                tries += 1

            if not complete:
                raise InterfaceError(
                    f"Unable to fulfill server request after {tries} "
                    f"attempts. Last server response: {packet}"
                )

            logger.debug(
                "Last response from server: %s length: %d",
                packet,
                len(packet),
            )

            # Receive OK packet from server.
            packet = self._socket.recv()
            logger.debug("<< Ok packet from server: %s", packet)

        return bytes(packet)

    def _get_connection(self) -> MySQLSocket:
        """Get connection based on configuration

        This method will return the appropriated connection object using
        the connection parameters.

        Returns subclass of MySQLBaseSocket.
        """
        conn: Optional[MySQLSocket] = None
        if self._unix_socket and os.name == "posix":
            conn = MySQLUnixSocket(unix_socket=self.unix_socket)
        else:
            conn = MySQLTCPSocket(
                host=self.server_host,
                port=self.server_port,
                force_ipv6=self._force_ipv6,
            )

        conn.set_connection_timeout(self._connection_timeout)
        return conn

    def _open_connection(self) -> None:
        """Open the connection to the MySQL server

        This method sets up and opens the connection to the MySQL server.

        Raises on errors.
        """
        if self._auth_plugin == "authentication_kerberos_client" and not self._user:
            cls = get_auth_plugin(self._auth_plugin, self._auth_plugin_class)
            self._user = cls.get_user_from_credentials()

        self._protocol = MySQLProtocol()
        self._socket = self._get_connection()
        try:
            self._socket.open_connection()

            # do initial handshake
            self._do_handshake()

            # start authentication negotiation
            self._do_auth(
                self._user,
                self._password,
                self._database,
                self._client_flags,
                self._charset_id,
                self._ssl,
                self._conn_attrs,
            )
            self.set_converter_class(self._converter_class)

            if self._client_flags & ClientFlag.COMPRESS:
                # update the network layer accordingly
                self._socket.switch_to_compressed_mode()

            self._socket.set_connection_timeout(None)
        except Exception:
            # close socket
            self._socket.close_connection()
            raise

        if (
            not self._ssl_disabled
            and hasattr(self._socket.sock, "version")
            and callable(self._socket.sock.version)
        ):
            # Raise a deprecation warning if TLSv1 or TLSv1.1 is being used
            tls_version = self._socket.sock.version()
            if tls_version in ("TLSv1", "TLSv1.1"):
                warn_msg = (
                    f"This connection is using {tls_version} which is now "
                    "deprecated and will be removed in a future release of "
                    "MySQL Connector/Python"
                )
                warnings.warn(warn_msg, DeprecationWarning)

    def shutdown(self) -> None:
        """Shut down connection to MySQL Server."""
        if not self._socket:
            return

        try:
            self._socket.shutdown()
        except (AttributeError, Error):
            pass  # Getting an exception would mean we are disconnected.

    def close(self) -> None:
        """Disconnect from the MySQL server"""
        if self._span and self._span.is_recording():
            record_exception_event(self._span, sys.exc_info()[1])

        if not self._socket:
            return

        try:
            self.cmd_quit()
        except (AttributeError, Error):
            pass  # Getting an exception would mean we are disconnected.

        try:
            self._socket.close_connection()
        except Exception as err:
            if OTEL_ENABLED:
                record_exception_event(self._span, err)
            raise
        finally:
            if OTEL_ENABLED:
                end_span(self._span)

        self._handshake = None

    disconnect = close

    def _send_cmd(
        self,
        command: int,
        argument: Optional[bytes] = None,
        packet_number: int = 0,
        packet: Optional[bytes] = None,
        expect_response: bool = True,
        compressed_packet_number: int = 0,
    ) -> Optional[bytearray]:
        """Send a command to the MySQL server

        This method sends a command with an optional argument.
        If packet is not None, it will be sent and the argument will be
        ignored.

        The packet_number is optional and should usually not be used.

        Some commands might not result in the MySQL server returning
        a response. If a command does not return anything, you should
        set expect_response to False. The _send_cmd method will then
        return None instead of a MySQL packet.

        Returns a MySQL packet or None.
        """
        self.handle_unread_result()

        try:
            self._socket.send(
                self._protocol.make_command(command, packet or argument),
                packet_number,
                compressed_packet_number,
            )
        except AttributeError as err:
            raise OperationalError("MySQL Connection not available") from err

        if not expect_response:
            return None
        return self._socket.recv()

    def _send_data(self, data_file: BinaryIO, send_empty_packet: bool = False) -> bytes:
        """Send data to the MySQL server

        This method accepts a file-like object and sends its data
        as is to the MySQL server. If the send_empty_packet is
        True, it will send an extra empty package (for example
        when using LOAD LOCAL DATA INFILE).

        Returns a MySQL packet.
        """
        self.handle_unread_result()

        if not hasattr(data_file, "read"):
            raise ValueError("expecting a file-like object")

        chunk_size = 131072  # 128 KB
        try:
            buf = data_file.read(chunk_size - 16)
            while buf:
                self._socket.send(buf)
                buf = data_file.read(chunk_size - 16)
        except AttributeError as err:
            raise OperationalError("MySQL Connection not available") from err

        if send_empty_packet:
            try:
                self._socket.send(b"")
            except AttributeError as err:
                raise OperationalError("MySQL Connection not available") from err

        return bytes(self._socket.recv())

    def _handle_server_status(self, flags: int) -> None:
        """Handle the server flags found in MySQL packets

        This method handles the server flags send by MySQL OK and EOF
        packets. It, for example, checks whether there exists more result
        sets or whether there is an ongoing transaction.
        """
        self._have_next_result = flag_is_set(ServerFlag.MORE_RESULTS_EXISTS, flags)
        self._in_transaction = flag_is_set(ServerFlag.STATUS_IN_TRANS, flags)

    @property
    def in_transaction(self) -> bool:
        """MySQL session has started a transaction"""
        return self._in_transaction

    def _handle_ok(self, packet: bytes) -> OkPacketType:
        """Handle a MySQL OK packet

        This method handles a MySQL OK packet. When the packet is found to
        be an Error packet, an error will be raised. If the packet is neither
        an OK or an Error packet, InterfaceError will be raised.

        Returns a dict()
        """
        if packet[4] == 0:
            ok_pkt = self._protocol.parse_ok(packet)
            self._handle_server_status(ok_pkt["status_flag"])
            return ok_pkt
        if packet[4] == 255:
            raise get_exception(packet)
        raise InterfaceError("Expected OK packet")

    def _handle_eof(self, packet: bytes) -> EofPacketType:
        """Handle a MySQL EOF packet

        This method handles a MySQL EOF packet. When the packet is found to
        be an Error packet, an error will be raised. If the packet is neither
        and OK or an Error packet, InterfaceError will be raised.

        Returns a dict()
        """
        if packet[4] == 254:
            eof = self._protocol.parse_eof(packet)
            self._handle_server_status(eof["status_flag"])
            return eof
        if packet[4] == 255:
            raise get_exception(packet)
        raise InterfaceError("Expected EOF packet")

    def _handle_load_data_infile(self, filename: str) -> OkPacketType:
        """Handle a LOAD DATA INFILE LOCAL request"""
        file_name = os.path.abspath(filename)
        if os.path.islink(file_name):
            raise OperationalError("Use of symbolic link is not allowed")
        if not self._allow_local_infile and not self._allow_local_infile_in_path:
            raise DatabaseError(
                "LOAD DATA LOCAL INFILE file request rejected due to "
                "restrictions on access."
            )
        if not self._allow_local_infile and self._allow_local_infile_in_path:
            # validate filename is inside of allow_local_infile_in_path path.
            infile_path = os.path.abspath(self._allow_local_infile_in_path)
            c_path = None
            try:
                c_path = os.path.commonpath([infile_path, file_name])
            except ValueError as err:
                err_msg = (
                    "{} while loading file `{}` and path `{}` given"
                    " in allow_local_infile_in_path"
                )
                raise InterfaceError(
                    err_msg.format(str(err), file_name, infile_path)
                ) from err

            if c_path != infile_path:
                err_msg = (
                    "The file `{}` is not found in the given "
                    "allow_local_infile_in_path {}"
                )
                raise DatabaseError(err_msg.format(file_name, infile_path))

        try:
            data_file = open(file_name, "rb")  # pylint: disable=consider-using-with
            return self._handle_ok(self._send_data(data_file, send_empty_packet=True))
        except IOError:
            # Send a empty packet to cancel the operation
            try:
                self._socket.send(b"")
            except AttributeError as err:
                raise OperationalError("MySQL Connection not available") from err
            raise InterfaceError(f"File '{file_name}' could not be read") from None
        finally:
            try:
                data_file.close()
            except (IOError, NameError):
                pass

    def _handle_result(self, packet: bytes) -> ResultType:
        """Handle a MySQL Result

        This method handles a MySQL result, for example, after sending the
        query command. OK and EOF packets will be handled and returned. If
        the packet is an Error packet, an Error-exception will be
        raised.

        The dictionary returned of:
        - columns: column information
        - eof: the EOF-packet information

        Returns a dict()
        """
        if not packet or len(packet) < 4:
            raise InterfaceError("Empty response")
        if packet[4] == 0:
            return self._handle_ok(packet)
        if packet[4] == 251:
            filename = packet[5:].decode()
            return self._handle_load_data_infile(filename)
        if packet[4] == 254:
            return self._handle_eof(packet)
        if packet[4] == 255:
            raise get_exception(packet)

        # We have a text result set
        column_count = self._protocol.parse_column_count(packet)
        if not column_count or not isinstance(column_count, int):
            raise InterfaceError("Illegal result set")

        self._columns_desc = [
            None,
        ] * column_count
        for i in range(0, column_count):
            self._columns_desc[i] = self._protocol.parse_column(
                self._socket.recv(), self.python_charset
            )

        eof = self._handle_eof(self._socket.recv())
        self.unread_result = True
        return {"columns": self._columns_desc, "eof": eof}

    def get_row(
        self,
        binary: bool = False,
        columns: Optional[List[DescriptionType]] = None,
        raw: Optional[bool] = None,
    ) -> Tuple[Optional[RowType], Optional[EofPacketType]]:
        """Get the next rows returned by the MySQL server

        This method gets one row from the result set after sending, for
        example, the query command. The result is a tuple consisting of the
        row and the EOF packet.
        If no row was available in the result set, the row data will be None.

        Returns a tuple.
        """
        (rows, eof) = self.get_rows(count=1, binary=binary, columns=columns, raw=raw)
        if rows:
            return (rows[0], eof)
        return (None, eof)

    def get_rows(
        self,
        count: Optional[int] = None,
        binary: bool = False,
        columns: Optional[List[DescriptionType]] = None,
        raw: Optional[bool] = None,
        prep_stmt: Any = None,
    ) -> Tuple[List[RowType], Optional[EofPacketType]]:
        """Get all rows returned by the MySQL server

        This method gets all rows returned by the MySQL server after sending,
        for example, the query command. The result is a tuple consisting of
        a list of rows and the EOF packet.

        Returns a tuple()
        """
        if raw is None:
            raw = self._raw

        if not self.unread_result:
            raise InternalError("No result set available")

        rows: Tuple[List[Tuple[Any, ...]], Optional[EofPacketType]] = ([], None)
        try:
            if binary:
                charset = self.charset
                if charset == "utf8mb4":
                    charset = "utf8"
                rows = self._protocol.read_binary_result(
                    self._socket, columns, count, charset
                )
            else:
                rows = self._protocol.read_text_result(
                    self._socket, self._server_version, count=count
                )
        except Error as err:
            self.unread_result = False
            raise err

        rows, eof_p = rows
        if (
            not (binary or raw)
            and self._columns_desc is not None
            and rows
            and hasattr(self, "converter")
        ):
            row_to_python = self.converter.row_to_python
            rows = [row_to_python(row, self._columns_desc) for row in rows]

        if eof_p is not None:
            self._handle_server_status(
                eof_p["status_flag"]
                if "status_flag" in eof_p
                else eof_p["server_status"]
            )
            self.unread_result = False

        return rows, eof_p

    def consume_results(self) -> None:
        """Consume results"""
        if self.unread_result:
            self.get_rows()

    def cmd_init_db(self, database: str) -> OkPacketType:
        """Change the current database

        This method changes the current (default) database by sending the
        INIT_DB command. The result is a dictionary containing the OK packet
        information.

        Returns a dict()
        """
        return self._handle_ok(
            self._send_cmd(ServerCmd.INIT_DB, database.encode("utf-8"))
        )

    @with_context_propagation
    def cmd_query(
        self,
        query: StrOrBytes,
        raw: bool = False,
        buffered: bool = False,
        raw_as_string: bool = False,
    ) -> ResultType:
        """Send a query to the MySQL server

        This method send the query to the MySQL server and returns the result.

        If there was a text result, a tuple will be returned consisting of
        the number of columns and a list containing information about these
        columns.

        When the query doesn't return a text result, the OK or EOF packet
        information as dictionary will be returned. In case the result was
        an error, exception Error will be raised.

        Returns a tuple()
        """
        if not isinstance(query, bytearray):
            if isinstance(query, str):
                query = query.encode("utf-8")
            query = bytearray(query)
        # Prepare query attrs
        charset = self.charset if self.charset != "utf8mb4" else "utf8"
        packet = bytearray()
        if not self._query_attrs_supported and self._query_attrs:
            warnings.warn(
                "This version of the server does not support Query Attributes",
                category=Warning,
            )
        if self._client_flags & ClientFlag.CLIENT_QUERY_ATTRIBUTES:
            names = []
            types = []
            values: List[bytes] = []
            null_bitmap = [0] * ((len(self._query_attrs) + 7) // 8)
            for pos, attr_tuple in enumerate(self._query_attrs.items()):
                value = attr_tuple[1]
                flags = 0
                if value is None:
                    null_bitmap[(pos // 8)] |= 1 << (pos % 8)
                    types.append(int1store(FieldType.NULL) + int1store(flags))
                    continue
                if isinstance(value, int):
                    (
                        packed,
                        field_type,
                        flags,
                    ) = self._protocol.prepare_binary_integer(value)
                    values.append(packed)
                elif isinstance(value, str):
                    value = value.encode(charset)
                    values.append(lc_int(len(value)) + value)
                    field_type = FieldType.STRING
                elif isinstance(value, bytes):
                    values.append(lc_int(len(value)) + value)
                    field_type = FieldType.STRING
                elif isinstance(value, Decimal):
                    values.append(
                        lc_int(len(str(value).encode(charset)))
                        + str(value).encode(charset)
                    )
                    field_type = FieldType.DECIMAL
                elif isinstance(value, float):
                    values.append(struct.pack("<d", value))
                    field_type = FieldType.DOUBLE
                elif isinstance(value, (datetime.datetime, datetime.date)):
                    (
                        packed,
                        field_type,
                    ) = self._protocol.prepare_binary_timestamp(value)
                    values.append(packed)
                elif isinstance(value, (datetime.timedelta, datetime.time)):
                    (packed, field_type) = self._protocol.prepare_binary_time(value)
                    values.append(packed)
                else:
                    raise ProgrammingError(
                        "MySQL binary protocol can not handle "
                        f"'{value.__class__.__name__}' objects"
                    )
                types.append(int1store(field_type) + int1store(flags))
                name = attr_tuple[0].encode(charset)
                names.append(lc_int(len(name)) + name)

            # int<lenenc>    parameter_count    Number of parameters
            packet.extend(lc_int(len(self._query_attrs)))
            # int<lenenc>    parameter_set_count    Number of parameter sets.
            # Currently always 1
            packet.extend(lc_int(1))
            if values:
                packet.extend(
                    b"".join([struct.pack("B", bit) for bit in null_bitmap])
                    + int1store(1)
                )
                for _type, name in zip(types, names):
                    packet.extend(_type)
                    packet.extend(name)

                for value in values:
                    packet.extend(value)

        packet.extend(query)
        query = bytes(packet)
        try:
            result = self._handle_result(self._send_cmd(ServerCmd.QUERY, query))
        except ProgrammingError as err:
            if err.errno == 3948 and "Loading local data is disabled" in err.msg:
                err_msg = (
                    "LOAD DATA LOCAL INFILE file request rejected due "
                    "to restrictions on access."
                )
                raise DatabaseError(err_msg) from err
            raise
        if self._have_next_result:
            raise InterfaceError(
                "Use cmd_query_iter for statements with multiple queries."
            )

        return result

    def cmd_query_iter(
        self, statements: StrOrBytes
    ) -> Generator[ResultType, None, None]:
        """Send one or more statements to the MySQL server

        Similar to the cmd_query method, but instead returns a generator
        object to iterate through results. It sends the statements to the
        MySQL server and through the iterator you can get the results.

        statement = 'SELECT 1; INSERT INTO t1 VALUES (); SELECT 2'
        for result in cnx.cmd_query(statement, iterate=True):
            if 'columns' in result:
                columns = result['columns']
                rows = cnx.get_rows()
            else:
                # do something useful with INSERT result

        Returns a generator.
        """
        packet = bytearray()
        if not isinstance(statements, bytearray):
            if isinstance(statements, str):
                statements = statements.encode("utf8")
            statements = bytearray(statements)

        if self._client_flags & ClientFlag.CLIENT_QUERY_ATTRIBUTES:
            # int<lenenc>    parameter_count    Number of parameters
            packet.extend(lc_int(0))
            # int<lenenc>    parameter_set_count    Number of parameter sets.
            # Currently always 1
            packet.extend(lc_int(1))

        packet.extend(statements)
        query = bytes(packet)
        # Handle the first query result
        yield self._handle_result(self._send_cmd(ServerCmd.QUERY, query))

        # Handle next results, if any
        while self._have_next_result:
            self.handle_unread_result()
            yield self._handle_result(self._socket.recv())

    def cmd_refresh(self, options: int) -> OkPacketType:
        """Send the Refresh command to the MySQL server

        This method sends the Refresh command to the MySQL server. The options
        argument should be a bitwise value using constants.RefreshOption.
        Usage example:
         RefreshOption = mysql.connector.RefreshOption
         refresh = RefreshOption.LOG | RefreshOption.THREADS
         cnx.cmd_refresh(refresh)

        The result is a dictionary with the OK packet information.

        Returns a dict()
        """
        return self._handle_ok(self._send_cmd(ServerCmd.REFRESH, int4store(options)))

    def cmd_quit(self) -> bytes:
        """Close the current connection with the server

        This method sends the QUIT command to the MySQL server, closing the
        current connection. Since the no response can be returned to the
        client, cmd_quit() will return the packet it send.

        Returns a str()
        """
        self.handle_unread_result()

        packet = self._protocol.make_command(ServerCmd.QUIT)
        self._socket.send(packet, 0, 0)
        return bytes(packet)

    def cmd_shutdown(self, shutdown_type: Optional[int] = None) -> EofPacketType:
        """Shut down the MySQL Server

        This method sends the SHUTDOWN command to the MySQL server and is only
        possible if the current user has SUPER privileges. The result is a
        dictionary containing the OK packet information.

        Note: Most applications and scripts do not the SUPER privilege.

        Returns a dict()
        """
        if shutdown_type:
            if not ShutdownType.get_info(shutdown_type):
                raise InterfaceError("Invalid shutdown type")
            atype = shutdown_type
        else:
            atype = ShutdownType.SHUTDOWN_DEFAULT
        return self._handle_eof(self._send_cmd(ServerCmd.SHUTDOWN, int4store(atype)))

    def cmd_statistics(self) -> StatsPacketType:
        """Send the statistics command to the MySQL Server

        This method sends the STATISTICS command to the MySQL server. The
        result is a dictionary with various statistical information.

        Returns a dict()
        """
        self.handle_unread_result()

        packet = self._protocol.make_command(ServerCmd.STATISTICS)
        self._socket.send(packet, 0, 0)
        return self._protocol.parse_statistics(self._socket.recv())

    def cmd_process_kill(self, mysql_pid: int) -> OkPacketType:
        """Kill a MySQL process

        This method send the PROCESS_KILL command to the server along with
        the process ID. The result is a dictionary with the OK packet
        information.

        Returns a dict()
        """
        return self._handle_ok(
            self._send_cmd(ServerCmd.PROCESS_KILL, int4store(mysql_pid))
        )

    def cmd_debug(self) -> EofPacketType:
        """Send the DEBUG command

        This method sends the DEBUG command to the MySQL server, which
        requires the MySQL user to have SUPER privilege. The output will go
        to the MySQL server error log and the result of this method is a
        dictionary with EOF packet information.

        Returns a dict()
        """
        return self._handle_eof(self._send_cmd(ServerCmd.DEBUG))

    def cmd_ping(self) -> OkPacketType:
        """Send the PING command

        This method sends the PING command to the MySQL server. It is used to
        check if the the connection is still valid. The result of this
        method is dictionary with OK packet information.

        Returns a dict()
        """
        return self._handle_ok(self._send_cmd(ServerCmd.PING))

    def cmd_change_user(
        self,
        username: str = "",
        password: str = "",
        database: str = "",
        charset: int = 45,
        password1: str = "",
        password2: str = "",
        password3: str = "",
        oci_config_file: str = "",
        oci_config_profile: str = "",
    ) -> Optional[OkPacketType]:
        """Change the current logged in user

        This method allows to change the current logged in user information.
        The result is a dictionary with OK packet information.

        Returns a dict()
        """
        if not isinstance(charset, int):
            raise ValueError("charset must be an integer")
        if charset < 0:
            raise ValueError("charset should be either zero or a postive integer")

        self._mfa_nfactor = 1
        self._user = username
        self._password = password
        self._password1 = password1
        self._password2 = password2
        self._password3 = password3

        if self._password1 and password != self._password1:
            password = self._password1

        self.handle_unread_result()

        if self._compress:
            raise NotSupportedError("Change user is not supported with compression")
        packet = self._protocol.make_change_user(
            handshake=self._handshake,
            username=username,
            password=password,
            database=database,
            charset=charset,
            client_flags=self._client_flags,
            ssl_enabled=self._ssl_active,
            auth_plugin=self._auth_plugin,
            conn_attrs=self._conn_attrs,
        )
        self._socket.send(packet, 0, 0)

        if oci_config_file:
            self._oci_config_file = oci_config_file

        self._oci_config_profile = oci_config_profile

        ok_packet = self._auth_switch_request(username, password)

        if not (self._client_flags & ClientFlag.CONNECT_WITH_DB) and database:
            self.cmd_init_db(database)

        self._charset_id = charset
        self._post_connection()

        return ok_packet

    @property
    def database(self) -> str:
        """Get the current database"""
        return self.info_query("SELECT DATABASE()")[0]  # type: ignore[return-value]

    @database.setter
    def database(self, value: str) -> None:
        """Set the current database"""
        self.cmd_init_db(value)

    def is_connected(self) -> bool:
        """Reports whether the connection to MySQL Server is available

        This method checks whether the connection to MySQL is available.
        It is similar to ping(), but unlike the ping()-method, either True
        or False is returned and no exception is raised.

        Returns True or False.
        """
        try:
            self.cmd_ping()
        except Error:
            return False  # This method does not raise
        return True

    def set_allow_local_infile_in_path(self, path: str) -> None:
        """Set the path that user can upload files.

        Args:
            path (str): Path that user can upload files.
        """
        self._allow_local_infile_in_path = path

    def reset_session(
        self,
        user_variables: Optional[Dict[str, Any]] = None,
        session_variables: Optional[Dict[str, Any]] = None,
    ) -> None:
        """Clears the current active session

        This method resets the session state, if the MySQL server is 5.7.3
        or later active session will be reset without re-authenticating.
        For other server versions session will be reset by re-authenticating.

        It is possible to provide a sequence of variables and their values to
        be set after clearing the session. This is possible for both user
        defined variables and session variables.
        This method takes two arguments user_variables and session_variables
        which are dictionaries.

        Raises OperationalError if not connected, InternalError if there are
        unread results and InterfaceError on errors.
        """
        if not self.is_connected():
            raise OperationalError("MySQL Connection not available.")

        if not self.cmd_reset_connection():
            try:
                self.cmd_change_user(
                    self._user,
                    self._password,
                    self._database,
                    self._charset_id,
                    self._password1,
                    self._password2,
                    self._password3,
                    self._oci_config_file,
                    self._oci_config_profile,
                )
            except ProgrammingError:
                self.reconnect()

        cur = self.cursor()
        if user_variables:
            for key, value in user_variables.items():
                cur.execute(f"SET @`{key}` = %s", (value,))
        if session_variables:
            for key, value in session_variables.items():
                cur.execute(f"SET SESSION `{key}` = %s", (value,))

    def ping(self, reconnect: bool = False, attempts: int = 1, delay: int = 0) -> None:
        """Check availability of the MySQL server

        When reconnect is set to True, one or more attempts are made to try
        to reconnect to the MySQL server using the reconnect()-method.

        delay is the number of seconds to wait between each retry.

        When the connection is not available, an InterfaceError is raised. Use
        the is_connected()-method if you just want to check the connection
        without raising an error.

        Raises InterfaceError on errors.
        """
        try:
            self.cmd_ping()
        except Error as err:
            if reconnect:
                self.reconnect(attempts=attempts, delay=delay)
            else:
                raise InterfaceError("Connection to MySQL is not available") from err

    @property
    def connection_id(self) -> Optional[int]:
        """MySQL connection ID"""
        if self._handshake:
            return self._handshake.get("server_threadid")  # type: ignore[return-value]
        return None

    def cursor(
        self,
        buffered: Optional[bool] = None,
        raw: Optional[bool] = None,
        prepared: Optional[bool] = None,
        cursor_class: Optional[Type[MySQLCursor]] = None,
        dictionary: Optional[bool] = None,
        named_tuple: Optional[bool] = None,
    ) -> MySQLCursor:
        """Instantiates and returns a cursor

        By default, MySQLCursor is returned. Depending on the options
        while connecting, a buffered and/or raw cursor is instantiated
        instead. Also depending upon the cursor options, rows can be
        returned as dictionary or named tuple.

        Dictionary and namedtuple based cursors are available with buffered
        output but not raw.

        It is possible to also give a custom cursor through the
        cursor_class parameter, but it needs to be a subclass of
        mysql.connector.cursor.CursorBase.

        Raises ProgrammingError when cursor_class is not a subclass of
        CursorBase. Raises ValueError when cursor is not available.

        Returns a cursor-object
        """
        self.handle_unread_result()

        if not self.is_connected():
            raise OperationalError("MySQL Connection not available")
        if cursor_class is not None:
            if not issubclass(cursor_class, CursorBase):
                raise ProgrammingError(
                    "Cursor class needs be to subclass of cursor.CursorBase"
                )
            return (cursor_class)(self)

        buffered = buffered if buffered is not None else self._buffered
        raw = raw if raw is not None else self._raw

        cursor_type = 0
        if buffered is True:
            cursor_type |= 1
        if raw is True:
            cursor_type |= 2
        if dictionary is True:
            cursor_type |= 4
        if named_tuple is True:
            cursor_type |= 8
        if prepared is True:
            cursor_type |= 16

        types = {
            0: MySQLCursor,  # 0
            1: MySQLCursorBuffered,
            2: MySQLCursorRaw,
            3: MySQLCursorBufferedRaw,
            4: MySQLCursorDict,
            5: MySQLCursorBufferedDict,
            8: MySQLCursorNamedTuple,
            9: MySQLCursorBufferedNamedTuple,
            16: MySQLCursorPrepared,
            18: MySQLCursorPreparedRaw,
            20: MySQLCursorPreparedDict,
            24: MySQLCursorPreparedNamedTuple,
        }
        try:
            return (types[cursor_type])(self)
        except KeyError:
            args = ("buffered", "raw", "dictionary", "named_tuple", "prepared")
            raise ValueError(
                "Cursor not available with given criteria: "
                + ", ".join([args[i] for i in range(5) if cursor_type & (1 << i) != 0])
            ) from None

    def commit(self) -> None:
        """Commit current transaction"""
        self._execute_query("COMMIT")

    def rollback(self) -> None:
        """Rollback current transaction"""
        if self.unread_result:
            self.get_rows()

        self._execute_query("ROLLBACK")

    def _execute_query(self, query: StrOrBytes) -> None:
        """Execute a query

        This method simply calls cmd_query() after checking for unread
        result. If there are still unread result, an InterfaceError
        is raised. Otherwise whatever cmd_query() returns is returned.

        Returns a dict()
        """
        self.handle_unread_result()
        self.cmd_query(query)

    def info_query(self, query: StrOrBytes) -> Optional[RowType]:
        """Send a query which only returns 1 row"""
        cursor = self.cursor(buffered=True)
        cursor.execute(query)
        return cursor.fetchone()

    def _handle_binary_ok(self, packet: bytes) -> Dict[str, int]:
        """Handle a MySQL Binary Protocol OK packet

        This method handles a MySQL Binary Protocol OK packet. When the
        packet is found to be an Error packet, an error will be raised. If
        the packet is neither an OK or an Error packet, InterfaceError
        will be raised.

        Returns a dict()
        """
        if packet[4] == 0:
            return self._protocol.parse_binary_prepare_ok(packet)
        if packet[4] == 255:
            raise get_exception(packet)
        raise InterfaceError("Expected Binary OK packet")

    def _handle_binary_result(
        self, packet: bytes
    ) -> Union[OkPacketType, Tuple[int, List[DescriptionType], EofPacketType]]:
        """Handle a MySQL Result

        This method handles a MySQL result, for example, after sending the
        query command. OK and EOF packets will be handled and returned. If
        the packet is an Error packet, an Error exception will be raised.

        The tuple returned by this method consist of:
        - the number of columns in the result,
        - a list of tuples with information about the columns,
        - the EOF packet information as a dictionary.

        Returns tuple() or dict()
        """
        if not packet or len(packet) < 4:
            raise InterfaceError("Empty response")
        if packet[4] == 0:
            return self._handle_ok(packet)
        if packet[4] == 254:
            return self._handle_eof(packet)
        if packet[4] == 255:
            raise get_exception(packet)

        # We have a binary result set
        column_count = self._protocol.parse_column_count(packet)
        if not column_count or not isinstance(column_count, int):
            raise InterfaceError("Illegal result set.")

        columns: List[DescriptionType] = [None] * column_count
        for i in range(0, column_count):
            columns[i] = self._protocol.parse_column(
                self._socket.recv(), self.python_charset
            )

        eof = self._handle_eof(self._socket.recv())
        return (column_count, columns, eof)

    def cmd_stmt_fetch(self, statement_id: int, rows: int = 1) -> None:
        """Fetch a MySQL statement Result Set

        This method will send the FETCH command to MySQL together with the
        given statement id and the number of rows to fetch.
        """
        packet = self._protocol.make_stmt_fetch(statement_id, rows)
        self.unread_result = False
        self._send_cmd(ServerCmd.STMT_FETCH, packet, expect_response=False)
        self.unread_result = True

    def cmd_stmt_prepare(
        self, statement: bytes
    ) -> Mapping[str, Union[int, List[DescriptionType]]]:
        """Prepare a MySQL statement

        This method will send the PREPARE command to MySQL together with the
        given statement.

        Returns a dict()
        """
        packet = self._send_cmd(ServerCmd.STMT_PREPARE, statement)
        result = self._handle_binary_ok(packet)

        result["columns"] = []
        result["parameters"] = []
        if result["num_params"] > 0:
            for _ in range(0, result["num_params"]):
                result["parameters"].append(
                    self._protocol.parse_column(
                        self._socket.recv(), self.python_charset
                    )
                )
            self._handle_eof(self._socket.recv())
        if result["num_columns"] > 0:
            for _ in range(0, result["num_columns"]):
                result["columns"].append(
                    self._protocol.parse_column(
                        self._socket.recv(), self.python_charset
                    )
                )
            self._handle_eof(self._socket.recv())

        return result

    @with_context_propagation
    def cmd_stmt_execute(
        self,
        statement_id: int,
        data: Sequence[SupportedMysqlBinaryProtocolTypes] = (),
        parameters: Sequence[Any] = (),
        flags: int = 0,
    ) -> Union[OkPacketType, Tuple[int, List[DescriptionType], EofPacketType]]:
        """Execute a prepared MySQL statement"""
        parameters = list(parameters)
        long_data_used = {}

        if data:
            for param_id, _ in enumerate(parameters):
                if isinstance(data[param_id], IOBase):
                    binary = True
                    try:
                        binary = "b" not in data[param_id].mode  # type: ignore[union-attr]
                    except AttributeError:
                        pass
                    self.cmd_stmt_send_long_data(statement_id, param_id, data[param_id])
                    long_data_used[param_id] = (binary,)
        if not self._query_attrs_supported and self._query_attrs:
            warnings.warn(
                "This version of the server does not support Query Attributes",
                category=Warning,
            )
        if self._client_flags & ClientFlag.CLIENT_QUERY_ATTRIBUTES:
            execute_packet = self._protocol.make_stmt_execute(
                statement_id,
                data,
                tuple(parameters),
                flags,
                long_data_used,
                self.charset,
                self.query_attrs,
                self._converter_str_fallback,
            )
        else:
            execute_packet = self._protocol.make_stmt_execute(
                statement_id,
                data,
                tuple(parameters),
                flags,
                long_data_used,
                self.charset,
                converter_str_fallback=self._converter_str_fallback,
            )
        packet = self._send_cmd(ServerCmd.STMT_EXECUTE, packet=execute_packet)
        result = self._handle_binary_result(packet)
        return result

    def cmd_stmt_close(self, statement_id: int) -> None:
        """Deallocate a prepared MySQL statement

        This method deallocates the prepared statement using the
        statement_id. Note that the MySQL server does not return
        anything.
        """
        self._send_cmd(
            ServerCmd.STMT_CLOSE,
            int4store(statement_id),
            expect_response=False,
        )

    def cmd_stmt_send_long_data(
        self, statement_id: int, param_id: int, data: BinaryIO
    ) -> int:
        """Send data for a column

        This methods send data for a column (for example BLOB) for statement
        identified by statement_id. The param_id indicate which parameter
        the data belongs too.
        The data argument should be a file-like object.

        Since MySQL does not send anything back, no error is raised. When
        the MySQL server is not reachable, an OperationalError is raised.

        cmd_stmt_send_long_data should be called before cmd_stmt_execute.

        The total bytes send is returned.

        Returns int.
        """
        chunk_size = 131072  # 128 KB
        total_sent = 0
        try:
            buf = data.read(chunk_size)
            while buf:
                packet = self._protocol.prepare_stmt_send_long_data(
                    statement_id, param_id, buf
                )
                self._send_cmd(
                    ServerCmd.STMT_SEND_LONG_DATA,
                    packet=packet,
                    expect_response=False,
                )
                total_sent += len(buf)
                buf = data.read(chunk_size)
        except AttributeError as err:
            raise OperationalError("MySQL Connection not available") from err

        return total_sent

    def cmd_stmt_reset(self, statement_id: int) -> None:
        """Reset data for prepared statement sent as long data

        The result is a dictionary with OK packet information.

        Returns a dict()
        """
        self._handle_ok(self._send_cmd(ServerCmd.STMT_RESET, int4store(statement_id)))

    def cmd_reset_connection(self) -> bool:
        """Resets the session state without re-authenticating

        Reset command only works on MySQL server 5.7.3 or later.
        The result is True for a successful reset otherwise False.

        Returns bool
        """
        try:
            self._handle_ok(self._send_cmd(ServerCmd.RESET_CONNECTION))
            self._post_connection()
            return True
        except (NotSupportedError, OperationalError):
            return False

    def handle_unread_result(self) -> None:
        """Check whether there is an unread result"""
        if self.can_consume_results:
            self.consume_results()
        elif self.unread_result:
            raise InternalError("Unread result found")

virt/lib/python3.9/site-packages/mysql/connector/connection 2.py
PrinceDisant/LostInNL
# Copyright (c) 2009, 2023, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

# mypy: disable-error-code="arg-type,operator,attr-defined,assignment"

"""Implementing communication with MySQL servers."""

import datetime
import getpass
import os
import socket
import struct
import sys
import warnings

from decimal import Decimal
from io import IOBase
from typing import (
    Any,
    BinaryIO,
    Dict,
    Generator,
    List,
    Mapping,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
)

from . import version
from .abstracts import MySQLConnectionAbstract
from .authentication import get_auth_plugin
from .constants import (
    CharacterSet,
    ClientFlag,
    FieldType,
    ServerCmd,
    ServerFlag,
    ShutdownType,
    flag_is_set,
)
from .conversion import MySQLConverter
from .cursor import (
    CursorBase,
    MySQLCursor,
    MySQLCursorBuffered,
    MySQLCursorBufferedDict,
    MySQLCursorBufferedNamedTuple,
    MySQLCursorBufferedRaw,
    MySQLCursorDict,
    MySQLCursorNamedTuple,
    MySQLCursorPrepared,
    MySQLCursorPreparedDict,
    MySQLCursorPreparedNamedTuple,
    MySQLCursorPreparedRaw,
    MySQLCursorRaw,
)
from .errors import (
    DatabaseError,
    Error,
    InterfaceError,
    InternalError,
    NotSupportedError,
    OperationalError,
    ProgrammingError,
    get_exception,
)
from .logger import logger
from .network import MySQLSocket, MySQLTCPSocket, MySQLUnixSocket
from .opentelemetry.constants import OTEL_ENABLED
from .opentelemetry.context_propagation import with_context_propagation
from .plugins import BaseAuthPlugin
from .protocol import MySQLProtocol
from .types import (
    ConnAttrsType,
    DescriptionType,
    EofPacketType,
    HandShakeType,
    OkPacketType,
    ResultType,
    RowType,
    StatsPacketType,
    StrOrBytes,
    SupportedMysqlBinaryProtocolTypes,
)
from .utils import get_platform, int1store, int4store, lc_int

if OTEL_ENABLED:
    from .opentelemetry.instrumentation import end_span, record_exception_event


class MySQLConnection(MySQLConnectionAbstract):
    """Connection to a MySQL Server"""

    def __init__(self, **kwargs: Any) -> None:
        self._protocol: Optional[MySQLProtocol] = None
        self._socket: Optional[MySQLSocket] = None
        self._handshake: Optional[HandShakeType] = None
        super().__init__()

        self._converter_class: Type[MySQLConverter] = MySQLConverter

        self._client_flags: int = ClientFlag.get_default()
        self._charset_id: int = 45
        self._sql_mode: Optional[str] = None
        self._time_zone: Optional[str] = None
        self._autocommit: bool = False

        self._user: str = ""
        self._password: str = ""
        self._database: str = ""
        self._host: str = "127.0.0.1"
        self._port: int = 3306
        self._unix_socket: Optional[str] = None
        self._client_host: str = ""
        self._client_port: int = 0
        self._ssl: Dict[str, Optional[Union[str, bool, List[str]]]] = {}
        self._force_ipv6: bool = False

        self._use_unicode: bool = True
        self._get_warnings: bool = False
        self._raise_on_warnings: bool = False
        self._buffered: bool = False
        self._unread_result: bool = False
        self._have_next_result: bool = False
        self._raw: bool = False
        self._in_transaction: bool = False

        self._prepared_statements: Any = None

        self._ssl_active: bool = False
        self._auth_plugin: Optional[str] = None
        self._krb_service_principal: Optional[str] = None
        self._pool_config_version: Any = None
        self._query_attrs_supported: int = False

        self._columns_desc: List[DescriptionType] = []
        self._mfa_nfactor: int = 1

        if kwargs:
            try:
                self.connect(**kwargs)
            except Exception:
                # Tidy-up underlying socket on failure
                self.close()
                self._socket = None
                raise

    def _add_default_conn_attrs(self) -> None:
        """Add the default connection attributes."""
        platform = get_platform()
        license_chunks = version.LICENSE.split(" ")
        if license_chunks[0] == "GPLv2":
            client_license = "GPL-2.0"
        else:
            client_license = "Commercial"
        default_conn_attrs = {
            "_pid": str(os.getpid()),
            "_platform": platform["arch"],
            "_source_host": socket.gethostname(),
            "_client_name": "mysql-connector-python",
            "_client_license": client_license,
            "_client_version": ".".join([str(x) for x in version.VERSION[0:3]]),
            "_os": platform["version"],
        }

        self._conn_attrs.update((default_conn_attrs))

    def _do_handshake(self) -> None:
        """Get the handshake from the MySQL server"""
        packet = self._socket.recv()
        if packet[4] == 255:
            raise get_exception(packet)

        self._handshake = None
        handshake = self._protocol.parse_handshake(packet)

        server_version = handshake["server_version_original"]

        self._server_version = self._check_server_version(
            server_version
            if isinstance(server_version, (str, bytes, bytearray))
            else "Unknown"
        )
        CharacterSet.set_mysql_version(self._server_version)

        if not handshake["capabilities"] & ClientFlag.SSL:
            if self._auth_plugin == "mysql_clear_password" and not self.is_secure:
                raise InterfaceError(
                    "Clear password authentication is not supported over "
                    "insecure channels"
                )
            if self._ssl.get("verify_cert"):
                raise InterfaceError(
                    "SSL is required but the server doesn't support it",
                    errno=2026,
                )
            self._client_flags &= ~ClientFlag.SSL
        elif not self._ssl_disabled:
            self._client_flags |= ClientFlag.SSL

        if handshake["capabilities"] & ClientFlag.PLUGIN_AUTH:
            self.set_client_flags([ClientFlag.PLUGIN_AUTH])

        if handshake["capabilities"] & ClientFlag.CLIENT_QUERY_ATTRIBUTES:
            self._query_attrs_supported = True
            self.set_client_flags([ClientFlag.CLIENT_QUERY_ATTRIBUTES])

        if handshake["capabilities"] & ClientFlag.MULTI_FACTOR_AUTHENTICATION:
            self.set_client_flags([ClientFlag.MULTI_FACTOR_AUTHENTICATION])

        self._handshake = handshake

    def _do_auth(
        self,
        username: Optional[str] = None,
        password: Optional[str] = None,
        database: Optional[str] = None,
        client_flags: int = 0,
        charset: int = 45,
        ssl_options: Optional[Dict[str, Optional[Union[str, bool, List[str]]]]] = None,
        conn_attrs: Optional[ConnAttrsType] = None,
    ) -> bool:
        """Authenticate with the MySQL server

        Authentication happens in two parts. We first send a response to the
        handshake. The MySQL server will then send either an AuthSwitchRequest
        or an error packet.

        Raises NotSupportedError when we get the old, insecure password
        reply back. Raises any error coming from MySQL.
        """
        self._ssl_active = False
        if ssl_options is None:
            ssl_options = {}
        if not self._ssl_disabled and (client_flags & ClientFlag.SSL):
            packet = self._protocol.make_auth_ssl(
                charset=charset, client_flags=client_flags
            )
            self._socket.send(packet)
            if ssl_options.get("tls_ciphersuites") is not None:
                tls_ciphersuites = ":".join(ssl_options.get("tls_ciphersuites"))
            else:
                tls_ciphersuites = ""
            self._socket.switch_to_ssl(
                ssl_options.get("ca"),
                ssl_options.get("cert"),
                ssl_options.get("key"),
                ssl_options.get("verify_cert") or False,
                ssl_options.get("verify_identity") or False,
                tls_ciphersuites,
                ssl_options.get("tls_versions"),
            )
            self._ssl_active = True

        if self._password1 and password != self._password1:
            password = self._password1

        logger.debug("# _do_auth(): self._auth_plugin: %s", self._auth_plugin)
        if (
            self._auth_plugin.startswith("authentication_oci")
            or (
                self._auth_plugin.startswith("authentication_kerberos")
                and os.name == "nt"
            )
        ) and not username:
            username = getpass.getuser()
            logger.debug(
                "MySQL user is empty, OS user: %s will be used for %s",
                username,
                self._auth_plugin,
            )

        packet = self._protocol.make_auth(
            handshake=self._handshake,
            username=username,
            password=password,
            database=database,
            charset=charset,
            client_flags=client_flags,
            ssl_enabled=self._ssl_active,
            auth_plugin=self._auth_plugin,
            conn_attrs=conn_attrs,
            auth_plugin_class=self._auth_plugin_class,
        )
        self._socket.send(packet)
        self._auth_switch_request(username, password)

        if not (client_flags & ClientFlag.CONNECT_WITH_DB) and database:
            self.cmd_init_db(database)

        return True

    def _auth_switch_request(
        self, username: Optional[str] = None, password: Optional[str] = None
    ) -> Optional[OkPacketType]:
        """Handle second part of authentication

        Raises NotSupportedError when we get the old, insecure password
        reply back. Raises any error coming from MySQL.
        """
        auth = None
        new_auth_plugin: Optional[str] = (
            self._auth_plugin or self._handshake["auth_plugin"]
        )
        logger.debug("new_auth_plugin: %s", new_auth_plugin)
        packet = self._socket.recv()
        if packet[4] == 254 and len(packet) == 5:
            raise NotSupportedError(
                "Authentication with old (insecure) passwords "
                "is not supported. For more information, lookup "
                "Password Hashing in the latest MySQL manual"
            )
        if packet[4] == 254:
            # AuthSwitchRequest
            (
                new_auth_plugin,
                auth_data,
            ) = self._protocol.parse_auth_switch_request(packet)
            auth = get_auth_plugin(new_auth_plugin, self._auth_plugin_class)(
                auth_data,
                username=username or self._user,
                password=password,
                ssl_enabled=self.is_secure,
            )
            packet = self._auth_continue(auth, new_auth_plugin, auth_data)

        if packet[4] == 1:
            auth_data = self._protocol.parse_auth_more_data(packet)
            auth = get_auth_plugin(new_auth_plugin, self._auth_plugin_class)(
                auth_data, password=password, ssl_enabled=self.is_secure
            )
            if new_auth_plugin == "caching_sha2_password":
                response = auth.auth_response()
                if response:
                    self._socket.send(response)
                packet = self._socket.recv()

        if packet[4] == 0:
            return self._handle_ok(packet)
        if packet[4] == 2:
            return self._handle_mfa(packet)
        if packet[4] == 255:
            raise get_exception(packet)
        return None

    def _handle_mfa(self, packet: bytes) -> Optional[OkPacketType]:
        """Handle Multi Factor Authentication."""
        self._mfa_nfactor += 1
        if self._mfa_nfactor == 2:
            password = self._password2
        elif self._mfa_nfactor == 3:
            password = self._password3
        else:
            raise InterfaceError(
                "Failed Multi Factor Authentication (invalid N factor)"
            )

        logger.debug("# MFA N Factor #%d", self._mfa_nfactor)

        packet, auth_plugin = self._protocol.parse_auth_next_factor(packet[4:])
        auth = get_auth_plugin(auth_plugin, self._auth_plugin_class)(
            None,
            username=self._user,
            password=password,
            ssl_enabled=self.is_secure,
        )
        packet = self._auth_continue(auth, auth_plugin, packet)

        if packet[4] == 1:
            auth_data = self._protocol.parse_auth_more_data(packet)
            auth = get_auth_plugin(auth_plugin, self._auth_plugin_class)(
                auth_data, password=password, ssl_enabled=self.is_secure
            )
            if auth_plugin == "caching_sha2_password":
                response = auth.auth_response()
                if response:
                    self._socket.send(response)
                packet = self._socket.recv()

        if packet[4] == 0:
            return self._handle_ok(packet)
        if packet[4] == 2:
            return self._handle_mfa(packet)
        if packet[4] == 255:
            raise get_exception(packet)
        return None

    def _auth_continue(
        self, auth: BaseAuthPlugin, auth_plugin: str, auth_data: bytes
    ) -> bytes:
        """Continue with the authentication."""
        if auth_plugin == "authentication_ldap_sasl_client":
            logger.debug("# auth_data: %s", auth_data)
            response = auth.auth_response(self._krb_service_principal)
        elif auth_plugin == "authentication_kerberos_client":
            logger.debug("# auth_data: %s", auth_data)
            response = auth.auth_response(auth_data)
        elif auth_plugin == "authentication_oci_client":
            logger.debug("# oci configuration file path: %s", self._oci_config_file)
            auth.oci_config_file = self._oci_config_file
            auth.oci_config_profile = self._oci_config_profile
            response = auth.auth_response()
        else:
            response = auth.auth_response()

        logger.debug("# request: %s size: %s", response, len(response))
        self._socket.send(response)
        packet = self._socket.recv()
        logger.debug("# server response packet: %s", packet)
        if (
            auth_plugin == "authentication_ldap_sasl_client"
            and len(packet) >= 6
            and packet[5] == 114
            and packet[6] == 61
        ):  # 'r' and '='
            # Continue with sasl authentication
            dec_response = packet[5:]
            cresponse = auth.auth_continue(dec_response)
            self._socket.send(cresponse)
            packet = self._socket.recv()
            if packet[5] == 118 and packet[6] == 61:  # 'v' and '='
                if auth.auth_finalize(packet[5:]):
                    # receive packed OK
                    packet = self._socket.recv()
        elif (
            auth_plugin == "authentication_ldap_sasl_client"
            and auth_data == b"GSSAPI"
            and packet[4] != 255
        ):
            rcode_size = 5  # header size for the response status code.
            logger.debug("# Continue with sasl GSSAPI authentication")
            logger.debug("# response header: %s", packet[: rcode_size + 1])
            logger.debug("# response size: %s", len(packet))

            logger.debug("# Negotiate a service request")
            complete = False
            tries = 0  # To avoid a infinite loop attempt no more than feedback messages
            while not complete and tries < 5:
                logger.debug("%s Attempt %s %s", "-" * 20, tries + 1, "-" * 20)
                logger.debug("<< server response: %s", packet)
                logger.debug("# response code: %s", packet[: rcode_size + 1])
                step, complete = auth.auth_continue_krb(packet[rcode_size:])
                logger.debug(" >> response to server: %s", step)
                self._socket.send(step or b"")
                packet = self._socket.recv()
                tries += 1
            if not complete:
                raise InterfaceError(
                    f"Unable to fulfill server request after {tries} "
                    f"attempts. Last server response: {packet}"
                )
            logger.debug(
                " last GSSAPI response from server: %s length: %d",
                packet,
                len(packet),
            )
            last_step = auth.auth_accept_close_handshake(packet[rcode_size:])
            logger.debug(
                " >> last response to server: %s length: %d",
                last_step,
                len(last_step),
            )
            self._socket.send(last_step)
            # Receive final handshake from server
            packet = self._socket.recv()
            logger.debug("<< final handshake from server: %s", packet)

            # receive OK packet from server.
            packet = self._socket.recv()
            logger.debug("<< ok packet from server: %s", packet)
        elif auth_plugin == "authentication_kerberos_client" and packet[4] != 255:
            rcode_size = 5  # Reader size for the response status code
            logger.debug("# Continue with GSSAPI authentication")
            logger.debug("# Response header: %s", packet[: rcode_size + 1])
            logger.debug("# Response size: %s", len(packet))
            logger.debug("# Negotiate a service request")
            complete = False
            tries = 0

            while not complete and tries < 5:
                logger.debug("%s Attempt %s %s", "-" * 20, tries + 1, "-" * 20)
                logger.debug("<< Server response: %s", packet)
                logger.debug("# Response code: %s", packet[: rcode_size + 1])
                token, complete = auth.auth_continue(packet[rcode_size:])
                if token:
                    self._socket.send(token)
                if complete:
                    break
                packet = self._socket.recv()

                logger.debug(">> Response to server: %s", token)
                tries += 1

            if not complete:
                raise InterfaceError(
                    f"Unable to fulfill server request after {tries} "
                    f"attempts. Last server response: {packet}"
                )

            logger.debug(
                "Last response from server: %s length: %d",
                packet,
                len(packet),
            )

            # Receive OK packet from server.
            packet = self._socket.recv()
            logger.debug("<< Ok packet from server: %s", packet)

        return bytes(packet)

    def _get_connection(self) -> MySQLSocket:
        """Get connection based on configuration

        This method will return the appropriated connection object using
        the connection parameters.

        Returns subclass of MySQLBaseSocket.
        """
        conn: Optional[MySQLSocket] = None
        if self._unix_socket and os.name == "posix":
            conn = MySQLUnixSocket(unix_socket=self.unix_socket)
        else:
            conn = MySQLTCPSocket(
                host=self.server_host,
                port=self.server_port,
                force_ipv6=self._force_ipv6,
            )

        conn.set_connection_timeout(self._connection_timeout)
        return conn

    def _open_connection(self) -> None:
        """Open the connection to the MySQL server

        This method sets up and opens the connection to the MySQL server.

        Raises on errors.
        """
        if self._auth_plugin == "authentication_kerberos_client" and not self._user:
            cls = get_auth_plugin(self._auth_plugin, self._auth_plugin_class)
            self._user = cls.get_user_from_credentials()

        self._protocol = MySQLProtocol()
        self._socket = self._get_connection()
        try:
            self._socket.open_connection()

            # do initial handshake
            self._do_handshake()

            # start authentication negotiation
            self._do_auth(
                self._user,
                self._password,
                self._database,
                self._client_flags,
                self._charset_id,
                self._ssl,
                self._conn_attrs,
            )
            self.set_converter_class(self._converter_class)

            if self._client_flags & ClientFlag.COMPRESS:
                # update the network layer accordingly
                self._socket.switch_to_compressed_mode()

            self._socket.set_connection_timeout(None)
        except Exception:
            # close socket
            self._socket.close_connection()
            raise

        if (
            not self._ssl_disabled
            and hasattr(self._socket.sock, "version")
            and callable(self._socket.sock.version)
        ):
            # Raise a deprecation warning if TLSv1 or TLSv1.1 is being used
            tls_version = self._socket.sock.version()
            if tls_version in ("TLSv1", "TLSv1.1"):
                warn_msg = (
                    f"This connection is using {tls_version} which is now "
                    "deprecated and will be removed in a future release of "
                    "MySQL Connector/Python"
                )
                warnings.warn(warn_msg, DeprecationWarning)

    def shutdown(self) -> None:
        """Shut down connection to MySQL Server."""
        if not self._socket:
            return

        try:
            self._socket.shutdown()
        except (AttributeError, Error):
            pass  # Getting an exception would mean we are disconnected.

    def close(self) -> None:
        """Disconnect from the MySQL server"""
        if self._span and self._span.is_recording():
            record_exception_event(self._span, sys.exc_info()[1])

        if not self._socket:
            return

        try:
            self.cmd_quit()
        except (AttributeError, Error):
            pass  # Getting an exception would mean we are disconnected.

        try:
            self._socket.close_connection()
        except Exception as err:
            if OTEL_ENABLED:
                record_exception_event(self._span, err)
            raise
        finally:
            if OTEL_ENABLED:
                end_span(self._span)

        self._handshake = None

    disconnect = close

    def _send_cmd(
        self,
        command: int,
        argument: Optional[bytes] = None,
        packet_number: int = 0,
        packet: Optional[bytes] = None,
        expect_response: bool = True,
        compressed_packet_number: int = 0,
    ) -> Optional[bytearray]:
        """Send a command to the MySQL server

        This method sends a command with an optional argument.
        If packet is not None, it will be sent and the argument will be
        ignored.

        The packet_number is optional and should usually not be used.

        Some commands might not result in the MySQL server returning
        a response. If a command does not return anything, you should
        set expect_response to False. The _send_cmd method will then
        return None instead of a MySQL packet.

        Returns a MySQL packet or None.
        """
        self.handle_unread_result()

        try:
            self._socket.send(
                self._protocol.make_command(command, packet or argument),
                packet_number,
                compressed_packet_number,
            )
        except AttributeError as err:
            raise OperationalError("MySQL Connection not available") from err

        if not expect_response:
            return None
        return self._socket.recv()

    def _send_data(self, data_file: BinaryIO, send_empty_packet: bool = False) -> bytes:
        """Send data to the MySQL server

        This method accepts a file-like object and sends its data
        as is to the MySQL server. If the send_empty_packet is
        True, it will send an extra empty package (for example
        when using LOAD LOCAL DATA INFILE).

        Returns a MySQL packet.
        """
        self.handle_unread_result()

        if not hasattr(data_file, "read"):
            raise ValueError("expecting a file-like object")

        chunk_size = 131072  # 128 KB
        try:
            buf = data_file.read(chunk_size - 16)
            while buf:
                self._socket.send(buf)
                buf = data_file.read(chunk_size - 16)
        except AttributeError as err:
            raise OperationalError("MySQL Connection not available") from err

        if send_empty_packet:
            try:
                self._socket.send(b"")
            except AttributeError as err:
                raise OperationalError("MySQL Connection not available") from err

        return bytes(self._socket.recv())

    def _handle_server_status(self, flags: int) -> None:
        """Handle the server flags found in MySQL packets

        This method handles the server flags send by MySQL OK and EOF
        packets. It, for example, checks whether there exists more result
        sets or whether there is an ongoing transaction.
        """
        self._have_next_result = flag_is_set(ServerFlag.MORE_RESULTS_EXISTS, flags)
        self._in_transaction = flag_is_set(ServerFlag.STATUS_IN_TRANS, flags)

    @property
    def in_transaction(self) -> bool:
        """MySQL session has started a transaction"""
        return self._in_transaction

    def _handle_ok(self, packet: bytes) -> OkPacketType:
        """Handle a MySQL OK packet

        This method handles a MySQL OK packet. When the packet is found to
        be an Error packet, an error will be raised. If the packet is neither
        an OK or an Error packet, InterfaceError will be raised.

        Returns a dict()
        """
        if packet[4] == 0:
            ok_pkt = self._protocol.parse_ok(packet)
            self._handle_server_status(ok_pkt["status_flag"])
            return ok_pkt
        if packet[4] == 255:
            raise get_exception(packet)
        raise InterfaceError("Expected OK packet")

    def _handle_eof(self, packet: bytes) -> EofPacketType:
        """Handle a MySQL EOF packet

        This method handles a MySQL EOF packet. When the packet is found to
        be an Error packet, an error will be raised. If the packet is neither
        and OK or an Error packet, InterfaceError will be raised.

        Returns a dict()
        """
        if packet[4] == 254:
            eof = self._protocol.parse_eof(packet)
            self._handle_server_status(eof["status_flag"])
            return eof
        if packet[4] == 255:
            raise get_exception(packet)
        raise InterfaceError("Expected EOF packet")

    def _handle_load_data_infile(self, filename: str) -> OkPacketType:
        """Handle a LOAD DATA INFILE LOCAL request"""
        file_name = os.path.abspath(filename)
        if os.path.islink(file_name):
            raise OperationalError("Use of symbolic link is not allowed")
        if not self._allow_local_infile and not self._allow_local_infile_in_path:
            raise DatabaseError(
                "LOAD DATA LOCAL INFILE file request rejected due to "
                "restrictions on access."
            )
        if not self._allow_local_infile and self._allow_local_infile_in_path:
            # validate filename is inside of allow_local_infile_in_path path.
            infile_path = os.path.abspath(self._allow_local_infile_in_path)
            c_path = None
            try:
                c_path = os.path.commonpath([infile_path, file_name])
            except ValueError as err:
                err_msg = (
                    "{} while loading file `{}` and path `{}` given"
                    " in allow_local_infile_in_path"
                )
                raise InterfaceError(
                    err_msg.format(str(err), file_name, infile_path)
                ) from err

            if c_path != infile_path:
                err_msg = (
                    "The file `{}` is not found in the given "
                    "allow_local_infile_in_path {}"
                )
                raise DatabaseError(err_msg.format(file_name, infile_path))

        try:
            data_file = open(file_name, "rb")  # pylint: disable=consider-using-with
            return self._handle_ok(self._send_data(data_file, send_empty_packet=True))
        except IOError:
            # Send a empty packet to cancel the operation
            try:
                self._socket.send(b"")
            except AttributeError as err:
                raise OperationalError("MySQL Connection not available") from err
            raise InterfaceError(f"File '{file_name}' could not be read") from None
        finally:
            try:
                data_file.close()
            except (IOError, NameError):
                pass

    def _handle_result(self, packet: bytes) -> ResultType:
        """Handle a MySQL Result

        This method handles a MySQL result, for example, after sending the
        query command. OK and EOF packets will be handled and returned. If
        the packet is an Error packet, an Error-exception will be
        raised.

        The dictionary returned of:
        - columns: column information
        - eof: the EOF-packet information

        Returns a dict()
        """
        if not packet or len(packet) < 4:
            raise InterfaceError("Empty response")
        if packet[4] == 0:
            return self._handle_ok(packet)
        if packet[4] == 251:
            filename = packet[5:].decode()
            return self._handle_load_data_infile(filename)
        if packet[4] == 254:
            return self._handle_eof(packet)
        if packet[4] == 255:
            raise get_exception(packet)

        # We have a text result set
        column_count = self._protocol.parse_column_count(packet)
        if not column_count or not isinstance(column_count, int):
            raise InterfaceError("Illegal result set")

        self._columns_desc = [
            None,
        ] * column_count
        for i in range(0, column_count):
            self._columns_desc[i] = self._protocol.parse_column(
                self._socket.recv(), self.python_charset
            )

        eof = self._handle_eof(self._socket.recv())
        self.unread_result = True
        return {"columns": self._columns_desc, "eof": eof}

    def get_row(
        self,
        binary: bool = False,
        columns: Optional[List[DescriptionType]] = None,
        raw: Optional[bool] = None,
    ) -> Tuple[Optional[RowType], Optional[EofPacketType]]:
        """Get the next rows returned by the MySQL server

        This method gets one row from the result set after sending, for
        example, the query command. The result is a tuple consisting of the
        row and the EOF packet.
        If no row was available in the result set, the row data will be None.

        Returns a tuple.
        """
        (rows, eof) = self.get_rows(count=1, binary=binary, columns=columns, raw=raw)
        if rows:
            return (rows[0], eof)
        return (None, eof)

    def get_rows(
        self,
        count: Optional[int] = None,
        binary: bool = False,
        columns: Optional[List[DescriptionType]] = None,
        raw: Optional[bool] = None,
        prep_stmt: Any = None,
    ) -> Tuple[List[RowType], Optional[EofPacketType]]:
        """Get all rows returned by the MySQL server

        This method gets all rows returned by the MySQL server after sending,
        for example, the query command. The result is a tuple consisting of
        a list of rows and the EOF packet.

        Returns a tuple()
        """
        if raw is None:
            raw = self._raw

        if not self.unread_result:
            raise InternalError("No result set available")

        rows: Tuple[List[Tuple[Any, ...]], Optional[EofPacketType]] = ([], None)
        try:
            if binary:
                charset = self.charset
                if charset == "utf8mb4":
                    charset = "utf8"
                rows = self._protocol.read_binary_result(
                    self._socket, columns, count, charset
                )
            else:
                rows = self._protocol.read_text_result(
                    self._socket, self._server_version, count=count
                )
        except Error as err:
            self.unread_result = False
            raise err

        rows, eof_p = rows
        if (
            not (binary or raw)
            and self._columns_desc is not None
            and rows
            and hasattr(self, "converter")
        ):
            row_to_python = self.converter.row_to_python
            rows = [row_to_python(row, self._columns_desc) for row in rows]

        if eof_p is not None:
            self._handle_server_status(
                eof_p["status_flag"]
                if "status_flag" in eof_p
                else eof_p["server_status"]
            )
            self.unread_result = False

        return rows, eof_p

    def consume_results(self) -> None:
        """Consume results"""
        if self.unread_result:
            self.get_rows()

    def cmd_init_db(self, database: str) -> OkPacketType:
        """Change the current database

        This method changes the current (default) database by sending the
        INIT_DB command. The result is a dictionary containing the OK packet
        information.

        Returns a dict()
        """
        return self._handle_ok(
            self._send_cmd(ServerCmd.INIT_DB, database.encode("utf-8"))
        )

    @with_context_propagation
    def cmd_query(
        self,
        query: StrOrBytes,
        raw: bool = False,
        buffered: bool = False,
        raw_as_string: bool = False,
    ) -> ResultType:
        """Send a query to the MySQL server

        This method send the query to the MySQL server and returns the result.

        If there was a text result, a tuple will be returned consisting of
        the number of columns and a list containing information about these
        columns.

        When the query doesn't return a text result, the OK or EOF packet
        information as dictionary will be returned. In case the result was
        an error, exception Error will be raised.

        Returns a tuple()
        """
        if not isinstance(query, bytearray):
            if isinstance(query, str):
                query = query.encode("utf-8")
            query = bytearray(query)
        # Prepare query attrs
        charset = self.charset if self.charset != "utf8mb4" else "utf8"
        packet = bytearray()
        if not self._query_attrs_supported and self._query_attrs:
            warnings.warn(
                "This version of the server does not support Query Attributes",
                category=Warning,
            )
        if self._client_flags & ClientFlag.CLIENT_QUERY_ATTRIBUTES:
            names = []
            types = []
            values: List[bytes] = []
            null_bitmap = [0] * ((len(self._query_attrs) + 7) // 8)
            for pos, attr_tuple in enumerate(self._query_attrs.items()):
                value = attr_tuple[1]
                flags = 0
                if value is None:
                    null_bitmap[(pos // 8)] |= 1 << (pos % 8)
                    types.append(int1store(FieldType.NULL) + int1store(flags))
                    continue
                if isinstance(value, int):
                    (
                        packed,
                        field_type,
                        flags,
                    ) = self._protocol.prepare_binary_integer(value)
                    values.append(packed)
                elif isinstance(value, str):
                    value = value.encode(charset)
                    values.append(lc_int(len(value)) + value)
                    field_type = FieldType.STRING
                elif isinstance(value, bytes):
                    values.append(lc_int(len(value)) + value)
                    field_type = FieldType.STRING
                elif isinstance(value, Decimal):
                    values.append(
                        lc_int(len(str(value).encode(charset)))
                        + str(value).encode(charset)
                    )
                    field_type = FieldType.DECIMAL
                elif isinstance(value, float):
                    values.append(struct.pack("<d", value))
                    field_type = FieldType.DOUBLE
                elif isinstance(value, (datetime.datetime, datetime.date)):
                    (
                        packed,
                        field_type,
                    ) = self._protocol.prepare_binary_timestamp(value)
                    values.append(packed)
                elif isinstance(value, (datetime.timedelta, datetime.time)):
                    (packed, field_type) = self._protocol.prepare_binary_time(value)
                    values.append(packed)
                else:
                    raise ProgrammingError(
                        "MySQL binary protocol can not handle "
                        f"'{value.__class__.__name__}' objects"
                    )
                types.append(int1store(field_type) + int1store(flags))
                name = attr_tuple[0].encode(charset)
                names.append(lc_int(len(name)) + name)

            # int<lenenc>    parameter_count    Number of parameters
            packet.extend(lc_int(len(self._query_attrs)))
            # int<lenenc>    parameter_set_count    Number of parameter sets.
            # Currently always 1
            packet.extend(lc_int(1))
            if values:
                packet.extend(
                    b"".join([struct.pack("B", bit) for bit in null_bitmap])
                    + int1store(1)
                )
                for _type, name in zip(types, names):
                    packet.extend(_type)
                    packet.extend(name)

                for value in values:
                    packet.extend(value)

        packet.extend(query)
        query = bytes(packet)
        try:
            result = self._handle_result(self._send_cmd(ServerCmd.QUERY, query))
        except ProgrammingError as err:
            if err.errno == 3948 and "Loading local data is disabled" in err.msg:
                err_msg = (
                    "LOAD DATA LOCAL INFILE file request rejected due "
                    "to restrictions on access."
                )
                raise DatabaseError(err_msg) from err
            raise
        if self._have_next_result:
            raise InterfaceError(
                "Use cmd_query_iter for statements with multiple queries."
            )

        return result

    def cmd_query_iter(
        self, statements: StrOrBytes
    ) -> Generator[ResultType, None, None]:
        """Send one or more statements to the MySQL server

        Similar to the cmd_query method, but instead returns a generator
        object to iterate through results. It sends the statements to the
        MySQL server and through the iterator you can get the results.

        statement = 'SELECT 1; INSERT INTO t1 VALUES (); SELECT 2'
        for result in cnx.cmd_query(statement, iterate=True):
            if 'columns' in result:
                columns = result['columns']
                rows = cnx.get_rows()
            else:
                # do something useful with INSERT result

        Returns a generator.
        """
        packet = bytearray()
        if not isinstance(statements, bytearray):
            if isinstance(statements, str):
                statements = statements.encode("utf8")
            statements = bytearray(statements)

        if self._client_flags & ClientFlag.CLIENT_QUERY_ATTRIBUTES:
            # int<lenenc>    parameter_count    Number of parameters
            packet.extend(lc_int(0))
            # int<lenenc>    parameter_set_count    Number of parameter sets.
            # Currently always 1
            packet.extend(lc_int(1))

        packet.extend(statements)
        query = bytes(packet)
        # Handle the first query result
        yield self._handle_result(self._send_cmd(ServerCmd.QUERY, query))

        # Handle next results, if any
        while self._have_next_result:
            self.handle_unread_result()
            yield self._handle_result(self._socket.recv())

    def cmd_refresh(self, options: int) -> OkPacketType:
        """Send the Refresh command to the MySQL server

        This method sends the Refresh command to the MySQL server. The options
        argument should be a bitwise value using constants.RefreshOption.
        Usage example:
         RefreshOption = mysql.connector.RefreshOption
         refresh = RefreshOption.LOG | RefreshOption.THREADS
         cnx.cmd_refresh(refresh)

        The result is a dictionary with the OK packet information.

        Returns a dict()
        """
        return self._handle_ok(self._send_cmd(ServerCmd.REFRESH, int4store(options)))

    def cmd_quit(self) -> bytes:
        """Close the current connection with the server

        This method sends the QUIT command to the MySQL server, closing the
        current connection. Since the no response can be returned to the
        client, cmd_quit() will return the packet it send.

        Returns a str()
        """
        self.handle_unread_result()

        packet = self._protocol.make_command(ServerCmd.QUIT)
        self._socket.send(packet, 0, 0)
        return bytes(packet)

    def cmd_shutdown(self, shutdown_type: Optional[int] = None) -> EofPacketType:
        """Shut down the MySQL Server

        This method sends the SHUTDOWN command to the MySQL server and is only
        possible if the current user has SUPER privileges. The result is a
        dictionary containing the OK packet information.

        Note: Most applications and scripts do not the SUPER privilege.

        Returns a dict()
        """
        if shutdown_type:
            if not ShutdownType.get_info(shutdown_type):
                raise InterfaceError("Invalid shutdown type")
            atype = shutdown_type
        else:
            atype = ShutdownType.SHUTDOWN_DEFAULT
        return self._handle_eof(self._send_cmd(ServerCmd.SHUTDOWN, int4store(atype)))

    def cmd_statistics(self) -> StatsPacketType:
        """Send the statistics command to the MySQL Server

        This method sends the STATISTICS command to the MySQL server. The
        result is a dictionary with various statistical information.

        Returns a dict()
        """
        self.handle_unread_result()

        packet = self._protocol.make_command(ServerCmd.STATISTICS)
        self._socket.send(packet, 0, 0)
        return self._protocol.parse_statistics(self._socket.recv())

    def cmd_process_kill(self, mysql_pid: int) -> OkPacketType:
        """Kill a MySQL process

        This method send the PROCESS_KILL command to the server along with
        the process ID. The result is a dictionary with the OK packet
        information.

        Returns a dict()
        """
        return self._handle_ok(
            self._send_cmd(ServerCmd.PROCESS_KILL, int4store(mysql_pid))
        )

    def cmd_debug(self) -> EofPacketType:
        """Send the DEBUG command

        This method sends the DEBUG command to the MySQL server, which
        requires the MySQL user to have SUPER privilege. The output will go
        to the MySQL server error log and the result of this method is a
        dictionary with EOF packet information.

        Returns a dict()
        """
        return self._handle_eof(self._send_cmd(ServerCmd.DEBUG))

    def cmd_ping(self) -> OkPacketType:
        """Send the PING command

        This method sends the PING command to the MySQL server. It is used to
        check if the the connection is still valid. The result of this
        method is dictionary with OK packet information.

        Returns a dict()
        """
        return self._handle_ok(self._send_cmd(ServerCmd.PING))

    def cmd_change_user(
        self,
        username: str = "",
        password: str = "",
        database: str = "",
        charset: int = 45,
        password1: str = "",
        password2: str = "",
        password3: str = "",
        oci_config_file: str = "",
        oci_config_profile: str = "",
    ) -> Optional[OkPacketType]:
        """Change the current logged in user

        This method allows to change the current logged in user information.
        The result is a dictionary with OK packet information.

        Returns a dict()
        """
        if not isinstance(charset, int):
            raise ValueError("charset must be an integer")
        if charset < 0:
            raise ValueError("charset should be either zero or a postive integer")

        self._mfa_nfactor = 1
        self._user = username
        self._password = password
        self._password1 = password1
        self._password2 = password2
        self._password3 = password3

        if self._password1 and password != self._password1:
            password = self._password1

        self.handle_unread_result()

        if self._compress:
            raise NotSupportedError("Change user is not supported with compression")
        packet = self._protocol.make_change_user(
            handshake=self._handshake,
            username=username,
            password=password,
            database=database,
            charset=charset,
            client_flags=self._client_flags,
            ssl_enabled=self._ssl_active,
            auth_plugin=self._auth_plugin,
            conn_attrs=self._conn_attrs,
        )
        self._socket.send(packet, 0, 0)

        if oci_config_file:
            self._oci_config_file = oci_config_file

        self._oci_config_profile = oci_config_profile

        ok_packet = self._auth_switch_request(username, password)

        if not (self._client_flags & ClientFlag.CONNECT_WITH_DB) and database:
            self.cmd_init_db(database)

        self._charset_id = charset
        self._post_connection()

        return ok_packet

    @property
    def database(self) -> str:
        """Get the current database"""
        return self.info_query("SELECT DATABASE()")[0]  # type: ignore[return-value]

    @database.setter
    def database(self, value: str) -> None:
        """Set the current database"""
        self.cmd_init_db(value)

    def is_connected(self) -> bool:
        """Reports whether the connection to MySQL Server is available

        This method checks whether the connection to MySQL is available.
        It is similar to ping(), but unlike the ping()-method, either True
        or False is returned and no exception is raised.

        Returns True or False.
        """
        try:
            self.cmd_ping()
        except Error:
            return False  # This method does not raise
        return True

    def set_allow_local_infile_in_path(self, path: str) -> None:
        """Set the path that user can upload files.

        Args:
            path (str): Path that user can upload files.
        """
        self._allow_local_infile_in_path = path

    def reset_session(
        self,
        user_variables: Optional[Dict[str, Any]] = None,
        session_variables: Optional[Dict[str, Any]] = None,
    ) -> None:
        """Clears the current active session

        This method resets the session state, if the MySQL server is 5.7.3
        or later active session will be reset without re-authenticating.
        For other server versions session will be reset by re-authenticating.

        It is possible to provide a sequence of variables and their values to
        be set after clearing the session. This is possible for both user
        defined variables and session variables.
        This method takes two arguments user_variables and session_variables
        which are dictionaries.

        Raises OperationalError if not connected, InternalError if there are
        unread results and InterfaceError on errors.
        """
        if not self.is_connected():
            raise OperationalError("MySQL Connection not available.")

        if not self.cmd_reset_connection():
            try:
                self.cmd_change_user(
                    self._user,
                    self._password,
                    self._database,
                    self._charset_id,
                    self._password1,
                    self._password2,
                    self._password3,
                    self._oci_config_file,
                    self._oci_config_profile,
                )
            except ProgrammingError:
                self.reconnect()

        cur = self.cursor()
        if user_variables:
            for key, value in user_variables.items():
                cur.execute(f"SET @`{key}` = %s", (value,))
        if session_variables:
            for key, value in session_variables.items():
                cur.execute(f"SET SESSION `{key}` = %s", (value,))

    def ping(self, reconnect: bool = False, attempts: int = 1, delay: int = 0) -> None:
        """Check availability of the MySQL server

        When reconnect is set to True, one or more attempts are made to try
        to reconnect to the MySQL server using the reconnect()-method.

        delay is the number of seconds to wait between each retry.

        When the connection is not available, an InterfaceError is raised. Use
        the is_connected()-method if you just want to check the connection
        without raising an error.

        Raises InterfaceError on errors.
        """
        try:
            self.cmd_ping()
        except Error as err:
            if reconnect:
                self.reconnect(attempts=attempts, delay=delay)
            else:
                raise InterfaceError("Connection to MySQL is not available") from err

    @property
    def connection_id(self) -> Optional[int]:
        """MySQL connection ID"""
        if self._handshake:
            return self._handshake.get("server_threadid")  # type: ignore[return-value]
        return None

    def cursor(
        self,
        buffered: Optional[bool] = None,
        raw: Optional[bool] = None,
        prepared: Optional[bool] = None,
        cursor_class: Optional[Type[MySQLCursor]] = None,
        dictionary: Optional[bool] = None,
        named_tuple: Optional[bool] = None,
    ) -> MySQLCursor:
        """Instantiates and returns a cursor

        By default, MySQLCursor is returned. Depending on the options
        while connecting, a buffered and/or raw cursor is instantiated
        instead. Also depending upon the cursor options, rows can be
        returned as dictionary or named tuple.

        Dictionary and namedtuple based cursors are available with buffered
        output but not raw.

        It is possible to also give a custom cursor through the
        cursor_class parameter, but it needs to be a subclass of
        mysql.connector.cursor.CursorBase.

        Raises ProgrammingError when cursor_class is not a subclass of
        CursorBase. Raises ValueError when cursor is not available.

        Returns a cursor-object
        """
        self.handle_unread_result()

        if not self.is_connected():
            raise OperationalError("MySQL Connection not available")
        if cursor_class is not None:
            if not issubclass(cursor_class, CursorBase):
                raise ProgrammingError(
                    "Cursor class needs be to subclass of cursor.CursorBase"
                )
            return (cursor_class)(self)

        buffered = buffered if buffered is not None else self._buffered
        raw = raw if raw is not None else self._raw

        cursor_type = 0
        if buffered is True:
            cursor_type |= 1
        if raw is True:
            cursor_type |= 2
        if dictionary is True:
            cursor_type |= 4
        if named_tuple is True:
            cursor_type |= 8
        if prepared is True:
            cursor_type |= 16

        types = {
            0: MySQLCursor,  # 0
            1: MySQLCursorBuffered,
            2: MySQLCursorRaw,
            3: MySQLCursorBufferedRaw,
            4: MySQLCursorDict,
            5: MySQLCursorBufferedDict,
            8: MySQLCursorNamedTuple,
            9: MySQLCursorBufferedNamedTuple,
            16: MySQLCursorPrepared,
            18: MySQLCursorPreparedRaw,
            20: MySQLCursorPreparedDict,
            24: MySQLCursorPreparedNamedTuple,
        }
        try:
            return (types[cursor_type])(self)
        except KeyError:
            args = ("buffered", "raw", "dictionary", "named_tuple", "prepared")
            raise ValueError(
                "Cursor not available with given criteria: "
                + ", ".join([args[i] for i in range(5) if cursor_type & (1 << i) != 0])
            ) from None

    def commit(self) -> None:
        """Commit current transaction"""
        self._execute_query("COMMIT")

    def rollback(self) -> None:
        """Rollback current transaction"""
        if self.unread_result:
            self.get_rows()

        self._execute_query("ROLLBACK")

    def _execute_query(self, query: StrOrBytes) -> None:
        """Execute a query

        This method simply calls cmd_query() after checking for unread
        result. If there are still unread result, an InterfaceError
        is raised. Otherwise whatever cmd_query() returns is returned.

        Returns a dict()
        """
        self.handle_unread_result()
        self.cmd_query(query)

    def info_query(self, query: StrOrBytes) -> Optional[RowType]:
        """Send a query which only returns 1 row"""
        cursor = self.cursor(buffered=True)
        cursor.execute(query)
        return cursor.fetchone()

    def _handle_binary_ok(self, packet: bytes) -> Dict[str, int]:
        """Handle a MySQL Binary Protocol OK packet

        This method handles a MySQL Binary Protocol OK packet. When the
        packet is found to be an Error packet, an error will be raised. If
        the packet is neither an OK or an Error packet, InterfaceError
        will be raised.

        Returns a dict()
        """
        if packet[4] == 0:
            return self._protocol.parse_binary_prepare_ok(packet)
        if packet[4] == 255:
            raise get_exception(packet)
        raise InterfaceError("Expected Binary OK packet")

    def _handle_binary_result(
        self, packet: bytes
    ) -> Union[OkPacketType, Tuple[int, List[DescriptionType], EofPacketType]]:
        """Handle a MySQL Result

        This method handles a MySQL result, for example, after sending the
        query command. OK and EOF packets will be handled and returned. If
        the packet is an Error packet, an Error exception will be raised.

        The tuple returned by this method consist of:
        - the number of columns in the result,
        - a list of tuples with information about the columns,
        - the EOF packet information as a dictionary.

        Returns tuple() or dict()
        """
        if not packet or len(packet) < 4:
            raise InterfaceError("Empty response")
        if packet[4] == 0:
            return self._handle_ok(packet)
        if packet[4] == 254:
            return self._handle_eof(packet)
        if packet[4] == 255:
            raise get_exception(packet)

        # We have a binary result set
        column_count = self._protocol.parse_column_count(packet)
        if not column_count or not isinstance(column_count, int):
            raise InterfaceError("Illegal result set.")

        columns: List[DescriptionType] = [None] * column_count
        for i in range(0, column_count):
            columns[i] = self._protocol.parse_column(
                self._socket.recv(), self.python_charset
            )

        eof = self._handle_eof(self._socket.recv())
        return (column_count, columns, eof)

    def cmd_stmt_fetch(self, statement_id: int, rows: int = 1) -> None:
        """Fetch a MySQL statement Result Set

        This method will send the FETCH command to MySQL together with the
        given statement id and the number of rows to fetch.
        """
        packet = self._protocol.make_stmt_fetch(statement_id, rows)
        self.unread_result = False
        self._send_cmd(ServerCmd.STMT_FETCH, packet, expect_response=False)
        self.unread_result = True

    def cmd_stmt_prepare(
        self, statement: bytes
    ) -> Mapping[str, Union[int, List[DescriptionType]]]:
        """Prepare a MySQL statement

        This method will send the PREPARE command to MySQL together with the
        given statement.

        Returns a dict()
        """
        packet = self._send_cmd(ServerCmd.STMT_PREPARE, statement)
        result = self._handle_binary_ok(packet)

        result["columns"] = []
        result["parameters"] = []
        if result["num_params"] > 0:
            for _ in range(0, result["num_params"]):
                result["parameters"].append(
                    self._protocol.parse_column(
                        self._socket.recv(), self.python_charset
                    )
                )
            self._handle_eof(self._socket.recv())
        if result["num_columns"] > 0:
            for _ in range(0, result["num_columns"]):
                result["columns"].append(
                    self._protocol.parse_column(
                        self._socket.recv(), self.python_charset
                    )
                )
            self._handle_eof(self._socket.recv())

        return result

    @with_context_propagation
    def cmd_stmt_execute(
        self,
        statement_id: int,
        data: Sequence[SupportedMysqlBinaryProtocolTypes] = (),
        parameters: Sequence[Any] = (),
        flags: int = 0,
    ) -> Union[OkPacketType, Tuple[int, List[DescriptionType], EofPacketType]]:
        """Execute a prepared MySQL statement"""
        parameters = list(parameters)
        long_data_used = {}

        if data:
            for param_id, _ in enumerate(parameters):
                if isinstance(data[param_id], IOBase):
                    binary = True
                    try:
                        binary = "b" not in data[param_id].mode  # type: ignore[union-attr]
                    except AttributeError:
                        pass
                    self.cmd_stmt_send_long_data(statement_id, param_id, data[param_id])
                    long_data_used[param_id] = (binary,)
        if not self._query_attrs_supported and self._query_attrs:
            warnings.warn(
                "This version of the server does not support Query Attributes",
                category=Warning,
            )
        if self._client_flags & ClientFlag.CLIENT_QUERY_ATTRIBUTES:
            execute_packet = self._protocol.make_stmt_execute(
                statement_id,
                data,
                tuple(parameters),
                flags,
                long_data_used,
                self.charset,
                self.query_attrs,
                self._converter_str_fallback,
            )
        else:
            execute_packet = self._protocol.make_stmt_execute(
                statement_id,
                data,
                tuple(parameters),
                flags,
                long_data_used,
                self.charset,
                converter_str_fallback=self._converter_str_fallback,
            )
        packet = self._send_cmd(ServerCmd.STMT_EXECUTE, packet=execute_packet)
        result = self._handle_binary_result(packet)
        return result

    def cmd_stmt_close(self, statement_id: int) -> None:
        """Deallocate a prepared MySQL statement

        This method deallocates the prepared statement using the
        statement_id. Note that the MySQL server does not return
        anything.
        """
        self._send_cmd(
            ServerCmd.STMT_CLOSE,
            int4store(statement_id),
            expect_response=False,
        )

    def cmd_stmt_send_long_data(
        self, statement_id: int, param_id: int, data: BinaryIO
    ) -> int:
        """Send data for a column

        This methods send data for a column (for example BLOB) for statement
        identified by statement_id. The param_id indicate which parameter
        the data belongs too.
        The data argument should be a file-like object.

        Since MySQL does not send anything back, no error is raised. When
        the MySQL server is not reachable, an OperationalError is raised.

        cmd_stmt_send_long_data should be called before cmd_stmt_execute.

        The total bytes send is returned.

        Returns int.
        """
        chunk_size = 131072  # 128 KB
        total_sent = 0
        try:
            buf = data.read(chunk_size)
            while buf:
                packet = self._protocol.prepare_stmt_send_long_data(
                    statement_id, param_id, buf
                )
                self._send_cmd(
                    ServerCmd.STMT_SEND_LONG_DATA,
                    packet=packet,
                    expect_response=False,
                )
                total_sent += len(buf)
                buf = data.read(chunk_size)
        except AttributeError as err:
            raise OperationalError("MySQL Connection not available") from err

        return total_sent

    def cmd_stmt_reset(self, statement_id: int) -> None:
        """Reset data for prepared statement sent as long data

        The result is a dictionary with OK packet information.

        Returns a dict()
        """
        self._handle_ok(self._send_cmd(ServerCmd.STMT_RESET, int4store(statement_id)))

    def cmd_reset_connection(self) -> bool:
        """Resets the session state without re-authenticating

        Reset command only works on MySQL server 5.7.3 or later.
        The result is True for a successful reset otherwise False.

        Returns bool
        """
        try:
            self._handle_ok(self._send_cmd(ServerCmd.RESET_CONNECTION))
            self._post_connection()
            return True
        except (NotSupportedError, OperationalError):
            return False

    def handle_unread_result(self) -> None:
        """Check whether there is an unread result"""
        if self.can_consume_results:
            self.consume_results()
        elif self.unread_result:
            raise InternalError("Unread result found")

virt/lib/python3.9/site-packages/mysql/connector/connection 3.py
PrinceDisant/LostInNL
# Copyright (c) 2009, 2023, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

# mypy: disable-error-code="arg-type,operator,attr-defined,assignment"

"""Implementing communication with MySQL servers."""

import datetime
import getpass
import os
import socket
import struct
import sys
import warnings

from decimal import Decimal
from io import IOBase
from typing import (
    Any,
    BinaryIO,
    Dict,
    Generator,
    List,
    Mapping,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
)

from . import version
from .abstracts import MySQLConnectionAbstract
from .authentication import get_auth_plugin
from .constants import (
    CharacterSet,
    ClientFlag,
    FieldType,
    ServerCmd,
    ServerFlag,
    ShutdownType,
    flag_is_set,
)
from .conversion import MySQLConverter
from .cursor import (
    CursorBase,
    MySQLCursor,
    MySQLCursorBuffered,
    MySQLCursorBufferedDict,
    MySQLCursorBufferedNamedTuple,
    MySQLCursorBufferedRaw,
    MySQLCursorDict,
    MySQLCursorNamedTuple,
    MySQLCursorPrepared,
    MySQLCursorPreparedDict,
    MySQLCursorPreparedNamedTuple,
    MySQLCursorPreparedRaw,
    MySQLCursorRaw,
)
from .errors import (
    DatabaseError,
    Error,
    InterfaceError,
    InternalError,
    NotSupportedError,
    OperationalError,
    ProgrammingError,
    get_exception,
)
from .logger import logger
from .network import MySQLSocket, MySQLTCPSocket, MySQLUnixSocket
from .opentelemetry.constants import OTEL_ENABLED
from .opentelemetry.context_propagation import with_context_propagation
from .plugins import BaseAuthPlugin
from .protocol import MySQLProtocol
from .types import (
    ConnAttrsType,
    DescriptionType,
    EofPacketType,
    HandShakeType,
    OkPacketType,
    ResultType,
    RowType,
    StatsPacketType,
    StrOrBytes,
    SupportedMysqlBinaryProtocolTypes,
)
from .utils import get_platform, int1store, int4store, lc_int

if OTEL_ENABLED:
    from .opentelemetry.instrumentation import end_span, record_exception_event


class MySQLConnection(MySQLConnectionAbstract):
    """Connection to a MySQL Server"""

    def __init__(self, **kwargs: Any) -> None:
        self._protocol: Optional[MySQLProtocol] = None
        self._socket: Optional[MySQLSocket] = None
        self._handshake: Optional[HandShakeType] = None
        super().__init__()

        self._converter_class: Type[MySQLConverter] = MySQLConverter

        self._client_flags: int = ClientFlag.get_default()
        self._charset_id: int = 45
        self._sql_mode: Optional[str] = None
        self._time_zone: Optional[str] = None
        self._autocommit: bool = False

        self._user: str = ""
        self._password: str = ""
        self._database: str = ""
        self._host: str = "127.0.0.1"
        self._port: int = 3306
        self._unix_socket: Optional[str] = None
        self._client_host: str = ""
        self._client_port: int = 0
        self._ssl: Dict[str, Optional[Union[str, bool, List[str]]]] = {}
        self._force_ipv6: bool = False

        self._use_unicode: bool = True
        self._get_warnings: bool = False
        self._raise_on_warnings: bool = False
        self._buffered: bool = False
        self._unread_result: bool = False
        self._have_next_result: bool = False
        self._raw: bool = False
        self._in_transaction: bool = False

        self._prepared_statements: Any = None

        self._ssl_active: bool = False
        self._auth_plugin: Optional[str] = None
        self._krb_service_principal: Optional[str] = None
        self._pool_config_version: Any = None
        self._query_attrs_supported: int = False

        self._columns_desc: List[DescriptionType] = []
        self._mfa_nfactor: int = 1

        if kwargs:
            try:
                self.connect(**kwargs)
            except Exception:
                # Tidy-up underlying socket on failure
                self.close()
                self._socket = None
                raise

    def _add_default_conn_attrs(self) -> None:
        """Add the default connection attributes."""
        platform = get_platform()
        license_chunks = version.LICENSE.split(" ")
        if license_chunks[0] == "GPLv2":
            client_license = "GPL-2.0"
        else:
            client_license = "Commercial"
        default_conn_attrs = {
            "_pid": str(os.getpid()),
            "_platform": platform["arch"],
            "_source_host": socket.gethostname(),
            "_client_name": "mysql-connector-python",
            "_client_license": client_license,
            "_client_version": ".".join([str(x) for x in version.VERSION[0:3]]),
            "_os": platform["version"],
        }

        self._conn_attrs.update((default_conn_attrs))

    def _do_handshake(self) -> None:
        """Get the handshake from the MySQL server"""
        packet = self._socket.recv()
        if packet[4] == 255:
            raise get_exception(packet)

        self._handshake = None
        handshake = self._protocol.parse_handshake(packet)

        server_version = handshake["server_version_original"]

        self._server_version = self._check_server_version(
            server_version
            if isinstance(server_version, (str, bytes, bytearray))
            else "Unknown"
        )
        CharacterSet.set_mysql_version(self._server_version)

        if not handshake["capabilities"] & ClientFlag.SSL:
            if self._auth_plugin == "mysql_clear_password" and not self.is_secure:
                raise InterfaceError(
                    "Clear password authentication is not supported over "
                    "insecure channels"
                )
            if self._ssl.get("verify_cert"):
                raise InterfaceError(
                    "SSL is required but the server doesn't support it",
                    errno=2026,
                )
            self._client_flags &= ~ClientFlag.SSL
        elif not self._ssl_disabled:
            self._client_flags |= ClientFlag.SSL

        if handshake["capabilities"] & ClientFlag.PLUGIN_AUTH:
            self.set_client_flags([ClientFlag.PLUGIN_AUTH])

        if handshake["capabilities"] & ClientFlag.CLIENT_QUERY_ATTRIBUTES:
            self._query_attrs_supported = True
            self.set_client_flags([ClientFlag.CLIENT_QUERY_ATTRIBUTES])

        if handshake["capabilities"] & ClientFlag.MULTI_FACTOR_AUTHENTICATION:
            self.set_client_flags([ClientFlag.MULTI_FACTOR_AUTHENTICATION])

        self._handshake = handshake

    def _do_auth(
        self,
        username: Optional[str] = None,
        password: Optional[str] = None,
        database: Optional[str] = None,
        client_flags: int = 0,
        charset: int = 45,
        ssl_options: Optional[Dict[str, Optional[Union[str, bool, List[str]]]]] = None,
        conn_attrs: Optional[ConnAttrsType] = None,
    ) -> bool:
        """Authenticate with the MySQL server

        Authentication happens in two parts. We first send a response to the
        handshake. The MySQL server will then send either an AuthSwitchRequest
        or an error packet.

        Raises NotSupportedError when we get the old, insecure password
        reply back. Raises any error coming from MySQL.
        """
        self._ssl_active = False
        if ssl_options is None:
            ssl_options = {}
        if not self._ssl_disabled and (client_flags & ClientFlag.SSL):
            packet = self._protocol.make_auth_ssl(
                charset=charset, client_flags=client_flags
            )
            self._socket.send(packet)
            if ssl_options.get("tls_ciphersuites") is not None:
                tls_ciphersuites = ":".join(ssl_options.get("tls_ciphersuites"))
            else:
                tls_ciphersuites = ""
            self._socket.switch_to_ssl(
                ssl_options.get("ca"),
                ssl_options.get("cert"),
                ssl_options.get("key"),
                ssl_options.get("verify_cert") or False,
                ssl_options.get("verify_identity") or False,
                tls_ciphersuites,
                ssl_options.get("tls_versions"),
            )
            self._ssl_active = True

        if self._password1 and password != self._password1:
            password = self._password1

        logger.debug("# _do_auth(): self._auth_plugin: %s", self._auth_plugin)
        if (
            self._auth_plugin.startswith("authentication_oci")
            or (
                self._auth_plugin.startswith("authentication_kerberos")
                and os.name == "nt"
            )
        ) and not username:
            username = getpass.getuser()
            logger.debug(
                "MySQL user is empty, OS user: %s will be used for %s",
                username,
                self._auth_plugin,
            )

        packet = self._protocol.make_auth(
            handshake=self._handshake,
            username=username,
            password=password,
            database=database,
            charset=charset,
            client_flags=client_flags,
            ssl_enabled=self._ssl_active,
            auth_plugin=self._auth_plugin,
            conn_attrs=conn_attrs,
            auth_plugin_class=self._auth_plugin_class,
        )
        self._socket.send(packet)
        self._auth_switch_request(username, password)

        if not (client_flags & ClientFlag.CONNECT_WITH_DB) and database:
            self.cmd_init_db(database)

        return True

    def _auth_switch_request(
        self, username: Optional[str] = None, password: Optional[str] = None
    ) -> Optional[OkPacketType]:
        """Handle second part of authentication

        Raises NotSupportedError when we get the old, insecure password
        reply back. Raises any error coming from MySQL.
        """
        auth = None
        new_auth_plugin: Optional[str] = (
            self._auth_plugin or self._handshake["auth_plugin"]
        )
        logger.debug("new_auth_plugin: %s", new_auth_plugin)
        packet = self._socket.recv()
        if packet[4] == 254 and len(packet) == 5:
            raise NotSupportedError(
                "Authentication with old (insecure) passwords "
                "is not supported. For more information, lookup "
                "Password Hashing in the latest MySQL manual"
            )
        if packet[4] == 254:
            # AuthSwitchRequest
            (
                new_auth_plugin,
                auth_data,
            ) = self._protocol.parse_auth_switch_request(packet)
            auth = get_auth_plugin(new_auth_plugin, self._auth_plugin_class)(
                auth_data,
                username=username or self._user,
                password=password,
                ssl_enabled=self.is_secure,
            )
            packet = self._auth_continue(auth, new_auth_plugin, auth_data)

        if packet[4] == 1:
            auth_data = self._protocol.parse_auth_more_data(packet)
            auth = get_auth_plugin(new_auth_plugin, self._auth_plugin_class)(
                auth_data, password=password, ssl_enabled=self.is_secure
            )
            if new_auth_plugin == "caching_sha2_password":
                response = auth.auth_response()
                if response:
                    self._socket.send(response)
                packet = self._socket.recv()

        if packet[4] == 0:
            return self._handle_ok(packet)
        if packet[4] == 2:
            return self._handle_mfa(packet)
        if packet[4] == 255:
            raise get_exception(packet)
        return None

    def _handle_mfa(self, packet: bytes) -> Optional[OkPacketType]:
        """Handle Multi Factor Authentication."""
        self._mfa_nfactor += 1
        if self._mfa_nfactor == 2:
            password = self._password2
        elif self._mfa_nfactor == 3:
            password = self._password3
        else:
            raise InterfaceError(
                "Failed Multi Factor Authentication (invalid N factor)"
            )

        logger.debug("# MFA N Factor #%d", self._mfa_nfactor)

        packet, auth_plugin = self._protocol.parse_auth_next_factor(packet[4:])
        auth = get_auth_plugin(auth_plugin, self._auth_plugin_class)(
            None,
            username=self._user,
            password=password,
            ssl_enabled=self.is_secure,
        )
        packet = self._auth_continue(auth, auth_plugin, packet)

        if packet[4] == 1:
            auth_data = self._protocol.parse_auth_more_data(packet)
            auth = get_auth_plugin(auth_plugin, self._auth_plugin_class)(
                auth_data, password=password, ssl_enabled=self.is_secure
            )
            if auth_plugin == "caching_sha2_password":
                response = auth.auth_response()
                if response:
                    self._socket.send(response)
                packet = self._socket.recv()

        if packet[4] == 0:
            return self._handle_ok(packet)
        if packet[4] == 2:
            return self._handle_mfa(packet)
        if packet[4] == 255:
            raise get_exception(packet)
        return None

    def _auth_continue(
        self, auth: BaseAuthPlugin, auth_plugin: str, auth_data: bytes
    ) -> bytes:
        """Continue with the authentication."""
        if auth_plugin == "authentication_ldap_sasl_client":
            logger.debug("# auth_data: %s", auth_data)
            response = auth.auth_response(self._krb_service_principal)
        elif auth_plugin == "authentication_kerberos_client":
            logger.debug("# auth_data: %s", auth_data)
            response = auth.auth_response(auth_data)
        elif auth_plugin == "authentication_oci_client":
            logger.debug("# oci configuration file path: %s", self._oci_config_file)
            auth.oci_config_file = self._oci_config_file
            auth.oci_config_profile = self._oci_config_profile
            response = auth.auth_response()
        else:
            response = auth.auth_response()

        logger.debug("# request: %s size: %s", response, len(response))
        self._socket.send(response)
        packet = self._socket.recv()
        logger.debug("# server response packet: %s", packet)
        if (
            auth_plugin == "authentication_ldap_sasl_client"
            and len(packet) >= 6
            and packet[5] == 114
            and packet[6] == 61
        ):  # 'r' and '='
            # Continue with sasl authentication
            dec_response = packet[5:]
            cresponse = auth.auth_continue(dec_response)
            self._socket.send(cresponse)
            packet = self._socket.recv()
            if packet[5] == 118 and packet[6] == 61:  # 'v' and '='
                if auth.auth_finalize(packet[5:]):
                    # receive packed OK
                    packet = self._socket.recv()
        elif (
            auth_plugin == "authentication_ldap_sasl_client"
            and auth_data == b"GSSAPI"
            and packet[4] != 255
        ):
            rcode_size = 5  # header size for the response status code.
            logger.debug("# Continue with sasl GSSAPI authentication")
            logger.debug("# response header: %s", packet[: rcode_size + 1])
            logger.debug("# response size: %s", len(packet))

            logger.debug("# Negotiate a service request")
            complete = False
            tries = 0  # To avoid a infinite loop attempt no more than feedback messages
            while not complete and tries < 5:
                logger.debug("%s Attempt %s %s", "-" * 20, tries + 1, "-" * 20)
                logger.debug("<< server response: %s", packet)
                logger.debug("# response code: %s", packet[: rcode_size + 1])
                step, complete = auth.auth_continue_krb(packet[rcode_size:])
                logger.debug(" >> response to server: %s", step)
                self._socket.send(step or b"")
                packet = self._socket.recv()
                tries += 1
            if not complete:
                raise InterfaceError(
                    f"Unable to fulfill server request after {tries} "
                    f"attempts. Last server response: {packet}"
                )
            logger.debug(
                " last GSSAPI response from server: %s length: %d",
                packet,
                len(packet),
            )
            last_step = auth.auth_accept_close_handshake(packet[rcode_size:])
            logger.debug(
                " >> last response to server: %s length: %d",
                last_step,
                len(last_step),
            )
            self._socket.send(last_step)
            # Receive final handshake from server
            packet = self._socket.recv()
            logger.debug("<< final handshake from server: %s", packet)

            # receive OK packet from server.
            packet = self._socket.recv()
            logger.debug("<< ok packet from server: %s", packet)
        elif auth_plugin == "authentication_kerberos_client" and packet[4] != 255:
            rcode_size = 5  # Reader size for the response status code
            logger.debug("# Continue with GSSAPI authentication")
            logger.debug("# Response header: %s", packet[: rcode_size + 1])
            logger.debug("# Response size: %s", len(packet))
            logger.debug("# Negotiate a service request")
            complete = False
            tries = 0

            while not complete and tries < 5:
                logger.debug("%s Attempt %s %s", "-" * 20, tries + 1, "-" * 20)
                logger.debug("<< Server response: %s", packet)
                logger.debug("# Response code: %s", packet[: rcode_size + 1])
                token, complete = auth.auth_continue(packet[rcode_size:])
                if token:
                    self._socket.send(token)
                if complete:
                    break
                packet = self._socket.recv()

                logger.debug(">> Response to server: %s", token)
                tries += 1

            if not complete:
                raise InterfaceError(
                    f"Unable to fulfill server request after {tries} "
                    f"attempts. Last server response: {packet}"
                )

            logger.debug(
                "Last response from server: %s length: %d",
                packet,
                len(packet),
            )

            # Receive OK packet from server.
            packet = self._socket.recv()
            logger.debug("<< Ok packet from server: %s", packet)

        return bytes(packet)

    def _get_connection(self) -> MySQLSocket:
        """Get connection based on configuration

        This method will return the appropriated connection object using
        the connection parameters.

        Returns subclass of MySQLBaseSocket.
        """
        conn: Optional[MySQLSocket] = None
        if self._unix_socket and os.name == "posix":
            conn = MySQLUnixSocket(unix_socket=self.unix_socket)
        else:
            conn = MySQLTCPSocket(
                host=self.server_host,
                port=self.server_port,
                force_ipv6=self._force_ipv6,
            )

        conn.set_connection_timeout(self._connection_timeout)
        return conn

    def _open_connection(self) -> None:
        """Open the connection to the MySQL server

        This method sets up and opens the connection to the MySQL server.

        Raises on errors.
        """
        if self._auth_plugin == "authentication_kerberos_client" and not self._user:
            cls = get_auth_plugin(self._auth_plugin, self._auth_plugin_class)
            self._user = cls.get_user_from_credentials()

        self._protocol = MySQLProtocol()
        self._socket = self._get_connection()
        try:
            self._socket.open_connection()

            # do initial handshake
            self._do_handshake()

            # start authentication negotiation
            self._do_auth(
                self._user,
                self._password,
                self._database,
                self._client_flags,
                self._charset_id,
                self._ssl,
                self._conn_attrs,
            )
            self.set_converter_class(self._converter_class)

            if self._client_flags & ClientFlag.COMPRESS:
                # update the network layer accordingly
                self._socket.switch_to_compressed_mode()

            self._socket.set_connection_timeout(None)
        except Exception:
            # close socket
            self._socket.close_connection()
            raise

        if (
            not self._ssl_disabled
            and hasattr(self._socket.sock, "version")
            and callable(self._socket.sock.version)
        ):
            # Raise a deprecation warning if TLSv1 or TLSv1.1 is being used
            tls_version = self._socket.sock.version()
            if tls_version in ("TLSv1", "TLSv1.1"):
                warn_msg = (
                    f"This connection is using {tls_version} which is now "
                    "deprecated and will be removed in a future release of "
                    "MySQL Connector/Python"
                )
                warnings.warn(warn_msg, DeprecationWarning)

    def shutdown(self) -> None:
        """Shut down connection to MySQL Server."""
        if not self._socket:
            return

        try:
            self._socket.shutdown()
        except (AttributeError, Error):
            pass  # Getting an exception would mean we are disconnected.

    def close(self) -> None:
        """Disconnect from the MySQL server"""
        if self._span and self._span.is_recording():
            record_exception_event(self._span, sys.exc_info()[1])

        if not self._socket:
            return

        try:
            self.cmd_quit()
        except (AttributeError, Error):
            pass  # Getting an exception would mean we are disconnected.

        try:
            self._socket.close_connection()
        except Exception as err:
            if OTEL_ENABLED:
                record_exception_event(self._span, err)
            raise
        finally:
            if OTEL_ENABLED:
                end_span(self._span)

        self._handshake = None

    disconnect = close

    def _send_cmd(
        self,
        command: int,
        argument: Optional[bytes] = None,
        packet_number: int = 0,
        packet: Optional[bytes] = None,
        expect_response: bool = True,
        compressed_packet_number: int = 0,
    ) -> Optional[bytearray]:
        """Send a command to the MySQL server

        This method sends a command with an optional argument.
        If packet is not None, it will be sent and the argument will be
        ignored.

        The packet_number is optional and should usually not be used.

        Some commands might not result in the MySQL server returning
        a response. If a command does not return anything, you should
        set expect_response to False. The _send_cmd method will then
        return None instead of a MySQL packet.

        Returns a MySQL packet or None.
        """
        self.handle_unread_result()

        try:
            self._socket.send(
                self._protocol.make_command(command, packet or argument),
                packet_number,
                compressed_packet_number,
            )
        except AttributeError as err:
            raise OperationalError("MySQL Connection not available") from err

        if not expect_response:
            return None
        return self._socket.recv()

    def _send_data(self, data_file: BinaryIO, send_empty_packet: bool = False) -> bytes:
        """Send data to the MySQL server

        This method accepts a file-like object and sends its data
        as is to the MySQL server. If the send_empty_packet is
        True, it will send an extra empty package (for example
        when using LOAD LOCAL DATA INFILE).

        Returns a MySQL packet.
        """
        self.handle_unread_result()

        if not hasattr(data_file, "read"):
            raise ValueError("expecting a file-like object")

        chunk_size = 131072  # 128 KB
        try:
            buf = data_file.read(chunk_size - 16)
            while buf:
                self._socket.send(buf)
                buf = data_file.read(chunk_size - 16)
        except AttributeError as err:
            raise OperationalError("MySQL Connection not available") from err

        if send_empty_packet:
            try:
                self._socket.send(b"")
            except AttributeError as err:
                raise OperationalError("MySQL Connection not available") from err

        return bytes(self._socket.recv())

    def _handle_server_status(self, flags: int) -> None:
        """Handle the server flags found in MySQL packets

        This method handles the server flags send by MySQL OK and EOF
        packets. It, for example, checks whether there exists more result
        sets or whether there is an ongoing transaction.
        """
        self._have_next_result = flag_is_set(ServerFlag.MORE_RESULTS_EXISTS, flags)
        self._in_transaction = flag_is_set(ServerFlag.STATUS_IN_TRANS, flags)

    @property
    def in_transaction(self) -> bool:
        """MySQL session has started a transaction"""
        return self._in_transaction

    def _handle_ok(self, packet: bytes) -> OkPacketType:
        """Handle a MySQL OK packet

        This method handles a MySQL OK packet. When the packet is found to
        be an Error packet, an error will be raised. If the packet is neither
        an OK or an Error packet, InterfaceError will be raised.

        Returns a dict()
        """
        if packet[4] == 0:
            ok_pkt = self._protocol.parse_ok(packet)
            self._handle_server_status(ok_pkt["status_flag"])
            return ok_pkt
        if packet[4] == 255:
            raise get_exception(packet)
        raise InterfaceError("Expected OK packet")

    def _handle_eof(self, packet: bytes) -> EofPacketType:
        """Handle a MySQL EOF packet

        This method handles a MySQL EOF packet. When the packet is found to
        be an Error packet, an error will be raised. If the packet is neither
        and OK or an Error packet, InterfaceError will be raised.

        Returns a dict()
        """
        if packet[4] == 254:
            eof = self._protocol.parse_eof(packet)
            self._handle_server_status(eof["status_flag"])
            return eof
        if packet[4] == 255:
            raise get_exception(packet)
        raise InterfaceError("Expected EOF packet")

    def _handle_load_data_infile(self, filename: str) -> OkPacketType:
        """Handle a LOAD DATA INFILE LOCAL request"""
        file_name = os.path.abspath(filename)
        if os.path.islink(file_name):
            raise OperationalError("Use of symbolic link is not allowed")
        if not self._allow_local_infile and not self._allow_local_infile_in_path:
            raise DatabaseError(
                "LOAD DATA LOCAL INFILE file request rejected due to "
                "restrictions on access."
            )
        if not self._allow_local_infile and self._allow_local_infile_in_path:
            # validate filename is inside of allow_local_infile_in_path path.
            infile_path = os.path.abspath(self._allow_local_infile_in_path)
            c_path = None
            try:
                c_path = os.path.commonpath([infile_path, file_name])
            except ValueError as err:
                err_msg = (
                    "{} while loading file `{}` and path `{}` given"
                    " in allow_local_infile_in_path"
                )
                raise InterfaceError(
                    err_msg.format(str(err), file_name, infile_path)
                ) from err

            if c_path != infile_path:
                err_msg = (
                    "The file `{}` is not found in the given "
                    "allow_local_infile_in_path {}"
                )
                raise DatabaseError(err_msg.format(file_name, infile_path))

        try:
            data_file = open(file_name, "rb")  # pylint: disable=consider-using-with
            return self._handle_ok(self._send_data(data_file, send_empty_packet=True))
        except IOError:
            # Send a empty packet to cancel the operation
            try:
                self._socket.send(b"")
            except AttributeError as err:
                raise OperationalError("MySQL Connection not available") from err
            raise InterfaceError(f"File '{file_name}' could not be read") from None
        finally:
            try:
                data_file.close()
            except (IOError, NameError):
                pass

    def _handle_result(self, packet: bytes) -> ResultType:
        """Handle a MySQL Result

        This method handles a MySQL result, for example, after sending the
        query command. OK and EOF packets will be handled and returned. If
        the packet is an Error packet, an Error-exception will be
        raised.

        The dictionary returned of:
        - columns: column information
        - eof: the EOF-packet information

        Returns a dict()
        """
        if not packet or len(packet) < 4:
            raise InterfaceError("Empty response")
        if packet[4] == 0:
            return self._handle_ok(packet)
        if packet[4] == 251:
            filename = packet[5:].decode()
            return self._handle_load_data_infile(filename)
        if packet[4] == 254:
            return self._handle_eof(packet)
        if packet[4] == 255:
            raise get_exception(packet)

        # We have a text result set
        column_count = self._protocol.parse_column_count(packet)
        if not column_count or not isinstance(column_count, int):
            raise InterfaceError("Illegal result set")

        self._columns_desc = [
            None,
        ] * column_count
        for i in range(0, column_count):
            self._columns_desc[i] = self._protocol.parse_column(
                self._socket.recv(), self.python_charset
            )

        eof = self._handle_eof(self._socket.recv())
        self.unread_result = True
        return {"columns": self._columns_desc, "eof": eof}

    def get_row(
        self,
        binary: bool = False,
        columns: Optional[List[DescriptionType]] = None,
        raw: Optional[bool] = None,
    ) -> Tuple[Optional[RowType], Optional[EofPacketType]]:
        """Get the next rows returned by the MySQL server

        This method gets one row from the result set after sending, for
        example, the query command. The result is a tuple consisting of the
        row and the EOF packet.
        If no row was available in the result set, the row data will be None.

        Returns a tuple.
        """
        (rows, eof) = self.get_rows(count=1, binary=binary, columns=columns, raw=raw)
        if rows:
            return (rows[0], eof)
        return (None, eof)

    def get_rows(
        self,
        count: Optional[int] = None,
        binary: bool = False,
        columns: Optional[List[DescriptionType]] = None,
        raw: Optional[bool] = None,
        prep_stmt: Any = None,
    ) -> Tuple[List[RowType], Optional[EofPacketType]]:
        """Get all rows returned by the MySQL server

        This method gets all rows returned by the MySQL server after sending,
        for example, the query command. The result is a tuple consisting of
        a list of rows and the EOF packet.

        Returns a tuple()
        """
        if raw is None:
            raw = self._raw

        if not self.unread_result:
            raise InternalError("No result set available")

        rows: Tuple[List[Tuple[Any, ...]], Optional[EofPacketType]] = ([], None)
        try:
            if binary:
                charset = self.charset
                if charset == "utf8mb4":
                    charset = "utf8"
                rows = self._protocol.read_binary_result(
                    self._socket, columns, count, charset
                )
            else:
                rows = self._protocol.read_text_result(
                    self._socket, self._server_version, count=count
                )
        except Error as err:
            self.unread_result = False
            raise err

        rows, eof_p = rows
        if (
            not (binary or raw)
            and self._columns_desc is not None
            and rows
            and hasattr(self, "converter")
        ):
            row_to_python = self.converter.row_to_python
            rows = [row_to_python(row, self._columns_desc) for row in rows]

        if eof_p is not None:
            self._handle_server_status(
                eof_p["status_flag"]
                if "status_flag" in eof_p
                else eof_p["server_status"]
            )
            self.unread_result = False

        return rows, eof_p

    def consume_results(self) -> None:
        """Consume results"""
        if self.unread_result:
            self.get_rows()

    def cmd_init_db(self, database: str) -> OkPacketType:
        """Change the current database

        This method changes the current (default) database by sending the
        INIT_DB command. The result is a dictionary containing the OK packet
        information.

        Returns a dict()
        """
        return self._handle_ok(
            self._send_cmd(ServerCmd.INIT_DB, database.encode("utf-8"))
        )

    @with_context_propagation
    def cmd_query(
        self,
        query: StrOrBytes,
        raw: bool = False,
        buffered: bool = False,
        raw_as_string: bool = False,
    ) -> ResultType:
        """Send a query to the MySQL server

        This method send the query to the MySQL server and returns the result.

        If there was a text result, a tuple will be returned consisting of
        the number of columns and a list containing information about these
        columns.

        When the query doesn't return a text result, the OK or EOF packet
        information as dictionary will be returned. In case the result was
        an error, exception Error will be raised.

        Returns a tuple()
        """
        if not isinstance(query, bytearray):
            if isinstance(query, str):
                query = query.encode("utf-8")
            query = bytearray(query)
        # Prepare query attrs
        charset = self.charset if self.charset != "utf8mb4" else "utf8"
        packet = bytearray()
        if not self._query_attrs_supported and self._query_attrs:
            warnings.warn(
                "This version of the server does not support Query Attributes",
                category=Warning,
            )
        if self._client_flags & ClientFlag.CLIENT_QUERY_ATTRIBUTES:
            names = []
            types = []
            values: List[bytes] = []
            null_bitmap = [0] * ((len(self._query_attrs) + 7) // 8)
            for pos, attr_tuple in enumerate(self._query_attrs.items()):
                value = attr_tuple[1]
                flags = 0
                if value is None:
                    null_bitmap[(pos // 8)] |= 1 << (pos % 8)
                    types.append(int1store(FieldType.NULL) + int1store(flags))
                    continue
                if isinstance(value, int):
                    (
                        packed,
                        field_type,
                        flags,
                    ) = self._protocol.prepare_binary_integer(value)
                    values.append(packed)
                elif isinstance(value, str):
                    value = value.encode(charset)
                    values.append(lc_int(len(value)) + value)
                    field_type = FieldType.STRING
                elif isinstance(value, bytes):
                    values.append(lc_int(len(value)) + value)
                    field_type = FieldType.STRING
                elif isinstance(value, Decimal):
                    values.append(
                        lc_int(len(str(value).encode(charset)))
                        + str(value).encode(charset)
                    )
                    field_type = FieldType.DECIMAL
                elif isinstance(value, float):
                    values.append(struct.pack("<d", value))
                    field_type = FieldType.DOUBLE
                elif isinstance(value, (datetime.datetime, datetime.date)):
                    (
                        packed,
                        field_type,
                    ) = self._protocol.prepare_binary_timestamp(value)
                    values.append(packed)
                elif isinstance(value, (datetime.timedelta, datetime.time)):
                    (packed, field_type) = self._protocol.prepare_binary_time(value)
                    values.append(packed)
                else:
                    raise ProgrammingError(
                        "MySQL binary protocol can not handle "
                        f"'{value.__class__.__name__}' objects"
                    )
                types.append(int1store(field_type) + int1store(flags))
                name = attr_tuple[0].encode(charset)
                names.append(lc_int(len(name)) + name)

            # int<lenenc>    parameter_count    Number of parameters
            packet.extend(lc_int(len(self._query_attrs)))
            # int<lenenc>    parameter_set_count    Number of parameter sets.
            # Currently always 1
            packet.extend(lc_int(1))
            if values:
                packet.extend(
                    b"".join([struct.pack("B", bit) for bit in null_bitmap])
                    + int1store(1)
                )
                for _type, name in zip(types, names):
                    packet.extend(_type)
                    packet.extend(name)

                for value in values:
                    packet.extend(value)

        packet.extend(query)
        query = bytes(packet)
        try:
            result = self._handle_result(self._send_cmd(ServerCmd.QUERY, query))
        except ProgrammingError as err:
            if err.errno == 3948 and "Loading local data is disabled" in err.msg:
                err_msg = (
                    "LOAD DATA LOCAL INFILE file request rejected due "
                    "to restrictions on access."
                )
                raise DatabaseError(err_msg) from err
            raise
        if self._have_next_result:
            raise InterfaceError(
                "Use cmd_query_iter for statements with multiple queries."
            )

        return result

    def cmd_query_iter(
        self, statements: StrOrBytes
    ) -> Generator[ResultType, None, None]:
        """Send one or more statements to the MySQL server

        Similar to the cmd_query method, but instead returns a generator
        object to iterate through results. It sends the statements to the
        MySQL server and through the iterator you can get the results.

        statement = 'SELECT 1; INSERT INTO t1 VALUES (); SELECT 2'
        for result in cnx.cmd_query(statement, iterate=True):
            if 'columns' in result:
                columns = result['columns']
                rows = cnx.get_rows()
            else:
                # do something useful with INSERT result

        Returns a generator.
        """
        packet = bytearray()
        if not isinstance(statements, bytearray):
            if isinstance(statements, str):
                statements = statements.encode("utf8")
            statements = bytearray(statements)

        if self._client_flags & ClientFlag.CLIENT_QUERY_ATTRIBUTES:
            # int<lenenc>    parameter_count    Number of parameters
            packet.extend(lc_int(0))
            # int<lenenc>    parameter_set_count    Number of parameter sets.
            # Currently always 1
            packet.extend(lc_int(1))

        packet.extend(statements)
        query = bytes(packet)
        # Handle the first query result
        yield self._handle_result(self._send_cmd(ServerCmd.QUERY, query))

        # Handle next results, if any
        while self._have_next_result:
            self.handle_unread_result()
            yield self._handle_result(self._socket.recv())

    def cmd_refresh(self, options: int) -> OkPacketType:
        """Send the Refresh command to the MySQL server

        This method sends the Refresh command to the MySQL server. The options
        argument should be a bitwise value using constants.RefreshOption.
        Usage example:
         RefreshOption = mysql.connector.RefreshOption
         refresh = RefreshOption.LOG | RefreshOption.THREADS
         cnx.cmd_refresh(refresh)

        The result is a dictionary with the OK packet information.

        Returns a dict()
        """
        return self._handle_ok(self._send_cmd(ServerCmd.REFRESH, int4store(options)))

    def cmd_quit(self) -> bytes:
        """Close the current connection with the server

        This method sends the QUIT command to the MySQL server, closing the
        current connection. Since the no response can be returned to the
        client, cmd_quit() will return the packet it send.

        Returns a str()
        """
        self.handle_unread_result()

        packet = self._protocol.make_command(ServerCmd.QUIT)
        self._socket.send(packet, 0, 0)
        return bytes(packet)

    def cmd_shutdown(self, shutdown_type: Optional[int] = None) -> EofPacketType:
        """Shut down the MySQL Server

        This method sends the SHUTDOWN command to the MySQL server and is only
        possible if the current user has SUPER privileges. The result is a
        dictionary containing the OK packet information.

        Note: Most applications and scripts do not the SUPER privilege.

        Returns a dict()
        """
        if shutdown_type:
            if not ShutdownType.get_info(shutdown_type):
                raise InterfaceError("Invalid shutdown type")
            atype = shutdown_type
        else:
            atype = ShutdownType.SHUTDOWN_DEFAULT
        return self._handle_eof(self._send_cmd(ServerCmd.SHUTDOWN, int4store(atype)))

    def cmd_statistics(self) -> StatsPacketType:
        """Send the statistics command to the MySQL Server

        This method sends the STATISTICS command to the MySQL server. The
        result is a dictionary with various statistical information.

        Returns a dict()
        """
        self.handle_unread_result()

        packet = self._protocol.make_command(ServerCmd.STATISTICS)
        self._socket.send(packet, 0, 0)
        return self._protocol.parse_statistics(self._socket.recv())

    def cmd_process_kill(self, mysql_pid: int) -> OkPacketType:
        """Kill a MySQL process

        This method send the PROCESS_KILL command to the server along with
        the process ID. The result is a dictionary with the OK packet
        information.

        Returns a dict()
        """
        return self._handle_ok(
            self._send_cmd(ServerCmd.PROCESS_KILL, int4store(mysql_pid))
        )

    def cmd_debug(self) -> EofPacketType:
        """Send the DEBUG command

        This method sends the DEBUG command to the MySQL server, which
        requires the MySQL user to have SUPER privilege. The output will go
        to the MySQL server error log and the result of this method is a
        dictionary with EOF packet information.

        Returns a dict()
        """
        return self._handle_eof(self._send_cmd(ServerCmd.DEBUG))

    def cmd_ping(self) -> OkPacketType:
        """Send the PING command

        This method sends the PING command to the MySQL server. It is used to
        check if the the connection is still valid. The result of this
        method is dictionary with OK packet information.

        Returns a dict()
        """
        return self._handle_ok(self._send_cmd(ServerCmd.PING))

    def cmd_change_user(
        self,
        username: str = "",
        password: str = "",
        database: str = "",
        charset: int = 45,
        password1: str = "",
        password2: str = "",
        password3: str = "",
        oci_config_file: str = "",
        oci_config_profile: str = "",
    ) -> Optional[OkPacketType]:
        """Change the current logged in user

        This method allows to change the current logged in user information.
        The result is a dictionary with OK packet information.

        Returns a dict()
        """
        if not isinstance(charset, int):
            raise ValueError("charset must be an integer")
        if charset < 0:
            raise ValueError("charset should be either zero or a postive integer")

        self._mfa_nfactor = 1
        self._user = username
        self._password = password
        self._password1 = password1
        self._password2 = password2
        self._password3 = password3

        if self._password1 and password != self._password1:
            password = self._password1

        self.handle_unread_result()

        if self._compress:
            raise NotSupportedError("Change user is not supported with compression")
        packet = self._protocol.make_change_user(
            handshake=self._handshake,
            username=username,
            password=password,
            database=database,
            charset=charset,
            client_flags=self._client_flags,
            ssl_enabled=self._ssl_active,
            auth_plugin=self._auth_plugin,
            conn_attrs=self._conn_attrs,
        )
        self._socket.send(packet, 0, 0)

        if oci_config_file:
            self._oci_config_file = oci_config_file

        self._oci_config_profile = oci_config_profile

        ok_packet = self._auth_switch_request(username, password)

        if not (self._client_flags & ClientFlag.CONNECT_WITH_DB) and database:
            self.cmd_init_db(database)

        self._charset_id = charset
        self._post_connection()

        return ok_packet

    @property
    def database(self) -> str:
        """Get the current database"""
        return self.info_query("SELECT DATABASE()")[0]  # type: ignore[return-value]

    @database.setter
    def database(self, value: str) -> None:
        """Set the current database"""
        self.cmd_init_db(value)

    def is_connected(self) -> bool:
        """Reports whether the connection to MySQL Server is available

        This method checks whether the connection to MySQL is available.
        It is similar to ping(), but unlike the ping()-method, either True
        or False is returned and no exception is raised.

        Returns True or False.
        """
        try:
            self.cmd_ping()
        except Error:
            return False  # This method does not raise
        return True

    def set_allow_local_infile_in_path(self, path: str) -> None:
        """Set the path that user can upload files.

        Args:
            path (str): Path that user can upload files.
        """
        self._allow_local_infile_in_path = path

    def reset_session(
        self,
        user_variables: Optional[Dict[str, Any]] = None,
        session_variables: Optional[Dict[str, Any]] = None,
    ) -> None:
        """Clears the current active session

        This method resets the session state, if the MySQL server is 5.7.3
        or later active session will be reset without re-authenticating.
        For other server versions session will be reset by re-authenticating.

        It is possible to provide a sequence of variables and their values to
        be set after clearing the session. This is possible for both user
        defined variables and session variables.
        This method takes two arguments user_variables and session_variables
        which are dictionaries.

        Raises OperationalError if not connected, InternalError if there are
        unread results and InterfaceError on errors.
        """
        if not self.is_connected():
            raise OperationalError("MySQL Connection not available.")

        if not self.cmd_reset_connection():
            try:
                self.cmd_change_user(
                    self._user,
                    self._password,
                    self._database,
                    self._charset_id,
                    self._password1,
                    self._password2,
                    self._password3,
                    self._oci_config_file,
                    self._oci_config_profile,
                )
            except ProgrammingError:
                self.reconnect()

        cur = self.cursor()
        if user_variables:
            for key, value in user_variables.items():
                cur.execute(f"SET @`{key}` = %s", (value,))
        if session_variables:
            for key, value in session_variables.items():
                cur.execute(f"SET SESSION `{key}` = %s", (value,))

    def ping(self, reconnect: bool = False, attempts: int = 1, delay: int = 0) -> None:
        """Check availability of the MySQL server

        When reconnect is set to True, one or more attempts are made to try
        to reconnect to the MySQL server using the reconnect()-method.

        delay is the number of seconds to wait between each retry.

        When the connection is not available, an InterfaceError is raised. Use
        the is_connected()-method if you just want to check the connection
        without raising an error.

        Raises InterfaceError on errors.
        """
        try:
            self.cmd_ping()
        except Error as err:
            if reconnect:
                self.reconnect(attempts=attempts, delay=delay)
            else:
                raise InterfaceError("Connection to MySQL is not available") from err

    @property
    def connection_id(self) -> Optional[int]:
        """MySQL connection ID"""
        if self._handshake:
            return self._handshake.get("server_threadid")  # type: ignore[return-value]
        return None

    def cursor(
        self,
        buffered: Optional[bool] = None,
        raw: Optional[bool] = None,
        prepared: Optional[bool] = None,
        cursor_class: Optional[Type[MySQLCursor]] = None,
        dictionary: Optional[bool] = None,
        named_tuple: Optional[bool] = None,
    ) -> MySQLCursor:
        """Instantiates and returns a cursor

        By default, MySQLCursor is returned. Depending on the options
        while connecting, a buffered and/or raw cursor is instantiated
        instead. Also depending upon the cursor options, rows can be
        returned as dictionary or named tuple.

        Dictionary and namedtuple based cursors are available with buffered
        output but not raw.

        It is possible to also give a custom cursor through the
        cursor_class parameter, but it needs to be a subclass of
        mysql.connector.cursor.CursorBase.

        Raises ProgrammingError when cursor_class is not a subclass of
        CursorBase. Raises ValueError when cursor is not available.

        Returns a cursor-object
        """
        self.handle_unread_result()

        if not self.is_connected():
            raise OperationalError("MySQL Connection not available")
        if cursor_class is not None:
            if not issubclass(cursor_class, CursorBase):
                raise ProgrammingError(
                    "Cursor class needs be to subclass of cursor.CursorBase"
                )
            return (cursor_class)(self)

        buffered = buffered if buffered is not None else self._buffered
        raw = raw if raw is not None else self._raw

        cursor_type = 0
        if buffered is True:
            cursor_type |= 1
        if raw is True:
            cursor_type |= 2
        if dictionary is True:
            cursor_type |= 4
        if named_tuple is True:
            cursor_type |= 8
        if prepared is True:
            cursor_type |= 16

        types = {
            0: MySQLCursor,  # 0
            1: MySQLCursorBuffered,
            2: MySQLCursorRaw,
            3: MySQLCursorBufferedRaw,
            4: MySQLCursorDict,
            5: MySQLCursorBufferedDict,
            8: MySQLCursorNamedTuple,
            9: MySQLCursorBufferedNamedTuple,
            16: MySQLCursorPrepared,
            18: MySQLCursorPreparedRaw,
            20: MySQLCursorPreparedDict,
            24: MySQLCursorPreparedNamedTuple,
        }
        try:
            return (types[cursor_type])(self)
        except KeyError:
            args = ("buffered", "raw", "dictionary", "named_tuple", "prepared")
            raise ValueError(
                "Cursor not available with given criteria: "
                + ", ".join([args[i] for i in range(5) if cursor_type & (1 << i) != 0])
            ) from None

    def commit(self) -> None:
        """Commit current transaction"""
        self._execute_query("COMMIT")

    def rollback(self) -> None:
        """Rollback current transaction"""
        if self.unread_result:
            self.get_rows()

        self._execute_query("ROLLBACK")

    def _execute_query(self, query: StrOrBytes) -> None:
        """Execute a query

        This method simply calls cmd_query() after checking for unread
        result. If there are still unread result, an InterfaceError
        is raised. Otherwise whatever cmd_query() returns is returned.

        Returns a dict()
        """
        self.handle_unread_result()
        self.cmd_query(query)

    def info_query(self, query: StrOrBytes) -> Optional[RowType]:
        """Send a query which only returns 1 row"""
        cursor = self.cursor(buffered=True)
        cursor.execute(query)
        return cursor.fetchone()

    def _handle_binary_ok(self, packet: bytes) -> Dict[str, int]:
        """Handle a MySQL Binary Protocol OK packet

        This method handles a MySQL Binary Protocol OK packet. When the
        packet is found to be an Error packet, an error will be raised. If
        the packet is neither an OK or an Error packet, InterfaceError
        will be raised.

        Returns a dict()
        """
        if packet[4] == 0:
            return self._protocol.parse_binary_prepare_ok(packet)
        if packet[4] == 255:
            raise get_exception(packet)
        raise InterfaceError("Expected Binary OK packet")

    def _handle_binary_result(
        self, packet: bytes
    ) -> Union[OkPacketType, Tuple[int, List[DescriptionType], EofPacketType]]:
        """Handle a MySQL Result

        This method handles a MySQL result, for example, after sending the
        query command. OK and EOF packets will be handled and returned. If
        the packet is an Error packet, an Error exception will be raised.

        The tuple returned by this method consist of:
        - the number of columns in the result,
        - a list of tuples with information about the columns,
        - the EOF packet information as a dictionary.

        Returns tuple() or dict()
        """
        if not packet or len(packet) < 4:
            raise InterfaceError("Empty response")
        if packet[4] == 0:
            return self._handle_ok(packet)
        if packet[4] == 254:
            return self._handle_eof(packet)
        if packet[4] == 255:
            raise get_exception(packet)

        # We have a binary result set
        column_count = self._protocol.parse_column_count(packet)
        if not column_count or not isinstance(column_count, int):
            raise InterfaceError("Illegal result set.")

        columns: List[DescriptionType] = [None] * column_count
        for i in range(0, column_count):
            columns[i] = self._protocol.parse_column(
                self._socket.recv(), self.python_charset
            )

        eof = self._handle_eof(self._socket.recv())
        return (column_count, columns, eof)

    def cmd_stmt_fetch(self, statement_id: int, rows: int = 1) -> None:
        """Fetch a MySQL statement Result Set

        This method will send the FETCH command to MySQL together with the
        given statement id and the number of rows to fetch.
        """
        packet = self._protocol.make_stmt_fetch(statement_id, rows)
        self.unread_result = False
        self._send_cmd(ServerCmd.STMT_FETCH, packet, expect_response=False)
        self.unread_result = True

    def cmd_stmt_prepare(
        self, statement: bytes
    ) -> Mapping[str, Union[int, List[DescriptionType]]]:
        """Prepare a MySQL statement

        This method will send the PREPARE command to MySQL together with the
        given statement.

        Returns a dict()
        """
        packet = self._send_cmd(ServerCmd.STMT_PREPARE, statement)
        result = self._handle_binary_ok(packet)

        result["columns"] = []
        result["parameters"] = []
        if result["num_params"] > 0:
            for _ in range(0, result["num_params"]):
                result["parameters"].append(
                    self._protocol.parse_column(
                        self._socket.recv(), self.python_charset
                    )
                )
            self._handle_eof(self._socket.recv())
        if result["num_columns"] > 0:
            for _ in range(0, result["num_columns"]):
                result["columns"].append(
                    self._protocol.parse_column(
                        self._socket.recv(), self.python_charset
                    )
                )
            self._handle_eof(self._socket.recv())

        return result

    @with_context_propagation
    def cmd_stmt_execute(
        self,
        statement_id: int,
        data: Sequence[SupportedMysqlBinaryProtocolTypes] = (),
        parameters: Sequence[Any] = (),
        flags: int = 0,
    ) -> Union[OkPacketType, Tuple[int, List[DescriptionType], EofPacketType]]:
        """Execute a prepared MySQL statement"""
        parameters = list(parameters)
        long_data_used = {}

        if data:
            for param_id, _ in enumerate(parameters):
                if isinstance(data[param_id], IOBase):
                    binary = True
                    try:
                        binary = "b" not in data[param_id].mode  # type: ignore[union-attr]
                    except AttributeError:
                        pass
                    self.cmd_stmt_send_long_data(statement_id, param_id, data[param_id])
                    long_data_used[param_id] = (binary,)
        if not self._query_attrs_supported and self._query_attrs:
            warnings.warn(
                "This version of the server does not support Query Attributes",
                category=Warning,
            )
        if self._client_flags & ClientFlag.CLIENT_QUERY_ATTRIBUTES:
            execute_packet = self._protocol.make_stmt_execute(
                statement_id,
                data,
                tuple(parameters),
                flags,
                long_data_used,
                self.charset,
                self.query_attrs,
                self._converter_str_fallback,
            )
        else:
            execute_packet = self._protocol.make_stmt_execute(
                statement_id,
                data,
                tuple(parameters),
                flags,
                long_data_used,
                self.charset,
                converter_str_fallback=self._converter_str_fallback,
            )
        packet = self._send_cmd(ServerCmd.STMT_EXECUTE, packet=execute_packet)
        result = self._handle_binary_result(packet)
        return result

    def cmd_stmt_close(self, statement_id: int) -> None:
        """Deallocate a prepared MySQL statement

        This method deallocates the prepared statement using the
        statement_id. Note that the MySQL server does not return
        anything.
        """
        self._send_cmd(
            ServerCmd.STMT_CLOSE,
            int4store(statement_id),
            expect_response=False,
        )

    def cmd_stmt_send_long_data(
        self, statement_id: int, param_id: int, data: BinaryIO
    ) -> int:
        """Send data for a column

        This methods send data for a column (for example BLOB) for statement
        identified by statement_id. The param_id indicate which parameter
        the data belongs too.
        The data argument should be a file-like object.

        Since MySQL does not send anything back, no error is raised. When
        the MySQL server is not reachable, an OperationalError is raised.

        cmd_stmt_send_long_data should be called before cmd_stmt_execute.

        The total bytes send is returned.

        Returns int.
        """
        chunk_size = 131072  # 128 KB
        total_sent = 0
        try:
            buf = data.read(chunk_size)
            while buf:
                packet = self._protocol.prepare_stmt_send_long_data(
                    statement_id, param_id, buf
                )
                self._send_cmd(
                    ServerCmd.STMT_SEND_LONG_DATA,
                    packet=packet,
                    expect_response=False,
                )
                total_sent += len(buf)
                buf = data.read(chunk_size)
        except AttributeError as err:
            raise OperationalError("MySQL Connection not available") from err

        return total_sent

    def cmd_stmt_reset(self, statement_id: int) -> None:
        """Reset data for prepared statement sent as long data

        The result is a dictionary with OK packet information.

        Returns a dict()
        """
        self._handle_ok(self._send_cmd(ServerCmd.STMT_RESET, int4store(statement_id)))

    def cmd_reset_connection(self) -> bool:
        """Resets the session state without re-authenticating

        Reset command only works on MySQL server 5.7.3 or later.
        The result is True for a successful reset otherwise False.

        Returns bool
        """
        try:
            self._handle_ok(self._send_cmd(ServerCmd.RESET_CONNECTION))
            self._post_connection()
            return True
        except (NotSupportedError, OperationalError):
            return False

    def handle_unread_result(self) -> None:
        """Check whether there is an unread result"""
        if self.can_consume_results:
            self.consume_results()
        elif self.unread_result:
            raise InternalError("Unread result found")

virt/lib/python3.9/site-packages/mysql/connector/connection.py
PrinceDisant/LostInNL
# Copyright (c) 2014, 2023, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

# mypy: disable-error-code="arg-type,index"

"""Connection class using the C Extension."""

import os
import platform
import socket
import sys

from typing import Any, Dict, List, Optional, Sequence, Tuple, Type, Union

from . import version
from .abstracts import MySQLConnectionAbstract
from .constants import CharacterSet, ClientFlag, FieldFlag, ServerFlag, ShutdownType
from .conversion import MySQLConverter
from .errors import (
    InterfaceError,
    InternalError,
    OperationalError,
    ProgrammingError,
    get_mysql_exception,
)
from .protocol import MySQLProtocol
from .types import (
    CextEofPacketType,
    CextResultType,
    DescriptionType,
    ParamsSequenceOrDictType,
    RowType,
    StatsPacketType,
    StrOrBytes,
)

HAVE_CMYSQL = False

try:
    import _mysql_connector

    from _mysql_connector import MySQLInterfaceError, MySQLPrepStmt

    from .cursor_cext import (
        CMySQLCursor,
        CMySQLCursorBuffered,
        CMySQLCursorBufferedDict,
        CMySQLCursorBufferedNamedTuple,
        CMySQLCursorBufferedRaw,
        CMySQLCursorDict,
        CMySQLCursorNamedTuple,
        CMySQLCursorPrepared,
        CMySQLCursorPreparedDict,
        CMySQLCursorPreparedNamedTuple,
        CMySQLCursorPreparedRaw,
        CMySQLCursorRaw,
    )

    HAVE_CMYSQL = True
except ImportError as exc:
    raise ImportError(
        f"MySQL Connector/Python C Extension not available ({exc})"
    ) from exc

from .opentelemetry.constants import OTEL_ENABLED
from .opentelemetry.context_propagation import with_context_propagation

if OTEL_ENABLED:
    from .opentelemetry.instrumentation import end_span, record_exception_event


class CMySQLConnection(MySQLConnectionAbstract):
    """Class initiating a MySQL Connection using Connector/C."""

    def __init__(self, **kwargs: Any) -> None:
        """Initialization"""
        if not HAVE_CMYSQL:
            raise RuntimeError("MySQL Connector/Python C Extension not available")
        self._cmysql: Optional[
            _mysql_connector.MySQL  # pylint: disable=c-extension-no-member
        ] = None
        self._columns: List[DescriptionType] = []
        self._plugin_dir: str = os.path.join(
            os.path.dirname(os.path.abspath(_mysql_connector.__file__)),
            "mysql",
            "vendor",
            "plugin",
        )
        if platform.system() == "Linux":
            # Use the authentication plugins from system if they aren't bundled
            if not os.path.exists(self._plugin_dir):
                self._plugin_dir = (
                    "/usr/lib64/mysql/plugin"
                    if os.path.exists("/usr/lib64/mysql/plugin")
                    else "/usr/lib/mysql/plugin"
                )

        self.converter: Optional[MySQLConverter] = None
        super().__init__()

        if kwargs:
            try:
                self.connect(**kwargs)
            except Exception:
                self.close()
                raise

    def _add_default_conn_attrs(self) -> None:
        """Add default connection attributes"""
        license_chunks = version.LICENSE.split(" ")
        if license_chunks[0] == "GPLv2":
            client_license = "GPL-2.0"
        else:
            client_license = "Commercial"

        self._conn_attrs.update(
            {
                "_connector_name": "mysql-connector-python",
                "_connector_license": client_license,
                "_connector_version": ".".join([str(x) for x in version.VERSION[0:3]]),
                "_source_host": socket.gethostname(),
            }
        )

    def _do_handshake(self) -> None:
        """Gather information of the MySQL server before authentication"""
        self._handshake = {
            "protocol": self._cmysql.get_proto_info(),
            "server_version_original": self._cmysql.get_server_info(),
            "server_threadid": self._cmysql.thread_id(),
            "charset": None,
            "server_status": None,
            "auth_plugin": None,
            "auth_data": None,
            "capabilities": self._cmysql.st_server_capabilities(),
        }

        self._server_version = self._check_server_version(
            self._handshake["server_version_original"]
        )
        CharacterSet.set_mysql_version(self._server_version)

    @property
    def _server_status(self) -> int:
        """Returns the server status attribute of MYSQL structure"""
        return self._cmysql.st_server_status()

    def set_allow_local_infile_in_path(self, path: str) -> None:
        """set local_infile_in_path

        Set allow_local_infile_in_path.
        """

        if self._cmysql:
            self._cmysql.set_load_data_local_infile_option(path)

    def set_unicode(self, value: bool = True) -> None:
        """Toggle unicode mode

        Set whether we return string fields as unicode or not.
        Default is True.
        """
        self._use_unicode = value
        if self._cmysql:
            self._cmysql.use_unicode(value)
        if self.converter:
            self.converter.set_unicode(value)

    @property
    def autocommit(self) -> bool:
        """Get whether autocommit is on or off"""
        value = self.info_query("SELECT @@session.autocommit")[0]
        return value == 1

    @autocommit.setter
    def autocommit(self, value: bool) -> None:
        """Toggle autocommit"""
        try:
            self._cmysql.autocommit(value)
            self._autocommit = value
        except MySQLInterfaceError as err:
            raise get_mysql_exception(
                msg=err.msg, errno=err.errno, sqlstate=err.sqlstate
            ) from err

    @property
    def database(self) -> str:
        """Get the current database"""
        return self.info_query("SELECT DATABASE()")[0]  # type: ignore[return-value]

    @database.setter
    def database(self, value: str) -> None:
        """Set the current database"""
        try:
            self._cmysql.select_db(value)
        except MySQLInterfaceError as err:
            raise get_mysql_exception(
                msg=err.msg, errno=err.errno, sqlstate=err.sqlstate
            ) from err

    @property
    def in_transaction(self) -> int:
        """MySQL session has started a transaction"""
        return self._server_status & ServerFlag.STATUS_IN_TRANS

    def _open_connection(self) -> None:
        charset_name = CharacterSet.get_info(self._charset_id)[0]
        # pylint: disable=c-extension-no-member
        self._cmysql = _mysql_connector.MySQL(
            buffered=self._buffered,
            raw=self._raw,
            charset_name=charset_name,
            connection_timeout=(self._connection_timeout or 0),
            use_unicode=self._use_unicode,
            auth_plugin=self._auth_plugin,
            plugin_dir=self._plugin_dir,
        )
        # pylint: enable=c-extension-no-member
        if not self.isset_client_flag(ClientFlag.CONNECT_ARGS):
            self._conn_attrs = {}
        cnx_kwargs = {
            "host": self._host,
            "user": self._user,
            "password": self._password,
            "password1": self._password1,
            "password2": self._password2,
            "password3": self._password3,
            "database": self._database,
            "port": self._port,
            "client_flags": self._client_flags,
            "unix_socket": self._unix_socket,
            "compress": self._compress,
            "ssl_disabled": True,
            "conn_attrs": self._conn_attrs,
            "local_infile": self._allow_local_infile,
            "load_data_local_dir": self._allow_local_infile_in_path,
            "oci_config_file": self._oci_config_file,
            "oci_config_profile": self._oci_config_profile,
            "fido_callback": self._fido_callback,
        }

        tls_versions = self._ssl.get("tls_versions")
        if tls_versions is not None:
            tls_versions.sort(reverse=True)  # type: ignore[union-attr]
            tls_versions = ",".join(tls_versions)
        if self._ssl.get("tls_ciphersuites") is not None:
            ssl_ciphersuites = self._ssl.get("tls_ciphersuites")[0]
            tls_ciphersuites = self._ssl.get("tls_ciphersuites")[1]
        else:
            ssl_ciphersuites = None
            tls_ciphersuites = None
        if (
            tls_versions is not None
            and "TLSv1.3" in tls_versions
            and not tls_ciphersuites
        ):
            tls_ciphersuites = "TLS_AES_256_GCM_SHA384"
        if not self._ssl_disabled:
            cnx_kwargs.update(
                {
                    "ssl_ca": self._ssl.get("ca"),
                    "ssl_cert": self._ssl.get("cert"),
                    "ssl_key": self._ssl.get("key"),
                    "ssl_cipher_suites": ssl_ciphersuites,
                    "tls_versions": tls_versions,
                    "tls_cipher_suites": tls_ciphersuites,
                    "ssl_verify_cert": self._ssl.get("verify_cert") or False,
                    "ssl_verify_identity": self._ssl.get("verify_identity") or False,
                    "ssl_disabled": self._ssl_disabled,
                }
            )

        if os.name == "nt" and self._auth_plugin_class == "MySQLKerberosAuthPlugin":
            cnx_kwargs["use_kerberos_gssapi"] = True

        try:
            self._cmysql.connect(**cnx_kwargs)
            self._cmysql.converter_str_fallback = self._converter_str_fallback
            if self.converter:
                self.converter.str_fallback = self._converter_str_fallback
        except MySQLInterfaceError as err:
            raise get_mysql_exception(
                msg=err.msg, errno=err.errno, sqlstate=err.sqlstate
            ) from err

        self._do_handshake()

    def close(self) -> None:
        """Disconnect from the MySQL server"""
        if self._span and self._span.is_recording():
            record_exception_event(self._span, sys.exc_info()[1])

        if not self._cmysql:
            return

        try:
            self.free_result()
            self._cmysql.close()
        except MySQLInterfaceError as err:
            if OTEL_ENABLED:
                record_exception_event(self._span, err)
            raise get_mysql_exception(
                msg=err.msg, errno=err.errno, sqlstate=err.sqlstate
            ) from err
        finally:
            if OTEL_ENABLED:
                end_span(self._span)

    disconnect = close

    def is_closed(self) -> bool:
        """Return True if the connection to MySQL Server is closed."""
        return not self._cmysql.connected()

    def is_connected(self) -> bool:
        """Reports whether the connection to MySQL Server is available"""
        if self._cmysql:
            self.handle_unread_result()
            return self._cmysql.ping()

        return False

    def ping(self, reconnect: bool = False, attempts: int = 1, delay: int = 0) -> None:
        """Check availability of the MySQL server

        When reconnect is set to True, one or more attempts are made to try
        to reconnect to the MySQL server using the reconnect()-method.

        delay is the number of seconds to wait between each retry.

        When the connection is not available, an InterfaceError is raised. Use
        the is_connected()-method if you just want to check the connection
        without raising an error.

        Raises InterfaceError on errors.
        """
        self.handle_unread_result()

        try:
            connected = self._cmysql.ping()
        except AttributeError:
            pass  # Raise or reconnect later
        else:
            if connected:
                return

        if reconnect:
            self.reconnect(attempts=attempts, delay=delay)
        else:
            raise InterfaceError("Connection to MySQL is not available")

    def set_character_set_name(self, charset: str) -> None:
        """Sets the default character set name for current connection."""
        self._cmysql.set_character_set(charset)

    def info_query(self, query: StrOrBytes) -> Optional[RowType]:
        """Send a query which only returns 1 row"""
        first_row = ()
        try:
            self._cmysql.query(query)
            if self._cmysql.have_result_set:
                first_row = self._cmysql.fetch_row()
                if self._cmysql.fetch_row():
                    self._cmysql.free_result()
                    raise InterfaceError("Query should not return more than 1 row")
            self._cmysql.free_result()
        except MySQLInterfaceError as err:
            raise get_mysql_exception(
                msg=err.msg, errno=err.errno, sqlstate=err.sqlstate
            ) from err

        return first_row

    @property
    def connection_id(self) -> Optional[int]:
        """MySQL connection ID"""
        try:
            return self._cmysql.thread_id()
        except MySQLInterfaceError:
            pass  # Just return None

        return None

    def get_rows(
        self,
        count: Optional[int] = None,
        binary: bool = False,
        columns: Optional[List[DescriptionType]] = None,
        raw: Optional[bool] = None,
        prep_stmt: Optional[MySQLPrepStmt] = None,
    ) -> Tuple[List[RowType], Optional[CextEofPacketType]]:
        """Get all or a subset of rows returned by the MySQL server"""
        unread_result = prep_stmt.have_result_set if prep_stmt else self.unread_result
        if not (self._cmysql and unread_result):
            raise InternalError("No result set available")

        if raw is None:
            raw = self._raw

        rows: List[Tuple[Any, ...]] = []
        if count is not None and count <= 0:
            raise AttributeError("count should be 1 or higher, or None")

        counter = 0
        try:
            fetch_row = prep_stmt.fetch_row if prep_stmt else self._cmysql.fetch_row
            if self.converter:
                # When using a converter class, the C extension should not
                # convert the values. This can be accomplished by setting
                # the raw option to True.
                self._cmysql.raw(True)
            row = fetch_row()
            while row:
                if not self._raw and self.converter:
                    row = list(row)
                    for i, _ in enumerate(row):
                        if not raw:
                            row[i] = self.converter.to_python(self._columns[i], row[i])
                    row = tuple(row)
                rows.append(row)
                counter += 1
                if count and counter == count:
                    break
                row = fetch_row()
            if not row:
                _eof: Optional[CextEofPacketType] = self.fetch_eof_columns(prep_stmt)[
                    "eof"
                ]  # type: ignore[assignment]
                if prep_stmt:
                    prep_stmt.free_result()
                    self._unread_result = False
                else:
                    self.free_result()
            else:
                _eof = None
        except MySQLInterfaceError as err:
            if prep_stmt:
                prep_stmt.free_result()
                raise InterfaceError(str(err)) from err
            self.free_result()
            raise get_mysql_exception(
                msg=err.msg, errno=err.errno, sqlstate=err.sqlstate
            ) from err

        return rows, _eof

    def get_row(
        self,
        binary: bool = False,
        columns: Optional[List[DescriptionType]] = None,
        raw: Optional[bool] = None,
        prep_stmt: Optional[MySQLPrepStmt] = None,
    ) -> Tuple[Optional[RowType], CextEofPacketType]:
        """Get the next rows returned by the MySQL server"""
        try:
            rows, eof = self.get_rows(
                count=1,
                binary=binary,
                columns=columns,
                raw=raw,
                prep_stmt=prep_stmt,
            )
            if rows:
                return (rows[0], eof)
            return (None, eof)
        except IndexError:
            # No row available
            return (None, None)

    def next_result(self) -> Optional[bool]:
        """Reads the next result"""
        if self._cmysql:
            self._cmysql.consume_result()
            return self._cmysql.next_result()
        return None

    def free_result(self) -> None:
        """Frees the result"""
        if self._cmysql:
            self._cmysql.free_result()

    def commit(self) -> None:
        """Commit current transaction"""
        if self._cmysql:
            self.handle_unread_result()
            self._cmysql.commit()

    def rollback(self) -> None:
        """Rollback current transaction"""
        if self._cmysql:
            self._cmysql.consume_result()
            self._cmysql.rollback()

    def cmd_init_db(self, database: str) -> None:
        """Change the current database"""
        try:
            self._cmysql.select_db(database)
        except MySQLInterfaceError as err:
            raise get_mysql_exception(
                msg=err.msg, errno=err.errno, sqlstate=err.sqlstate
            ) from err

    def fetch_eof_columns(
        self, prep_stmt: Optional[MySQLPrepStmt] = None
    ) -> CextResultType:
        """Fetch EOF and column information"""
        have_result_set = (
            prep_stmt.have_result_set if prep_stmt else self._cmysql.have_result_set
        )
        if not have_result_set:
            raise InterfaceError("No result set")

        fields = prep_stmt.fetch_fields() if prep_stmt else self._cmysql.fetch_fields()
        self._columns = []
        for col in fields:
            self._columns.append(
                (
                    col[4],
                    int(col[8]),
                    None,
                    None,
                    None,
                    None,
                    ~int(col[9]) & FieldFlag.NOT_NULL,
                    int(col[9]),
                    int(col[6]),
                )
            )

        return {
            "eof": {
                "status_flag": self._server_status,
                "warning_count": self._cmysql.st_warning_count(),
            },
            "columns": self._columns,
        }

    def fetch_eof_status(self) -> Optional[CextEofPacketType]:
        """Fetch EOF and status information"""
        if self._cmysql:
            return {
                "warning_count": self._cmysql.st_warning_count(),
                "field_count": self._cmysql.st_field_count(),
                "insert_id": self._cmysql.insert_id(),
                "affected_rows": self._cmysql.affected_rows(),
                "server_status": self._server_status,
            }

        return None

    def cmd_stmt_prepare(self, statement: bytes) -> MySQLPrepStmt:
        """Prepares the SQL statement"""
        if not self._cmysql:
            raise OperationalError("MySQL Connection not available")

        try:
            stmt = self._cmysql.stmt_prepare(statement)
            stmt.converter_str_fallback = self._converter_str_fallback
            return stmt
        except MySQLInterfaceError as err:
            raise InterfaceError(str(err)) from err

    def cmd_stmt_execute(
        self, statement_id: MySQLPrepStmt, *args: Any
    ) -> Optional[Union[CextEofPacketType, CextResultType]]:
        """Executes the prepared statement"""
        try:
            statement_id.stmt_execute(*args)
        except MySQLInterfaceError as err:
            raise InterfaceError(str(err)) from err

        self._columns = []
        if not statement_id.have_result_set:
            # No result
            self._unread_result = False
            return self.fetch_eof_status()

        self._unread_result = True
        return self.fetch_eof_columns(statement_id)

    def cmd_stmt_close(self, statement_id: MySQLPrepStmt) -> None:
        """Closes the prepared statement"""
        if self._unread_result:
            raise InternalError("Unread result found")
        statement_id.stmt_close()

    def cmd_stmt_reset(self, statement_id: MySQLPrepStmt) -> None:
        """Resets the prepared statement"""
        if self._unread_result:
            raise InternalError("Unread result found")
        statement_id.stmt_reset()

    @with_context_propagation
    def cmd_query(
        self,
        query: StrOrBytes,
        raw: Optional[bool] = None,
        buffered: bool = False,
        raw_as_string: bool = False,
    ) -> Optional[Union[CextEofPacketType, CextResultType]]:
        """Send a query to the MySQL server"""
        self.handle_unread_result()
        if raw is None:
            raw = self._raw
        try:
            if not isinstance(query, bytes):
                query = query.encode("utf-8")
            self._cmysql.query(
                query,
                raw=raw,
                buffered=buffered,
                raw_as_string=raw_as_string,
                query_attrs=self.query_attrs,
            )
        except MySQLInterfaceError as err:
            raise get_mysql_exception(
                err.errno, msg=err.msg, sqlstate=err.sqlstate
            ) from err
        except AttributeError as err:
            addr = (
                self._unix_socket if self._unix_socket else f"{self._host}:{self._port}"
            )
            raise OperationalError(
                errno=2055, values=(addr, "Connection not available.")
            ) from err

        self._columns = []
        if not self._cmysql.have_result_set:
            # No result
            return self.fetch_eof_status()

        return self.fetch_eof_columns()

    _execute_query = cmd_query

    def cursor(
        self,
        buffered: Optional[bool] = None,
        raw: Optional[bool] = None,
        prepared: Optional[bool] = None,
        cursor_class: Optional[Type[CMySQLCursor]] = None,
        dictionary: Optional[bool] = None,
        named_tuple: Optional[bool] = None,
    ) -> CMySQLCursor:
        """Instantiates and returns a cursor using C Extension

        By default, CMySQLCursor is returned. Depending on the options
        while connecting, a buffered and/or raw cursor is instantiated
        instead. Also depending upon the cursor options, rows can be
        returned as dictionary or named tuple.

        Dictionary and namedtuple based cursors are available with buffered
        output but not raw.

        It is possible to also give a custom cursor through the
        cursor_class parameter, but it needs to be a subclass of
        mysql.connector.cursor_cext.CMySQLCursor.

        Raises ProgrammingError when cursor_class is not a subclass of
        CursorBase. Raises ValueError when cursor is not available.

        Returns instance of CMySQLCursor or subclass.

        :param buffered: Return a buffering cursor
        :param raw: Return a raw cursor
        :param prepared: Return a cursor which uses prepared statements
        :param cursor_class: Use a custom cursor class
        :param dictionary: Rows are returned as dictionary
        :param named_tuple: Rows are returned as named tuple
        :return: Subclass of CMySQLCursor
        :rtype: CMySQLCursor or subclass
        """
        self.handle_unread_result(prepared)
        if not self.is_connected():
            raise OperationalError("MySQL Connection not available.")
        if cursor_class is not None:
            if not issubclass(cursor_class, CMySQLCursor):
                raise ProgrammingError(
                    "Cursor class needs be to subclass of cursor_cext.CMySQLCursor"
                )
            return (cursor_class)(self)

        buffered = buffered or self._buffered
        raw = raw or self._raw

        cursor_type = 0
        if buffered is True:
            cursor_type |= 1
        if raw is True:
            cursor_type |= 2
        if dictionary is True:
            cursor_type |= 4
        if named_tuple is True:
            cursor_type |= 8
        if prepared is True:
            cursor_type |= 16

        types = {
            0: CMySQLCursor,  # 0
            1: CMySQLCursorBuffered,
            2: CMySQLCursorRaw,
            3: CMySQLCursorBufferedRaw,
            4: CMySQLCursorDict,
            5: CMySQLCursorBufferedDict,
            8: CMySQLCursorNamedTuple,
            9: CMySQLCursorBufferedNamedTuple,
            16: CMySQLCursorPrepared,
            18: CMySQLCursorPreparedRaw,
            20: CMySQLCursorPreparedDict,
            24: CMySQLCursorPreparedNamedTuple,
        }
        try:
            return (types[cursor_type])(self)
        except KeyError:
            args = ("buffered", "raw", "dictionary", "named_tuple", "prepared")
            raise ValueError(
                "Cursor not available with given criteria: "
                + ", ".join([args[i] for i in range(5) if cursor_type & (1 << i) != 0])
            ) from None

    @property
    def num_rows(self) -> int:
        """Returns number of rows of current result set"""
        if not self._cmysql.have_result_set:
            raise InterfaceError("No result set")

        return self._cmysql.num_rows()

    @property
    def warning_count(self) -> int:
        """Returns number of warnings"""
        if not self._cmysql:
            return 0

        return self._cmysql.warning_count()

    @property
    def result_set_available(self) -> bool:
        """Check if a result set is available"""
        if not self._cmysql:
            return False

        return self._cmysql.have_result_set

    @property  # type: ignore[misc]
    def unread_result(self) -> bool:
        """Check if there are unread results or rows"""
        return self.result_set_available

    @property
    def more_results(self) -> bool:
        """Check if there are more results"""
        return self._cmysql.more_results()

    def prepare_for_mysql(
        self, params: ParamsSequenceOrDictType
    ) -> Union[Sequence[bytes], Dict[str, bytes],]:
        """Prepare parameters for statements

        This method is use by cursors to prepared parameters found in the
        list (or tuple) params.

        Returns dict.
        """
        result: Union[List[Any], Dict[str, Any]] = []
        if isinstance(params, (list, tuple)):
            if self.converter:
                result = [
                    self.converter.quote(
                        self.converter.escape(
                            self.converter.to_mysql(value), self._sql_mode
                        )
                    )
                    for value in params
                ]
            else:
                result = self._cmysql.convert_to_mysql(*params)
        elif isinstance(params, dict):
            result = {}
            if self.converter:
                for key, value in params.items():
                    result[key] = self.converter.quote(
                        self.converter.escape(
                            self.converter.to_mysql(value), self._sql_mode
                        )
                    )
            else:
                for key, value in params.items():
                    result[key] = self._cmysql.convert_to_mysql(value)[0]
        else:
            raise ProgrammingError(
                f"Could not process parameters: {type(params).__name__}({params}),"
                " it must be of type list, tuple or dict"
            )

        return result

    def consume_results(self) -> None:
        """Consume the current result

        This method consume the result by reading (consuming) all rows.
        """
        self._cmysql.consume_result()

    def cmd_change_user(
        self,
        username: str = "",
        password: str = "",
        database: str = "",
        charset: int = 45,
        password1: str = "",
        password2: str = "",
        password3: str = "",
        oci_config_file: Optional[str] = None,
        oci_config_profile: Optional[str] = None,
    ) -> None:
        """Change the current logged in user"""
        try:
            self._cmysql.change_user(
                username,
                password,
                database,
                password1,
                password2,
                password3,
                oci_config_file,
                oci_config_profile,
            )

        except MySQLInterfaceError as err:
            raise get_mysql_exception(
                msg=err.msg, errno=err.errno, sqlstate=err.sqlstate
            ) from err

        self._charset_id = charset
        self._user = username  # updating user accordingly
        self._post_connection()

    def cmd_reset_connection(self) -> bool:
        """Resets the session state without re-authenticating

        Reset command only works on MySQL server 5.7.3 or later.
        The result is True for a successful reset otherwise False.

        Returns bool
        """
        res = self._cmysql.reset_connection()
        if res:
            self._post_connection()
        return res

    def cmd_refresh(self, options: int) -> Optional[CextEofPacketType]:
        """Send the Refresh command to the MySQL server"""
        try:
            self.handle_unread_result()
            self._cmysql.refresh(options)
        except MySQLInterfaceError as err:
            raise get_mysql_exception(
                msg=err.msg, errno=err.errno, sqlstate=err.sqlstate
            ) from err

        return self.fetch_eof_status()

    def cmd_quit(self) -> None:
        """Close the current connection with the server"""
        self.close()

    def cmd_shutdown(self, shutdown_type: Optional[int] = None) -> None:
        """Shut down the MySQL Server"""
        if not self._cmysql:
            raise OperationalError("MySQL Connection not available")

        if shutdown_type:
            if not ShutdownType.get_info(shutdown_type):
                raise InterfaceError("Invalid shutdown type")
            level = shutdown_type
        else:
            level = ShutdownType.SHUTDOWN_DEFAULT

        try:
            self._cmysql.shutdown(level)
        except MySQLInterfaceError as err:
            raise get_mysql_exception(
                msg=err.msg, errno=err.errno, sqlstate=err.sqlstate
            ) from err
        self.close()

    def cmd_statistics(self) -> StatsPacketType:
        """Return statistics from the MySQL server"""
        self.handle_unread_result()

        try:
            stat = self._cmysql.stat()
            return MySQLProtocol().parse_statistics(stat, with_header=False)
        except (MySQLInterfaceError, InterfaceError) as err:
            raise get_mysql_exception(
                msg=err.msg, errno=err.errno, sqlstate=err.sqlstate
            ) from err

    def cmd_process_kill(self, mysql_pid: int) -> None:
        """Kill a MySQL process"""
        if not isinstance(mysql_pid, int):
            raise ValueError("MySQL PID must be int")
        self.info_query(f"KILL {mysql_pid}")

    def cmd_debug(self) -> Any:
        """Send the DEBUG command"""
        raise NotImplementedError

    def cmd_ping(self) -> Any:
        """Send the PING command"""
        raise NotImplementedError

    def cmd_query_iter(self, statements: Any) -> Any:
        """Send one or more statements to the MySQL server"""
        raise NotImplementedError

    def cmd_stmt_send_long_data(
        self, statement_id: Any, param_id: Any, data: Any
    ) -> Any:
        """Send data for a column"""
        raise NotImplementedError

    def handle_unread_result(self, prepared: bool = False) -> None:
        """Check whether there is an unread result"""
        unread_result = self._unread_result if prepared is True else self.unread_result
        if self.can_consume_results:
            self.consume_results()
        elif unread_result:
            raise InternalError("Unread result found")

    def reset_session(
        self,
        user_variables: Optional[Dict[str, Any]] = None,
        session_variables: Optional[Dict[str, Any]] = None,
    ) -> None:
        """Clears the current active session

        This method resets the session state, if the MySQL server is 5.7.3
        or later active session will be reset without re-authenticating.
        For other server versions session will be reset by re-authenticating.

        It is possible to provide a sequence of variables and their values to
        be set after clearing the session. This is possible for both user
        defined variables and session variables.
        This method takes two arguments user_variables and session_variables
        which are dictionaries.

        Raises OperationalError if not connected, InternalError if there are
        unread results and InterfaceError on errors.
        """
        if not self.is_connected():
            raise OperationalError("MySQL Connection not available.")

        if not self.cmd_reset_connection():
            try:
                self.cmd_change_user(
                    self._user,
                    self._password,
                    self._database,
                    self._charset_id,
                    self._password1,
                    self._password2,
                    self._password3,
                    self._oci_config_file,
                    self._oci_config_profile,
                )
            except ProgrammingError:
                self.reconnect()

        if user_variables or session_variables:
            cur = self.cursor()
            if user_variables:
                for key, value in user_variables.items():
                    cur.execute(f"SET @`{key}` = %s", (value,))
            if session_variables:
                for key, value in session_variables.items():
                    cur.execute(f"SET SESSION `{key}` = %s", (value,))
            cur.close()

virt/lib/python3.9/site-packages/mysql/connector/connection_cext 2.py
PrinceDisant/LostInNL
# Copyright (c) 2014, 2023, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

# mypy: disable-error-code="arg-type,index"

"""Connection class using the C Extension."""

import os
import platform
import socket
import sys

from typing import Any, Dict, List, Optional, Sequence, Tuple, Type, Union

from . import version
from .abstracts import MySQLConnectionAbstract
from .constants import CharacterSet, ClientFlag, FieldFlag, ServerFlag, ShutdownType
from .conversion import MySQLConverter
from .errors import (
    InterfaceError,
    InternalError,
    OperationalError,
    ProgrammingError,
    get_mysql_exception,
)
from .protocol import MySQLProtocol
from .types import (
    CextEofPacketType,
    CextResultType,
    DescriptionType,
    ParamsSequenceOrDictType,
    RowType,
    StatsPacketType,
    StrOrBytes,
)

HAVE_CMYSQL = False

try:
    import _mysql_connector

    from _mysql_connector import MySQLInterfaceError, MySQLPrepStmt

    from .cursor_cext import (
        CMySQLCursor,
        CMySQLCursorBuffered,
        CMySQLCursorBufferedDict,
        CMySQLCursorBufferedNamedTuple,
        CMySQLCursorBufferedRaw,
        CMySQLCursorDict,
        CMySQLCursorNamedTuple,
        CMySQLCursorPrepared,
        CMySQLCursorPreparedDict,
        CMySQLCursorPreparedNamedTuple,
        CMySQLCursorPreparedRaw,
        CMySQLCursorRaw,
    )

    HAVE_CMYSQL = True
except ImportError as exc:
    raise ImportError(
        f"MySQL Connector/Python C Extension not available ({exc})"
    ) from exc

from .opentelemetry.constants import OTEL_ENABLED
from .opentelemetry.context_propagation import with_context_propagation

if OTEL_ENABLED:
    from .opentelemetry.instrumentation import end_span, record_exception_event


class CMySQLConnection(MySQLConnectionAbstract):
    """Class initiating a MySQL Connection using Connector/C."""

    def __init__(self, **kwargs: Any) -> None:
        """Initialization"""
        if not HAVE_CMYSQL:
            raise RuntimeError("MySQL Connector/Python C Extension not available")
        self._cmysql: Optional[
            _mysql_connector.MySQL  # pylint: disable=c-extension-no-member
        ] = None
        self._columns: List[DescriptionType] = []
        self._plugin_dir: str = os.path.join(
            os.path.dirname(os.path.abspath(_mysql_connector.__file__)),
            "mysql",
            "vendor",
            "plugin",
        )
        if platform.system() == "Linux":
            # Use the authentication plugins from system if they aren't bundled
            if not os.path.exists(self._plugin_dir):
                self._plugin_dir = (
                    "/usr/lib64/mysql/plugin"
                    if os.path.exists("/usr/lib64/mysql/plugin")
                    else "/usr/lib/mysql/plugin"
                )

        self.converter: Optional[MySQLConverter] = None
        super().__init__()

        if kwargs:
            try:
                self.connect(**kwargs)
            except Exception:
                self.close()
                raise

    def _add_default_conn_attrs(self) -> None:
        """Add default connection attributes"""
        license_chunks = version.LICENSE.split(" ")
        if license_chunks[0] == "GPLv2":
            client_license = "GPL-2.0"
        else:
            client_license = "Commercial"

        self._conn_attrs.update(
            {
                "_connector_name": "mysql-connector-python",
                "_connector_license": client_license,
                "_connector_version": ".".join([str(x) for x in version.VERSION[0:3]]),
                "_source_host": socket.gethostname(),
            }
        )

    def _do_handshake(self) -> None:
        """Gather information of the MySQL server before authentication"""
        self._handshake = {
            "protocol": self._cmysql.get_proto_info(),
            "server_version_original": self._cmysql.get_server_info(),
            "server_threadid": self._cmysql.thread_id(),
            "charset": None,
            "server_status": None,
            "auth_plugin": None,
            "auth_data": None,
            "capabilities": self._cmysql.st_server_capabilities(),
        }

        self._server_version = self._check_server_version(
            self._handshake["server_version_original"]
        )
        CharacterSet.set_mysql_version(self._server_version)

    @property
    def _server_status(self) -> int:
        """Returns the server status attribute of MYSQL structure"""
        return self._cmysql.st_server_status()

    def set_allow_local_infile_in_path(self, path: str) -> None:
        """set local_infile_in_path

        Set allow_local_infile_in_path.
        """

        if self._cmysql:
            self._cmysql.set_load_data_local_infile_option(path)

    def set_unicode(self, value: bool = True) -> None:
        """Toggle unicode mode

        Set whether we return string fields as unicode or not.
        Default is True.
        """
        self._use_unicode = value
        if self._cmysql:
            self._cmysql.use_unicode(value)
        if self.converter:
            self.converter.set_unicode(value)

    @property
    def autocommit(self) -> bool:
        """Get whether autocommit is on or off"""
        value = self.info_query("SELECT @@session.autocommit")[0]
        return value == 1

    @autocommit.setter
    def autocommit(self, value: bool) -> None:
        """Toggle autocommit"""
        try:
            self._cmysql.autocommit(value)
            self._autocommit = value
        except MySQLInterfaceError as err:
            raise get_mysql_exception(
                msg=err.msg, errno=err.errno, sqlstate=err.sqlstate
            ) from err

    @property
    def database(self) -> str:
        """Get the current database"""
        return self.info_query("SELECT DATABASE()")[0]  # type: ignore[return-value]

    @database.setter
    def database(self, value: str) -> None:
        """Set the current database"""
        try:
            self._cmysql.select_db(value)
        except MySQLInterfaceError as err:
            raise get_mysql_exception(
                msg=err.msg, errno=err.errno, sqlstate=err.sqlstate
            ) from err

    @property
    def in_transaction(self) -> int:
        """MySQL session has started a transaction"""
        return self._server_status & ServerFlag.STATUS_IN_TRANS

    def _open_connection(self) -> None:
        charset_name = CharacterSet.get_info(self._charset_id)[0]
        # pylint: disable=c-extension-no-member
        self._cmysql = _mysql_connector.MySQL(
            buffered=self._buffered,
            raw=self._raw,
            charset_name=charset_name,
            connection_timeout=(self._connection_timeout or 0),
            use_unicode=self._use_unicode,
            auth_plugin=self._auth_plugin,
            plugin_dir=self._plugin_dir,
        )
        # pylint: enable=c-extension-no-member
        if not self.isset_client_flag(ClientFlag.CONNECT_ARGS):
            self._conn_attrs = {}
        cnx_kwargs = {
            "host": self._host,
            "user": self._user,
            "password": self._password,
            "password1": self._password1,
            "password2": self._password2,
            "password3": self._password3,
            "database": self._database,
            "port": self._port,
            "client_flags": self._client_flags,
            "unix_socket": self._unix_socket,
            "compress": self._compress,
            "ssl_disabled": True,
            "conn_attrs": self._conn_attrs,
            "local_infile": self._allow_local_infile,
            "load_data_local_dir": self._allow_local_infile_in_path,
            "oci_config_file": self._oci_config_file,
            "oci_config_profile": self._oci_config_profile,
            "fido_callback": self._fido_callback,
        }

        tls_versions = self._ssl.get("tls_versions")
        if tls_versions is not None:
            tls_versions.sort(reverse=True)  # type: ignore[union-attr]
            tls_versions = ",".join(tls_versions)
        if self._ssl.get("tls_ciphersuites") is not None:
            ssl_ciphersuites = self._ssl.get("tls_ciphersuites")[0]
            tls_ciphersuites = self._ssl.get("tls_ciphersuites")[1]
        else:
            ssl_ciphersuites = None
            tls_ciphersuites = None
        if (
            tls_versions is not None
            and "TLSv1.3" in tls_versions
            and not tls_ciphersuites
        ):
            tls_ciphersuites = "TLS_AES_256_GCM_SHA384"
        if not self._ssl_disabled:
            cnx_kwargs.update(
                {
                    "ssl_ca": self._ssl.get("ca"),
                    "ssl_cert": self._ssl.get("cert"),
                    "ssl_key": self._ssl.get("key"),
                    "ssl_cipher_suites": ssl_ciphersuites,
                    "tls_versions": tls_versions,
                    "tls_cipher_suites": tls_ciphersuites,
                    "ssl_verify_cert": self._ssl.get("verify_cert") or False,
                    "ssl_verify_identity": self._ssl.get("verify_identity") or False,
                    "ssl_disabled": self._ssl_disabled,
                }
            )

        if os.name == "nt" and self._auth_plugin_class == "MySQLKerberosAuthPlugin":
            cnx_kwargs["use_kerberos_gssapi"] = True

        try:
            self._cmysql.connect(**cnx_kwargs)
            self._cmysql.converter_str_fallback = self._converter_str_fallback
            if self.converter:
                self.converter.str_fallback = self._converter_str_fallback
        except MySQLInterfaceError as err:
            raise get_mysql_exception(
                msg=err.msg, errno=err.errno, sqlstate=err.sqlstate
            ) from err

        self._do_handshake()

    def close(self) -> None:
        """Disconnect from the MySQL server"""
        if self._span and self._span.is_recording():
            record_exception_event(self._span, sys.exc_info()[1])

        if not self._cmysql:
            return

        try:
            self.free_result()
            self._cmysql.close()
        except MySQLInterfaceError as err:
            if OTEL_ENABLED:
                record_exception_event(self._span, err)
            raise get_mysql_exception(
                msg=err.msg, errno=err.errno, sqlstate=err.sqlstate
            ) from err
        finally:
            if OTEL_ENABLED:
                end_span(self._span)

    disconnect = close

    def is_closed(self) -> bool:
        """Return True if the connection to MySQL Server is closed."""
        return not self._cmysql.connected()

    def is_connected(self) -> bool:
        """Reports whether the connection to MySQL Server is available"""
        if self._cmysql:
            self.handle_unread_result()
            return self._cmysql.ping()

        return False

    def ping(self, reconnect: bool = False, attempts: int = 1, delay: int = 0) -> None:
        """Check availability of the MySQL server

        When reconnect is set to True, one or more attempts are made to try
        to reconnect to the MySQL server using the reconnect()-method.

        delay is the number of seconds to wait between each retry.

        When the connection is not available, an InterfaceError is raised. Use
        the is_connected()-method if you just want to check the connection
        without raising an error.

        Raises InterfaceError on errors.
        """
        self.handle_unread_result()

        try:
            connected = self._cmysql.ping()
        except AttributeError:
            pass  # Raise or reconnect later
        else:
            if connected:
                return

        if reconnect:
            self.reconnect(attempts=attempts, delay=delay)
        else:
            raise InterfaceError("Connection to MySQL is not available")

    def set_character_set_name(self, charset: str) -> None:
        """Sets the default character set name for current connection."""
        self._cmysql.set_character_set(charset)

    def info_query(self, query: StrOrBytes) -> Optional[RowType]:
        """Send a query which only returns 1 row"""
        first_row = ()
        try:
            self._cmysql.query(query)
            if self._cmysql.have_result_set:
                first_row = self._cmysql.fetch_row()
                if self._cmysql.fetch_row():
                    self._cmysql.free_result()
                    raise InterfaceError("Query should not return more than 1 row")
            self._cmysql.free_result()
        except MySQLInterfaceError as err:
            raise get_mysql_exception(
                msg=err.msg, errno=err.errno, sqlstate=err.sqlstate
            ) from err

        return first_row

    @property
    def connection_id(self) -> Optional[int]:
        """MySQL connection ID"""
        try:
            return self._cmysql.thread_id()
        except MySQLInterfaceError:
            pass  # Just return None

        return None

    def get_rows(
        self,
        count: Optional[int] = None,
        binary: bool = False,
        columns: Optional[List[DescriptionType]] = None,
        raw: Optional[bool] = None,
        prep_stmt: Optional[MySQLPrepStmt] = None,
    ) -> Tuple[List[RowType], Optional[CextEofPacketType]]:
        """Get all or a subset of rows returned by the MySQL server"""
        unread_result = prep_stmt.have_result_set if prep_stmt else self.unread_result
        if not (self._cmysql and unread_result):
            raise InternalError("No result set available")

        if raw is None:
            raw = self._raw

        rows: List[Tuple[Any, ...]] = []
        if count is not None and count <= 0:
            raise AttributeError("count should be 1 or higher, or None")

        counter = 0
        try:
            fetch_row = prep_stmt.fetch_row if prep_stmt else self._cmysql.fetch_row
            if self.converter:
                # When using a converter class, the C extension should not
                # convert the values. This can be accomplished by setting
                # the raw option to True.
                self._cmysql.raw(True)
            row = fetch_row()
            while row:
                if not self._raw and self.converter:
                    row = list(row)
                    for i, _ in enumerate(row):
                        if not raw:
                            row[i] = self.converter.to_python(self._columns[i], row[i])
                    row = tuple(row)
                rows.append(row)
                counter += 1
                if count and counter == count:
                    break
                row = fetch_row()
            if not row:
                _eof: Optional[CextEofPacketType] = self.fetch_eof_columns(prep_stmt)[
                    "eof"
                ]  # type: ignore[assignment]
                if prep_stmt:
                    prep_stmt.free_result()
                    self._unread_result = False
                else:
                    self.free_result()
            else:
                _eof = None
        except MySQLInterfaceError as err:
            if prep_stmt:
                prep_stmt.free_result()
                raise InterfaceError(str(err)) from err
            self.free_result()
            raise get_mysql_exception(
                msg=err.msg, errno=err.errno, sqlstate=err.sqlstate
            ) from err

        return rows, _eof

    def get_row(
        self,
        binary: bool = False,
        columns: Optional[List[DescriptionType]] = None,
        raw: Optional[bool] = None,
        prep_stmt: Optional[MySQLPrepStmt] = None,
    ) -> Tuple[Optional[RowType], CextEofPacketType]:
        """Get the next rows returned by the MySQL server"""
        try:
            rows, eof = self.get_rows(
                count=1,
                binary=binary,
                columns=columns,
                raw=raw,
                prep_stmt=prep_stmt,
            )
            if rows:
                return (rows[0], eof)
            return (None, eof)
        except IndexError:
            # No row available
            return (None, None)

    def next_result(self) -> Optional[bool]:
        """Reads the next result"""
        if self._cmysql:
            self._cmysql.consume_result()
            return self._cmysql.next_result()
        return None

    def free_result(self) -> None:
        """Frees the result"""
        if self._cmysql:
            self._cmysql.free_result()

    def commit(self) -> None:
        """Commit current transaction"""
        if self._cmysql:
            self.handle_unread_result()
            self._cmysql.commit()

    def rollback(self) -> None:
        """Rollback current transaction"""
        if self._cmysql:
            self._cmysql.consume_result()
            self._cmysql.rollback()

    def cmd_init_db(self, database: str) -> None:
        """Change the current database"""
        try:
            self._cmysql.select_db(database)
        except MySQLInterfaceError as err:
            raise get_mysql_exception(
                msg=err.msg, errno=err.errno, sqlstate=err.sqlstate
            ) from err

    def fetch_eof_columns(
        self, prep_stmt: Optional[MySQLPrepStmt] = None
    ) -> CextResultType:
        """Fetch EOF and column information"""
        have_result_set = (
            prep_stmt.have_result_set if prep_stmt else self._cmysql.have_result_set
        )
        if not have_result_set:
            raise InterfaceError("No result set")

        fields = prep_stmt.fetch_fields() if prep_stmt else self._cmysql.fetch_fields()
        self._columns = []
        for col in fields:
            self._columns.append(
                (
                    col[4],
                    int(col[8]),
                    None,
                    None,
                    None,
                    None,
                    ~int(col[9]) & FieldFlag.NOT_NULL,
                    int(col[9]),
                    int(col[6]),
                )
            )

        return {
            "eof": {
                "status_flag": self._server_status,
                "warning_count": self._cmysql.st_warning_count(),
            },
            "columns": self._columns,
        }

    def fetch_eof_status(self) -> Optional[CextEofPacketType]:
        """Fetch EOF and status information"""
        if self._cmysql:
            return {
                "warning_count": self._cmysql.st_warning_count(),
                "field_count": self._cmysql.st_field_count(),
                "insert_id": self._cmysql.insert_id(),
                "affected_rows": self._cmysql.affected_rows(),
                "server_status": self._server_status,
            }

        return None

    def cmd_stmt_prepare(self, statement: bytes) -> MySQLPrepStmt:
        """Prepares the SQL statement"""
        if not self._cmysql:
            raise OperationalError("MySQL Connection not available")

        try:
            stmt = self._cmysql.stmt_prepare(statement)
            stmt.converter_str_fallback = self._converter_str_fallback
            return stmt
        except MySQLInterfaceError as err:
            raise InterfaceError(str(err)) from err

    def cmd_stmt_execute(
        self, statement_id: MySQLPrepStmt, *args: Any
    ) -> Optional[Union[CextEofPacketType, CextResultType]]:
        """Executes the prepared statement"""
        try:
            statement_id.stmt_execute(*args)
        except MySQLInterfaceError as err:
            raise InterfaceError(str(err)) from err

        self._columns = []
        if not statement_id.have_result_set:
            # No result
            self._unread_result = False
            return self.fetch_eof_status()

        self._unread_result = True
        return self.fetch_eof_columns(statement_id)

    def cmd_stmt_close(self, statement_id: MySQLPrepStmt) -> None:
        """Closes the prepared statement"""
        if self._unread_result:
            raise InternalError("Unread result found")
        statement_id.stmt_close()

    def cmd_stmt_reset(self, statement_id: MySQLPrepStmt) -> None:
        """Resets the prepared statement"""
        if self._unread_result:
            raise InternalError("Unread result found")
        statement_id.stmt_reset()

    @with_context_propagation
    def cmd_query(
        self,
        query: StrOrBytes,
        raw: Optional[bool] = None,
        buffered: bool = False,
        raw_as_string: bool = False,
    ) -> Optional[Union[CextEofPacketType, CextResultType]]:
        """Send a query to the MySQL server"""
        self.handle_unread_result()
        if raw is None:
            raw = self._raw
        try:
            if not isinstance(query, bytes):
                query = query.encode("utf-8")
            self._cmysql.query(
                query,
                raw=raw,
                buffered=buffered,
                raw_as_string=raw_as_string,
                query_attrs=self.query_attrs,
            )
        except MySQLInterfaceError as err:
            raise get_mysql_exception(
                err.errno, msg=err.msg, sqlstate=err.sqlstate
            ) from err
        except AttributeError as err:
            addr = (
                self._unix_socket if self._unix_socket else f"{self._host}:{self._port}"
            )
            raise OperationalError(
                errno=2055, values=(addr, "Connection not available.")
            ) from err

        self._columns = []
        if not self._cmysql.have_result_set:
            # No result
            return self.fetch_eof_status()

        return self.fetch_eof_columns()

    _execute_query = cmd_query

    def cursor(
        self,
        buffered: Optional[bool] = None,
        raw: Optional[bool] = None,
        prepared: Optional[bool] = None,
        cursor_class: Optional[Type[CMySQLCursor]] = None,
        dictionary: Optional[bool] = None,
        named_tuple: Optional[bool] = None,
    ) -> CMySQLCursor:
        """Instantiates and returns a cursor using C Extension

        By default, CMySQLCursor is returned. Depending on the options
        while connecting, a buffered and/or raw cursor is instantiated
        instead. Also depending upon the cursor options, rows can be
        returned as dictionary or named tuple.

        Dictionary and namedtuple based cursors are available with buffered
        output but not raw.

        It is possible to also give a custom cursor through the
        cursor_class parameter, but it needs to be a subclass of
        mysql.connector.cursor_cext.CMySQLCursor.

        Raises ProgrammingError when cursor_class is not a subclass of
        CursorBase. Raises ValueError when cursor is not available.

        Returns instance of CMySQLCursor or subclass.

        :param buffered: Return a buffering cursor
        :param raw: Return a raw cursor
        :param prepared: Return a cursor which uses prepared statements
        :param cursor_class: Use a custom cursor class
        :param dictionary: Rows are returned as dictionary
        :param named_tuple: Rows are returned as named tuple
        :return: Subclass of CMySQLCursor
        :rtype: CMySQLCursor or subclass
        """
        self.handle_unread_result(prepared)
        if not self.is_connected():
            raise OperationalError("MySQL Connection not available.")
        if cursor_class is not None:
            if not issubclass(cursor_class, CMySQLCursor):
                raise ProgrammingError(
                    "Cursor class needs be to subclass of cursor_cext.CMySQLCursor"
                )
            return (cursor_class)(self)

        buffered = buffered or self._buffered
        raw = raw or self._raw

        cursor_type = 0
        if buffered is True:
            cursor_type |= 1
        if raw is True:
            cursor_type |= 2
        if dictionary is True:
            cursor_type |= 4
        if named_tuple is True:
            cursor_type |= 8
        if prepared is True:
            cursor_type |= 16

        types = {
            0: CMySQLCursor,  # 0
            1: CMySQLCursorBuffered,
            2: CMySQLCursorRaw,
            3: CMySQLCursorBufferedRaw,
            4: CMySQLCursorDict,
            5: CMySQLCursorBufferedDict,
            8: CMySQLCursorNamedTuple,
            9: CMySQLCursorBufferedNamedTuple,
            16: CMySQLCursorPrepared,
            18: CMySQLCursorPreparedRaw,
            20: CMySQLCursorPreparedDict,
            24: CMySQLCursorPreparedNamedTuple,
        }
        try:
            return (types[cursor_type])(self)
        except KeyError:
            args = ("buffered", "raw", "dictionary", "named_tuple", "prepared")
            raise ValueError(
                "Cursor not available with given criteria: "
                + ", ".join([args[i] for i in range(5) if cursor_type & (1 << i) != 0])
            ) from None

    @property
    def num_rows(self) -> int:
        """Returns number of rows of current result set"""
        if not self._cmysql.have_result_set:
            raise InterfaceError("No result set")

        return self._cmysql.num_rows()

    @property
    def warning_count(self) -> int:
        """Returns number of warnings"""
        if not self._cmysql:
            return 0

        return self._cmysql.warning_count()

    @property
    def result_set_available(self) -> bool:
        """Check if a result set is available"""
        if not self._cmysql:
            return False

        return self._cmysql.have_result_set

    @property  # type: ignore[misc]
    def unread_result(self) -> bool:
        """Check if there are unread results or rows"""
        return self.result_set_available

    @property
    def more_results(self) -> bool:
        """Check if there are more results"""
        return self._cmysql.more_results()

    def prepare_for_mysql(
        self, params: ParamsSequenceOrDictType
    ) -> Union[Sequence[bytes], Dict[str, bytes],]:
        """Prepare parameters for statements

        This method is use by cursors to prepared parameters found in the
        list (or tuple) params.

        Returns dict.
        """
        result: Union[List[Any], Dict[str, Any]] = []
        if isinstance(params, (list, tuple)):
            if self.converter:
                result = [
                    self.converter.quote(
                        self.converter.escape(
                            self.converter.to_mysql(value), self._sql_mode
                        )
                    )
                    for value in params
                ]
            else:
                result = self._cmysql.convert_to_mysql(*params)
        elif isinstance(params, dict):
            result = {}
            if self.converter:
                for key, value in params.items():
                    result[key] = self.converter.quote(
                        self.converter.escape(
                            self.converter.to_mysql(value), self._sql_mode
                        )
                    )
            else:
                for key, value in params.items():
                    result[key] = self._cmysql.convert_to_mysql(value)[0]
        else:
            raise ProgrammingError(
                f"Could not process parameters: {type(params).__name__}({params}),"
                " it must be of type list, tuple or dict"
            )

        return result

    def consume_results(self) -> None:
        """Consume the current result

        This method consume the result by reading (consuming) all rows.
        """
        self._cmysql.consume_result()

    def cmd_change_user(
        self,
        username: str = "",
        password: str = "",
        database: str = "",
        charset: int = 45,
        password1: str = "",
        password2: str = "",
        password3: str = "",
        oci_config_file: Optional[str] = None,
        oci_config_profile: Optional[str] = None,
    ) -> None:
        """Change the current logged in user"""
        try:
            self._cmysql.change_user(
                username,
                password,
                database,
                password1,
                password2,
                password3,
                oci_config_file,
                oci_config_profile,
            )

        except MySQLInterfaceError as err:
            raise get_mysql_exception(
                msg=err.msg, errno=err.errno, sqlstate=err.sqlstate
            ) from err

        self._charset_id = charset
        self._user = username  # updating user accordingly
        self._post_connection()

    def cmd_reset_connection(self) -> bool:
        """Resets the session state without re-authenticating

        Reset command only works on MySQL server 5.7.3 or later.
        The result is True for a successful reset otherwise False.

        Returns bool
        """
        res = self._cmysql.reset_connection()
        if res:
            self._post_connection()
        return res

    def cmd_refresh(self, options: int) -> Optional[CextEofPacketType]:
        """Send the Refresh command to the MySQL server"""
        try:
            self.handle_unread_result()
            self._cmysql.refresh(options)
        except MySQLInterfaceError as err:
            raise get_mysql_exception(
                msg=err.msg, errno=err.errno, sqlstate=err.sqlstate
            ) from err

        return self.fetch_eof_status()

    def cmd_quit(self) -> None:
        """Close the current connection with the server"""
        self.close()

    def cmd_shutdown(self, shutdown_type: Optional[int] = None) -> None:
        """Shut down the MySQL Server"""
        if not self._cmysql:
            raise OperationalError("MySQL Connection not available")

        if shutdown_type:
            if not ShutdownType.get_info(shutdown_type):
                raise InterfaceError("Invalid shutdown type")
            level = shutdown_type
        else:
            level = ShutdownType.SHUTDOWN_DEFAULT

        try:
            self._cmysql.shutdown(level)
        except MySQLInterfaceError as err:
            raise get_mysql_exception(
                msg=err.msg, errno=err.errno, sqlstate=err.sqlstate
            ) from err
        self.close()

    def cmd_statistics(self) -> StatsPacketType:
        """Return statistics from the MySQL server"""
        self.handle_unread_result()

        try:
            stat = self._cmysql.stat()
            return MySQLProtocol().parse_statistics(stat, with_header=False)
        except (MySQLInterfaceError, InterfaceError) as err:
            raise get_mysql_exception(
                msg=err.msg, errno=err.errno, sqlstate=err.sqlstate
            ) from err

    def cmd_process_kill(self, mysql_pid: int) -> None:
        """Kill a MySQL process"""
        if not isinstance(mysql_pid, int):
            raise ValueError("MySQL PID must be int")
        self.info_query(f"KILL {mysql_pid}")

    def cmd_debug(self) -> Any:
        """Send the DEBUG command"""
        raise NotImplementedError

    def cmd_ping(self) -> Any:
        """Send the PING command"""
        raise NotImplementedError

    def cmd_query_iter(self, statements: Any) -> Any:
        """Send one or more statements to the MySQL server"""
        raise NotImplementedError

    def cmd_stmt_send_long_data(
        self, statement_id: Any, param_id: Any, data: Any
    ) -> Any:
        """Send data for a column"""
        raise NotImplementedError

    def handle_unread_result(self, prepared: bool = False) -> None:
        """Check whether there is an unread result"""
        unread_result = self._unread_result if prepared is True else self.unread_result
        if self.can_consume_results:
            self.consume_results()
        elif unread_result:
            raise InternalError("Unread result found")

    def reset_session(
        self,
        user_variables: Optional[Dict[str, Any]] = None,
        session_variables: Optional[Dict[str, Any]] = None,
    ) -> None:
        """Clears the current active session

        This method resets the session state, if the MySQL server is 5.7.3
        or later active session will be reset without re-authenticating.
        For other server versions session will be reset by re-authenticating.

        It is possible to provide a sequence of variables and their values to
        be set after clearing the session. This is possible for both user
        defined variables and session variables.
        This method takes two arguments user_variables and session_variables
        which are dictionaries.

        Raises OperationalError if not connected, InternalError if there are
        unread results and InterfaceError on errors.
        """
        if not self.is_connected():
            raise OperationalError("MySQL Connection not available.")

        if not self.cmd_reset_connection():
            try:
                self.cmd_change_user(
                    self._user,
                    self._password,
                    self._database,
                    self._charset_id,
                    self._password1,
                    self._password2,
                    self._password3,
                    self._oci_config_file,
                    self._oci_config_profile,
                )
            except ProgrammingError:
                self.reconnect()

        if user_variables or session_variables:
            cur = self.cursor()
            if user_variables:
                for key, value in user_variables.items():
                    cur.execute(f"SET @`{key}` = %s", (value,))
            if session_variables:
                for key, value in session_variables.items():
                    cur.execute(f"SET SESSION `{key}` = %s", (value,))
            cur.close()

virt/lib/python3.9/site-packages/mysql/connector/connection_cext 3.py
PrinceDisant/LostInNL
# Copyright (c) 2014, 2023, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

# mypy: disable-error-code="arg-type,index"

"""Connection class using the C Extension."""

import os
import platform
import socket
import sys

from typing import Any, Dict, List, Optional, Sequence, Tuple, Type, Union

from . import version
from .abstracts import MySQLConnectionAbstract
from .constants import CharacterSet, ClientFlag, FieldFlag, ServerFlag, ShutdownType
from .conversion import MySQLConverter
from .errors import (
    InterfaceError,
    InternalError,
    OperationalError,
    ProgrammingError,
    get_mysql_exception,
)
from .protocol import MySQLProtocol
from .types import (
    CextEofPacketType,
    CextResultType,
    DescriptionType,
    ParamsSequenceOrDictType,
    RowType,
    StatsPacketType,
    StrOrBytes,
)

HAVE_CMYSQL = False

try:
    import _mysql_connector

    from _mysql_connector import MySQLInterfaceError, MySQLPrepStmt

    from .cursor_cext import (
        CMySQLCursor,
        CMySQLCursorBuffered,
        CMySQLCursorBufferedDict,
        CMySQLCursorBufferedNamedTuple,
        CMySQLCursorBufferedRaw,
        CMySQLCursorDict,
        CMySQLCursorNamedTuple,
        CMySQLCursorPrepared,
        CMySQLCursorPreparedDict,
        CMySQLCursorPreparedNamedTuple,
        CMySQLCursorPreparedRaw,
        CMySQLCursorRaw,
    )

    HAVE_CMYSQL = True
except ImportError as exc:
    raise ImportError(
        f"MySQL Connector/Python C Extension not available ({exc})"
    ) from exc

from .opentelemetry.constants import OTEL_ENABLED
from .opentelemetry.context_propagation import with_context_propagation

if OTEL_ENABLED:
    from .opentelemetry.instrumentation import end_span, record_exception_event


class CMySQLConnection(MySQLConnectionAbstract):
    """Class initiating a MySQL Connection using Connector/C."""

    def __init__(self, **kwargs: Any) -> None:
        """Initialization"""
        if not HAVE_CMYSQL:
            raise RuntimeError("MySQL Connector/Python C Extension not available")
        self._cmysql: Optional[
            _mysql_connector.MySQL  # pylint: disable=c-extension-no-member
        ] = None
        self._columns: List[DescriptionType] = []
        self._plugin_dir: str = os.path.join(
            os.path.dirname(os.path.abspath(_mysql_connector.__file__)),
            "mysql",
            "vendor",
            "plugin",
        )
        if platform.system() == "Linux":
            # Use the authentication plugins from system if they aren't bundled
            if not os.path.exists(self._plugin_dir):
                self._plugin_dir = (
                    "/usr/lib64/mysql/plugin"
                    if os.path.exists("/usr/lib64/mysql/plugin")
                    else "/usr/lib/mysql/plugin"
                )

        self.converter: Optional[MySQLConverter] = None
        super().__init__()

        if kwargs:
            try:
                self.connect(**kwargs)
            except Exception:
                self.close()
                raise

    def _add_default_conn_attrs(self) -> None:
        """Add default connection attributes"""
        license_chunks = version.LICENSE.split(" ")
        if license_chunks[0] == "GPLv2":
            client_license = "GPL-2.0"
        else:
            client_license = "Commercial"

        self._conn_attrs.update(
            {
                "_connector_name": "mysql-connector-python",
                "_connector_license": client_license,
                "_connector_version": ".".join([str(x) for x in version.VERSION[0:3]]),
                "_source_host": socket.gethostname(),
            }
        )

    def _do_handshake(self) -> None:
        """Gather information of the MySQL server before authentication"""
        self._handshake = {
            "protocol": self._cmysql.get_proto_info(),
            "server_version_original": self._cmysql.get_server_info(),
            "server_threadid": self._cmysql.thread_id(),
            "charset": None,
            "server_status": None,
            "auth_plugin": None,
            "auth_data": None,
            "capabilities": self._cmysql.st_server_capabilities(),
        }

        self._server_version = self._check_server_version(
            self._handshake["server_version_original"]
        )
        CharacterSet.set_mysql_version(self._server_version)

    @property
    def _server_status(self) -> int:
        """Returns the server status attribute of MYSQL structure"""
        return self._cmysql.st_server_status()

    def set_allow_local_infile_in_path(self, path: str) -> None:
        """set local_infile_in_path

        Set allow_local_infile_in_path.
        """

        if self._cmysql:
            self._cmysql.set_load_data_local_infile_option(path)

    def set_unicode(self, value: bool = True) -> None:
        """Toggle unicode mode

        Set whether we return string fields as unicode or not.
        Default is True.
        """
        self._use_unicode = value
        if self._cmysql:
            self._cmysql.use_unicode(value)
        if self.converter:
            self.converter.set_unicode(value)

    @property
    def autocommit(self) -> bool:
        """Get whether autocommit is on or off"""
        value = self.info_query("SELECT @@session.autocommit")[0]
        return value == 1

    @autocommit.setter
    def autocommit(self, value: bool) -> None:
        """Toggle autocommit"""
        try:
            self._cmysql.autocommit(value)
            self._autocommit = value
        except MySQLInterfaceError as err:
            raise get_mysql_exception(
                msg=err.msg, errno=err.errno, sqlstate=err.sqlstate
            ) from err

    @property
    def database(self) -> str:
        """Get the current database"""
        return self.info_query("SELECT DATABASE()")[0]  # type: ignore[return-value]

    @database.setter
    def database(self, value: str) -> None:
        """Set the current database"""
        try:
            self._cmysql.select_db(value)
        except MySQLInterfaceError as err:
            raise get_mysql_exception(
                msg=err.msg, errno=err.errno, sqlstate=err.sqlstate
            ) from err

    @property
    def in_transaction(self) -> int:
        """MySQL session has started a transaction"""
        return self._server_status & ServerFlag.STATUS_IN_TRANS

    def _open_connection(self) -> None:
        charset_name = CharacterSet.get_info(self._charset_id)[0]
        # pylint: disable=c-extension-no-member
        self._cmysql = _mysql_connector.MySQL(
            buffered=self._buffered,
            raw=self._raw,
            charset_name=charset_name,
            connection_timeout=(self._connection_timeout or 0),
            use_unicode=self._use_unicode,
            auth_plugin=self._auth_plugin,
            plugin_dir=self._plugin_dir,
        )
        # pylint: enable=c-extension-no-member
        if not self.isset_client_flag(ClientFlag.CONNECT_ARGS):
            self._conn_attrs = {}
        cnx_kwargs = {
            "host": self._host,
            "user": self._user,
            "password": self._password,
            "password1": self._password1,
            "password2": self._password2,
            "password3": self._password3,
            "database": self._database,
            "port": self._port,
            "client_flags": self._client_flags,
            "unix_socket": self._unix_socket,
            "compress": self._compress,
            "ssl_disabled": True,
            "conn_attrs": self._conn_attrs,
            "local_infile": self._allow_local_infile,
            "load_data_local_dir": self._allow_local_infile_in_path,
            "oci_config_file": self._oci_config_file,
            "oci_config_profile": self._oci_config_profile,
            "fido_callback": self._fido_callback,
        }

        tls_versions = self._ssl.get("tls_versions")
        if tls_versions is not None:
            tls_versions.sort(reverse=True)  # type: ignore[union-attr]
            tls_versions = ",".join(tls_versions)
        if self._ssl.get("tls_ciphersuites") is not None:
            ssl_ciphersuites = self._ssl.get("tls_ciphersuites")[0]
            tls_ciphersuites = self._ssl.get("tls_ciphersuites")[1]
        else:
            ssl_ciphersuites = None
            tls_ciphersuites = None
        if (
            tls_versions is not None
            and "TLSv1.3" in tls_versions
            and not tls_ciphersuites
        ):
            tls_ciphersuites = "TLS_AES_256_GCM_SHA384"
        if not self._ssl_disabled:
            cnx_kwargs.update(
                {
                    "ssl_ca": self._ssl.get("ca"),
                    "ssl_cert": self._ssl.get("cert"),
                    "ssl_key": self._ssl.get("key"),
                    "ssl_cipher_suites": ssl_ciphersuites,
                    "tls_versions": tls_versions,
                    "tls_cipher_suites": tls_ciphersuites,
                    "ssl_verify_cert": self._ssl.get("verify_cert") or False,
                    "ssl_verify_identity": self._ssl.get("verify_identity") or False,
                    "ssl_disabled": self._ssl_disabled,
                }
            )

        if os.name == "nt" and self._auth_plugin_class == "MySQLKerberosAuthPlugin":
            cnx_kwargs["use_kerberos_gssapi"] = True

        try:
            self._cmysql.connect(**cnx_kwargs)
            self._cmysql.converter_str_fallback = self._converter_str_fallback
            if self.converter:
                self.converter.str_fallback = self._converter_str_fallback
        except MySQLInterfaceError as err:
            raise get_mysql_exception(
                msg=err.msg, errno=err.errno, sqlstate=err.sqlstate
            ) from err

        self._do_handshake()

    def close(self) -> None:
        """Disconnect from the MySQL server"""
        if self._span and self._span.is_recording():
            record_exception_event(self._span, sys.exc_info()[1])

        if not self._cmysql:
            return

        try:
            self.free_result()
            self._cmysql.close()
        except MySQLInterfaceError as err:
            if OTEL_ENABLED:
                record_exception_event(self._span, err)
            raise get_mysql_exception(
                msg=err.msg, errno=err.errno, sqlstate=err.sqlstate
            ) from err
        finally:
            if OTEL_ENABLED:
                end_span(self._span)

    disconnect = close

    def is_closed(self) -> bool:
        """Return True if the connection to MySQL Server is closed."""
        return not self._cmysql.connected()

    def is_connected(self) -> bool:
        """Reports whether the connection to MySQL Server is available"""
        if self._cmysql:
            self.handle_unread_result()
            return self._cmysql.ping()

        return False

    def ping(self, reconnect: bool = False, attempts: int = 1, delay: int = 0) -> None:
        """Check availability of the MySQL server

        When reconnect is set to True, one or more attempts are made to try
        to reconnect to the MySQL server using the reconnect()-method.

        delay is the number of seconds to wait between each retry.

        When the connection is not available, an InterfaceError is raised. Use
        the is_connected()-method if you just want to check the connection
        without raising an error.

        Raises InterfaceError on errors.
        """
        self.handle_unread_result()

        try:
            connected = self._cmysql.ping()
        except AttributeError:
            pass  # Raise or reconnect later
        else:
            if connected:
                return

        if reconnect:
            self.reconnect(attempts=attempts, delay=delay)
        else:
            raise InterfaceError("Connection to MySQL is not available")

    def set_character_set_name(self, charset: str) -> None:
        """Sets the default character set name for current connection."""
        self._cmysql.set_character_set(charset)

    def info_query(self, query: StrOrBytes) -> Optional[RowType]:
        """Send a query which only returns 1 row"""
        first_row = ()
        try:
            self._cmysql.query(query)
            if self._cmysql.have_result_set:
                first_row = self._cmysql.fetch_row()
                if self._cmysql.fetch_row():
                    self._cmysql.free_result()
                    raise InterfaceError("Query should not return more than 1 row")
            self._cmysql.free_result()
        except MySQLInterfaceError as err:
            raise get_mysql_exception(
                msg=err.msg, errno=err.errno, sqlstate=err.sqlstate
            ) from err

        return first_row

    @property
    def connection_id(self) -> Optional[int]:
        """MySQL connection ID"""
        try:
            return self._cmysql.thread_id()
        except MySQLInterfaceError:
            pass  # Just return None

        return None

    def get_rows(
        self,
        count: Optional[int] = None,
        binary: bool = False,
        columns: Optional[List[DescriptionType]] = None,
        raw: Optional[bool] = None,
        prep_stmt: Optional[MySQLPrepStmt] = None,
    ) -> Tuple[List[RowType], Optional[CextEofPacketType]]:
        """Get all or a subset of rows returned by the MySQL server"""
        unread_result = prep_stmt.have_result_set if prep_stmt else self.unread_result
        if not (self._cmysql and unread_result):
            raise InternalError("No result set available")

        if raw is None:
            raw = self._raw

        rows: List[Tuple[Any, ...]] = []
        if count is not None and count <= 0:
            raise AttributeError("count should be 1 or higher, or None")

        counter = 0
        try:
            fetch_row = prep_stmt.fetch_row if prep_stmt else self._cmysql.fetch_row
            if self.converter:
                # When using a converter class, the C extension should not
                # convert the values. This can be accomplished by setting
                # the raw option to True.
                self._cmysql.raw(True)
            row = fetch_row()
            while row:
                if not self._raw and self.converter:
                    row = list(row)
                    for i, _ in enumerate(row):
                        if not raw:
                            row[i] = self.converter.to_python(self._columns[i], row[i])
                    row = tuple(row)
                rows.append(row)
                counter += 1
                if count and counter == count:
                    break
                row = fetch_row()
            if not row:
                _eof: Optional[CextEofPacketType] = self.fetch_eof_columns(prep_stmt)[
                    "eof"
                ]  # type: ignore[assignment]
                if prep_stmt:
                    prep_stmt.free_result()
                    self._unread_result = False
                else:
                    self.free_result()
            else:
                _eof = None
        except MySQLInterfaceError as err:
            if prep_stmt:
                prep_stmt.free_result()
                raise InterfaceError(str(err)) from err
            self.free_result()
            raise get_mysql_exception(
                msg=err.msg, errno=err.errno, sqlstate=err.sqlstate
            ) from err

        return rows, _eof

    def get_row(
        self,
        binary: bool = False,
        columns: Optional[List[DescriptionType]] = None,
        raw: Optional[bool] = None,
        prep_stmt: Optional[MySQLPrepStmt] = None,
    ) -> Tuple[Optional[RowType], CextEofPacketType]:
        """Get the next rows returned by the MySQL server"""
        try:
            rows, eof = self.get_rows(
                count=1,
                binary=binary,
                columns=columns,
                raw=raw,
                prep_stmt=prep_stmt,
            )
            if rows:
                return (rows[0], eof)
            return (None, eof)
        except IndexError:
            # No row available
            return (None, None)

    def next_result(self) -> Optional[bool]:
        """Reads the next result"""
        if self._cmysql:
            self._cmysql.consume_result()
            return self._cmysql.next_result()
        return None

    def free_result(self) -> None:
        """Frees the result"""
        if self._cmysql:
            self._cmysql.free_result()

    def commit(self) -> None:
        """Commit current transaction"""
        if self._cmysql:
            self.handle_unread_result()
            self._cmysql.commit()

    def rollback(self) -> None:
        """Rollback current transaction"""
        if self._cmysql:
            self._cmysql.consume_result()
            self._cmysql.rollback()

    def cmd_init_db(self, database: str) -> None:
        """Change the current database"""
        try:
            self._cmysql.select_db(database)
        except MySQLInterfaceError as err:
            raise get_mysql_exception(
                msg=err.msg, errno=err.errno, sqlstate=err.sqlstate
            ) from err

    def fetch_eof_columns(
        self, prep_stmt: Optional[MySQLPrepStmt] = None
    ) -> CextResultType:
        """Fetch EOF and column information"""
        have_result_set = (
            prep_stmt.have_result_set if prep_stmt else self._cmysql.have_result_set
        )
        if not have_result_set:
            raise InterfaceError("No result set")

        fields = prep_stmt.fetch_fields() if prep_stmt else self._cmysql.fetch_fields()
        self._columns = []
        for col in fields:
            self._columns.append(
                (
                    col[4],
                    int(col[8]),
                    None,
                    None,
                    None,
                    None,
                    ~int(col[9]) & FieldFlag.NOT_NULL,
                    int(col[9]),
                    int(col[6]),
                )
            )

        return {
            "eof": {
                "status_flag": self._server_status,
                "warning_count": self._cmysql.st_warning_count(),
            },
            "columns": self._columns,
        }

    def fetch_eof_status(self) -> Optional[CextEofPacketType]:
        """Fetch EOF and status information"""
        if self._cmysql:
            return {
                "warning_count": self._cmysql.st_warning_count(),
                "field_count": self._cmysql.st_field_count(),
                "insert_id": self._cmysql.insert_id(),
                "affected_rows": self._cmysql.affected_rows(),
                "server_status": self._server_status,
            }

        return None

    def cmd_stmt_prepare(self, statement: bytes) -> MySQLPrepStmt:
        """Prepares the SQL statement"""
        if not self._cmysql:
            raise OperationalError("MySQL Connection not available")

        try:
            stmt = self._cmysql.stmt_prepare(statement)
            stmt.converter_str_fallback = self._converter_str_fallback
            return stmt
        except MySQLInterfaceError as err:
            raise InterfaceError(str(err)) from err

    def cmd_stmt_execute(
        self, statement_id: MySQLPrepStmt, *args: Any
    ) -> Optional[Union[CextEofPacketType, CextResultType]]:
        """Executes the prepared statement"""
        try:
            statement_id.stmt_execute(*args)
        except MySQLInterfaceError as err:
            raise InterfaceError(str(err)) from err

        self._columns = []
        if not statement_id.have_result_set:
            # No result
            self._unread_result = False
            return self.fetch_eof_status()

        self._unread_result = True
        return self.fetch_eof_columns(statement_id)

    def cmd_stmt_close(self, statement_id: MySQLPrepStmt) -> None:
        """Closes the prepared statement"""
        if self._unread_result:
            raise InternalError("Unread result found")
        statement_id.stmt_close()

    def cmd_stmt_reset(self, statement_id: MySQLPrepStmt) -> None:
        """Resets the prepared statement"""
        if self._unread_result:
            raise InternalError("Unread result found")
        statement_id.stmt_reset()

    @with_context_propagation
    def cmd_query(
        self,
        query: StrOrBytes,
        raw: Optional[bool] = None,
        buffered: bool = False,
        raw_as_string: bool = False,
    ) -> Optional[Union[CextEofPacketType, CextResultType]]:
        """Send a query to the MySQL server"""
        self.handle_unread_result()
        if raw is None:
            raw = self._raw
        try:
            if not isinstance(query, bytes):
                query = query.encode("utf-8")
            self._cmysql.query(
                query,
                raw=raw,
                buffered=buffered,
                raw_as_string=raw_as_string,
                query_attrs=self.query_attrs,
            )
        except MySQLInterfaceError as err:
            raise get_mysql_exception(
                err.errno, msg=err.msg, sqlstate=err.sqlstate
            ) from err
        except AttributeError as err:
            addr = (
                self._unix_socket if self._unix_socket else f"{self._host}:{self._port}"
            )
            raise OperationalError(
                errno=2055, values=(addr, "Connection not available.")
            ) from err

        self._columns = []
        if not self._cmysql.have_result_set:
            # No result
            return self.fetch_eof_status()

        return self.fetch_eof_columns()

    _execute_query = cmd_query

    def cursor(
        self,
        buffered: Optional[bool] = None,
        raw: Optional[bool] = None,
        prepared: Optional[bool] = None,
        cursor_class: Optional[Type[CMySQLCursor]] = None,
        dictionary: Optional[bool] = None,
        named_tuple: Optional[bool] = None,
    ) -> CMySQLCursor:
        """Instantiates and returns a cursor using C Extension

        By default, CMySQLCursor is returned. Depending on the options
        while connecting, a buffered and/or raw cursor is instantiated
        instead. Also depending upon the cursor options, rows can be
        returned as dictionary or named tuple.

        Dictionary and namedtuple based cursors are available with buffered
        output but not raw.

        It is possible to also give a custom cursor through the
        cursor_class parameter, but it needs to be a subclass of
        mysql.connector.cursor_cext.CMySQLCursor.

        Raises ProgrammingError when cursor_class is not a subclass of
        CursorBase. Raises ValueError when cursor is not available.

        Returns instance of CMySQLCursor or subclass.

        :param buffered: Return a buffering cursor
        :param raw: Return a raw cursor
        :param prepared: Return a cursor which uses prepared statements
        :param cursor_class: Use a custom cursor class
        :param dictionary: Rows are returned as dictionary
        :param named_tuple: Rows are returned as named tuple
        :return: Subclass of CMySQLCursor
        :rtype: CMySQLCursor or subclass
        """
        self.handle_unread_result(prepared)
        if not self.is_connected():
            raise OperationalError("MySQL Connection not available.")
        if cursor_class is not None:
            if not issubclass(cursor_class, CMySQLCursor):
                raise ProgrammingError(
                    "Cursor class needs be to subclass of cursor_cext.CMySQLCursor"
                )
            return (cursor_class)(self)

        buffered = buffered or self._buffered
        raw = raw or self._raw

        cursor_type = 0
        if buffered is True:
            cursor_type |= 1
        if raw is True:
            cursor_type |= 2
        if dictionary is True:
            cursor_type |= 4
        if named_tuple is True:
            cursor_type |= 8
        if prepared is True:
            cursor_type |= 16

        types = {
            0: CMySQLCursor,  # 0
            1: CMySQLCursorBuffered,
            2: CMySQLCursorRaw,
            3: CMySQLCursorBufferedRaw,
            4: CMySQLCursorDict,
            5: CMySQLCursorBufferedDict,
            8: CMySQLCursorNamedTuple,
            9: CMySQLCursorBufferedNamedTuple,
            16: CMySQLCursorPrepared,
            18: CMySQLCursorPreparedRaw,
            20: CMySQLCursorPreparedDict,
            24: CMySQLCursorPreparedNamedTuple,
        }
        try:
            return (types[cursor_type])(self)
        except KeyError:
            args = ("buffered", "raw", "dictionary", "named_tuple", "prepared")
            raise ValueError(
                "Cursor not available with given criteria: "
                + ", ".join([args[i] for i in range(5) if cursor_type & (1 << i) != 0])
            ) from None

    @property
    def num_rows(self) -> int:
        """Returns number of rows of current result set"""
        if not self._cmysql.have_result_set:
            raise InterfaceError("No result set")

        return self._cmysql.num_rows()

    @property
    def warning_count(self) -> int:
        """Returns number of warnings"""
        if not self._cmysql:
            return 0

        return self._cmysql.warning_count()

    @property
    def result_set_available(self) -> bool:
        """Check if a result set is available"""
        if not self._cmysql:
            return False

        return self._cmysql.have_result_set

    @property  # type: ignore[misc]
    def unread_result(self) -> bool:
        """Check if there are unread results or rows"""
        return self.result_set_available

    @property
    def more_results(self) -> bool:
        """Check if there are more results"""
        return self._cmysql.more_results()

    def prepare_for_mysql(
        self, params: ParamsSequenceOrDictType
    ) -> Union[Sequence[bytes], Dict[str, bytes],]:
        """Prepare parameters for statements

        This method is use by cursors to prepared parameters found in the
        list (or tuple) params.

        Returns dict.
        """
        result: Union[List[Any], Dict[str, Any]] = []
        if isinstance(params, (list, tuple)):
            if self.converter:
                result = [
                    self.converter.quote(
                        self.converter.escape(
                            self.converter.to_mysql(value), self._sql_mode
                        )
                    )
                    for value in params
                ]
            else:
                result = self._cmysql.convert_to_mysql(*params)
        elif isinstance(params, dict):
            result = {}
            if self.converter:
                for key, value in params.items():
                    result[key] = self.converter.quote(
                        self.converter.escape(
                            self.converter.to_mysql(value), self._sql_mode
                        )
                    )
            else:
                for key, value in params.items():
                    result[key] = self._cmysql.convert_to_mysql(value)[0]
        else:
            raise ProgrammingError(
                f"Could not process parameters: {type(params).__name__}({params}),"
                " it must be of type list, tuple or dict"
            )

        return result

    def consume_results(self) -> None:
        """Consume the current result

        This method consume the result by reading (consuming) all rows.
        """
        self._cmysql.consume_result()

    def cmd_change_user(
        self,
        username: str = "",
        password: str = "",
        database: str = "",
        charset: int = 45,
        password1: str = "",
        password2: str = "",
        password3: str = "",
        oci_config_file: Optional[str] = None,
        oci_config_profile: Optional[str] = None,
    ) -> None:
        """Change the current logged in user"""
        try:
            self._cmysql.change_user(
                username,
                password,
                database,
                password1,
                password2,
                password3,
                oci_config_file,
                oci_config_profile,
            )

        except MySQLInterfaceError as err:
            raise get_mysql_exception(
                msg=err.msg, errno=err.errno, sqlstate=err.sqlstate
            ) from err

        self._charset_id = charset
        self._user = username  # updating user accordingly
        self._post_connection()

    def cmd_reset_connection(self) -> bool:
        """Resets the session state without re-authenticating

        Reset command only works on MySQL server 5.7.3 or later.
        The result is True for a successful reset otherwise False.

        Returns bool
        """
        res = self._cmysql.reset_connection()
        if res:
            self._post_connection()
        return res

    def cmd_refresh(self, options: int) -> Optional[CextEofPacketType]:
        """Send the Refresh command to the MySQL server"""
        try:
            self.handle_unread_result()
            self._cmysql.refresh(options)
        except MySQLInterfaceError as err:
            raise get_mysql_exception(
                msg=err.msg, errno=err.errno, sqlstate=err.sqlstate
            ) from err

        return self.fetch_eof_status()

    def cmd_quit(self) -> None:
        """Close the current connection with the server"""
        self.close()

    def cmd_shutdown(self, shutdown_type: Optional[int] = None) -> None:
        """Shut down the MySQL Server"""
        if not self._cmysql:
            raise OperationalError("MySQL Connection not available")

        if shutdown_type:
            if not ShutdownType.get_info(shutdown_type):
                raise InterfaceError("Invalid shutdown type")
            level = shutdown_type
        else:
            level = ShutdownType.SHUTDOWN_DEFAULT

        try:
            self._cmysql.shutdown(level)
        except MySQLInterfaceError as err:
            raise get_mysql_exception(
                msg=err.msg, errno=err.errno, sqlstate=err.sqlstate
            ) from err
        self.close()

    def cmd_statistics(self) -> StatsPacketType:
        """Return statistics from the MySQL server"""
        self.handle_unread_result()

        try:
            stat = self._cmysql.stat()
            return MySQLProtocol().parse_statistics(stat, with_header=False)
        except (MySQLInterfaceError, InterfaceError) as err:
            raise get_mysql_exception(
                msg=err.msg, errno=err.errno, sqlstate=err.sqlstate
            ) from err

    def cmd_process_kill(self, mysql_pid: int) -> None:
        """Kill a MySQL process"""
        if not isinstance(mysql_pid, int):
            raise ValueError("MySQL PID must be int")
        self.info_query(f"KILL {mysql_pid}")

    def cmd_debug(self) -> Any:
        """Send the DEBUG command"""
        raise NotImplementedError

    def cmd_ping(self) -> Any:
        """Send the PING command"""
        raise NotImplementedError

    def cmd_query_iter(self, statements: Any) -> Any:
        """Send one or more statements to the MySQL server"""
        raise NotImplementedError

    def cmd_stmt_send_long_data(
        self, statement_id: Any, param_id: Any, data: Any
    ) -> Any:
        """Send data for a column"""
        raise NotImplementedError

    def handle_unread_result(self, prepared: bool = False) -> None:
        """Check whether there is an unread result"""
        unread_result = self._unread_result if prepared is True else self.unread_result
        if self.can_consume_results:
            self.consume_results()
        elif unread_result:
            raise InternalError("Unread result found")

    def reset_session(
        self,
        user_variables: Optional[Dict[str, Any]] = None,
        session_variables: Optional[Dict[str, Any]] = None,
    ) -> None:
        """Clears the current active session

        This method resets the session state, if the MySQL server is 5.7.3
        or later active session will be reset without re-authenticating.
        For other server versions session will be reset by re-authenticating.

        It is possible to provide a sequence of variables and their values to
        be set after clearing the session. This is possible for both user
        defined variables and session variables.
        This method takes two arguments user_variables and session_variables
        which are dictionaries.

        Raises OperationalError if not connected, InternalError if there are
        unread results and InterfaceError on errors.
        """
        if not self.is_connected():
            raise OperationalError("MySQL Connection not available.")

        if not self.cmd_reset_connection():
            try:
                self.cmd_change_user(
                    self._user,
                    self._password,
                    self._database,
                    self._charset_id,
                    self._password1,
                    self._password2,
                    self._password3,
                    self._oci_config_file,
                    self._oci_config_profile,
                )
            except ProgrammingError:
                self.reconnect()

        if user_variables or session_variables:
            cur = self.cursor()
            if user_variables:
                for key, value in user_variables.items():
                    cur.execute(f"SET @`{key}` = %s", (value,))
            if session_variables:
                for key, value in session_variables.items():
                    cur.execute(f"SET SESSION `{key}` = %s", (value,))
            cur.close()

virt/lib/python3.9/site-packages/mysql/connector/connection_cext.py
PrinceDisant/LostInNL
# Copyright (c) 2009, 2023, Oracle and/or its affiliates. All rights reserved.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

"""Various MySQL constants and character sets."""

import warnings

from abc import ABC, ABCMeta
from typing import Any, Dict, List, Optional, Sequence, Tuple, Union, ValuesView

from .charsets import MYSQL_CHARACTER_SETS, MYSQL_CHARACTER_SETS_57
from .errors import ProgrammingError

NET_BUFFER_LENGTH: int = 8192
MAX_MYSQL_TABLE_COLUMNS: int = 4096
# Flag used to send the Query Attributes with 0 (or more) parameters.
PARAMETER_COUNT_AVAILABLE: int = 8

DEFAULT_CONFIGURATION: Dict[str, Optional[Union[str, bool, int]]] = {
    "database": None,
    "user": "",
    "password": "",
    "password1": "",
    "password2": "",
    "password3": "",
    "host": "127.0.0.1",
    "port": 3306,
    "unix_socket": None,
    "use_unicode": True,
    "charset": "utf8mb4",
    "collation": None,
    "converter_class": None,
    "converter_str_fallback": False,
    "autocommit": False,
    "time_zone": None,
    "sql_mode": None,
    "get_warnings": False,
    "raise_on_warnings": False,
    "connection_timeout": None,
    "client_flags": 0,
    "compress": False,
    "buffered": False,
    "raw": False,
    "ssl_ca": None,
    "ssl_cert": None,
    "ssl_key": None,
    "ssl_verify_cert": False,
    "ssl_verify_identity": False,
    "ssl_cipher": None,
    "tls_ciphersuites": None,
    "ssl_disabled": False,
    "tls_versions": None,
    "passwd": None,
    "db": None,
    "connect_timeout": None,
    "dsn": None,
    "force_ipv6": False,
    "auth_plugin": None,
    "allow_local_infile": False,
    "allow_local_infile_in_path": None,
    "consume_results": False,
    "conn_attrs": None,
    "dns_srv": False,
    "use_pure": False,
    "krb_service_principal": None,
    "oci_config_file": None,
    "oci_config_profile": None,
    "fido_callback": None,
    "kerberos_auth_mode": None,
    "init_command": None,
}

CNX_POOL_ARGS: Tuple[str, str, str] = ("pool_name", "pool_size", "pool_reset_session")

TLS_VERSIONS: List[str] = ["TLSv1.2", "TLSv1.3"]

DEPRECATED_TLS_VERSIONS: List[str] = ["TLSv1", "TLSv1.1"]


def flag_is_set(flag: int, flags: int) -> bool:
    """Checks if the flag is set

    Returns boolean"""
    if (flags & flag) > 0:
        return True
    return False


def _obsolete_option(name: str, new_name: str, value: int) -> int:
    """Raise a deprecation warning and advise a new option name.

    Args:
        name (str): The name of the option.
        new_name (str): The new option name.
        value (int): The value of the option.

    Returns:
        int: The value of the option.
    """
    warnings.warn(
        f"The option '{name}' has been deprecated, use '{new_name}' instead.",
        category=DeprecationWarning,
    )
    return value


class _Constants(ABC):
    """Base class for constants."""

    prefix: str = ""
    desc: Dict[str, Tuple[int, str]] = {}

    @classmethod
    def get_desc(cls, name: str) -> Optional[str]:
        """Get description of given constant"""
        try:
            return cls.desc[name][1]
        except (IndexError, KeyError):
            return None

    @classmethod
    def get_info(cls, setid: int) -> Union[Optional[str], Tuple[str, str]]:
        """Get information about given constant"""
        for name, info in cls.desc.items():
            if info[0] == setid:
                return name
        return None

    @classmethod
    def get_full_info(cls) -> Union[str, Sequence[str]]:
        """get full information about given constant"""
        res: Union[str, List[str]] = []
        try:
            res = [f"{k} : {v[1]}" for k, v in cls.desc.items()]
        except (AttributeError, IndexError) as err:
            res = f"No information found in constant class. {err}"

        return res


class _Flags(_Constants):
    """Base class for classes describing flags"""

    @classmethod
    def get_bit_info(cls, value: int) -> List[str]:
        """Get the name of all bits set

        Returns a list of strings."""
        res = []
        for name, info in cls.desc.items():
            if value & info[0]:
                res.append(name)
        return res


class FieldType(_Constants):
    """MySQL Field Types"""

    prefix: str = "FIELD_TYPE_"
    DECIMAL: int = 0x00
    TINY: int = 0x01
    SHORT: int = 0x02
    LONG: int = 0x03
    FLOAT: int = 0x04
    DOUBLE: int = 0x05
    NULL: int = 0x06
    TIMESTAMP: int = 0x07
    LONGLONG: int = 0x08
    INT24: int = 0x09
    DATE: int = 0x0A
    TIME: int = 0x0B
    DATETIME: int = 0x0C
    YEAR: int = 0x0D
    NEWDATE: int = 0x0E
    VARCHAR: int = 0x0F
    BIT: int = 0x10
    JSON: int = 0xF5
    NEWDECIMAL: int = 0xF6
    ENUM: int = 0xF7
    SET: int = 0xF8
    TINY_BLOB: int = 0xF9
    MEDIUM_BLOB: int = 0xFA
    LONG_BLOB: int = 0xFB
    BLOB: int = 0xFC
    VAR_STRING: int = 0xFD
    STRING: int = 0xFE
    GEOMETRY: int = 0xFF

    desc: Dict[str, Tuple[int, str]] = {
        "DECIMAL": (0x00, "DECIMAL"),
        "TINY": (0x01, "TINY"),
        "SHORT": (0x02, "SHORT"),
        "LONG": (0x03, "LONG"),
        "FLOAT": (0x04, "FLOAT"),
        "DOUBLE": (0x05, "DOUBLE"),
        "NULL": (0x06, "NULL"),
        "TIMESTAMP": (0x07, "TIMESTAMP"),
        "LONGLONG": (0x08, "LONGLONG"),
        "INT24": (0x09, "INT24"),
        "DATE": (0x0A, "DATE"),
        "TIME": (0x0B, "TIME"),
        "DATETIME": (0x0C, "DATETIME"),
        "YEAR": (0x0D, "YEAR"),
        "NEWDATE": (0x0E, "NEWDATE"),
        "VARCHAR": (0x0F, "VARCHAR"),
        "BIT": (0x10, "BIT"),
        "JSON": (0xF5, "JSON"),
        "NEWDECIMAL": (0xF6, "NEWDECIMAL"),
        "ENUM": (0xF7, "ENUM"),
        "SET": (0xF8, "SET"),
        "TINY_BLOB": (0xF9, "TINY_BLOB"),
        "MEDIUM_BLOB": (0xFA, "MEDIUM_BLOB"),
        "LONG_BLOB": (0xFB, "LONG_BLOB"),
        "BLOB": (0xFC, "BLOB"),
        "VAR_STRING": (0xFD, "VAR_STRING"),
        "STRING": (0xFE, "STRING"),
        "GEOMETRY": (0xFF, "GEOMETRY"),
    }

    @classmethod
    def get_string_types(cls) -> List[int]:
        """Get the list of all string types"""
        return [
            cls.VARCHAR,
            cls.ENUM,
            cls.VAR_STRING,
            cls.STRING,
        ]

    @classmethod
    def get_binary_types(cls) -> List[int]:
        """Get the list of all binary types"""
        return [
            cls.TINY_BLOB,
            cls.MEDIUM_BLOB,
            cls.LONG_BLOB,
            cls.BLOB,
        ]

    @classmethod
    def get_number_types(cls) -> List[int]:
        """Get the list of all number types"""
        return [
            cls.DECIMAL,
            cls.NEWDECIMAL,
            cls.TINY,
            cls.SHORT,
            cls.LONG,
            cls.FLOAT,
            cls.DOUBLE,
            cls.LONGLONG,
            cls.INT24,
            cls.BIT,
            cls.YEAR,
        ]

    @classmethod
    def get_timestamp_types(cls) -> List[int]:
        """Get the list of all timestamp types"""
        return [
            cls.DATETIME,
            cls.TIMESTAMP,
        ]


class FieldFlag(_Flags):
    """MySQL Field Flags

    Field flags as found in MySQL sources mysql-src/include/mysql_com.h
    """

    _prefix: str = ""
    NOT_NULL: int = 1 << 0
    PRI_KEY: int = 1 << 1
    UNIQUE_KEY: int = 1 << 2
    MULTIPLE_KEY: int = 1 << 3
    BLOB: int = 1 << 4
    UNSIGNED: int = 1 << 5
    ZEROFILL: int = 1 << 6
    BINARY: int = 1 << 7

    ENUM: int = 1 << 8
    AUTO_INCREMENT: int = 1 << 9
    TIMESTAMP: int = 1 << 10
    SET: int = 1 << 11

    NO_DEFAULT_VALUE: int = 1 << 12
    ON_UPDATE_NOW: int = 1 << 13
    NUM: int = 1 << 14
    PART_KEY: int = 1 << 15
    GROUP: int = 1 << 14  # SAME AS NUM !!!!!!!????
    UNIQUE: int = 1 << 16
    BINCMP: int = 1 << 17

    GET_FIXED_FIELDS: int = 1 << 18
    FIELD_IN_PART_FUNC: int = 1 << 19
    FIELD_IN_ADD_INDEX: int = 1 << 20
    FIELD_IS_RENAMED: int = 1 << 21

    desc: Dict[str, Tuple[int, str]] = {
        "NOT_NULL": (1 << 0, "Field can't be NULL"),
        "PRI_KEY": (1 << 1, "Field is part of a primary key"),
        "UNIQUE_KEY": (1 << 2, "Field is part of a unique key"),
        "MULTIPLE_KEY": (1 << 3, "Field is part of a key"),
        "BLOB": (1 << 4, "Field is a blob"),
        "UNSIGNED": (1 << 5, "Field is unsigned"),
        "ZEROFILL": (1 << 6, "Field is zerofill"),
        "BINARY": (1 << 7, "Field is binary  "),
        "ENUM": (1 << 8, "field is an enum"),
        "AUTO_INCREMENT": (1 << 9, "field is a autoincrement field"),
        "TIMESTAMP": (1 << 10, "Field is a timestamp"),
        "SET": (1 << 11, "field is a set"),
        "NO_DEFAULT_VALUE": (1 << 12, "Field doesn't have default value"),
        "ON_UPDATE_NOW": (1 << 13, "Field is set to NOW on UPDATE"),
        "NUM": (1 << 14, "Field is num (for clients)"),
        "PART_KEY": (1 << 15, "Intern; Part of some key"),
        "GROUP": (1 << 14, "Intern: Group field"),  # Same as NUM
        "UNIQUE": (1 << 16, "Intern: Used by sql_yacc"),
        "BINCMP": (1 << 17, "Intern: Used by sql_yacc"),
        "GET_FIXED_FIELDS": (1 << 18, "Used to get fields in item tree"),
        "FIELD_IN_PART_FUNC": (1 << 19, "Field part of partition func"),
        "FIELD_IN_ADD_INDEX": (1 << 20, "Intern: Field used in ADD INDEX"),
        "FIELD_IS_RENAMED": (1 << 21, "Intern: Field is being renamed"),
    }


class ServerCmdMeta(ABCMeta):
    """ClientFlag Metaclass."""

    def __getattribute__(cls, name: str) -> Any:
        deprecated_options = (
            "FIELD_LIST",
            "REFRESH",
            "SHUTDOWN",
            "PROCESS_INFO",
            "PROCESS_KILL",
        )
        if name in deprecated_options:
            warnings.warn(
                f"The option 'ServerCmd.{name}' is deprecated and will be removed in "
                "a future release.",
                category=DeprecationWarning,
            )
        return super().__getattribute__(name)


class ServerCmd(_Constants, metaclass=ServerCmdMeta):
    """MySQL Server Commands"""

    _prefix: str = "COM_"
    SLEEP: int = 0
    QUIT: int = 1
    INIT_DB: int = 2
    QUERY: int = 3
    FIELD_LIST: int = 4
    CREATE_DB: int = 5
    DROP_DB: int = 6
    REFRESH: int = 7
    SHUTDOWN: int = 8
    STATISTICS: int = 9
    PROCESS_INFO: int = 10
    CONNECT: int = 11
    PROCESS_KILL: int = 12
    DEBUG: int = 13
    PING: int = 14
    TIME: int = 15
    DELAYED_INSERT: int = 16
    CHANGE_USER: int = 17
    BINLOG_DUMP: int = 18
    TABLE_DUMP: int = 19
    CONNECT_OUT: int = 20
    REGISTER_REPLICA: int = 21
    STMT_PREPARE: int = 22
    STMT_EXECUTE: int = 23
    STMT_SEND_LONG_DATA: int = 24
    STMT_CLOSE: int = 25
    STMT_RESET: int = 26
    SET_OPTION: int = 27
    STMT_FETCH: int = 28
    DAEMON: int = 29
    BINLOG_DUMP_GTID: int = 30
    RESET_CONNECTION: int = 31

    desc: Dict[str, Tuple[int, str]] = {
        "SLEEP": (0, "SLEEP"),
        "QUIT": (1, "QUIT"),
        "INIT_DB": (2, "INIT_DB"),
        "QUERY": (3, "QUERY"),
        "FIELD_LIST": (4, "FIELD_LIST"),
        "CREATE_DB": (5, "CREATE_DB"),
        "DROP_DB": (6, "DROP_DB"),
        "REFRESH": (7, "REFRESH"),
        "SHUTDOWN": (8, "SHUTDOWN"),
        "STATISTICS": (9, "STATISTICS"),
        "PROCESS_INFO": (10, "PROCESS_INFO"),
        "CONNECT": (11, "CONNECT"),
        "PROCESS_KILL": (12, "PROCESS_KILL"),
        "DEBUG": (13, "DEBUG"),
        "PING": (14, "PING"),
        "TIME": (15, "TIME"),
        "DELAYED_INSERT": (16, "DELAYED_INSERT"),
        "CHANGE_USER": (17, "CHANGE_USER"),
        "BINLOG_DUMP": (18, "BINLOG_DUMP"),
        "TABLE_DUMP": (19, "TABLE_DUMP"),
        "CONNECT_OUT": (20, "CONNECT_OUT"),
        "REGISTER_REPLICA": (21, "REGISTER_REPLICA"),
        "STMT_PREPARE": (22, "STMT_PREPARE"),
        "STMT_EXECUTE": (23, "STMT_EXECUTE"),
        "STMT_SEND_LONG_DATA": (24, "STMT_SEND_LONG_DATA"),
        "STMT_CLOSE": (25, "STMT_CLOSE"),
        "STMT_RESET": (26, "STMT_RESET"),
        "SET_OPTION": (27, "SET_OPTION"),
        "STMT_FETCH": (28, "STMT_FETCH"),
        "DAEMON": (29, "DAEMON"),
        "BINLOG_DUMP_GTID": (30, "BINLOG_DUMP_GTID"),
        "RESET_CONNECTION": (31, "RESET_CONNECTION"),
    }


class ClientFlag(_Flags):
    """MySQL Client Flags

    Client options as found in the MySQL sources mysql-src/include/mysql_com.h
    """

    LONG_PASSWD: int = 1 << 0
    FOUND_ROWS: int = 1 << 1
    LONG_FLAG: int = 1 << 2
    CONNECT_WITH_DB: int = 1 << 3
    NO_SCHEMA: int = 1 << 4
    COMPRESS: int = 1 << 5
    ODBC: int = 1 << 6
    LOCAL_FILES: int = 1 << 7
    IGNORE_SPACE: int = 1 << 8
    PROTOCOL_41: int = 1 << 9
    INTERACTIVE: int = 1 << 10
    SSL: int = 1 << 11
    IGNORE_SIGPIPE: int = 1 << 12
    TRANSACTIONS: int = 1 << 13
    RESERVED: int = 1 << 14
    SECURE_CONNECTION: int = 1 << 15
    MULTI_STATEMENTS: int = 1 << 16
    MULTI_RESULTS: int = 1 << 17
    PS_MULTI_RESULTS: int = 1 << 18
    PLUGIN_AUTH: int = 1 << 19
    CONNECT_ARGS: int = 1 << 20
    PLUGIN_AUTH_LENENC_CLIENT_DATA: int = 1 << 21
    CAN_HANDLE_EXPIRED_PASSWORDS: int = 1 << 22
    SESION_TRACK: int = 1 << 23  # deprecated
    SESSION_TRACK: int = 1 << 23
    DEPRECATE_EOF: int = 1 << 24
    CLIENT_QUERY_ATTRIBUTES: int = 1 << 27
    SSL_VERIFY_SERVER_CERT: int = 1 << 30
    REMEMBER_OPTIONS: int = 1 << 31
    MULTI_FACTOR_AUTHENTICATION: int = 1 << 28

    desc: Dict[str, Tuple[int, str]] = {
        "LONG_PASSWD": (1 << 0, "New more secure passwords"),
        "FOUND_ROWS": (1 << 1, "Found instead of affected rows"),
        "LONG_FLAG": (1 << 2, "Get all column flags"),
        "CONNECT_WITH_DB": (1 << 3, "One can specify db on connect"),
        "NO_SCHEMA": (1 << 4, "Don't allow database.table.column"),
        "COMPRESS": (1 << 5, "Can use compression protocol"),
        "ODBC": (1 << 6, "ODBC client"),
        "LOCAL_FILES": (1 << 7, "Can use LOAD DATA LOCAL"),
        "IGNORE_SPACE": (1 << 8, "Ignore spaces before ''"),
        "PROTOCOL_41": (1 << 9, "New 4.1 protocol"),
        "INTERACTIVE": (1 << 10, "This is an interactive client"),
        "SSL": (1 << 11, "Switch to SSL after handshake"),
        "IGNORE_SIGPIPE": (1 << 12, "IGNORE sigpipes"),
        "TRANSACTIONS": (1 << 13, "Client knows about transactions"),
        "RESERVED": (1 << 14, "Old flag for 4.1 protocol"),
        "SECURE_CONNECTION": (1 << 15, "New 4.1 authentication"),
        "MULTI_STATEMENTS": (1 << 16, "Enable/disable multi-stmt support"),
        "MULTI_RESULTS": (1 << 17, "Enable/disable multi-results"),
        "PS_MULTI_RESULTS": (1 << 18, "Multi-results in PS-protocol"),
        "PLUGIN_AUTH": (1 << 19, "Client supports plugin authentication"),
        "CONNECT_ARGS": (1 << 20, "Client supports connection attributes"),
        "PLUGIN_AUTH_LENENC_CLIENT_DATA": (
            1 << 21,
            "Enable authentication response packet to be larger than 255 bytes",
        ),
        "CAN_HANDLE_EXPIRED_PASSWORDS": (
            1 << 22,
            "Don't close the connection for a connection with expired password",
        ),
        "SESION_TRACK": (  # deprecated
            1 << 23,
            "Capable of handling server state change information",
        ),
        "SESSION_TRACK": (
            1 << 23,
            "Capable of handling server state change information",
        ),
        "DEPRECATE_EOF": (1 << 24, "Client no longer needs EOF packet"),
        "CLIENT_QUERY_ATTRIBUTES": (
            1 << 27,
            "Support optional extension for query parameters",
        ),
        "SSL_VERIFY_SERVER_CERT": (1 << 30, ""),
        "REMEMBER_OPTIONS": (1 << 31, ""),
    }

    default: List[int] = [
        LONG_PASSWD,
        LONG_FLAG,
        CONNECT_WITH_DB,
        PROTOCOL_41,
        TRANSACTIONS,
        SECURE_CONNECTION,
        MULTI_STATEMENTS,
        MULTI_RESULTS,
        CONNECT_ARGS,
    ]

    @classmethod
    def get_default(cls) -> int:
        """Get the default client options set

        Returns a flag with all the default client options set"""
        flags = 0
        for option in cls.default:
            flags |= option
        return flags


class ServerFlag(_Flags):
    """MySQL Server Flags

    Server flags as found in the MySQL sources mysql-src/include/mysql_com.h
    """

    _prefix: str = "SERVER_"
    STATUS_IN_TRANS: int = 1 << 0
    STATUS_AUTOCOMMIT: int = 1 << 1
    MORE_RESULTS_EXISTS: int = 1 << 3
    QUERY_NO_GOOD_INDEX_USED: int = 1 << 4
    QUERY_NO_INDEX_USED: int = 1 << 5
    STATUS_CURSOR_EXISTS: int = 1 << 6
    STATUS_LAST_ROW_SENT: int = 1 << 7
    STATUS_DB_DROPPED: int = 1 << 8
    STATUS_NO_BACKSLASH_ESCAPES: int = 1 << 9
    SERVER_STATUS_METADATA_CHANGED: int = 1 << 10
    SERVER_QUERY_WAS_SLOW: int = 1 << 11
    SERVER_PS_OUT_PARAMS: int = 1 << 12
    SERVER_STATUS_IN_TRANS_READONLY: int = 1 << 13
    SERVER_SESSION_STATE_CHANGED: int = 1 << 14

    desc: Dict[str, Tuple[int, str]] = {
        "SERVER_STATUS_IN_TRANS": (1 << 0, "Transaction has started"),
        "SERVER_STATUS_AUTOCOMMIT": (1 << 1, "Server in auto_commit mode"),
        "SERVER_MORE_RESULTS_EXISTS": (
            1 << 3,
            "Multi query - next query exists",
        ),
        "SERVER_QUERY_NO_GOOD_INDEX_USED": (1 << 4, ""),
        "SERVER_QUERY_NO_INDEX_USED": (1 << 5, ""),
        "SERVER_STATUS_CURSOR_EXISTS": (
            1 << 6,
            "Set when server opened a read-only non-scrollable cursor for a query.",
        ),
        "SERVER_STATUS_LAST_ROW_SENT": (
            1 << 7,
            "Set when a read-only cursor is exhausted",
        ),
        "SERVER_STATUS_DB_DROPPED": (1 << 8, "A database was dropped"),
        "SERVER_STATUS_NO_BACKSLASH_ESCAPES": (1 << 9, ""),
        "SERVER_STATUS_METADATA_CHANGED": (
            1024,
            "Set if after a prepared statement "
            "reprepare we discovered that the "
            "new statement returns a different "
            "number of result set columns.",
        ),
        "SERVER_QUERY_WAS_SLOW": (2048, ""),
        "SERVER_PS_OUT_PARAMS": (
            4096,
            "To mark ResultSet containing output parameter values.",
        ),
        "SERVER_STATUS_IN_TRANS_READONLY": (
            8192,
            "Set if multi-statement transaction is a read-only transaction.",
        ),
        "SERVER_SESSION_STATE_CHANGED": (
            1 << 14,
            "Session state has changed on the "
            "server because of the execution of "
            "the last statement",
        ),
    }


class RefreshOptionMeta(ABCMeta):
    """RefreshOption Metaclass."""

    @property
    def SLAVE(self) -> int:  # pylint: disable=bad-mcs-method-argument,invalid-name
        """Return the deprecated alias of RefreshOption.REPLICA.

        Raises a warning about this attribute deprecation.
        """
        return _obsolete_option(
            "RefreshOption.SLAVE",
            "RefreshOption.REPLICA",
            RefreshOption.REPLICA,
        )


class RefreshOption(_Constants, metaclass=RefreshOptionMeta):
    """MySQL Refresh command options.

    Options used when sending the COM_REFRESH server command.
    """

    _prefix: str = "REFRESH_"
    GRANT: int = 1 << 0
    LOG: int = 1 << 1
    TABLES: int = 1 << 2
    HOST: int = 1 << 3
    STATUS: int = 1 << 4
    THREADS: int = 1 << 5
    REPLICA: int = 1 << 6

    desc: Dict[str, Tuple[int, str]] = {
        "GRANT": (1 << 0, "Refresh grant tables"),
        "LOG": (1 << 1, "Start on new log file"),
        "TABLES": (1 << 2, "close all tables"),
        "HOST": (1 << 3, "Flush host cache"),
        "STATUS": (1 << 4, "Flush status variables"),
        "THREADS": (1 << 5, "Flush thread cache"),
        "REPLICA": (1 << 6, "Reset source info and restart replica thread"),
        "SLAVE": (1 << 6, "Deprecated option; use REPLICA instead."),
    }


class ShutdownType(_Constants):
    """MySQL Shutdown types

    Shutdown types used by the COM_SHUTDOWN server command.
    """

    _prefix: str = ""
    SHUTDOWN_DEFAULT: int = 0
    SHUTDOWN_WAIT_CONNECTIONS: int = 1
    SHUTDOWN_WAIT_TRANSACTIONS: int = 2
    SHUTDOWN_WAIT_UPDATES: int = 8
    SHUTDOWN_WAIT_ALL_BUFFERS: int = 16
    SHUTDOWN_WAIT_CRITICAL_BUFFERS: int = 17
    KILL_QUERY: int = 254
    KILL_CONNECTION: int = 255

    desc: Dict[str, Tuple[int, str]] = {
        "SHUTDOWN_DEFAULT": (
            SHUTDOWN_DEFAULT,
            "defaults to SHUTDOWN_WAIT_ALL_BUFFERS",
        ),
        "SHUTDOWN_WAIT_CONNECTIONS": (
            SHUTDOWN_WAIT_CONNECTIONS,
            "wait for existing connections to finish",
        ),
        "SHUTDOWN_WAIT_TRANSACTIONS": (
            SHUTDOWN_WAIT_TRANSACTIONS,
            "wait for existing trans to finish",
        ),
        "SHUTDOWN_WAIT_UPDATES": (
            SHUTDOWN_WAIT_UPDATES,
            "wait for existing updates to finish",
        ),
        "SHUTDOWN_WAIT_ALL_BUFFERS": (
            SHUTDOWN_WAIT_ALL_BUFFERS,
            "flush InnoDB and other storage engine buffers",
        ),
        "SHUTDOWN_WAIT_CRITICAL_BUFFERS": (
            SHUTDOWN_WAIT_CRITICAL_BUFFERS,
            "don't flush InnoDB buffers, flush other storage engines' buffers",
        ),
        "KILL_QUERY": (KILL_QUERY, "(no description)"),
        "KILL_CONNECTION": (KILL_CONNECTION, "(no description)"),
    }


class CharacterSet(_Constants):
    """MySQL supported character sets and collations

    List of character sets with their collations supported by MySQL. This
    maps to the character set we get from the server within the handshake
    packet.

    The list is hardcode so we avoid a database query when getting the
    name of the used character set or collation.
    """

    # Use LTS character set as default
    desc: List[
        Optional[Tuple[str, str, bool]]
    ] = MYSQL_CHARACTER_SETS_57  # type: ignore[assignment]
    mysql_version: Tuple[int, ...] = (5, 7)

    # Multi-byte character sets which use 5c (backslash) in characters
    slash_charsets: Tuple[int, ...] = (1, 13, 28, 84, 87, 88)

    @classmethod
    def set_mysql_version(cls, version: Tuple[int, ...]) -> None:
        """Set the MySQL major version and change the charset mapping if is 5.7.

        Args:
            version (tuple): MySQL version tuple.
        """
        cls.mysql_version = version[:2]
        if cls.mysql_version >= (8, 0):
            cls.desc = MYSQL_CHARACTER_SETS

    @classmethod
    def get_info(cls, setid: int) -> Tuple[str, str]:
        """Retrieves character set information as tuple using an ID

        Retrieves character set and collation information based on the
        given MySQL ID.

        Raises ProgrammingError when character set is not supported.

        Returns a tuple.
        """
        try:
            return cls.desc[setid][0:2]
        except IndexError:
            raise ProgrammingError(f"Character set '{setid}' unsupported") from None

    @classmethod
    def get_desc(cls, name: int) -> str:  # type: ignore[override]
        """Retrieves character set information as string using an ID

        Retrieves character set and collation information based on the
        given MySQL ID.

        Returns a tuple.
        """
        charset, collation = cls.get_info(name)
        return f"{charset}/{collation}"

    @classmethod
    def get_default_collation(cls, charset: Union[int, str]) -> Tuple[str, str, int]:
        """Retrieves the default collation for given character set

        Raises ProgrammingError when character set is not supported.

        Returns list (collation, charset, index)
        """
        if isinstance(charset, int):
            try:
                info = cls.desc[charset]
                return info[1], info[0], charset
            except (IndexError, KeyError) as err:
                raise ProgrammingError(
                    f"Character set ID '{charset}' unsupported"
                ) from err

        for cid, info in enumerate(cls.desc):
            if info is None:
                continue
            if info[0] == charset and info[2] is True:
                return info[1], info[0], cid

        raise ProgrammingError(f"Character set '{charset}' unsupported")

    @classmethod
    def get_charset_info(
        cls, charset: Optional[Union[int, str]] = None, collation: Optional[str] = None
    ) -> Tuple[int, str, str]:
        """Get character set information using charset name and/or collation

        Retrieves character set and collation information given character
        set name and/or a collation name.
        If charset is an integer, it will look up the character set based
        on the MySQL's ID.
        For example:
            get_charset_info('utf8',None)
            get_charset_info(collation='utf8_general_ci')
            get_charset_info(47)

        Raises ProgrammingError when character set is not supported.

        Returns a tuple with (id, characterset name, collation)
        """
        info: Optional[Union[Tuple[str, str, bool], Tuple[str, str, int]]] = None
        if isinstance(charset, int):
            try:
                info = cls.desc[charset]
                return (charset, info[0], info[1])
            except IndexError as err:
                raise ProgrammingError(f"Character set ID {charset} unknown") from err

        if charset in ("utf8", "utf-8") and cls.mysql_version >= (8, 0):
            charset = "utf8mb4"
        if charset is not None and collation is None:
            info = cls.get_default_collation(charset)
            return (info[2], info[1], info[0])
        if charset is None and collation is not None:
            for cid, info in enumerate(cls.desc):
                if info is None:
                    continue
                if collation == info[1]:
                    return (cid, info[0], info[1])
            raise ProgrammingError(f"Collation '{collation}' unknown")
        for cid, info in enumerate(cls.desc):
            if info is None:
                continue
            if info[0] == charset and info[1] == collation:
                return (cid, info[0], info[1])
        _ = cls.get_default_collation(charset)
        raise ProgrammingError(f"Collation '{collation}' unknown")

    @classmethod
    def get_supported(cls) -> Tuple[str, ...]:
        """Retrieves a list with names of all supproted character sets

        Returns a tuple.
        """
        res = []
        for info in cls.desc:
            if info and info[0] not in res:
                res.append(info[0])
        return tuple(res)


class SQLMode(_Constants):
    """MySQL SQL Modes

    The numeric values of SQL Modes are not interesting, only the names
    are used when setting the SQL_MODE system variable using the MySQL
    SET command.

    See http://dev.mysql.com/doc/refman/5.6/en/server-sql-mode.html
    """

    _prefix: str = "MODE_"
    REAL_AS_FLOAT: str = "REAL_AS_FLOAT"
    PIPES_AS_CONCAT: str = "PIPES_AS_CONCAT"
    ANSI_QUOTES: str = "ANSI_QUOTES"
    IGNORE_SPACE: str = "IGNORE_SPACE"
    NOT_USED: str = "NOT_USED"
    ONLY_FULL_GROUP_BY: str = "ONLY_FULL_GROUP_BY"
    NO_UNSIGNED_SUBTRACTION: str = "NO_UNSIGNED_SUBTRACTION"
    NO_DIR_IN_CREATE: str = "NO_DIR_IN_CREATE"
    POSTGRESQL: str = "POSTGRESQL"
    ORACLE: str = "ORACLE"
    MSSQL: str = "MSSQL"
    DB2: str = "DB2"
    MAXDB: str = "MAXDB"
    NO_KEY_OPTIONS: str = "NO_KEY_OPTIONS"
    NO_TABLE_OPTIONS: str = "NO_TABLE_OPTIONS"
    NO_FIELD_OPTIONS: str = "NO_FIELD_OPTIONS"
    MYSQL323: str = "MYSQL323"
    MYSQL40: str = "MYSQL40"
    ANSI: str = "ANSI"
    NO_AUTO_VALUE_ON_ZERO: str = "NO_AUTO_VALUE_ON_ZERO"
    NO_BACKSLASH_ESCAPES: str = "NO_BACKSLASH_ESCAPES"
    STRICT_TRANS_TABLES: str = "STRICT_TRANS_TABLES"
    STRICT_ALL_TABLES: str = "STRICT_ALL_TABLES"
    NO_ZERO_IN_DATE: str = "NO_ZERO_IN_DATE"
    NO_ZERO_DATE: str = "NO_ZERO_DATE"
    INVALID_DATES: str = "INVALID_DATES"
    ERROR_FOR_DIVISION_BY_ZERO: str = "ERROR_FOR_DIVISION_BY_ZERO"
    TRADITIONAL: str = "TRADITIONAL"
    NO_AUTO_CREATE_USER: str = "NO_AUTO_CREATE_USER"
    HIGH_NOT_PRECEDENCE: str = "HIGH_NOT_PRECEDENCE"
    NO_ENGINE_SUBSTITUTION: str = "NO_ENGINE_SUBSTITUTION"
    PAD_CHAR_TO_FULL_LENGTH: str = "PAD_CHAR_TO_FULL_LENGTH"

    @classmethod
    def get_desc(cls, name: str) -> Optional[str]:
        raise NotImplementedError

    @classmethod
    def get_info(cls, setid: int) -> Optional[str]:
        raise NotImplementedError

    @classmethod
    def get_full_info(cls) -> Tuple[str, ...]:
        """Returns a sequence of all available SQL Modes

        This class method returns a tuple containing all SQL Mode names. The
        names will be alphabetically sorted.

        Returns a tuple.
        """
        res = []
        for key in vars(cls).keys():
            if not key.startswith("_") and not hasattr(getattr(cls, key), "__call__"):
                res.append(key)
        return tuple(sorted(res))


CONN_ATTRS_DN: List[str] = [
    "_pid",
    "_platform",
    "_source_host",
    "_client_name",
    "_client_license",
    "_client_version",
    "_os",
    "_connector_name",
    "_connector_license",
    "_connector_version",
]

# TLS v1.0 cipher suites IANI to OpenSSL name translation
TLSV1_CIPHER_SUITES: Dict[str, str] = {
    "TLS_RSA_WITH_NULL_MD5": "NULL-MD5",
    "TLS_RSA_WITH_NULL_SHA": "NULL-SHA",
    "TLS_RSA_WITH_RC4_128_MD5": "RC4-MD5",
    "TLS_RSA_WITH_RC4_128_SHA": "RC4-SHA",
    "TLS_RSA_WITH_IDEA_CBC_SHA": "IDEA-CBC-SHA",
    "TLS_RSA_WITH_3DES_EDE_CBC_SHA": "DES-CBC3-SHA",
    "TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA": "Not implemented.",
    "TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA": "Not implemented.",
    "TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA": "DHE-DSS-DES-CBC3-SHA",
    "TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA": "DHE-RSA-DES-CBC3-SHA",
    "TLS_DH_anon_WITH_RC4_128_MD5": "ADH-RC4-MD5",
    "TLS_DH_anon_WITH_3DES_EDE_CBC_SHA": "ADH-DES-CBC3-SHA",
    # AES cipher suites from RFC3268, extending TLS v1.0
    "TLS_RSA_WITH_AES_128_CBC_SHA": "AES128-SHA",
    "TLS_RSA_WITH_AES_256_CBC_SHA": "AES256-SHA",
    "TLS_DH_DSS_WITH_AES_128_CBC_SHA": "DH-DSS-AES128-SHA",
    "TLS_DH_DSS_WITH_AES_256_CBC_SHA": "DH-DSS-AES256-SHA",
    "TLS_DH_RSA_WITH_AES_128_CBC_SHA": "DH-RSA-AES128-SHA",
    "TLS_DH_RSA_WITH_AES_256_CBC_SHA": "DH-RSA-AES256-SHA",
    "TLS_DHE_DSS_WITH_AES_128_CBC_SHA": "DHE-DSS-AES128-SHA",
    "TLS_DHE_DSS_WITH_AES_256_CBC_SHA": "DHE-DSS-AES256-SHA",
    "TLS_DHE_RSA_WITH_AES_128_CBC_SHA": "DHE-RSA-AES128-SHA",
    "TLS_DHE_RSA_WITH_AES_256_CBC_SHA": "DHE-RSA-AES256-SHA",
    "TLS_DH_anon_WITH_AES_128_CBC_SHA": "ADH-AES128-SHA",
    "TLS_DH_anon_WITH_AES_256_CBC_SHA": "ADH-AES256-SHA",
    # Camellia cipher suites from RFC4132, extending TLS v1.0
    "TLS_RSA_WITH_CAMELLIA_128_CBC_SHA": "CAMELLIA128-SHA",
    "TLS_RSA_WITH_CAMELLIA_256_CBC_SHA": "CAMELLIA256-SHA",
    "TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA": "DH-DSS-CAMELLIA128-SHA",
    "TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA": "DH-DSS-CAMELLIA256-SHA",
    "TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA": "DH-RSA-CAMELLIA128-SHA",
    "TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA": "DH-RSA-CAMELLIA256-SHA",
    "TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA": "DHE-DSS-CAMELLIA128-SHA",
    "TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA": "DHE-DSS-CAMELLIA256-SHA",
    "TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA": "DHE-RSA-CAMELLIA128-SHA",
    "TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA": "DHE-RSA-CAMELLIA256-SHA",
    "TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA": "ADH-CAMELLIA128-SHA",
    "TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA": "ADH-CAMELLIA256-SHA",
    # SEED cipher suites from RFC4162, extending TLS v1.0
    "TLS_RSA_WITH_SEED_CBC_SHA": "SEED-SHA",
    "TLS_DH_DSS_WITH_SEED_CBC_SHA": "DH-DSS-SEED-SHA",
    "TLS_DH_RSA_WITH_SEED_CBC_SHA": "DH-RSA-SEED-SHA",
    "TLS_DHE_DSS_WITH_SEED_CBC_SHA": "DHE-DSS-SEED-SHA",
    "TLS_DHE_RSA_WITH_SEED_CBC_SHA": "DHE-RSA-SEED-SHA",
    "TLS_DH_anon_WITH_SEED_CBC_SHA": "ADH-SEED-SHA",
    # GOST cipher suites from draft-chudov-cryptopro-cptls, extending TLS v1.0
    "TLS_GOSTR341094_WITH_28147_CNT_IMIT": "GOST94-GOST89-GOST89",
    "TLS_GOSTR341001_WITH_28147_CNT_IMIT": "GOST2001-GOST89-GOST89",
    "TLS_GOSTR341094_WITH_NULL_GOSTR3411": "GOST94-NULL-GOST94",
    "TLS_GOSTR341001_WITH_NULL_GOSTR3411": "GOST2001-NULL-GOST94",
}

# TLS v1.1 cipher suites IANI to OpenSSL name translation
TLSV1_1_CIPHER_SUITES: Dict[str, str] = TLSV1_CIPHER_SUITES

# TLS v1.2 cipher suites IANI to OpenSSL name translation
TLSV1_2_CIPHER_SUITES: Dict[str, str] = {
    "TLS_RSA_WITH_NULL_SHA256": "NULL-SHA256",
    "TLS_RSA_WITH_AES_128_CBC_SHA256": "AES128-SHA256",
    "TLS_RSA_WITH_AES_256_CBC_SHA256": "AES256-SHA256",
    "TLS_RSA_WITH_AES_128_GCM_SHA256": "AES128-GCM-SHA256",
    "TLS_RSA_WITH_AES_256_GCM_SHA384": "AES256-GCM-SHA384",
    "TLS_DH_RSA_WITH_AES_128_CBC_SHA256": "DH-RSA-AES128-SHA256",
    "TLS_DH_RSA_WITH_AES_256_CBC_SHA256": "DH-RSA-AES256-SHA256",
    "TLS_DH_RSA_WITH_AES_128_GCM_SHA256": "DH-RSA-AES128-GCM-SHA256",
    "TLS_DH_RSA_WITH_AES_256_GCM_SHA384": "DH-RSA-AES256-GCM-SHA384",
    "TLS_DH_DSS_WITH_AES_128_CBC_SHA256": "DH-DSS-AES128-SHA256",
    "TLS_DH_DSS_WITH_AES_256_CBC_SHA256": "DH-DSS-AES256-SHA256",
    "TLS_DH_DSS_WITH_AES_128_GCM_SHA256": "DH-DSS-AES128-GCM-SHA256",
    "TLS_DH_DSS_WITH_AES_256_GCM_SHA384": "DH-DSS-AES256-GCM-SHA384",
    "TLS_DHE_RSA_WITH_AES_128_CBC_SHA256": "DHE-RSA-AES128-SHA256",
    "TLS_DHE_RSA_WITH_AES_256_CBC_SHA256": "DHE-RSA-AES256-SHA256",
    "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256": "DHE-RSA-AES128-GCM-SHA256",
    "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384": "DHE-RSA-AES256-GCM-SHA384",
    "TLS_DHE_DSS_WITH_AES_128_CBC_SHA256": "DHE-DSS-AES128-SHA256",
    "TLS_DHE_DSS_WITH_AES_256_CBC_SHA256": "DHE-DSS-AES256-SHA256",
    "TLS_DHE_DSS_WITH_AES_128_GCM_SHA256": "DHE-DSS-AES128-GCM-SHA256",
    "TLS_DHE_DSS_WITH_AES_256_GCM_SHA384": "DHE-DSS-AES256-GCM-SHA384",
    "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256": "ECDHE-RSA-AES128-SHA256",
    "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384": "ECDHE-RSA-AES256-SHA384",
    "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256": "ECDHE-RSA-AES128-GCM-SHA256",
    "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384": "ECDHE-RSA-AES256-GCM-SHA384",
    "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256": "ECDHE-ECDSA-AES128-SHA256",
    "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384": "ECDHE-ECDSA-AES256-SHA384",
    "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256": "ECDHE-ECDSA-AES128-GCM-SHA256",
    "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384": "ECDHE-ECDSA-AES256-GCM-SHA384",
    "TLS_DH_anon_WITH_AES_128_CBC_SHA256": "ADH-AES128-SHA256",
    "TLS_DH_anon_WITH_AES_256_CBC_SHA256": "ADH-AES256-SHA256",
    "TLS_DH_anon_WITH_AES_128_GCM_SHA256": "ADH-AES128-GCM-SHA256",
    "TLS_DH_anon_WITH_AES_256_GCM_SHA384": "ADH-AES256-GCM-SHA384",
    "RSA_WITH_AES_128_CCM": "AES128-CCM",
    "RSA_WITH_AES_256_CCM": "AES256-CCM",
    "DHE_RSA_WITH_AES_128_CCM": "DHE-RSA-AES128-CCM",
    "DHE_RSA_WITH_AES_256_CCM": "DHE-RSA-AES256-CCM",
    "RSA_WITH_AES_128_CCM_8": "AES128-CCM8",
    "RSA_WITH_AES_256_CCM_8": "AES256-CCM8",
    "DHE_RSA_WITH_AES_128_CCM_8": "DHE-RSA-AES128-CCM8",
    "DHE_RSA_WITH_AES_256_CCM_8": "DHE-RSA-AES256-CCM8",
    "ECDHE_ECDSA_WITH_AES_128_CCM": "ECDHE-ECDSA-AES128-CCM",
    "ECDHE_ECDSA_WITH_AES_256_CCM": "ECDHE-ECDSA-AES256-CCM",
    "ECDHE_ECDSA_WITH_AES_128_CCM_8": "ECDHE-ECDSA-AES128-CCM8",
    "ECDHE_ECDSA_WITH_AES_256_CCM_8": "ECDHE-ECDSA-AES256-CCM8",
    # ARIA cipher suites from RFC6209, extending TLS v1.2
    "TLS_RSA_WITH_ARIA_128_GCM_SHA256": "ARIA128-GCM-SHA256",
    "TLS_RSA_WITH_ARIA_256_GCM_SHA384": "ARIA256-GCM-SHA384",
    "TLS_DHE_RSA_WITH_ARIA_128_GCM_SHA256": "DHE-RSA-ARIA128-GCM-SHA256",
    "TLS_DHE_RSA_WITH_ARIA_256_GCM_SHA384": "DHE-RSA-ARIA256-GCM-SHA384",
    "TLS_DHE_DSS_WITH_ARIA_128_GCM_SHA256": "DHE-DSS-ARIA128-GCM-SHA256",
    "TLS_DHE_DSS_WITH_ARIA_256_GCM_SHA384": "DHE-DSS-ARIA256-GCM-SHA384",
    "TLS_ECDHE_ECDSA_WITH_ARIA_128_GCM_SHA256": "ECDHE-ECDSA-ARIA128-GCM-SHA256",
    "TLS_ECDHE_ECDSA_WITH_ARIA_256_GCM_SHA384": "ECDHE-ECDSA-ARIA256-GCM-SHA384",
    "TLS_ECDHE_RSA_WITH_ARIA_128_GCM_SHA256": "ECDHE-ARIA128-GCM-SHA256",
    "TLS_ECDHE_RSA_WITH_ARIA_256_GCM_SHA384": "ECDHE-ARIA256-GCM-SHA384",
    "TLS_PSK_WITH_ARIA_128_GCM_SHA256": "PSK-ARIA128-GCM-SHA256",
    "TLS_PSK_WITH_ARIA_256_GCM_SHA384": "PSK-ARIA256-GCM-SHA384",
    "TLS_DHE_PSK_WITH_ARIA_128_GCM_SHA256": "DHE-PSK-ARIA128-GCM-SHA256",
    "TLS_DHE_PSK_WITH_ARIA_256_GCM_SHA384": "DHE-PSK-ARIA256-GCM-SHA384",
    "TLS_RSA_PSK_WITH_ARIA_128_GCM_SHA256": "RSA-PSK-ARIA128-GCM-SHA256",
    "TLS_RSA_PSK_WITH_ARIA_256_GCM_SHA384": "RSA-PSK-ARIA256-GCM-SHA384",
    # Camellia HMAC-Based cipher suites from RFC6367, extending TLS v1.2
    "TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256": "ECDHE-ECDSA-CAMELLIA128-SHA256",
    "TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384": "ECDHE-ECDSA-CAMELLIA256-SHA384",
    "TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256": "ECDHE-RSA-CAMELLIA128-SHA256",
    "TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384": "ECDHE-RSA-CAMELLIA256-SHA384",
    # Pre-shared keying (PSK) cipher suites",
    "PSK_WITH_NULL_SHA": "PSK-NULL-SHA",
    "DHE_PSK_WITH_NULL_SHA": "DHE-PSK-NULL-SHA",
    "RSA_PSK_WITH_NULL_SHA": "RSA-PSK-NULL-SHA",
    "PSK_WITH_RC4_128_SHA": "PSK-RC4-SHA",
    "PSK_WITH_3DES_EDE_CBC_SHA": "PSK-3DES-EDE-CBC-SHA",
    "PSK_WITH_AES_128_CBC_SHA": "PSK-AES128-CBC-SHA",
    "PSK_WITH_AES_256_CBC_SHA": "PSK-AES256-CBC-SHA",
    "DHE_PSK_WITH_RC4_128_SHA": "DHE-PSK-RC4-SHA",
    "DHE_PSK_WITH_3DES_EDE_CBC_SHA": "DHE-PSK-3DES-EDE-CBC-SHA",
    "DHE_PSK_WITH_AES_128_CBC_SHA": "DHE-PSK-AES128-CBC-SHA",
    "DHE_PSK_WITH_AES_256_CBC_SHA": "DHE-PSK-AES256-CBC-SHA",
    "RSA_PSK_WITH_RC4_128_SHA": "RSA-PSK-RC4-SHA",
    "RSA_PSK_WITH_3DES_EDE_CBC_SHA": "RSA-PSK-3DES-EDE-CBC-SHA",
    "RSA_PSK_WITH_AES_128_CBC_SHA": "RSA-PSK-AES128-CBC-SHA",
    "RSA_PSK_WITH_AES_256_CBC_SHA": "RSA-PSK-AES256-CBC-SHA",
    "PSK_WITH_AES_128_GCM_SHA256": "PSK-AES128-GCM-SHA256",
    "PSK_WITH_AES_256_GCM_SHA384": "PSK-AES256-GCM-SHA384",
    "DHE_PSK_WITH_AES_128_GCM_SHA256": "DHE-PSK-AES128-GCM-SHA256",
    "DHE_PSK_WITH_AES_256_GCM_SHA384": "DHE-PSK-AES256-GCM-SHA384",
    "RSA_PSK_WITH_AES_128_GCM_SHA256": "RSA-PSK-AES128-GCM-SHA256",
    "RSA_PSK_WITH_AES_256_GCM_SHA384": "RSA-PSK-AES256-GCM-SHA384",
    "PSK_WITH_AES_128_CBC_SHA256": "PSK-AES128-CBC-SHA256",
    "PSK_WITH_AES_256_CBC_SHA384": "PSK-AES256-CBC-SHA384",
    "PSK_WITH_NULL_SHA256": "PSK-NULL-SHA256",
    "PSK_WITH_NULL_SHA384": "PSK-NULL-SHA384",
    "DHE_PSK_WITH_AES_128_CBC_SHA256": "DHE-PSK-AES128-CBC-SHA256",
    "DHE_PSK_WITH_AES_256_CBC_SHA384": "DHE-PSK-AES256-CBC-SHA384",
    "DHE_PSK_WITH_NULL_SHA256": "DHE-PSK-NULL-SHA256",
    "DHE_PSK_WITH_NULL_SHA384": "DHE-PSK-NULL-SHA384",
    "RSA_PSK_WITH_AES_128_CBC_SHA256": "RSA-PSK-AES128-CBC-SHA256",
    "RSA_PSK_WITH_AES_256_CBC_SHA384": "RSA-PSK-AES256-CBC-SHA384",
    "RSA_PSK_WITH_NULL_SHA256": "RSA-PSK-NULL-SHA256",
    "RSA_PSK_WITH_NULL_SHA384": "RSA-PSK-NULL-SHA384",
    "ECDHE_PSK_WITH_RC4_128_SHA": "ECDHE-PSK-RC4-SHA",
    "ECDHE_PSK_WITH_3DES_EDE_CBC_SHA": "ECDHE-PSK-3DES-EDE-CBC-SHA",
    "ECDHE_PSK_WITH_AES_128_CBC_SHA": "ECDHE-PSK-AES128-CBC-SHA",
    "ECDHE_PSK_WITH_AES_256_CBC_SHA": "ECDHE-PSK-AES256-CBC-SHA",
    "ECDHE_PSK_WITH_AES_128_CBC_SHA256": "ECDHE-PSK-AES128-CBC-SHA256",
    "ECDHE_PSK_WITH_AES_256_CBC_SHA384": "ECDHE-PSK-AES256-CBC-SHA384",
    "ECDHE_PSK_WITH_NULL_SHA": "ECDHE-PSK-NULL-SHA",
    "ECDHE_PSK_WITH_NULL_SHA256": "ECDHE-PSK-NULL-SHA256",
    "ECDHE_PSK_WITH_NULL_SHA384": "ECDHE-PSK-NULL-SHA384",
    "PSK_WITH_CAMELLIA_128_CBC_SHA256": "PSK-CAMELLIA128-SHA256",
    "PSK_WITH_CAMELLIA_256_CBC_SHA384": "PSK-CAMELLIA256-SHA384",
    "DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256": "DHE-PSK-CAMELLIA128-SHA256",
    "DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384": "DHE-PSK-CAMELLIA256-SHA384",
    "RSA_PSK_WITH_CAMELLIA_128_CBC_SHA256": "RSA-PSK-CAMELLIA128-SHA256",
    "RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384": "RSA-PSK-CAMELLIA256-SHA384",
    "ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256": "ECDHE-PSK-CAMELLIA128-SHA256",
    "ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384": "ECDHE-PSK-CAMELLIA256-SHA384",
    "PSK_WITH_AES_128_CCM": "PSK-AES128-CCM",
    "PSK_WITH_AES_256_CCM": "PSK-AES256-CCM",
    "DHE_PSK_WITH_AES_128_CCM": "DHE-PSK-AES128-CCM",
    "DHE_PSK_WITH_AES_256_CCM": "DHE-PSK-AES256-CCM",
    "PSK_WITH_AES_128_CCM_8": "PSK-AES128-CCM8",
    "PSK_WITH_AES_256_CCM_8": "PSK-AES256-CCM8",
    "DHE_PSK_WITH_AES_128_CCM_8": "DHE-PSK-AES128-CCM8",
    "DHE_PSK_WITH_AES_256_CCM_8": "DHE-PSK-AES256-CCM8",
    # ChaCha20-Poly1305 cipher suites, extending TLS v1.2
    "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256": "ECDHE-RSA-CHACHA20-POLY1305",
    "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256": "ECDHE-ECDSA-CHACHA20-POLY1305",
    "TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256": "DHE-RSA-CHACHA20-POLY1305",
    "TLS_PSK_WITH_CHACHA20_POLY1305_SHA256": "PSK-CHACHA20-POLY1305",
    "TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256": "ECDHE-PSK-CHACHA20-POLY1305",
    "TLS_DHE_PSK_WITH_CHACHA20_POLY1305_SHA256": "DHE-PSK-CHACHA20-POLY1305",
    "TLS_RSA_PSK_WITH_CHACHA20_POLY1305_SHA256": "RSA-PSK-CHACHA20-POLY1305",
}

# TLS v1.3 cipher suites IANI to OpenSSL name translation
TLSV1_3_CIPHER_SUITES: Dict[str, str] = {
    "TLS_AES_128_GCM_SHA256": "TLS_AES_128_GCM_SHA256",
    "TLS_AES_256_GCM_SHA384": "TLS_AES_256_GCM_SHA384",
    "TLS_CHACHA20_POLY1305_SHA256": "TLS_CHACHA20_POLY1305_SHA256",
    "TLS_AES_128_CCM_SHA256": "TLS_AES_128_CCM_SHA256",
    "TLS_AES_128_CCM_8_SHA256": "TLS_AES_128_CCM_8_SHA256",
}

TLS_CIPHER_SUITES: Dict[str, Dict[str, str]] = {
    "TLSv1": TLSV1_CIPHER_SUITES,
    "TLSv1.1": TLSV1_1_CIPHER_SUITES,
    "TLSv1.2": TLSV1_2_CIPHER_SUITES,
    "TLSv1.3": TLSV1_3_CIPHER_SUITES,
}

OPENSSL_CS_NAMES: Dict[str, ValuesView[str]] = {
    "TLSv1": TLSV1_CIPHER_SUITES.values(),
    "TLSv1.1": TLSV1_1_CIPHER_SUITES.values(),
    "TLSv1.2": TLSV1_2_CIPHER_SUITES.values(),
    "TLSv1.3": TLSV1_3_CIPHER_SUITES.values(),
}

virt/lib/python3.9/site-packages/mysql/connector/constants 2.py
PrinceDisant/LostInNL
# Copyright (c) 2009, 2023, Oracle and/or its affiliates. All rights reserved.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

"""Various MySQL constants and character sets."""

import warnings

from abc import ABC, ABCMeta
from typing import Any, Dict, List, Optional, Sequence, Tuple, Union, ValuesView

from .charsets import MYSQL_CHARACTER_SETS, MYSQL_CHARACTER_SETS_57
from .errors import ProgrammingError

NET_BUFFER_LENGTH: int = 8192
MAX_MYSQL_TABLE_COLUMNS: int = 4096
# Flag used to send the Query Attributes with 0 (or more) parameters.
PARAMETER_COUNT_AVAILABLE: int = 8

DEFAULT_CONFIGURATION: Dict[str, Optional[Union[str, bool, int]]] = {
    "database": None,
    "user": "",
    "password": "",
    "password1": "",
    "password2": "",
    "password3": "",
    "host": "127.0.0.1",
    "port": 3306,
    "unix_socket": None,
    "use_unicode": True,
    "charset": "utf8mb4",
    "collation": None,
    "converter_class": None,
    "converter_str_fallback": False,
    "autocommit": False,
    "time_zone": None,
    "sql_mode": None,
    "get_warnings": False,
    "raise_on_warnings": False,
    "connection_timeout": None,
    "client_flags": 0,
    "compress": False,
    "buffered": False,
    "raw": False,
    "ssl_ca": None,
    "ssl_cert": None,
    "ssl_key": None,
    "ssl_verify_cert": False,
    "ssl_verify_identity": False,
    "ssl_cipher": None,
    "tls_ciphersuites": None,
    "ssl_disabled": False,
    "tls_versions": None,
    "passwd": None,
    "db": None,
    "connect_timeout": None,
    "dsn": None,
    "force_ipv6": False,
    "auth_plugin": None,
    "allow_local_infile": False,
    "allow_local_infile_in_path": None,
    "consume_results": False,
    "conn_attrs": None,
    "dns_srv": False,
    "use_pure": False,
    "krb_service_principal": None,
    "oci_config_file": None,
    "oci_config_profile": None,
    "fido_callback": None,
    "kerberos_auth_mode": None,
    "init_command": None,
}

CNX_POOL_ARGS: Tuple[str, str, str] = ("pool_name", "pool_size", "pool_reset_session")

TLS_VERSIONS: List[str] = ["TLSv1.2", "TLSv1.3"]

DEPRECATED_TLS_VERSIONS: List[str] = ["TLSv1", "TLSv1.1"]


def flag_is_set(flag: int, flags: int) -> bool:
    """Checks if the flag is set

    Returns boolean"""
    if (flags & flag) > 0:
        return True
    return False


def _obsolete_option(name: str, new_name: str, value: int) -> int:
    """Raise a deprecation warning and advise a new option name.

    Args:
        name (str): The name of the option.
        new_name (str): The new option name.
        value (int): The value of the option.

    Returns:
        int: The value of the option.
    """
    warnings.warn(
        f"The option '{name}' has been deprecated, use '{new_name}' instead.",
        category=DeprecationWarning,
    )
    return value


class _Constants(ABC):
    """Base class for constants."""

    prefix: str = ""
    desc: Dict[str, Tuple[int, str]] = {}

    @classmethod
    def get_desc(cls, name: str) -> Optional[str]:
        """Get description of given constant"""
        try:
            return cls.desc[name][1]
        except (IndexError, KeyError):
            return None

    @classmethod
    def get_info(cls, setid: int) -> Union[Optional[str], Tuple[str, str]]:
        """Get information about given constant"""
        for name, info in cls.desc.items():
            if info[0] == setid:
                return name
        return None

    @classmethod
    def get_full_info(cls) -> Union[str, Sequence[str]]:
        """get full information about given constant"""
        res: Union[str, List[str]] = []
        try:
            res = [f"{k} : {v[1]}" for k, v in cls.desc.items()]
        except (AttributeError, IndexError) as err:
            res = f"No information found in constant class. {err}"

        return res


class _Flags(_Constants):
    """Base class for classes describing flags"""

    @classmethod
    def get_bit_info(cls, value: int) -> List[str]:
        """Get the name of all bits set

        Returns a list of strings."""
        res = []
        for name, info in cls.desc.items():
            if value & info[0]:
                res.append(name)
        return res


class FieldType(_Constants):
    """MySQL Field Types"""

    prefix: str = "FIELD_TYPE_"
    DECIMAL: int = 0x00
    TINY: int = 0x01
    SHORT: int = 0x02
    LONG: int = 0x03
    FLOAT: int = 0x04
    DOUBLE: int = 0x05
    NULL: int = 0x06
    TIMESTAMP: int = 0x07
    LONGLONG: int = 0x08
    INT24: int = 0x09
    DATE: int = 0x0A
    TIME: int = 0x0B
    DATETIME: int = 0x0C
    YEAR: int = 0x0D
    NEWDATE: int = 0x0E
    VARCHAR: int = 0x0F
    BIT: int = 0x10
    JSON: int = 0xF5
    NEWDECIMAL: int = 0xF6
    ENUM: int = 0xF7
    SET: int = 0xF8
    TINY_BLOB: int = 0xF9
    MEDIUM_BLOB: int = 0xFA
    LONG_BLOB: int = 0xFB
    BLOB: int = 0xFC
    VAR_STRING: int = 0xFD
    STRING: int = 0xFE
    GEOMETRY: int = 0xFF

    desc: Dict[str, Tuple[int, str]] = {
        "DECIMAL": (0x00, "DECIMAL"),
        "TINY": (0x01, "TINY"),
        "SHORT": (0x02, "SHORT"),
        "LONG": (0x03, "LONG"),
        "FLOAT": (0x04, "FLOAT"),
        "DOUBLE": (0x05, "DOUBLE"),
        "NULL": (0x06, "NULL"),
        "TIMESTAMP": (0x07, "TIMESTAMP"),
        "LONGLONG": (0x08, "LONGLONG"),
        "INT24": (0x09, "INT24"),
        "DATE": (0x0A, "DATE"),
        "TIME": (0x0B, "TIME"),
        "DATETIME": (0x0C, "DATETIME"),
        "YEAR": (0x0D, "YEAR"),
        "NEWDATE": (0x0E, "NEWDATE"),
        "VARCHAR": (0x0F, "VARCHAR"),
        "BIT": (0x10, "BIT"),
        "JSON": (0xF5, "JSON"),
        "NEWDECIMAL": (0xF6, "NEWDECIMAL"),
        "ENUM": (0xF7, "ENUM"),
        "SET": (0xF8, "SET"),
        "TINY_BLOB": (0xF9, "TINY_BLOB"),
        "MEDIUM_BLOB": (0xFA, "MEDIUM_BLOB"),
        "LONG_BLOB": (0xFB, "LONG_BLOB"),
        "BLOB": (0xFC, "BLOB"),
        "VAR_STRING": (0xFD, "VAR_STRING"),
        "STRING": (0xFE, "STRING"),
        "GEOMETRY": (0xFF, "GEOMETRY"),
    }

    @classmethod
    def get_string_types(cls) -> List[int]:
        """Get the list of all string types"""
        return [
            cls.VARCHAR,
            cls.ENUM,
            cls.VAR_STRING,
            cls.STRING,
        ]

    @classmethod
    def get_binary_types(cls) -> List[int]:
        """Get the list of all binary types"""
        return [
            cls.TINY_BLOB,
            cls.MEDIUM_BLOB,
            cls.LONG_BLOB,
            cls.BLOB,
        ]

    @classmethod
    def get_number_types(cls) -> List[int]:
        """Get the list of all number types"""
        return [
            cls.DECIMAL,
            cls.NEWDECIMAL,
            cls.TINY,
            cls.SHORT,
            cls.LONG,
            cls.FLOAT,
            cls.DOUBLE,
            cls.LONGLONG,
            cls.INT24,
            cls.BIT,
            cls.YEAR,
        ]

    @classmethod
    def get_timestamp_types(cls) -> List[int]:
        """Get the list of all timestamp types"""
        return [
            cls.DATETIME,
            cls.TIMESTAMP,
        ]


class FieldFlag(_Flags):
    """MySQL Field Flags

    Field flags as found in MySQL sources mysql-src/include/mysql_com.h
    """

    _prefix: str = ""
    NOT_NULL: int = 1 << 0
    PRI_KEY: int = 1 << 1
    UNIQUE_KEY: int = 1 << 2
    MULTIPLE_KEY: int = 1 << 3
    BLOB: int = 1 << 4
    UNSIGNED: int = 1 << 5
    ZEROFILL: int = 1 << 6
    BINARY: int = 1 << 7

    ENUM: int = 1 << 8
    AUTO_INCREMENT: int = 1 << 9
    TIMESTAMP: int = 1 << 10
    SET: int = 1 << 11

    NO_DEFAULT_VALUE: int = 1 << 12
    ON_UPDATE_NOW: int = 1 << 13
    NUM: int = 1 << 14
    PART_KEY: int = 1 << 15
    GROUP: int = 1 << 14  # SAME AS NUM !!!!!!!????
    UNIQUE: int = 1 << 16
    BINCMP: int = 1 << 17

    GET_FIXED_FIELDS: int = 1 << 18
    FIELD_IN_PART_FUNC: int = 1 << 19
    FIELD_IN_ADD_INDEX: int = 1 << 20
    FIELD_IS_RENAMED: int = 1 << 21

    desc: Dict[str, Tuple[int, str]] = {
        "NOT_NULL": (1 << 0, "Field can't be NULL"),
        "PRI_KEY": (1 << 1, "Field is part of a primary key"),
        "UNIQUE_KEY": (1 << 2, "Field is part of a unique key"),
        "MULTIPLE_KEY": (1 << 3, "Field is part of a key"),
        "BLOB": (1 << 4, "Field is a blob"),
        "UNSIGNED": (1 << 5, "Field is unsigned"),
        "ZEROFILL": (1 << 6, "Field is zerofill"),
        "BINARY": (1 << 7, "Field is binary  "),
        "ENUM": (1 << 8, "field is an enum"),
        "AUTO_INCREMENT": (1 << 9, "field is a autoincrement field"),
        "TIMESTAMP": (1 << 10, "Field is a timestamp"),
        "SET": (1 << 11, "field is a set"),
        "NO_DEFAULT_VALUE": (1 << 12, "Field doesn't have default value"),
        "ON_UPDATE_NOW": (1 << 13, "Field is set to NOW on UPDATE"),
        "NUM": (1 << 14, "Field is num (for clients)"),
        "PART_KEY": (1 << 15, "Intern; Part of some key"),
        "GROUP": (1 << 14, "Intern: Group field"),  # Same as NUM
        "UNIQUE": (1 << 16, "Intern: Used by sql_yacc"),
        "BINCMP": (1 << 17, "Intern: Used by sql_yacc"),
        "GET_FIXED_FIELDS": (1 << 18, "Used to get fields in item tree"),
        "FIELD_IN_PART_FUNC": (1 << 19, "Field part of partition func"),
        "FIELD_IN_ADD_INDEX": (1 << 20, "Intern: Field used in ADD INDEX"),
        "FIELD_IS_RENAMED": (1 << 21, "Intern: Field is being renamed"),
    }


class ServerCmdMeta(ABCMeta):
    """ClientFlag Metaclass."""

    def __getattribute__(cls, name: str) -> Any:
        deprecated_options = (
            "FIELD_LIST",
            "REFRESH",
            "SHUTDOWN",
            "PROCESS_INFO",
            "PROCESS_KILL",
        )
        if name in deprecated_options:
            warnings.warn(
                f"The option 'ServerCmd.{name}' is deprecated and will be removed in "
                "a future release.",
                category=DeprecationWarning,
            )
        return super().__getattribute__(name)


class ServerCmd(_Constants, metaclass=ServerCmdMeta):
    """MySQL Server Commands"""

    _prefix: str = "COM_"
    SLEEP: int = 0
    QUIT: int = 1
    INIT_DB: int = 2
    QUERY: int = 3
    FIELD_LIST: int = 4
    CREATE_DB: int = 5
    DROP_DB: int = 6
    REFRESH: int = 7
    SHUTDOWN: int = 8
    STATISTICS: int = 9
    PROCESS_INFO: int = 10
    CONNECT: int = 11
    PROCESS_KILL: int = 12
    DEBUG: int = 13
    PING: int = 14
    TIME: int = 15
    DELAYED_INSERT: int = 16
    CHANGE_USER: int = 17
    BINLOG_DUMP: int = 18
    TABLE_DUMP: int = 19
    CONNECT_OUT: int = 20
    REGISTER_REPLICA: int = 21
    STMT_PREPARE: int = 22
    STMT_EXECUTE: int = 23
    STMT_SEND_LONG_DATA: int = 24
    STMT_CLOSE: int = 25
    STMT_RESET: int = 26
    SET_OPTION: int = 27
    STMT_FETCH: int = 28
    DAEMON: int = 29
    BINLOG_DUMP_GTID: int = 30
    RESET_CONNECTION: int = 31

    desc: Dict[str, Tuple[int, str]] = {
        "SLEEP": (0, "SLEEP"),
        "QUIT": (1, "QUIT"),
        "INIT_DB": (2, "INIT_DB"),
        "QUERY": (3, "QUERY"),
        "FIELD_LIST": (4, "FIELD_LIST"),
        "CREATE_DB": (5, "CREATE_DB"),
        "DROP_DB": (6, "DROP_DB"),
        "REFRESH": (7, "REFRESH"),
        "SHUTDOWN": (8, "SHUTDOWN"),
        "STATISTICS": (9, "STATISTICS"),
        "PROCESS_INFO": (10, "PROCESS_INFO"),
        "CONNECT": (11, "CONNECT"),
        "PROCESS_KILL": (12, "PROCESS_KILL"),
        "DEBUG": (13, "DEBUG"),
        "PING": (14, "PING"),
        "TIME": (15, "TIME"),
        "DELAYED_INSERT": (16, "DELAYED_INSERT"),
        "CHANGE_USER": (17, "CHANGE_USER"),
        "BINLOG_DUMP": (18, "BINLOG_DUMP"),
        "TABLE_DUMP": (19, "TABLE_DUMP"),
        "CONNECT_OUT": (20, "CONNECT_OUT"),
        "REGISTER_REPLICA": (21, "REGISTER_REPLICA"),
        "STMT_PREPARE": (22, "STMT_PREPARE"),
        "STMT_EXECUTE": (23, "STMT_EXECUTE"),
        "STMT_SEND_LONG_DATA": (24, "STMT_SEND_LONG_DATA"),
        "STMT_CLOSE": (25, "STMT_CLOSE"),
        "STMT_RESET": (26, "STMT_RESET"),
        "SET_OPTION": (27, "SET_OPTION"),
        "STMT_FETCH": (28, "STMT_FETCH"),
        "DAEMON": (29, "DAEMON"),
        "BINLOG_DUMP_GTID": (30, "BINLOG_DUMP_GTID"),
        "RESET_CONNECTION": (31, "RESET_CONNECTION"),
    }


class ClientFlag(_Flags):
    """MySQL Client Flags

    Client options as found in the MySQL sources mysql-src/include/mysql_com.h
    """

    LONG_PASSWD: int = 1 << 0
    FOUND_ROWS: int = 1 << 1
    LONG_FLAG: int = 1 << 2
    CONNECT_WITH_DB: int = 1 << 3
    NO_SCHEMA: int = 1 << 4
    COMPRESS: int = 1 << 5
    ODBC: int = 1 << 6
    LOCAL_FILES: int = 1 << 7
    IGNORE_SPACE: int = 1 << 8
    PROTOCOL_41: int = 1 << 9
    INTERACTIVE: int = 1 << 10
    SSL: int = 1 << 11
    IGNORE_SIGPIPE: int = 1 << 12
    TRANSACTIONS: int = 1 << 13
    RESERVED: int = 1 << 14
    SECURE_CONNECTION: int = 1 << 15
    MULTI_STATEMENTS: int = 1 << 16
    MULTI_RESULTS: int = 1 << 17
    PS_MULTI_RESULTS: int = 1 << 18
    PLUGIN_AUTH: int = 1 << 19
    CONNECT_ARGS: int = 1 << 20
    PLUGIN_AUTH_LENENC_CLIENT_DATA: int = 1 << 21
    CAN_HANDLE_EXPIRED_PASSWORDS: int = 1 << 22
    SESION_TRACK: int = 1 << 23  # deprecated
    SESSION_TRACK: int = 1 << 23
    DEPRECATE_EOF: int = 1 << 24
    CLIENT_QUERY_ATTRIBUTES: int = 1 << 27
    SSL_VERIFY_SERVER_CERT: int = 1 << 30
    REMEMBER_OPTIONS: int = 1 << 31
    MULTI_FACTOR_AUTHENTICATION: int = 1 << 28

    desc: Dict[str, Tuple[int, str]] = {
        "LONG_PASSWD": (1 << 0, "New more secure passwords"),
        "FOUND_ROWS": (1 << 1, "Found instead of affected rows"),
        "LONG_FLAG": (1 << 2, "Get all column flags"),
        "CONNECT_WITH_DB": (1 << 3, "One can specify db on connect"),
        "NO_SCHEMA": (1 << 4, "Don't allow database.table.column"),
        "COMPRESS": (1 << 5, "Can use compression protocol"),
        "ODBC": (1 << 6, "ODBC client"),
        "LOCAL_FILES": (1 << 7, "Can use LOAD DATA LOCAL"),
        "IGNORE_SPACE": (1 << 8, "Ignore spaces before ''"),
        "PROTOCOL_41": (1 << 9, "New 4.1 protocol"),
        "INTERACTIVE": (1 << 10, "This is an interactive client"),
        "SSL": (1 << 11, "Switch to SSL after handshake"),
        "IGNORE_SIGPIPE": (1 << 12, "IGNORE sigpipes"),
        "TRANSACTIONS": (1 << 13, "Client knows about transactions"),
        "RESERVED": (1 << 14, "Old flag for 4.1 protocol"),
        "SECURE_CONNECTION": (1 << 15, "New 4.1 authentication"),
        "MULTI_STATEMENTS": (1 << 16, "Enable/disable multi-stmt support"),
        "MULTI_RESULTS": (1 << 17, "Enable/disable multi-results"),
        "PS_MULTI_RESULTS": (1 << 18, "Multi-results in PS-protocol"),
        "PLUGIN_AUTH": (1 << 19, "Client supports plugin authentication"),
        "CONNECT_ARGS": (1 << 20, "Client supports connection attributes"),
        "PLUGIN_AUTH_LENENC_CLIENT_DATA": (
            1 << 21,
            "Enable authentication response packet to be larger than 255 bytes",
        ),
        "CAN_HANDLE_EXPIRED_PASSWORDS": (
            1 << 22,
            "Don't close the connection for a connection with expired password",
        ),
        "SESION_TRACK": (  # deprecated
            1 << 23,
            "Capable of handling server state change information",
        ),
        "SESSION_TRACK": (
            1 << 23,
            "Capable of handling server state change information",
        ),
        "DEPRECATE_EOF": (1 << 24, "Client no longer needs EOF packet"),
        "CLIENT_QUERY_ATTRIBUTES": (
            1 << 27,
            "Support optional extension for query parameters",
        ),
        "SSL_VERIFY_SERVER_CERT": (1 << 30, ""),
        "REMEMBER_OPTIONS": (1 << 31, ""),
    }

    default: List[int] = [
        LONG_PASSWD,
        LONG_FLAG,
        CONNECT_WITH_DB,
        PROTOCOL_41,
        TRANSACTIONS,
        SECURE_CONNECTION,
        MULTI_STATEMENTS,
        MULTI_RESULTS,
        CONNECT_ARGS,
    ]

    @classmethod
    def get_default(cls) -> int:
        """Get the default client options set

        Returns a flag with all the default client options set"""
        flags = 0
        for option in cls.default:
            flags |= option
        return flags


class ServerFlag(_Flags):
    """MySQL Server Flags

    Server flags as found in the MySQL sources mysql-src/include/mysql_com.h
    """

    _prefix: str = "SERVER_"
    STATUS_IN_TRANS: int = 1 << 0
    STATUS_AUTOCOMMIT: int = 1 << 1
    MORE_RESULTS_EXISTS: int = 1 << 3
    QUERY_NO_GOOD_INDEX_USED: int = 1 << 4
    QUERY_NO_INDEX_USED: int = 1 << 5
    STATUS_CURSOR_EXISTS: int = 1 << 6
    STATUS_LAST_ROW_SENT: int = 1 << 7
    STATUS_DB_DROPPED: int = 1 << 8
    STATUS_NO_BACKSLASH_ESCAPES: int = 1 << 9
    SERVER_STATUS_METADATA_CHANGED: int = 1 << 10
    SERVER_QUERY_WAS_SLOW: int = 1 << 11
    SERVER_PS_OUT_PARAMS: int = 1 << 12
    SERVER_STATUS_IN_TRANS_READONLY: int = 1 << 13
    SERVER_SESSION_STATE_CHANGED: int = 1 << 14

    desc: Dict[str, Tuple[int, str]] = {
        "SERVER_STATUS_IN_TRANS": (1 << 0, "Transaction has started"),
        "SERVER_STATUS_AUTOCOMMIT": (1 << 1, "Server in auto_commit mode"),
        "SERVER_MORE_RESULTS_EXISTS": (
            1 << 3,
            "Multi query - next query exists",
        ),
        "SERVER_QUERY_NO_GOOD_INDEX_USED": (1 << 4, ""),
        "SERVER_QUERY_NO_INDEX_USED": (1 << 5, ""),
        "SERVER_STATUS_CURSOR_EXISTS": (
            1 << 6,
            "Set when server opened a read-only non-scrollable cursor for a query.",
        ),
        "SERVER_STATUS_LAST_ROW_SENT": (
            1 << 7,
            "Set when a read-only cursor is exhausted",
        ),
        "SERVER_STATUS_DB_DROPPED": (1 << 8, "A database was dropped"),
        "SERVER_STATUS_NO_BACKSLASH_ESCAPES": (1 << 9, ""),
        "SERVER_STATUS_METADATA_CHANGED": (
            1024,
            "Set if after a prepared statement "
            "reprepare we discovered that the "
            "new statement returns a different "
            "number of result set columns.",
        ),
        "SERVER_QUERY_WAS_SLOW": (2048, ""),
        "SERVER_PS_OUT_PARAMS": (
            4096,
            "To mark ResultSet containing output parameter values.",
        ),
        "SERVER_STATUS_IN_TRANS_READONLY": (
            8192,
            "Set if multi-statement transaction is a read-only transaction.",
        ),
        "SERVER_SESSION_STATE_CHANGED": (
            1 << 14,
            "Session state has changed on the "
            "server because of the execution of "
            "the last statement",
        ),
    }


class RefreshOptionMeta(ABCMeta):
    """RefreshOption Metaclass."""

    @property
    def SLAVE(self) -> int:  # pylint: disable=bad-mcs-method-argument,invalid-name
        """Return the deprecated alias of RefreshOption.REPLICA.

        Raises a warning about this attribute deprecation.
        """
        return _obsolete_option(
            "RefreshOption.SLAVE",
            "RefreshOption.REPLICA",
            RefreshOption.REPLICA,
        )


class RefreshOption(_Constants, metaclass=RefreshOptionMeta):
    """MySQL Refresh command options.

    Options used when sending the COM_REFRESH server command.
    """

    _prefix: str = "REFRESH_"
    GRANT: int = 1 << 0
    LOG: int = 1 << 1
    TABLES: int = 1 << 2
    HOST: int = 1 << 3
    STATUS: int = 1 << 4
    THREADS: int = 1 << 5
    REPLICA: int = 1 << 6

    desc: Dict[str, Tuple[int, str]] = {
        "GRANT": (1 << 0, "Refresh grant tables"),
        "LOG": (1 << 1, "Start on new log file"),
        "TABLES": (1 << 2, "close all tables"),
        "HOST": (1 << 3, "Flush host cache"),
        "STATUS": (1 << 4, "Flush status variables"),
        "THREADS": (1 << 5, "Flush thread cache"),
        "REPLICA": (1 << 6, "Reset source info and restart replica thread"),
        "SLAVE": (1 << 6, "Deprecated option; use REPLICA instead."),
    }


class ShutdownType(_Constants):
    """MySQL Shutdown types

    Shutdown types used by the COM_SHUTDOWN server command.
    """

    _prefix: str = ""
    SHUTDOWN_DEFAULT: int = 0
    SHUTDOWN_WAIT_CONNECTIONS: int = 1
    SHUTDOWN_WAIT_TRANSACTIONS: int = 2
    SHUTDOWN_WAIT_UPDATES: int = 8
    SHUTDOWN_WAIT_ALL_BUFFERS: int = 16
    SHUTDOWN_WAIT_CRITICAL_BUFFERS: int = 17
    KILL_QUERY: int = 254
    KILL_CONNECTION: int = 255

    desc: Dict[str, Tuple[int, str]] = {
        "SHUTDOWN_DEFAULT": (
            SHUTDOWN_DEFAULT,
            "defaults to SHUTDOWN_WAIT_ALL_BUFFERS",
        ),
        "SHUTDOWN_WAIT_CONNECTIONS": (
            SHUTDOWN_WAIT_CONNECTIONS,
            "wait for existing connections to finish",
        ),
        "SHUTDOWN_WAIT_TRANSACTIONS": (
            SHUTDOWN_WAIT_TRANSACTIONS,
            "wait for existing trans to finish",
        ),
        "SHUTDOWN_WAIT_UPDATES": (
            SHUTDOWN_WAIT_UPDATES,
            "wait for existing updates to finish",
        ),
        "SHUTDOWN_WAIT_ALL_BUFFERS": (
            SHUTDOWN_WAIT_ALL_BUFFERS,
            "flush InnoDB and other storage engine buffers",
        ),
        "SHUTDOWN_WAIT_CRITICAL_BUFFERS": (
            SHUTDOWN_WAIT_CRITICAL_BUFFERS,
            "don't flush InnoDB buffers, flush other storage engines' buffers",
        ),
        "KILL_QUERY": (KILL_QUERY, "(no description)"),
        "KILL_CONNECTION": (KILL_CONNECTION, "(no description)"),
    }


class CharacterSet(_Constants):
    """MySQL supported character sets and collations

    List of character sets with their collations supported by MySQL. This
    maps to the character set we get from the server within the handshake
    packet.

    The list is hardcode so we avoid a database query when getting the
    name of the used character set or collation.
    """

    # Use LTS character set as default
    desc: List[
        Optional[Tuple[str, str, bool]]
    ] = MYSQL_CHARACTER_SETS_57  # type: ignore[assignment]
    mysql_version: Tuple[int, ...] = (5, 7)

    # Multi-byte character sets which use 5c (backslash) in characters
    slash_charsets: Tuple[int, ...] = (1, 13, 28, 84, 87, 88)

    @classmethod
    def set_mysql_version(cls, version: Tuple[int, ...]) -> None:
        """Set the MySQL major version and change the charset mapping if is 5.7.

        Args:
            version (tuple): MySQL version tuple.
        """
        cls.mysql_version = version[:2]
        if cls.mysql_version >= (8, 0):
            cls.desc = MYSQL_CHARACTER_SETS

    @classmethod
    def get_info(cls, setid: int) -> Tuple[str, str]:
        """Retrieves character set information as tuple using an ID

        Retrieves character set and collation information based on the
        given MySQL ID.

        Raises ProgrammingError when character set is not supported.

        Returns a tuple.
        """
        try:
            return cls.desc[setid][0:2]
        except IndexError:
            raise ProgrammingError(f"Character set '{setid}' unsupported") from None

    @classmethod
    def get_desc(cls, name: int) -> str:  # type: ignore[override]
        """Retrieves character set information as string using an ID

        Retrieves character set and collation information based on the
        given MySQL ID.

        Returns a tuple.
        """
        charset, collation = cls.get_info(name)
        return f"{charset}/{collation}"

    @classmethod
    def get_default_collation(cls, charset: Union[int, str]) -> Tuple[str, str, int]:
        """Retrieves the default collation for given character set

        Raises ProgrammingError when character set is not supported.

        Returns list (collation, charset, index)
        """
        if isinstance(charset, int):
            try:
                info = cls.desc[charset]
                return info[1], info[0], charset
            except (IndexError, KeyError) as err:
                raise ProgrammingError(
                    f"Character set ID '{charset}' unsupported"
                ) from err

        for cid, info in enumerate(cls.desc):
            if info is None:
                continue
            if info[0] == charset and info[2] is True:
                return info[1], info[0], cid

        raise ProgrammingError(f"Character set '{charset}' unsupported")

    @classmethod
    def get_charset_info(
        cls, charset: Optional[Union[int, str]] = None, collation: Optional[str] = None
    ) -> Tuple[int, str, str]:
        """Get character set information using charset name and/or collation

        Retrieves character set and collation information given character
        set name and/or a collation name.
        If charset is an integer, it will look up the character set based
        on the MySQL's ID.
        For example:
            get_charset_info('utf8',None)
            get_charset_info(collation='utf8_general_ci')
            get_charset_info(47)

        Raises ProgrammingError when character set is not supported.

        Returns a tuple with (id, characterset name, collation)
        """
        info: Optional[Union[Tuple[str, str, bool], Tuple[str, str, int]]] = None
        if isinstance(charset, int):
            try:
                info = cls.desc[charset]
                return (charset, info[0], info[1])
            except IndexError as err:
                raise ProgrammingError(f"Character set ID {charset} unknown") from err

        if charset in ("utf8", "utf-8") and cls.mysql_version >= (8, 0):
            charset = "utf8mb4"
        if charset is not None and collation is None:
            info = cls.get_default_collation(charset)
            return (info[2], info[1], info[0])
        if charset is None and collation is not None:
            for cid, info in enumerate(cls.desc):
                if info is None:
                    continue
                if collation == info[1]:
                    return (cid, info[0], info[1])
            raise ProgrammingError(f"Collation '{collation}' unknown")
        for cid, info in enumerate(cls.desc):
            if info is None:
                continue
            if info[0] == charset and info[1] == collation:
                return (cid, info[0], info[1])
        _ = cls.get_default_collation(charset)
        raise ProgrammingError(f"Collation '{collation}' unknown")

    @classmethod
    def get_supported(cls) -> Tuple[str, ...]:
        """Retrieves a list with names of all supproted character sets

        Returns a tuple.
        """
        res = []
        for info in cls.desc:
            if info and info[0] not in res:
                res.append(info[0])
        return tuple(res)


class SQLMode(_Constants):
    """MySQL SQL Modes

    The numeric values of SQL Modes are not interesting, only the names
    are used when setting the SQL_MODE system variable using the MySQL
    SET command.

    See http://dev.mysql.com/doc/refman/5.6/en/server-sql-mode.html
    """

    _prefix: str = "MODE_"
    REAL_AS_FLOAT: str = "REAL_AS_FLOAT"
    PIPES_AS_CONCAT: str = "PIPES_AS_CONCAT"
    ANSI_QUOTES: str = "ANSI_QUOTES"
    IGNORE_SPACE: str = "IGNORE_SPACE"
    NOT_USED: str = "NOT_USED"
    ONLY_FULL_GROUP_BY: str = "ONLY_FULL_GROUP_BY"
    NO_UNSIGNED_SUBTRACTION: str = "NO_UNSIGNED_SUBTRACTION"
    NO_DIR_IN_CREATE: str = "NO_DIR_IN_CREATE"
    POSTGRESQL: str = "POSTGRESQL"
    ORACLE: str = "ORACLE"
    MSSQL: str = "MSSQL"
    DB2: str = "DB2"
    MAXDB: str = "MAXDB"
    NO_KEY_OPTIONS: str = "NO_KEY_OPTIONS"
    NO_TABLE_OPTIONS: str = "NO_TABLE_OPTIONS"
    NO_FIELD_OPTIONS: str = "NO_FIELD_OPTIONS"
    MYSQL323: str = "MYSQL323"
    MYSQL40: str = "MYSQL40"
    ANSI: str = "ANSI"
    NO_AUTO_VALUE_ON_ZERO: str = "NO_AUTO_VALUE_ON_ZERO"
    NO_BACKSLASH_ESCAPES: str = "NO_BACKSLASH_ESCAPES"
    STRICT_TRANS_TABLES: str = "STRICT_TRANS_TABLES"
    STRICT_ALL_TABLES: str = "STRICT_ALL_TABLES"
    NO_ZERO_IN_DATE: str = "NO_ZERO_IN_DATE"
    NO_ZERO_DATE: str = "NO_ZERO_DATE"
    INVALID_DATES: str = "INVALID_DATES"
    ERROR_FOR_DIVISION_BY_ZERO: str = "ERROR_FOR_DIVISION_BY_ZERO"
    TRADITIONAL: str = "TRADITIONAL"
    NO_AUTO_CREATE_USER: str = "NO_AUTO_CREATE_USER"
    HIGH_NOT_PRECEDENCE: str = "HIGH_NOT_PRECEDENCE"
    NO_ENGINE_SUBSTITUTION: str = "NO_ENGINE_SUBSTITUTION"
    PAD_CHAR_TO_FULL_LENGTH: str = "PAD_CHAR_TO_FULL_LENGTH"

    @classmethod
    def get_desc(cls, name: str) -> Optional[str]:
        raise NotImplementedError

    @classmethod
    def get_info(cls, setid: int) -> Optional[str]:
        raise NotImplementedError

    @classmethod
    def get_full_info(cls) -> Tuple[str, ...]:
        """Returns a sequence of all available SQL Modes

        This class method returns a tuple containing all SQL Mode names. The
        names will be alphabetically sorted.

        Returns a tuple.
        """
        res = []
        for key in vars(cls).keys():
            if not key.startswith("_") and not hasattr(getattr(cls, key), "__call__"):
                res.append(key)
        return tuple(sorted(res))


CONN_ATTRS_DN: List[str] = [
    "_pid",
    "_platform",
    "_source_host",
    "_client_name",
    "_client_license",
    "_client_version",
    "_os",
    "_connector_name",
    "_connector_license",
    "_connector_version",
]

# TLS v1.0 cipher suites IANI to OpenSSL name translation
TLSV1_CIPHER_SUITES: Dict[str, str] = {
    "TLS_RSA_WITH_NULL_MD5": "NULL-MD5",
    "TLS_RSA_WITH_NULL_SHA": "NULL-SHA",
    "TLS_RSA_WITH_RC4_128_MD5": "RC4-MD5",
    "TLS_RSA_WITH_RC4_128_SHA": "RC4-SHA",
    "TLS_RSA_WITH_IDEA_CBC_SHA": "IDEA-CBC-SHA",
    "TLS_RSA_WITH_3DES_EDE_CBC_SHA": "DES-CBC3-SHA",
    "TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA": "Not implemented.",
    "TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA": "Not implemented.",
    "TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA": "DHE-DSS-DES-CBC3-SHA",
    "TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA": "DHE-RSA-DES-CBC3-SHA",
    "TLS_DH_anon_WITH_RC4_128_MD5": "ADH-RC4-MD5",
    "TLS_DH_anon_WITH_3DES_EDE_CBC_SHA": "ADH-DES-CBC3-SHA",
    # AES cipher suites from RFC3268, extending TLS v1.0
    "TLS_RSA_WITH_AES_128_CBC_SHA": "AES128-SHA",
    "TLS_RSA_WITH_AES_256_CBC_SHA": "AES256-SHA",
    "TLS_DH_DSS_WITH_AES_128_CBC_SHA": "DH-DSS-AES128-SHA",
    "TLS_DH_DSS_WITH_AES_256_CBC_SHA": "DH-DSS-AES256-SHA",
    "TLS_DH_RSA_WITH_AES_128_CBC_SHA": "DH-RSA-AES128-SHA",
    "TLS_DH_RSA_WITH_AES_256_CBC_SHA": "DH-RSA-AES256-SHA",
    "TLS_DHE_DSS_WITH_AES_128_CBC_SHA": "DHE-DSS-AES128-SHA",
    "TLS_DHE_DSS_WITH_AES_256_CBC_SHA": "DHE-DSS-AES256-SHA",
    "TLS_DHE_RSA_WITH_AES_128_CBC_SHA": "DHE-RSA-AES128-SHA",
    "TLS_DHE_RSA_WITH_AES_256_CBC_SHA": "DHE-RSA-AES256-SHA",
    "TLS_DH_anon_WITH_AES_128_CBC_SHA": "ADH-AES128-SHA",
    "TLS_DH_anon_WITH_AES_256_CBC_SHA": "ADH-AES256-SHA",
    # Camellia cipher suites from RFC4132, extending TLS v1.0
    "TLS_RSA_WITH_CAMELLIA_128_CBC_SHA": "CAMELLIA128-SHA",
    "TLS_RSA_WITH_CAMELLIA_256_CBC_SHA": "CAMELLIA256-SHA",
    "TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA": "DH-DSS-CAMELLIA128-SHA",
    "TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA": "DH-DSS-CAMELLIA256-SHA",
    "TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA": "DH-RSA-CAMELLIA128-SHA",
    "TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA": "DH-RSA-CAMELLIA256-SHA",
    "TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA": "DHE-DSS-CAMELLIA128-SHA",
    "TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA": "DHE-DSS-CAMELLIA256-SHA",
    "TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA": "DHE-RSA-CAMELLIA128-SHA",
    "TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA": "DHE-RSA-CAMELLIA256-SHA",
    "TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA": "ADH-CAMELLIA128-SHA",
    "TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA": "ADH-CAMELLIA256-SHA",
    # SEED cipher suites from RFC4162, extending TLS v1.0
    "TLS_RSA_WITH_SEED_CBC_SHA": "SEED-SHA",
    "TLS_DH_DSS_WITH_SEED_CBC_SHA": "DH-DSS-SEED-SHA",
    "TLS_DH_RSA_WITH_SEED_CBC_SHA": "DH-RSA-SEED-SHA",
    "TLS_DHE_DSS_WITH_SEED_CBC_SHA": "DHE-DSS-SEED-SHA",
    "TLS_DHE_RSA_WITH_SEED_CBC_SHA": "DHE-RSA-SEED-SHA",
    "TLS_DH_anon_WITH_SEED_CBC_SHA": "ADH-SEED-SHA",
    # GOST cipher suites from draft-chudov-cryptopro-cptls, extending TLS v1.0
    "TLS_GOSTR341094_WITH_28147_CNT_IMIT": "GOST94-GOST89-GOST89",
    "TLS_GOSTR341001_WITH_28147_CNT_IMIT": "GOST2001-GOST89-GOST89",
    "TLS_GOSTR341094_WITH_NULL_GOSTR3411": "GOST94-NULL-GOST94",
    "TLS_GOSTR341001_WITH_NULL_GOSTR3411": "GOST2001-NULL-GOST94",
}

# TLS v1.1 cipher suites IANI to OpenSSL name translation
TLSV1_1_CIPHER_SUITES: Dict[str, str] = TLSV1_CIPHER_SUITES

# TLS v1.2 cipher suites IANI to OpenSSL name translation
TLSV1_2_CIPHER_SUITES: Dict[str, str] = {
    "TLS_RSA_WITH_NULL_SHA256": "NULL-SHA256",
    "TLS_RSA_WITH_AES_128_CBC_SHA256": "AES128-SHA256",
    "TLS_RSA_WITH_AES_256_CBC_SHA256": "AES256-SHA256",
    "TLS_RSA_WITH_AES_128_GCM_SHA256": "AES128-GCM-SHA256",
    "TLS_RSA_WITH_AES_256_GCM_SHA384": "AES256-GCM-SHA384",
    "TLS_DH_RSA_WITH_AES_128_CBC_SHA256": "DH-RSA-AES128-SHA256",
    "TLS_DH_RSA_WITH_AES_256_CBC_SHA256": "DH-RSA-AES256-SHA256",
    "TLS_DH_RSA_WITH_AES_128_GCM_SHA256": "DH-RSA-AES128-GCM-SHA256",
    "TLS_DH_RSA_WITH_AES_256_GCM_SHA384": "DH-RSA-AES256-GCM-SHA384",
    "TLS_DH_DSS_WITH_AES_128_CBC_SHA256": "DH-DSS-AES128-SHA256",
    "TLS_DH_DSS_WITH_AES_256_CBC_SHA256": "DH-DSS-AES256-SHA256",
    "TLS_DH_DSS_WITH_AES_128_GCM_SHA256": "DH-DSS-AES128-GCM-SHA256",
    "TLS_DH_DSS_WITH_AES_256_GCM_SHA384": "DH-DSS-AES256-GCM-SHA384",
    "TLS_DHE_RSA_WITH_AES_128_CBC_SHA256": "DHE-RSA-AES128-SHA256",
    "TLS_DHE_RSA_WITH_AES_256_CBC_SHA256": "DHE-RSA-AES256-SHA256",
    "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256": "DHE-RSA-AES128-GCM-SHA256",
    "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384": "DHE-RSA-AES256-GCM-SHA384",
    "TLS_DHE_DSS_WITH_AES_128_CBC_SHA256": "DHE-DSS-AES128-SHA256",
    "TLS_DHE_DSS_WITH_AES_256_CBC_SHA256": "DHE-DSS-AES256-SHA256",
    "TLS_DHE_DSS_WITH_AES_128_GCM_SHA256": "DHE-DSS-AES128-GCM-SHA256",
    "TLS_DHE_DSS_WITH_AES_256_GCM_SHA384": "DHE-DSS-AES256-GCM-SHA384",
    "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256": "ECDHE-RSA-AES128-SHA256",
    "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384": "ECDHE-RSA-AES256-SHA384",
    "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256": "ECDHE-RSA-AES128-GCM-SHA256",
    "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384": "ECDHE-RSA-AES256-GCM-SHA384",
    "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256": "ECDHE-ECDSA-AES128-SHA256",
    "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384": "ECDHE-ECDSA-AES256-SHA384",
    "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256": "ECDHE-ECDSA-AES128-GCM-SHA256",
    "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384": "ECDHE-ECDSA-AES256-GCM-SHA384",
    "TLS_DH_anon_WITH_AES_128_CBC_SHA256": "ADH-AES128-SHA256",
    "TLS_DH_anon_WITH_AES_256_CBC_SHA256": "ADH-AES256-SHA256",
    "TLS_DH_anon_WITH_AES_128_GCM_SHA256": "ADH-AES128-GCM-SHA256",
    "TLS_DH_anon_WITH_AES_256_GCM_SHA384": "ADH-AES256-GCM-SHA384",
    "RSA_WITH_AES_128_CCM": "AES128-CCM",
    "RSA_WITH_AES_256_CCM": "AES256-CCM",
    "DHE_RSA_WITH_AES_128_CCM": "DHE-RSA-AES128-CCM",
    "DHE_RSA_WITH_AES_256_CCM": "DHE-RSA-AES256-CCM",
    "RSA_WITH_AES_128_CCM_8": "AES128-CCM8",
    "RSA_WITH_AES_256_CCM_8": "AES256-CCM8",
    "DHE_RSA_WITH_AES_128_CCM_8": "DHE-RSA-AES128-CCM8",
    "DHE_RSA_WITH_AES_256_CCM_8": "DHE-RSA-AES256-CCM8",
    "ECDHE_ECDSA_WITH_AES_128_CCM": "ECDHE-ECDSA-AES128-CCM",
    "ECDHE_ECDSA_WITH_AES_256_CCM": "ECDHE-ECDSA-AES256-CCM",
    "ECDHE_ECDSA_WITH_AES_128_CCM_8": "ECDHE-ECDSA-AES128-CCM8",
    "ECDHE_ECDSA_WITH_AES_256_CCM_8": "ECDHE-ECDSA-AES256-CCM8",
    # ARIA cipher suites from RFC6209, extending TLS v1.2
    "TLS_RSA_WITH_ARIA_128_GCM_SHA256": "ARIA128-GCM-SHA256",
    "TLS_RSA_WITH_ARIA_256_GCM_SHA384": "ARIA256-GCM-SHA384",
    "TLS_DHE_RSA_WITH_ARIA_128_GCM_SHA256": "DHE-RSA-ARIA128-GCM-SHA256",
    "TLS_DHE_RSA_WITH_ARIA_256_GCM_SHA384": "DHE-RSA-ARIA256-GCM-SHA384",
    "TLS_DHE_DSS_WITH_ARIA_128_GCM_SHA256": "DHE-DSS-ARIA128-GCM-SHA256",
    "TLS_DHE_DSS_WITH_ARIA_256_GCM_SHA384": "DHE-DSS-ARIA256-GCM-SHA384",
    "TLS_ECDHE_ECDSA_WITH_ARIA_128_GCM_SHA256": "ECDHE-ECDSA-ARIA128-GCM-SHA256",
    "TLS_ECDHE_ECDSA_WITH_ARIA_256_GCM_SHA384": "ECDHE-ECDSA-ARIA256-GCM-SHA384",
    "TLS_ECDHE_RSA_WITH_ARIA_128_GCM_SHA256": "ECDHE-ARIA128-GCM-SHA256",
    "TLS_ECDHE_RSA_WITH_ARIA_256_GCM_SHA384": "ECDHE-ARIA256-GCM-SHA384",
    "TLS_PSK_WITH_ARIA_128_GCM_SHA256": "PSK-ARIA128-GCM-SHA256",
    "TLS_PSK_WITH_ARIA_256_GCM_SHA384": "PSK-ARIA256-GCM-SHA384",
    "TLS_DHE_PSK_WITH_ARIA_128_GCM_SHA256": "DHE-PSK-ARIA128-GCM-SHA256",
    "TLS_DHE_PSK_WITH_ARIA_256_GCM_SHA384": "DHE-PSK-ARIA256-GCM-SHA384",
    "TLS_RSA_PSK_WITH_ARIA_128_GCM_SHA256": "RSA-PSK-ARIA128-GCM-SHA256",
    "TLS_RSA_PSK_WITH_ARIA_256_GCM_SHA384": "RSA-PSK-ARIA256-GCM-SHA384",
    # Camellia HMAC-Based cipher suites from RFC6367, extending TLS v1.2
    "TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256": "ECDHE-ECDSA-CAMELLIA128-SHA256",
    "TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384": "ECDHE-ECDSA-CAMELLIA256-SHA384",
    "TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256": "ECDHE-RSA-CAMELLIA128-SHA256",
    "TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384": "ECDHE-RSA-CAMELLIA256-SHA384",
    # Pre-shared keying (PSK) cipher suites",
    "PSK_WITH_NULL_SHA": "PSK-NULL-SHA",
    "DHE_PSK_WITH_NULL_SHA": "DHE-PSK-NULL-SHA",
    "RSA_PSK_WITH_NULL_SHA": "RSA-PSK-NULL-SHA",
    "PSK_WITH_RC4_128_SHA": "PSK-RC4-SHA",
    "PSK_WITH_3DES_EDE_CBC_SHA": "PSK-3DES-EDE-CBC-SHA",
    "PSK_WITH_AES_128_CBC_SHA": "PSK-AES128-CBC-SHA",
    "PSK_WITH_AES_256_CBC_SHA": "PSK-AES256-CBC-SHA",
    "DHE_PSK_WITH_RC4_128_SHA": "DHE-PSK-RC4-SHA",
    "DHE_PSK_WITH_3DES_EDE_CBC_SHA": "DHE-PSK-3DES-EDE-CBC-SHA",
    "DHE_PSK_WITH_AES_128_CBC_SHA": "DHE-PSK-AES128-CBC-SHA",
    "DHE_PSK_WITH_AES_256_CBC_SHA": "DHE-PSK-AES256-CBC-SHA",
    "RSA_PSK_WITH_RC4_128_SHA": "RSA-PSK-RC4-SHA",
    "RSA_PSK_WITH_3DES_EDE_CBC_SHA": "RSA-PSK-3DES-EDE-CBC-SHA",
    "RSA_PSK_WITH_AES_128_CBC_SHA": "RSA-PSK-AES128-CBC-SHA",
    "RSA_PSK_WITH_AES_256_CBC_SHA": "RSA-PSK-AES256-CBC-SHA",
    "PSK_WITH_AES_128_GCM_SHA256": "PSK-AES128-GCM-SHA256",
    "PSK_WITH_AES_256_GCM_SHA384": "PSK-AES256-GCM-SHA384",
    "DHE_PSK_WITH_AES_128_GCM_SHA256": "DHE-PSK-AES128-GCM-SHA256",
    "DHE_PSK_WITH_AES_256_GCM_SHA384": "DHE-PSK-AES256-GCM-SHA384",
    "RSA_PSK_WITH_AES_128_GCM_SHA256": "RSA-PSK-AES128-GCM-SHA256",
    "RSA_PSK_WITH_AES_256_GCM_SHA384": "RSA-PSK-AES256-GCM-SHA384",
    "PSK_WITH_AES_128_CBC_SHA256": "PSK-AES128-CBC-SHA256",
    "PSK_WITH_AES_256_CBC_SHA384": "PSK-AES256-CBC-SHA384",
    "PSK_WITH_NULL_SHA256": "PSK-NULL-SHA256",
    "PSK_WITH_NULL_SHA384": "PSK-NULL-SHA384",
    "DHE_PSK_WITH_AES_128_CBC_SHA256": "DHE-PSK-AES128-CBC-SHA256",
    "DHE_PSK_WITH_AES_256_CBC_SHA384": "DHE-PSK-AES256-CBC-SHA384",
    "DHE_PSK_WITH_NULL_SHA256": "DHE-PSK-NULL-SHA256",
    "DHE_PSK_WITH_NULL_SHA384": "DHE-PSK-NULL-SHA384",
    "RSA_PSK_WITH_AES_128_CBC_SHA256": "RSA-PSK-AES128-CBC-SHA256",
    "RSA_PSK_WITH_AES_256_CBC_SHA384": "RSA-PSK-AES256-CBC-SHA384",
    "RSA_PSK_WITH_NULL_SHA256": "RSA-PSK-NULL-SHA256",
    "RSA_PSK_WITH_NULL_SHA384": "RSA-PSK-NULL-SHA384",
    "ECDHE_PSK_WITH_RC4_128_SHA": "ECDHE-PSK-RC4-SHA",
    "ECDHE_PSK_WITH_3DES_EDE_CBC_SHA": "ECDHE-PSK-3DES-EDE-CBC-SHA",
    "ECDHE_PSK_WITH_AES_128_CBC_SHA": "ECDHE-PSK-AES128-CBC-SHA",
    "ECDHE_PSK_WITH_AES_256_CBC_SHA": "ECDHE-PSK-AES256-CBC-SHA",
    "ECDHE_PSK_WITH_AES_128_CBC_SHA256": "ECDHE-PSK-AES128-CBC-SHA256",
    "ECDHE_PSK_WITH_AES_256_CBC_SHA384": "ECDHE-PSK-AES256-CBC-SHA384",
    "ECDHE_PSK_WITH_NULL_SHA": "ECDHE-PSK-NULL-SHA",
    "ECDHE_PSK_WITH_NULL_SHA256": "ECDHE-PSK-NULL-SHA256",
    "ECDHE_PSK_WITH_NULL_SHA384": "ECDHE-PSK-NULL-SHA384",
    "PSK_WITH_CAMELLIA_128_CBC_SHA256": "PSK-CAMELLIA128-SHA256",
    "PSK_WITH_CAMELLIA_256_CBC_SHA384": "PSK-CAMELLIA256-SHA384",
    "DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256": "DHE-PSK-CAMELLIA128-SHA256",
    "DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384": "DHE-PSK-CAMELLIA256-SHA384",
    "RSA_PSK_WITH_CAMELLIA_128_CBC_SHA256": "RSA-PSK-CAMELLIA128-SHA256",
    "RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384": "RSA-PSK-CAMELLIA256-SHA384",
    "ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256": "ECDHE-PSK-CAMELLIA128-SHA256",
    "ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384": "ECDHE-PSK-CAMELLIA256-SHA384",
    "PSK_WITH_AES_128_CCM": "PSK-AES128-CCM",
    "PSK_WITH_AES_256_CCM": "PSK-AES256-CCM",
    "DHE_PSK_WITH_AES_128_CCM": "DHE-PSK-AES128-CCM",
    "DHE_PSK_WITH_AES_256_CCM": "DHE-PSK-AES256-CCM",
    "PSK_WITH_AES_128_CCM_8": "PSK-AES128-CCM8",
    "PSK_WITH_AES_256_CCM_8": "PSK-AES256-CCM8",
    "DHE_PSK_WITH_AES_128_CCM_8": "DHE-PSK-AES128-CCM8",
    "DHE_PSK_WITH_AES_256_CCM_8": "DHE-PSK-AES256-CCM8",
    # ChaCha20-Poly1305 cipher suites, extending TLS v1.2
    "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256": "ECDHE-RSA-CHACHA20-POLY1305",
    "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256": "ECDHE-ECDSA-CHACHA20-POLY1305",
    "TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256": "DHE-RSA-CHACHA20-POLY1305",
    "TLS_PSK_WITH_CHACHA20_POLY1305_SHA256": "PSK-CHACHA20-POLY1305",
    "TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256": "ECDHE-PSK-CHACHA20-POLY1305",
    "TLS_DHE_PSK_WITH_CHACHA20_POLY1305_SHA256": "DHE-PSK-CHACHA20-POLY1305",
    "TLS_RSA_PSK_WITH_CHACHA20_POLY1305_SHA256": "RSA-PSK-CHACHA20-POLY1305",
}

# TLS v1.3 cipher suites IANI to OpenSSL name translation
TLSV1_3_CIPHER_SUITES: Dict[str, str] = {
    "TLS_AES_128_GCM_SHA256": "TLS_AES_128_GCM_SHA256",
    "TLS_AES_256_GCM_SHA384": "TLS_AES_256_GCM_SHA384",
    "TLS_CHACHA20_POLY1305_SHA256": "TLS_CHACHA20_POLY1305_SHA256",
    "TLS_AES_128_CCM_SHA256": "TLS_AES_128_CCM_SHA256",
    "TLS_AES_128_CCM_8_SHA256": "TLS_AES_128_CCM_8_SHA256",
}

TLS_CIPHER_SUITES: Dict[str, Dict[str, str]] = {
    "TLSv1": TLSV1_CIPHER_SUITES,
    "TLSv1.1": TLSV1_1_CIPHER_SUITES,
    "TLSv1.2": TLSV1_2_CIPHER_SUITES,
    "TLSv1.3": TLSV1_3_CIPHER_SUITES,
}

OPENSSL_CS_NAMES: Dict[str, ValuesView[str]] = {
    "TLSv1": TLSV1_CIPHER_SUITES.values(),
    "TLSv1.1": TLSV1_1_CIPHER_SUITES.values(),
    "TLSv1.2": TLSV1_2_CIPHER_SUITES.values(),
    "TLSv1.3": TLSV1_3_CIPHER_SUITES.values(),
}

virt/lib/python3.9/site-packages/mysql/connector/constants 3.py
PrinceDisant/LostInNL
# Copyright (c) 2009, 2023, Oracle and/or its affiliates. All rights reserved.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

"""Various MySQL constants and character sets."""

import warnings

from abc import ABC, ABCMeta
from typing import Any, Dict, List, Optional, Sequence, Tuple, Union, ValuesView

from .charsets import MYSQL_CHARACTER_SETS, MYSQL_CHARACTER_SETS_57
from .errors import ProgrammingError

NET_BUFFER_LENGTH: int = 8192
MAX_MYSQL_TABLE_COLUMNS: int = 4096
# Flag used to send the Query Attributes with 0 (or more) parameters.
PARAMETER_COUNT_AVAILABLE: int = 8

DEFAULT_CONFIGURATION: Dict[str, Optional[Union[str, bool, int]]] = {
    "database": None,
    "user": "",
    "password": "",
    "password1": "",
    "password2": "",
    "password3": "",
    "host": "127.0.0.1",
    "port": 3306,
    "unix_socket": None,
    "use_unicode": True,
    "charset": "utf8mb4",
    "collation": None,
    "converter_class": None,
    "converter_str_fallback": False,
    "autocommit": False,
    "time_zone": None,
    "sql_mode": None,
    "get_warnings": False,
    "raise_on_warnings": False,
    "connection_timeout": None,
    "client_flags": 0,
    "compress": False,
    "buffered": False,
    "raw": False,
    "ssl_ca": None,
    "ssl_cert": None,
    "ssl_key": None,
    "ssl_verify_cert": False,
    "ssl_verify_identity": False,
    "ssl_cipher": None,
    "tls_ciphersuites": None,
    "ssl_disabled": False,
    "tls_versions": None,
    "passwd": None,
    "db": None,
    "connect_timeout": None,
    "dsn": None,
    "force_ipv6": False,
    "auth_plugin": None,
    "allow_local_infile": False,
    "allow_local_infile_in_path": None,
    "consume_results": False,
    "conn_attrs": None,
    "dns_srv": False,
    "use_pure": False,
    "krb_service_principal": None,
    "oci_config_file": None,
    "oci_config_profile": None,
    "fido_callback": None,
    "kerberos_auth_mode": None,
    "init_command": None,
}

CNX_POOL_ARGS: Tuple[str, str, str] = ("pool_name", "pool_size", "pool_reset_session")

TLS_VERSIONS: List[str] = ["TLSv1.2", "TLSv1.3"]

DEPRECATED_TLS_VERSIONS: List[str] = ["TLSv1", "TLSv1.1"]


def flag_is_set(flag: int, flags: int) -> bool:
    """Checks if the flag is set

    Returns boolean"""
    if (flags & flag) > 0:
        return True
    return False


def _obsolete_option(name: str, new_name: str, value: int) -> int:
    """Raise a deprecation warning and advise a new option name.

    Args:
        name (str): The name of the option.
        new_name (str): The new option name.
        value (int): The value of the option.

    Returns:
        int: The value of the option.
    """
    warnings.warn(
        f"The option '{name}' has been deprecated, use '{new_name}' instead.",
        category=DeprecationWarning,
    )
    return value


class _Constants(ABC):
    """Base class for constants."""

    prefix: str = ""
    desc: Dict[str, Tuple[int, str]] = {}

    @classmethod
    def get_desc(cls, name: str) -> Optional[str]:
        """Get description of given constant"""
        try:
            return cls.desc[name][1]
        except (IndexError, KeyError):
            return None

    @classmethod
    def get_info(cls, setid: int) -> Union[Optional[str], Tuple[str, str]]:
        """Get information about given constant"""
        for name, info in cls.desc.items():
            if info[0] == setid:
                return name
        return None

    @classmethod
    def get_full_info(cls) -> Union[str, Sequence[str]]:
        """get full information about given constant"""
        res: Union[str, List[str]] = []
        try:
            res = [f"{k} : {v[1]}" for k, v in cls.desc.items()]
        except (AttributeError, IndexError) as err:
            res = f"No information found in constant class. {err}"

        return res


class _Flags(_Constants):
    """Base class for classes describing flags"""

    @classmethod
    def get_bit_info(cls, value: int) -> List[str]:
        """Get the name of all bits set

        Returns a list of strings."""
        res = []
        for name, info in cls.desc.items():
            if value & info[0]:
                res.append(name)
        return res


class FieldType(_Constants):
    """MySQL Field Types"""

    prefix: str = "FIELD_TYPE_"
    DECIMAL: int = 0x00
    TINY: int = 0x01
    SHORT: int = 0x02
    LONG: int = 0x03
    FLOAT: int = 0x04
    DOUBLE: int = 0x05
    NULL: int = 0x06
    TIMESTAMP: int = 0x07
    LONGLONG: int = 0x08
    INT24: int = 0x09
    DATE: int = 0x0A
    TIME: int = 0x0B
    DATETIME: int = 0x0C
    YEAR: int = 0x0D
    NEWDATE: int = 0x0E
    VARCHAR: int = 0x0F
    BIT: int = 0x10
    JSON: int = 0xF5
    NEWDECIMAL: int = 0xF6
    ENUM: int = 0xF7
    SET: int = 0xF8
    TINY_BLOB: int = 0xF9
    MEDIUM_BLOB: int = 0xFA
    LONG_BLOB: int = 0xFB
    BLOB: int = 0xFC
    VAR_STRING: int = 0xFD
    STRING: int = 0xFE
    GEOMETRY: int = 0xFF

    desc: Dict[str, Tuple[int, str]] = {
        "DECIMAL": (0x00, "DECIMAL"),
        "TINY": (0x01, "TINY"),
        "SHORT": (0x02, "SHORT"),
        "LONG": (0x03, "LONG"),
        "FLOAT": (0x04, "FLOAT"),
        "DOUBLE": (0x05, "DOUBLE"),
        "NULL": (0x06, "NULL"),
        "TIMESTAMP": (0x07, "TIMESTAMP"),
        "LONGLONG": (0x08, "LONGLONG"),
        "INT24": (0x09, "INT24"),
        "DATE": (0x0A, "DATE"),
        "TIME": (0x0B, "TIME"),
        "DATETIME": (0x0C, "DATETIME"),
        "YEAR": (0x0D, "YEAR"),
        "NEWDATE": (0x0E, "NEWDATE"),
        "VARCHAR": (0x0F, "VARCHAR"),
        "BIT": (0x10, "BIT"),
        "JSON": (0xF5, "JSON"),
        "NEWDECIMAL": (0xF6, "NEWDECIMAL"),
        "ENUM": (0xF7, "ENUM"),
        "SET": (0xF8, "SET"),
        "TINY_BLOB": (0xF9, "TINY_BLOB"),
        "MEDIUM_BLOB": (0xFA, "MEDIUM_BLOB"),
        "LONG_BLOB": (0xFB, "LONG_BLOB"),
        "BLOB": (0xFC, "BLOB"),
        "VAR_STRING": (0xFD, "VAR_STRING"),
        "STRING": (0xFE, "STRING"),
        "GEOMETRY": (0xFF, "GEOMETRY"),
    }

    @classmethod
    def get_string_types(cls) -> List[int]:
        """Get the list of all string types"""
        return [
            cls.VARCHAR,
            cls.ENUM,
            cls.VAR_STRING,
            cls.STRING,
        ]

    @classmethod
    def get_binary_types(cls) -> List[int]:
        """Get the list of all binary types"""
        return [
            cls.TINY_BLOB,
            cls.MEDIUM_BLOB,
            cls.LONG_BLOB,
            cls.BLOB,
        ]

    @classmethod
    def get_number_types(cls) -> List[int]:
        """Get the list of all number types"""
        return [
            cls.DECIMAL,
            cls.NEWDECIMAL,
            cls.TINY,
            cls.SHORT,
            cls.LONG,
            cls.FLOAT,
            cls.DOUBLE,
            cls.LONGLONG,
            cls.INT24,
            cls.BIT,
            cls.YEAR,
        ]

    @classmethod
    def get_timestamp_types(cls) -> List[int]:
        """Get the list of all timestamp types"""
        return [
            cls.DATETIME,
            cls.TIMESTAMP,
        ]


class FieldFlag(_Flags):
    """MySQL Field Flags

    Field flags as found in MySQL sources mysql-src/include/mysql_com.h
    """

    _prefix: str = ""
    NOT_NULL: int = 1 << 0
    PRI_KEY: int = 1 << 1
    UNIQUE_KEY: int = 1 << 2
    MULTIPLE_KEY: int = 1 << 3
    BLOB: int = 1 << 4
    UNSIGNED: int = 1 << 5
    ZEROFILL: int = 1 << 6
    BINARY: int = 1 << 7

    ENUM: int = 1 << 8
    AUTO_INCREMENT: int = 1 << 9
    TIMESTAMP: int = 1 << 10
    SET: int = 1 << 11

    NO_DEFAULT_VALUE: int = 1 << 12
    ON_UPDATE_NOW: int = 1 << 13
    NUM: int = 1 << 14
    PART_KEY: int = 1 << 15
    GROUP: int = 1 << 14  # SAME AS NUM !!!!!!!????
    UNIQUE: int = 1 << 16
    BINCMP: int = 1 << 17

    GET_FIXED_FIELDS: int = 1 << 18
    FIELD_IN_PART_FUNC: int = 1 << 19
    FIELD_IN_ADD_INDEX: int = 1 << 20
    FIELD_IS_RENAMED: int = 1 << 21

    desc: Dict[str, Tuple[int, str]] = {
        "NOT_NULL": (1 << 0, "Field can't be NULL"),
        "PRI_KEY": (1 << 1, "Field is part of a primary key"),
        "UNIQUE_KEY": (1 << 2, "Field is part of a unique key"),
        "MULTIPLE_KEY": (1 << 3, "Field is part of a key"),
        "BLOB": (1 << 4, "Field is a blob"),
        "UNSIGNED": (1 << 5, "Field is unsigned"),
        "ZEROFILL": (1 << 6, "Field is zerofill"),
        "BINARY": (1 << 7, "Field is binary  "),
        "ENUM": (1 << 8, "field is an enum"),
        "AUTO_INCREMENT": (1 << 9, "field is a autoincrement field"),
        "TIMESTAMP": (1 << 10, "Field is a timestamp"),
        "SET": (1 << 11, "field is a set"),
        "NO_DEFAULT_VALUE": (1 << 12, "Field doesn't have default value"),
        "ON_UPDATE_NOW": (1 << 13, "Field is set to NOW on UPDATE"),
        "NUM": (1 << 14, "Field is num (for clients)"),
        "PART_KEY": (1 << 15, "Intern; Part of some key"),
        "GROUP": (1 << 14, "Intern: Group field"),  # Same as NUM
        "UNIQUE": (1 << 16, "Intern: Used by sql_yacc"),
        "BINCMP": (1 << 17, "Intern: Used by sql_yacc"),
        "GET_FIXED_FIELDS": (1 << 18, "Used to get fields in item tree"),
        "FIELD_IN_PART_FUNC": (1 << 19, "Field part of partition func"),
        "FIELD_IN_ADD_INDEX": (1 << 20, "Intern: Field used in ADD INDEX"),
        "FIELD_IS_RENAMED": (1 << 21, "Intern: Field is being renamed"),
    }


class ServerCmdMeta(ABCMeta):
    """ClientFlag Metaclass."""

    def __getattribute__(cls, name: str) -> Any:
        deprecated_options = (
            "FIELD_LIST",
            "REFRESH",
            "SHUTDOWN",
            "PROCESS_INFO",
            "PROCESS_KILL",
        )
        if name in deprecated_options:
            warnings.warn(
                f"The option 'ServerCmd.{name}' is deprecated and will be removed in "
                "a future release.",
                category=DeprecationWarning,
            )
        return super().__getattribute__(name)


class ServerCmd(_Constants, metaclass=ServerCmdMeta):
    """MySQL Server Commands"""

    _prefix: str = "COM_"
    SLEEP: int = 0
    QUIT: int = 1
    INIT_DB: int = 2
    QUERY: int = 3
    FIELD_LIST: int = 4
    CREATE_DB: int = 5
    DROP_DB: int = 6
    REFRESH: int = 7
    SHUTDOWN: int = 8
    STATISTICS: int = 9
    PROCESS_INFO: int = 10
    CONNECT: int = 11
    PROCESS_KILL: int = 12
    DEBUG: int = 13
    PING: int = 14
    TIME: int = 15
    DELAYED_INSERT: int = 16
    CHANGE_USER: int = 17
    BINLOG_DUMP: int = 18
    TABLE_DUMP: int = 19
    CONNECT_OUT: int = 20
    REGISTER_REPLICA: int = 21
    STMT_PREPARE: int = 22
    STMT_EXECUTE: int = 23
    STMT_SEND_LONG_DATA: int = 24
    STMT_CLOSE: int = 25
    STMT_RESET: int = 26
    SET_OPTION: int = 27
    STMT_FETCH: int = 28
    DAEMON: int = 29
    BINLOG_DUMP_GTID: int = 30
    RESET_CONNECTION: int = 31

    desc: Dict[str, Tuple[int, str]] = {
        "SLEEP": (0, "SLEEP"),
        "QUIT": (1, "QUIT"),
        "INIT_DB": (2, "INIT_DB"),
        "QUERY": (3, "QUERY"),
        "FIELD_LIST": (4, "FIELD_LIST"),
        "CREATE_DB": (5, "CREATE_DB"),
        "DROP_DB": (6, "DROP_DB"),
        "REFRESH": (7, "REFRESH"),
        "SHUTDOWN": (8, "SHUTDOWN"),
        "STATISTICS": (9, "STATISTICS"),
        "PROCESS_INFO": (10, "PROCESS_INFO"),
        "CONNECT": (11, "CONNECT"),
        "PROCESS_KILL": (12, "PROCESS_KILL"),
        "DEBUG": (13, "DEBUG"),
        "PING": (14, "PING"),
        "TIME": (15, "TIME"),
        "DELAYED_INSERT": (16, "DELAYED_INSERT"),
        "CHANGE_USER": (17, "CHANGE_USER"),
        "BINLOG_DUMP": (18, "BINLOG_DUMP"),
        "TABLE_DUMP": (19, "TABLE_DUMP"),
        "CONNECT_OUT": (20, "CONNECT_OUT"),
        "REGISTER_REPLICA": (21, "REGISTER_REPLICA"),
        "STMT_PREPARE": (22, "STMT_PREPARE"),
        "STMT_EXECUTE": (23, "STMT_EXECUTE"),
        "STMT_SEND_LONG_DATA": (24, "STMT_SEND_LONG_DATA"),
        "STMT_CLOSE": (25, "STMT_CLOSE"),
        "STMT_RESET": (26, "STMT_RESET"),
        "SET_OPTION": (27, "SET_OPTION"),
        "STMT_FETCH": (28, "STMT_FETCH"),
        "DAEMON": (29, "DAEMON"),
        "BINLOG_DUMP_GTID": (30, "BINLOG_DUMP_GTID"),
        "RESET_CONNECTION": (31, "RESET_CONNECTION"),
    }


class ClientFlag(_Flags):
    """MySQL Client Flags

    Client options as found in the MySQL sources mysql-src/include/mysql_com.h
    """

    LONG_PASSWD: int = 1 << 0
    FOUND_ROWS: int = 1 << 1
    LONG_FLAG: int = 1 << 2
    CONNECT_WITH_DB: int = 1 << 3
    NO_SCHEMA: int = 1 << 4
    COMPRESS: int = 1 << 5
    ODBC: int = 1 << 6
    LOCAL_FILES: int = 1 << 7
    IGNORE_SPACE: int = 1 << 8
    PROTOCOL_41: int = 1 << 9
    INTERACTIVE: int = 1 << 10
    SSL: int = 1 << 11
    IGNORE_SIGPIPE: int = 1 << 12
    TRANSACTIONS: int = 1 << 13
    RESERVED: int = 1 << 14
    SECURE_CONNECTION: int = 1 << 15
    MULTI_STATEMENTS: int = 1 << 16
    MULTI_RESULTS: int = 1 << 17
    PS_MULTI_RESULTS: int = 1 << 18
    PLUGIN_AUTH: int = 1 << 19
    CONNECT_ARGS: int = 1 << 20
    PLUGIN_AUTH_LENENC_CLIENT_DATA: int = 1 << 21
    CAN_HANDLE_EXPIRED_PASSWORDS: int = 1 << 22
    SESION_TRACK: int = 1 << 23  # deprecated
    SESSION_TRACK: int = 1 << 23
    DEPRECATE_EOF: int = 1 << 24
    CLIENT_QUERY_ATTRIBUTES: int = 1 << 27
    SSL_VERIFY_SERVER_CERT: int = 1 << 30
    REMEMBER_OPTIONS: int = 1 << 31
    MULTI_FACTOR_AUTHENTICATION: int = 1 << 28

    desc: Dict[str, Tuple[int, str]] = {
        "LONG_PASSWD": (1 << 0, "New more secure passwords"),
        "FOUND_ROWS": (1 << 1, "Found instead of affected rows"),
        "LONG_FLAG": (1 << 2, "Get all column flags"),
        "CONNECT_WITH_DB": (1 << 3, "One can specify db on connect"),
        "NO_SCHEMA": (1 << 4, "Don't allow database.table.column"),
        "COMPRESS": (1 << 5, "Can use compression protocol"),
        "ODBC": (1 << 6, "ODBC client"),
        "LOCAL_FILES": (1 << 7, "Can use LOAD DATA LOCAL"),
        "IGNORE_SPACE": (1 << 8, "Ignore spaces before ''"),
        "PROTOCOL_41": (1 << 9, "New 4.1 protocol"),
        "INTERACTIVE": (1 << 10, "This is an interactive client"),
        "SSL": (1 << 11, "Switch to SSL after handshake"),
        "IGNORE_SIGPIPE": (1 << 12, "IGNORE sigpipes"),
        "TRANSACTIONS": (1 << 13, "Client knows about transactions"),
        "RESERVED": (1 << 14, "Old flag for 4.1 protocol"),
        "SECURE_CONNECTION": (1 << 15, "New 4.1 authentication"),
        "MULTI_STATEMENTS": (1 << 16, "Enable/disable multi-stmt support"),
        "MULTI_RESULTS": (1 << 17, "Enable/disable multi-results"),
        "PS_MULTI_RESULTS": (1 << 18, "Multi-results in PS-protocol"),
        "PLUGIN_AUTH": (1 << 19, "Client supports plugin authentication"),
        "CONNECT_ARGS": (1 << 20, "Client supports connection attributes"),
        "PLUGIN_AUTH_LENENC_CLIENT_DATA": (
            1 << 21,
            "Enable authentication response packet to be larger than 255 bytes",
        ),
        "CAN_HANDLE_EXPIRED_PASSWORDS": (
            1 << 22,
            "Don't close the connection for a connection with expired password",
        ),
        "SESION_TRACK": (  # deprecated
            1 << 23,
            "Capable of handling server state change information",
        ),
        "SESSION_TRACK": (
            1 << 23,
            "Capable of handling server state change information",
        ),
        "DEPRECATE_EOF": (1 << 24, "Client no longer needs EOF packet"),
        "CLIENT_QUERY_ATTRIBUTES": (
            1 << 27,
            "Support optional extension for query parameters",
        ),
        "SSL_VERIFY_SERVER_CERT": (1 << 30, ""),
        "REMEMBER_OPTIONS": (1 << 31, ""),
    }

    default: List[int] = [
        LONG_PASSWD,
        LONG_FLAG,
        CONNECT_WITH_DB,
        PROTOCOL_41,
        TRANSACTIONS,
        SECURE_CONNECTION,
        MULTI_STATEMENTS,
        MULTI_RESULTS,
        CONNECT_ARGS,
    ]

    @classmethod
    def get_default(cls) -> int:
        """Get the default client options set

        Returns a flag with all the default client options set"""
        flags = 0
        for option in cls.default:
            flags |= option
        return flags


class ServerFlag(_Flags):
    """MySQL Server Flags

    Server flags as found in the MySQL sources mysql-src/include/mysql_com.h
    """

    _prefix: str = "SERVER_"
    STATUS_IN_TRANS: int = 1 << 0
    STATUS_AUTOCOMMIT: int = 1 << 1
    MORE_RESULTS_EXISTS: int = 1 << 3
    QUERY_NO_GOOD_INDEX_USED: int = 1 << 4
    QUERY_NO_INDEX_USED: int = 1 << 5
    STATUS_CURSOR_EXISTS: int = 1 << 6
    STATUS_LAST_ROW_SENT: int = 1 << 7
    STATUS_DB_DROPPED: int = 1 << 8
    STATUS_NO_BACKSLASH_ESCAPES: int = 1 << 9
    SERVER_STATUS_METADATA_CHANGED: int = 1 << 10
    SERVER_QUERY_WAS_SLOW: int = 1 << 11
    SERVER_PS_OUT_PARAMS: int = 1 << 12
    SERVER_STATUS_IN_TRANS_READONLY: int = 1 << 13
    SERVER_SESSION_STATE_CHANGED: int = 1 << 14

    desc: Dict[str, Tuple[int, str]] = {
        "SERVER_STATUS_IN_TRANS": (1 << 0, "Transaction has started"),
        "SERVER_STATUS_AUTOCOMMIT": (1 << 1, "Server in auto_commit mode"),
        "SERVER_MORE_RESULTS_EXISTS": (
            1 << 3,
            "Multi query - next query exists",
        ),
        "SERVER_QUERY_NO_GOOD_INDEX_USED": (1 << 4, ""),
        "SERVER_QUERY_NO_INDEX_USED": (1 << 5, ""),
        "SERVER_STATUS_CURSOR_EXISTS": (
            1 << 6,
            "Set when server opened a read-only non-scrollable cursor for a query.",
        ),
        "SERVER_STATUS_LAST_ROW_SENT": (
            1 << 7,
            "Set when a read-only cursor is exhausted",
        ),
        "SERVER_STATUS_DB_DROPPED": (1 << 8, "A database was dropped"),
        "SERVER_STATUS_NO_BACKSLASH_ESCAPES": (1 << 9, ""),
        "SERVER_STATUS_METADATA_CHANGED": (
            1024,
            "Set if after a prepared statement "
            "reprepare we discovered that the "
            "new statement returns a different "
            "number of result set columns.",
        ),
        "SERVER_QUERY_WAS_SLOW": (2048, ""),
        "SERVER_PS_OUT_PARAMS": (
            4096,
            "To mark ResultSet containing output parameter values.",
        ),
        "SERVER_STATUS_IN_TRANS_READONLY": (
            8192,
            "Set if multi-statement transaction is a read-only transaction.",
        ),
        "SERVER_SESSION_STATE_CHANGED": (
            1 << 14,
            "Session state has changed on the "
            "server because of the execution of "
            "the last statement",
        ),
    }


class RefreshOptionMeta(ABCMeta):
    """RefreshOption Metaclass."""

    @property
    def SLAVE(self) -> int:  # pylint: disable=bad-mcs-method-argument,invalid-name
        """Return the deprecated alias of RefreshOption.REPLICA.

        Raises a warning about this attribute deprecation.
        """
        return _obsolete_option(
            "RefreshOption.SLAVE",
            "RefreshOption.REPLICA",
            RefreshOption.REPLICA,
        )


class RefreshOption(_Constants, metaclass=RefreshOptionMeta):
    """MySQL Refresh command options.

    Options used when sending the COM_REFRESH server command.
    """

    _prefix: str = "REFRESH_"
    GRANT: int = 1 << 0
    LOG: int = 1 << 1
    TABLES: int = 1 << 2
    HOST: int = 1 << 3
    STATUS: int = 1 << 4
    THREADS: int = 1 << 5
    REPLICA: int = 1 << 6

    desc: Dict[str, Tuple[int, str]] = {
        "GRANT": (1 << 0, "Refresh grant tables"),
        "LOG": (1 << 1, "Start on new log file"),
        "TABLES": (1 << 2, "close all tables"),
        "HOST": (1 << 3, "Flush host cache"),
        "STATUS": (1 << 4, "Flush status variables"),
        "THREADS": (1 << 5, "Flush thread cache"),
        "REPLICA": (1 << 6, "Reset source info and restart replica thread"),
        "SLAVE": (1 << 6, "Deprecated option; use REPLICA instead."),
    }


class ShutdownType(_Constants):
    """MySQL Shutdown types

    Shutdown types used by the COM_SHUTDOWN server command.
    """

    _prefix: str = ""
    SHUTDOWN_DEFAULT: int = 0
    SHUTDOWN_WAIT_CONNECTIONS: int = 1
    SHUTDOWN_WAIT_TRANSACTIONS: int = 2
    SHUTDOWN_WAIT_UPDATES: int = 8
    SHUTDOWN_WAIT_ALL_BUFFERS: int = 16
    SHUTDOWN_WAIT_CRITICAL_BUFFERS: int = 17
    KILL_QUERY: int = 254
    KILL_CONNECTION: int = 255

    desc: Dict[str, Tuple[int, str]] = {
        "SHUTDOWN_DEFAULT": (
            SHUTDOWN_DEFAULT,
            "defaults to SHUTDOWN_WAIT_ALL_BUFFERS",
        ),
        "SHUTDOWN_WAIT_CONNECTIONS": (
            SHUTDOWN_WAIT_CONNECTIONS,
            "wait for existing connections to finish",
        ),
        "SHUTDOWN_WAIT_TRANSACTIONS": (
            SHUTDOWN_WAIT_TRANSACTIONS,
            "wait for existing trans to finish",
        ),
        "SHUTDOWN_WAIT_UPDATES": (
            SHUTDOWN_WAIT_UPDATES,
            "wait for existing updates to finish",
        ),
        "SHUTDOWN_WAIT_ALL_BUFFERS": (
            SHUTDOWN_WAIT_ALL_BUFFERS,
            "flush InnoDB and other storage engine buffers",
        ),
        "SHUTDOWN_WAIT_CRITICAL_BUFFERS": (
            SHUTDOWN_WAIT_CRITICAL_BUFFERS,
            "don't flush InnoDB buffers, flush other storage engines' buffers",
        ),
        "KILL_QUERY": (KILL_QUERY, "(no description)"),
        "KILL_CONNECTION": (KILL_CONNECTION, "(no description)"),
    }


class CharacterSet(_Constants):
    """MySQL supported character sets and collations

    List of character sets with their collations supported by MySQL. This
    maps to the character set we get from the server within the handshake
    packet.

    The list is hardcode so we avoid a database query when getting the
    name of the used character set or collation.
    """

    # Use LTS character set as default
    desc: List[
        Optional[Tuple[str, str, bool]]
    ] = MYSQL_CHARACTER_SETS_57  # type: ignore[assignment]
    mysql_version: Tuple[int, ...] = (5, 7)

    # Multi-byte character sets which use 5c (backslash) in characters
    slash_charsets: Tuple[int, ...] = (1, 13, 28, 84, 87, 88)

    @classmethod
    def set_mysql_version(cls, version: Tuple[int, ...]) -> None:
        """Set the MySQL major version and change the charset mapping if is 5.7.

        Args:
            version (tuple): MySQL version tuple.
        """
        cls.mysql_version = version[:2]
        if cls.mysql_version >= (8, 0):
            cls.desc = MYSQL_CHARACTER_SETS

    @classmethod
    def get_info(cls, setid: int) -> Tuple[str, str]:
        """Retrieves character set information as tuple using an ID

        Retrieves character set and collation information based on the
        given MySQL ID.

        Raises ProgrammingError when character set is not supported.

        Returns a tuple.
        """
        try:
            return cls.desc[setid][0:2]
        except IndexError:
            raise ProgrammingError(f"Character set '{setid}' unsupported") from None

    @classmethod
    def get_desc(cls, name: int) -> str:  # type: ignore[override]
        """Retrieves character set information as string using an ID

        Retrieves character set and collation information based on the
        given MySQL ID.

        Returns a tuple.
        """
        charset, collation = cls.get_info(name)
        return f"{charset}/{collation}"

    @classmethod
    def get_default_collation(cls, charset: Union[int, str]) -> Tuple[str, str, int]:
        """Retrieves the default collation for given character set

        Raises ProgrammingError when character set is not supported.

        Returns list (collation, charset, index)
        """
        if isinstance(charset, int):
            try:
                info = cls.desc[charset]
                return info[1], info[0], charset
            except (IndexError, KeyError) as err:
                raise ProgrammingError(
                    f"Character set ID '{charset}' unsupported"
                ) from err

        for cid, info in enumerate(cls.desc):
            if info is None:
                continue
            if info[0] == charset and info[2] is True:
                return info[1], info[0], cid

        raise ProgrammingError(f"Character set '{charset}' unsupported")

    @classmethod
    def get_charset_info(
        cls, charset: Optional[Union[int, str]] = None, collation: Optional[str] = None
    ) -> Tuple[int, str, str]:
        """Get character set information using charset name and/or collation

        Retrieves character set and collation information given character
        set name and/or a collation name.
        If charset is an integer, it will look up the character set based
        on the MySQL's ID.
        For example:
            get_charset_info('utf8',None)
            get_charset_info(collation='utf8_general_ci')
            get_charset_info(47)

        Raises ProgrammingError when character set is not supported.

        Returns a tuple with (id, characterset name, collation)
        """
        info: Optional[Union[Tuple[str, str, bool], Tuple[str, str, int]]] = None
        if isinstance(charset, int):
            try:
                info = cls.desc[charset]
                return (charset, info[0], info[1])
            except IndexError as err:
                raise ProgrammingError(f"Character set ID {charset} unknown") from err

        if charset in ("utf8", "utf-8") and cls.mysql_version >= (8, 0):
            charset = "utf8mb4"
        if charset is not None and collation is None:
            info = cls.get_default_collation(charset)
            return (info[2], info[1], info[0])
        if charset is None and collation is not None:
            for cid, info in enumerate(cls.desc):
                if info is None:
                    continue
                if collation == info[1]:
                    return (cid, info[0], info[1])
            raise ProgrammingError(f"Collation '{collation}' unknown")
        for cid, info in enumerate(cls.desc):
            if info is None:
                continue
            if info[0] == charset and info[1] == collation:
                return (cid, info[0], info[1])
        _ = cls.get_default_collation(charset)
        raise ProgrammingError(f"Collation '{collation}' unknown")

    @classmethod
    def get_supported(cls) -> Tuple[str, ...]:
        """Retrieves a list with names of all supproted character sets

        Returns a tuple.
        """
        res = []
        for info in cls.desc:
            if info and info[0] not in res:
                res.append(info[0])
        return tuple(res)


class SQLMode(_Constants):
    """MySQL SQL Modes

    The numeric values of SQL Modes are not interesting, only the names
    are used when setting the SQL_MODE system variable using the MySQL
    SET command.

    See http://dev.mysql.com/doc/refman/5.6/en/server-sql-mode.html
    """

    _prefix: str = "MODE_"
    REAL_AS_FLOAT: str = "REAL_AS_FLOAT"
    PIPES_AS_CONCAT: str = "PIPES_AS_CONCAT"
    ANSI_QUOTES: str = "ANSI_QUOTES"
    IGNORE_SPACE: str = "IGNORE_SPACE"
    NOT_USED: str = "NOT_USED"
    ONLY_FULL_GROUP_BY: str = "ONLY_FULL_GROUP_BY"
    NO_UNSIGNED_SUBTRACTION: str = "NO_UNSIGNED_SUBTRACTION"
    NO_DIR_IN_CREATE: str = "NO_DIR_IN_CREATE"
    POSTGRESQL: str = "POSTGRESQL"
    ORACLE: str = "ORACLE"
    MSSQL: str = "MSSQL"
    DB2: str = "DB2"
    MAXDB: str = "MAXDB"
    NO_KEY_OPTIONS: str = "NO_KEY_OPTIONS"
    NO_TABLE_OPTIONS: str = "NO_TABLE_OPTIONS"
    NO_FIELD_OPTIONS: str = "NO_FIELD_OPTIONS"
    MYSQL323: str = "MYSQL323"
    MYSQL40: str = "MYSQL40"
    ANSI: str = "ANSI"
    NO_AUTO_VALUE_ON_ZERO: str = "NO_AUTO_VALUE_ON_ZERO"
    NO_BACKSLASH_ESCAPES: str = "NO_BACKSLASH_ESCAPES"
    STRICT_TRANS_TABLES: str = "STRICT_TRANS_TABLES"
    STRICT_ALL_TABLES: str = "STRICT_ALL_TABLES"
    NO_ZERO_IN_DATE: str = "NO_ZERO_IN_DATE"
    NO_ZERO_DATE: str = "NO_ZERO_DATE"
    INVALID_DATES: str = "INVALID_DATES"
    ERROR_FOR_DIVISION_BY_ZERO: str = "ERROR_FOR_DIVISION_BY_ZERO"
    TRADITIONAL: str = "TRADITIONAL"
    NO_AUTO_CREATE_USER: str = "NO_AUTO_CREATE_USER"
    HIGH_NOT_PRECEDENCE: str = "HIGH_NOT_PRECEDENCE"
    NO_ENGINE_SUBSTITUTION: str = "NO_ENGINE_SUBSTITUTION"
    PAD_CHAR_TO_FULL_LENGTH: str = "PAD_CHAR_TO_FULL_LENGTH"

    @classmethod
    def get_desc(cls, name: str) -> Optional[str]:
        raise NotImplementedError

    @classmethod
    def get_info(cls, setid: int) -> Optional[str]:
        raise NotImplementedError

    @classmethod
    def get_full_info(cls) -> Tuple[str, ...]:
        """Returns a sequence of all available SQL Modes

        This class method returns a tuple containing all SQL Mode names. The
        names will be alphabetically sorted.

        Returns a tuple.
        """
        res = []
        for key in vars(cls).keys():
            if not key.startswith("_") and not hasattr(getattr(cls, key), "__call__"):
                res.append(key)
        return tuple(sorted(res))


CONN_ATTRS_DN: List[str] = [
    "_pid",
    "_platform",
    "_source_host",
    "_client_name",
    "_client_license",
    "_client_version",
    "_os",
    "_connector_name",
    "_connector_license",
    "_connector_version",
]

# TLS v1.0 cipher suites IANI to OpenSSL name translation
TLSV1_CIPHER_SUITES: Dict[str, str] = {
    "TLS_RSA_WITH_NULL_MD5": "NULL-MD5",
    "TLS_RSA_WITH_NULL_SHA": "NULL-SHA",
    "TLS_RSA_WITH_RC4_128_MD5": "RC4-MD5",
    "TLS_RSA_WITH_RC4_128_SHA": "RC4-SHA",
    "TLS_RSA_WITH_IDEA_CBC_SHA": "IDEA-CBC-SHA",
    "TLS_RSA_WITH_3DES_EDE_CBC_SHA": "DES-CBC3-SHA",
    "TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA": "Not implemented.",
    "TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA": "Not implemented.",
    "TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA": "DHE-DSS-DES-CBC3-SHA",
    "TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA": "DHE-RSA-DES-CBC3-SHA",
    "TLS_DH_anon_WITH_RC4_128_MD5": "ADH-RC4-MD5",
    "TLS_DH_anon_WITH_3DES_EDE_CBC_SHA": "ADH-DES-CBC3-SHA",
    # AES cipher suites from RFC3268, extending TLS v1.0
    "TLS_RSA_WITH_AES_128_CBC_SHA": "AES128-SHA",
    "TLS_RSA_WITH_AES_256_CBC_SHA": "AES256-SHA",
    "TLS_DH_DSS_WITH_AES_128_CBC_SHA": "DH-DSS-AES128-SHA",
    "TLS_DH_DSS_WITH_AES_256_CBC_SHA": "DH-DSS-AES256-SHA",
    "TLS_DH_RSA_WITH_AES_128_CBC_SHA": "DH-RSA-AES128-SHA",
    "TLS_DH_RSA_WITH_AES_256_CBC_SHA": "DH-RSA-AES256-SHA",
    "TLS_DHE_DSS_WITH_AES_128_CBC_SHA": "DHE-DSS-AES128-SHA",
    "TLS_DHE_DSS_WITH_AES_256_CBC_SHA": "DHE-DSS-AES256-SHA",
    "TLS_DHE_RSA_WITH_AES_128_CBC_SHA": "DHE-RSA-AES128-SHA",
    "TLS_DHE_RSA_WITH_AES_256_CBC_SHA": "DHE-RSA-AES256-SHA",
    "TLS_DH_anon_WITH_AES_128_CBC_SHA": "ADH-AES128-SHA",
    "TLS_DH_anon_WITH_AES_256_CBC_SHA": "ADH-AES256-SHA",
    # Camellia cipher suites from RFC4132, extending TLS v1.0
    "TLS_RSA_WITH_CAMELLIA_128_CBC_SHA": "CAMELLIA128-SHA",
    "TLS_RSA_WITH_CAMELLIA_256_CBC_SHA": "CAMELLIA256-SHA",
    "TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA": "DH-DSS-CAMELLIA128-SHA",
    "TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA": "DH-DSS-CAMELLIA256-SHA",
    "TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA": "DH-RSA-CAMELLIA128-SHA",
    "TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA": "DH-RSA-CAMELLIA256-SHA",
    "TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA": "DHE-DSS-CAMELLIA128-SHA",
    "TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA": "DHE-DSS-CAMELLIA256-SHA",
    "TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA": "DHE-RSA-CAMELLIA128-SHA",
    "TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA": "DHE-RSA-CAMELLIA256-SHA",
    "TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA": "ADH-CAMELLIA128-SHA",
    "TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA": "ADH-CAMELLIA256-SHA",
    # SEED cipher suites from RFC4162, extending TLS v1.0
    "TLS_RSA_WITH_SEED_CBC_SHA": "SEED-SHA",
    "TLS_DH_DSS_WITH_SEED_CBC_SHA": "DH-DSS-SEED-SHA",
    "TLS_DH_RSA_WITH_SEED_CBC_SHA": "DH-RSA-SEED-SHA",
    "TLS_DHE_DSS_WITH_SEED_CBC_SHA": "DHE-DSS-SEED-SHA",
    "TLS_DHE_RSA_WITH_SEED_CBC_SHA": "DHE-RSA-SEED-SHA",
    "TLS_DH_anon_WITH_SEED_CBC_SHA": "ADH-SEED-SHA",
    # GOST cipher suites from draft-chudov-cryptopro-cptls, extending TLS v1.0
    "TLS_GOSTR341094_WITH_28147_CNT_IMIT": "GOST94-GOST89-GOST89",
    "TLS_GOSTR341001_WITH_28147_CNT_IMIT": "GOST2001-GOST89-GOST89",
    "TLS_GOSTR341094_WITH_NULL_GOSTR3411": "GOST94-NULL-GOST94",
    "TLS_GOSTR341001_WITH_NULL_GOSTR3411": "GOST2001-NULL-GOST94",
}

# TLS v1.1 cipher suites IANI to OpenSSL name translation
TLSV1_1_CIPHER_SUITES: Dict[str, str] = TLSV1_CIPHER_SUITES

# TLS v1.2 cipher suites IANI to OpenSSL name translation
TLSV1_2_CIPHER_SUITES: Dict[str, str] = {
    "TLS_RSA_WITH_NULL_SHA256": "NULL-SHA256",
    "TLS_RSA_WITH_AES_128_CBC_SHA256": "AES128-SHA256",
    "TLS_RSA_WITH_AES_256_CBC_SHA256": "AES256-SHA256",
    "TLS_RSA_WITH_AES_128_GCM_SHA256": "AES128-GCM-SHA256",
    "TLS_RSA_WITH_AES_256_GCM_SHA384": "AES256-GCM-SHA384",
    "TLS_DH_RSA_WITH_AES_128_CBC_SHA256": "DH-RSA-AES128-SHA256",
    "TLS_DH_RSA_WITH_AES_256_CBC_SHA256": "DH-RSA-AES256-SHA256",
    "TLS_DH_RSA_WITH_AES_128_GCM_SHA256": "DH-RSA-AES128-GCM-SHA256",
    "TLS_DH_RSA_WITH_AES_256_GCM_SHA384": "DH-RSA-AES256-GCM-SHA384",
    "TLS_DH_DSS_WITH_AES_128_CBC_SHA256": "DH-DSS-AES128-SHA256",
    "TLS_DH_DSS_WITH_AES_256_CBC_SHA256": "DH-DSS-AES256-SHA256",
    "TLS_DH_DSS_WITH_AES_128_GCM_SHA256": "DH-DSS-AES128-GCM-SHA256",
    "TLS_DH_DSS_WITH_AES_256_GCM_SHA384": "DH-DSS-AES256-GCM-SHA384",
    "TLS_DHE_RSA_WITH_AES_128_CBC_SHA256": "DHE-RSA-AES128-SHA256",
    "TLS_DHE_RSA_WITH_AES_256_CBC_SHA256": "DHE-RSA-AES256-SHA256",
    "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256": "DHE-RSA-AES128-GCM-SHA256",
    "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384": "DHE-RSA-AES256-GCM-SHA384",
    "TLS_DHE_DSS_WITH_AES_128_CBC_SHA256": "DHE-DSS-AES128-SHA256",
    "TLS_DHE_DSS_WITH_AES_256_CBC_SHA256": "DHE-DSS-AES256-SHA256",
    "TLS_DHE_DSS_WITH_AES_128_GCM_SHA256": "DHE-DSS-AES128-GCM-SHA256",
    "TLS_DHE_DSS_WITH_AES_256_GCM_SHA384": "DHE-DSS-AES256-GCM-SHA384",
    "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256": "ECDHE-RSA-AES128-SHA256",
    "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384": "ECDHE-RSA-AES256-SHA384",
    "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256": "ECDHE-RSA-AES128-GCM-SHA256",
    "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384": "ECDHE-RSA-AES256-GCM-SHA384",
    "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256": "ECDHE-ECDSA-AES128-SHA256",
    "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384": "ECDHE-ECDSA-AES256-SHA384",
    "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256": "ECDHE-ECDSA-AES128-GCM-SHA256",
    "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384": "ECDHE-ECDSA-AES256-GCM-SHA384",
    "TLS_DH_anon_WITH_AES_128_CBC_SHA256": "ADH-AES128-SHA256",
    "TLS_DH_anon_WITH_AES_256_CBC_SHA256": "ADH-AES256-SHA256",
    "TLS_DH_anon_WITH_AES_128_GCM_SHA256": "ADH-AES128-GCM-SHA256",
    "TLS_DH_anon_WITH_AES_256_GCM_SHA384": "ADH-AES256-GCM-SHA384",
    "RSA_WITH_AES_128_CCM": "AES128-CCM",
    "RSA_WITH_AES_256_CCM": "AES256-CCM",
    "DHE_RSA_WITH_AES_128_CCM": "DHE-RSA-AES128-CCM",
    "DHE_RSA_WITH_AES_256_CCM": "DHE-RSA-AES256-CCM",
    "RSA_WITH_AES_128_CCM_8": "AES128-CCM8",
    "RSA_WITH_AES_256_CCM_8": "AES256-CCM8",
    "DHE_RSA_WITH_AES_128_CCM_8": "DHE-RSA-AES128-CCM8",
    "DHE_RSA_WITH_AES_256_CCM_8": "DHE-RSA-AES256-CCM8",
    "ECDHE_ECDSA_WITH_AES_128_CCM": "ECDHE-ECDSA-AES128-CCM",
    "ECDHE_ECDSA_WITH_AES_256_CCM": "ECDHE-ECDSA-AES256-CCM",
    "ECDHE_ECDSA_WITH_AES_128_CCM_8": "ECDHE-ECDSA-AES128-CCM8",
    "ECDHE_ECDSA_WITH_AES_256_CCM_8": "ECDHE-ECDSA-AES256-CCM8",
    # ARIA cipher suites from RFC6209, extending TLS v1.2
    "TLS_RSA_WITH_ARIA_128_GCM_SHA256": "ARIA128-GCM-SHA256",
    "TLS_RSA_WITH_ARIA_256_GCM_SHA384": "ARIA256-GCM-SHA384",
    "TLS_DHE_RSA_WITH_ARIA_128_GCM_SHA256": "DHE-RSA-ARIA128-GCM-SHA256",
    "TLS_DHE_RSA_WITH_ARIA_256_GCM_SHA384": "DHE-RSA-ARIA256-GCM-SHA384",
    "TLS_DHE_DSS_WITH_ARIA_128_GCM_SHA256": "DHE-DSS-ARIA128-GCM-SHA256",
    "TLS_DHE_DSS_WITH_ARIA_256_GCM_SHA384": "DHE-DSS-ARIA256-GCM-SHA384",
    "TLS_ECDHE_ECDSA_WITH_ARIA_128_GCM_SHA256": "ECDHE-ECDSA-ARIA128-GCM-SHA256",
    "TLS_ECDHE_ECDSA_WITH_ARIA_256_GCM_SHA384": "ECDHE-ECDSA-ARIA256-GCM-SHA384",
    "TLS_ECDHE_RSA_WITH_ARIA_128_GCM_SHA256": "ECDHE-ARIA128-GCM-SHA256",
    "TLS_ECDHE_RSA_WITH_ARIA_256_GCM_SHA384": "ECDHE-ARIA256-GCM-SHA384",
    "TLS_PSK_WITH_ARIA_128_GCM_SHA256": "PSK-ARIA128-GCM-SHA256",
    "TLS_PSK_WITH_ARIA_256_GCM_SHA384": "PSK-ARIA256-GCM-SHA384",
    "TLS_DHE_PSK_WITH_ARIA_128_GCM_SHA256": "DHE-PSK-ARIA128-GCM-SHA256",
    "TLS_DHE_PSK_WITH_ARIA_256_GCM_SHA384": "DHE-PSK-ARIA256-GCM-SHA384",
    "TLS_RSA_PSK_WITH_ARIA_128_GCM_SHA256": "RSA-PSK-ARIA128-GCM-SHA256",
    "TLS_RSA_PSK_WITH_ARIA_256_GCM_SHA384": "RSA-PSK-ARIA256-GCM-SHA384",
    # Camellia HMAC-Based cipher suites from RFC6367, extending TLS v1.2
    "TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256": "ECDHE-ECDSA-CAMELLIA128-SHA256",
    "TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384": "ECDHE-ECDSA-CAMELLIA256-SHA384",
    "TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256": "ECDHE-RSA-CAMELLIA128-SHA256",
    "TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384": "ECDHE-RSA-CAMELLIA256-SHA384",
    # Pre-shared keying (PSK) cipher suites",
    "PSK_WITH_NULL_SHA": "PSK-NULL-SHA",
    "DHE_PSK_WITH_NULL_SHA": "DHE-PSK-NULL-SHA",
    "RSA_PSK_WITH_NULL_SHA": "RSA-PSK-NULL-SHA",
    "PSK_WITH_RC4_128_SHA": "PSK-RC4-SHA",
    "PSK_WITH_3DES_EDE_CBC_SHA": "PSK-3DES-EDE-CBC-SHA",
    "PSK_WITH_AES_128_CBC_SHA": "PSK-AES128-CBC-SHA",
    "PSK_WITH_AES_256_CBC_SHA": "PSK-AES256-CBC-SHA",
    "DHE_PSK_WITH_RC4_128_SHA": "DHE-PSK-RC4-SHA",
    "DHE_PSK_WITH_3DES_EDE_CBC_SHA": "DHE-PSK-3DES-EDE-CBC-SHA",
    "DHE_PSK_WITH_AES_128_CBC_SHA": "DHE-PSK-AES128-CBC-SHA",
    "DHE_PSK_WITH_AES_256_CBC_SHA": "DHE-PSK-AES256-CBC-SHA",
    "RSA_PSK_WITH_RC4_128_SHA": "RSA-PSK-RC4-SHA",
    "RSA_PSK_WITH_3DES_EDE_CBC_SHA": "RSA-PSK-3DES-EDE-CBC-SHA",
    "RSA_PSK_WITH_AES_128_CBC_SHA": "RSA-PSK-AES128-CBC-SHA",
    "RSA_PSK_WITH_AES_256_CBC_SHA": "RSA-PSK-AES256-CBC-SHA",
    "PSK_WITH_AES_128_GCM_SHA256": "PSK-AES128-GCM-SHA256",
    "PSK_WITH_AES_256_GCM_SHA384": "PSK-AES256-GCM-SHA384",
    "DHE_PSK_WITH_AES_128_GCM_SHA256": "DHE-PSK-AES128-GCM-SHA256",
    "DHE_PSK_WITH_AES_256_GCM_SHA384": "DHE-PSK-AES256-GCM-SHA384",
    "RSA_PSK_WITH_AES_128_GCM_SHA256": "RSA-PSK-AES128-GCM-SHA256",
    "RSA_PSK_WITH_AES_256_GCM_SHA384": "RSA-PSK-AES256-GCM-SHA384",
    "PSK_WITH_AES_128_CBC_SHA256": "PSK-AES128-CBC-SHA256",
    "PSK_WITH_AES_256_CBC_SHA384": "PSK-AES256-CBC-SHA384",
    "PSK_WITH_NULL_SHA256": "PSK-NULL-SHA256",
    "PSK_WITH_NULL_SHA384": "PSK-NULL-SHA384",
    "DHE_PSK_WITH_AES_128_CBC_SHA256": "DHE-PSK-AES128-CBC-SHA256",
    "DHE_PSK_WITH_AES_256_CBC_SHA384": "DHE-PSK-AES256-CBC-SHA384",
    "DHE_PSK_WITH_NULL_SHA256": "DHE-PSK-NULL-SHA256",
    "DHE_PSK_WITH_NULL_SHA384": "DHE-PSK-NULL-SHA384",
    "RSA_PSK_WITH_AES_128_CBC_SHA256": "RSA-PSK-AES128-CBC-SHA256",
    "RSA_PSK_WITH_AES_256_CBC_SHA384": "RSA-PSK-AES256-CBC-SHA384",
    "RSA_PSK_WITH_NULL_SHA256": "RSA-PSK-NULL-SHA256",
    "RSA_PSK_WITH_NULL_SHA384": "RSA-PSK-NULL-SHA384",
    "ECDHE_PSK_WITH_RC4_128_SHA": "ECDHE-PSK-RC4-SHA",
    "ECDHE_PSK_WITH_3DES_EDE_CBC_SHA": "ECDHE-PSK-3DES-EDE-CBC-SHA",
    "ECDHE_PSK_WITH_AES_128_CBC_SHA": "ECDHE-PSK-AES128-CBC-SHA",
    "ECDHE_PSK_WITH_AES_256_CBC_SHA": "ECDHE-PSK-AES256-CBC-SHA",
    "ECDHE_PSK_WITH_AES_128_CBC_SHA256": "ECDHE-PSK-AES128-CBC-SHA256",
    "ECDHE_PSK_WITH_AES_256_CBC_SHA384": "ECDHE-PSK-AES256-CBC-SHA384",
    "ECDHE_PSK_WITH_NULL_SHA": "ECDHE-PSK-NULL-SHA",
    "ECDHE_PSK_WITH_NULL_SHA256": "ECDHE-PSK-NULL-SHA256",
    "ECDHE_PSK_WITH_NULL_SHA384": "ECDHE-PSK-NULL-SHA384",
    "PSK_WITH_CAMELLIA_128_CBC_SHA256": "PSK-CAMELLIA128-SHA256",
    "PSK_WITH_CAMELLIA_256_CBC_SHA384": "PSK-CAMELLIA256-SHA384",
    "DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256": "DHE-PSK-CAMELLIA128-SHA256",
    "DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384": "DHE-PSK-CAMELLIA256-SHA384",
    "RSA_PSK_WITH_CAMELLIA_128_CBC_SHA256": "RSA-PSK-CAMELLIA128-SHA256",
    "RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384": "RSA-PSK-CAMELLIA256-SHA384",
    "ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256": "ECDHE-PSK-CAMELLIA128-SHA256",
    "ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384": "ECDHE-PSK-CAMELLIA256-SHA384",
    "PSK_WITH_AES_128_CCM": "PSK-AES128-CCM",
    "PSK_WITH_AES_256_CCM": "PSK-AES256-CCM",
    "DHE_PSK_WITH_AES_128_CCM": "DHE-PSK-AES128-CCM",
    "DHE_PSK_WITH_AES_256_CCM": "DHE-PSK-AES256-CCM",
    "PSK_WITH_AES_128_CCM_8": "PSK-AES128-CCM8",
    "PSK_WITH_AES_256_CCM_8": "PSK-AES256-CCM8",
    "DHE_PSK_WITH_AES_128_CCM_8": "DHE-PSK-AES128-CCM8",
    "DHE_PSK_WITH_AES_256_CCM_8": "DHE-PSK-AES256-CCM8",
    # ChaCha20-Poly1305 cipher suites, extending TLS v1.2
    "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256": "ECDHE-RSA-CHACHA20-POLY1305",
    "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256": "ECDHE-ECDSA-CHACHA20-POLY1305",
    "TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256": "DHE-RSA-CHACHA20-POLY1305",
    "TLS_PSK_WITH_CHACHA20_POLY1305_SHA256": "PSK-CHACHA20-POLY1305",
    "TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256": "ECDHE-PSK-CHACHA20-POLY1305",
    "TLS_DHE_PSK_WITH_CHACHA20_POLY1305_SHA256": "DHE-PSK-CHACHA20-POLY1305",
    "TLS_RSA_PSK_WITH_CHACHA20_POLY1305_SHA256": "RSA-PSK-CHACHA20-POLY1305",
}

# TLS v1.3 cipher suites IANI to OpenSSL name translation
TLSV1_3_CIPHER_SUITES: Dict[str, str] = {
    "TLS_AES_128_GCM_SHA256": "TLS_AES_128_GCM_SHA256",
    "TLS_AES_256_GCM_SHA384": "TLS_AES_256_GCM_SHA384",
    "TLS_CHACHA20_POLY1305_SHA256": "TLS_CHACHA20_POLY1305_SHA256",
    "TLS_AES_128_CCM_SHA256": "TLS_AES_128_CCM_SHA256",
    "TLS_AES_128_CCM_8_SHA256": "TLS_AES_128_CCM_8_SHA256",
}

TLS_CIPHER_SUITES: Dict[str, Dict[str, str]] = {
    "TLSv1": TLSV1_CIPHER_SUITES,
    "TLSv1.1": TLSV1_1_CIPHER_SUITES,
    "TLSv1.2": TLSV1_2_CIPHER_SUITES,
    "TLSv1.3": TLSV1_3_CIPHER_SUITES,
}

OPENSSL_CS_NAMES: Dict[str, ValuesView[str]] = {
    "TLSv1": TLSV1_CIPHER_SUITES.values(),
    "TLSv1.1": TLSV1_1_CIPHER_SUITES.values(),
    "TLSv1.2": TLSV1_2_CIPHER_SUITES.values(),
    "TLSv1.3": TLSV1_3_CIPHER_SUITES.values(),
}

virt/lib/python3.9/site-packages/mysql/connector/constants.py
PrinceDisant/LostInNL
# Copyright (c) 2009, 2023, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

"""Converting MySQL and Python types
"""

import datetime
import math
import struct
import time

from decimal import Decimal
from typing import Any, Callable, Dict, List, Optional, Set, Tuple, Union

from .constants import CharacterSet, FieldFlag, FieldType
from .custom_types import HexLiteral
from .types import (
    DescriptionType,
    RowType,
    StrOrBytes,
    ToMysqlInputTypes,
    ToMysqlOutputTypes,
    ToPythonOutputTypes,
)
from .utils import NUMERIC_TYPES

CONVERT_ERROR = "Could not convert '{value}' to python {pytype}"


class MySQLConverterBase:
    """Base class for conversion classes

    All class dealing with converting to and from MySQL data types must
    be a subclass of this class.
    """

    def __init__(
        self,
        charset: Optional[str] = "utf8",
        use_unicode: bool = True,
        str_fallback: bool = False,
    ) -> None:
        self.python_types: Optional[Tuple[Any, ...]] = None
        self.mysql_types: Optional[Tuple[Any, ...]] = None
        self.charset: Optional[str] = None
        self.charset_id: int = 0
        self.set_charset(charset)
        self.use_unicode: bool = use_unicode
        self.str_fallback: bool = str_fallback
        self._cache_field_types: Dict[
            int,
            Callable[[bytes, DescriptionType], ToPythonOutputTypes],
        ] = {}

    def set_charset(self, charset: Optional[str]) -> None:
        """Set character set"""
        if charset in ("utf8mb4", "utf8mb3"):
            charset = "utf8"
        if charset is not None:
            self.charset = charset
        else:
            # default to utf8
            self.charset = "utf8"
        self.charset_id = CharacterSet.get_charset_info(self.charset)[0]

    def set_unicode(self, value: bool = True) -> None:
        """Set whether to use Unicode"""
        self.use_unicode = value

    def to_mysql(
        self, value: ToMysqlInputTypes
    ) -> Union[ToMysqlInputTypes, HexLiteral]:
        """Convert Python data type to MySQL"""
        type_name = value.__class__.__name__.lower()
        try:
            converted: ToMysqlOutputTypes = getattr(self, f"_{type_name}_to_mysql")(
                value
            )
            return converted
        except AttributeError:
            return value

    def to_python(
        self, vtype: DescriptionType, value: Optional[bytes]
    ) -> ToPythonOutputTypes:
        """Convert MySQL data type to Python"""

        if (value == b"\x00" or value is None) and vtype[1] != FieldType.BIT:
            # Don't go further when we hit a NULL value
            return None

        if not self._cache_field_types:
            self._cache_field_types = {}
            for name, info in FieldType.desc.items():
                try:
                    self._cache_field_types[info[0]] = getattr(
                        self, f"_{name.lower()}_to_python"
                    )
                except AttributeError:
                    # We ignore field types which has no method
                    pass
        if value is None:
            return None
        try:
            return self._cache_field_types[vtype[1]](value, vtype)
        except KeyError:
            return value

    @staticmethod
    def escape(
        value: Any,
        sql_mode: Optional[str] = None,  # pylint: disable=unused-argument
    ) -> Any:
        """Escape buffer for sending to MySQL"""
        return value

    @staticmethod
    def quote(buf: Any) -> StrOrBytes:
        """Quote buffer for sending to MySQL"""
        return str(buf)


class MySQLConverter(MySQLConverterBase):
    """Default conversion class for MySQL Connector/Python.

     o escape method: for escaping values send to MySQL
     o quoting method: for quoting values send to MySQL in statements
     o conversion mapping: maps Python and MySQL data types to
       function for converting them.

    Whenever one needs to convert values differently, a converter_class
    argument can be given while instantiating a new connection like
    cnx.connect(converter_class=CustomMySQLConverterClass).

    """

    def __init__(
        self,
        charset: Optional[str] = None,
        use_unicode: bool = True,
        str_fallback: bool = False,
    ) -> None:
        MySQLConverterBase.__init__(self, charset, use_unicode, str_fallback)
        self._cache_field_types: Dict[
            int,
            Callable[[bytes, DescriptionType], ToPythonOutputTypes],
        ] = {}

    @staticmethod
    def escape(value: Any, sql_mode: Optional[str] = None) -> Any:
        """
        Escapes special characters as they are expected to by when MySQL
        receives them.
        As found in MySQL source mysys/charset.c

        Returns the value if not a string, or the escaped string.
        """
        if isinstance(value, (bytes, bytearray)):
            if sql_mode == "NO_BACKSLASH_ESCAPES":
                return value.replace(b"'", b"''")
            value = value.replace(b"\\", b"\\\\")
            value = value.replace(b"\n", b"\\n")
            value = value.replace(b"\r", b"\\r")
            value = value.replace(b"\047", b"\134\047")  # single quotes
            value = value.replace(b"\042", b"\134\042")  # double quotes
            value = value.replace(b"\032", b"\134\032")  # for Win32
        elif isinstance(value, str) and not isinstance(value, HexLiteral):
            if sql_mode == "NO_BACKSLASH_ESCAPES":
                return value.replace("'", "''")
            value = value.replace("\\", "\\\\")
            value = value.replace("\n", "\\n")
            value = value.replace("\r", "\\r")
            value = value.replace("\047", "\134\047")  # single quotes
            value = value.replace("\042", "\134\042")  # double quotes
            value = value.replace("\032", "\134\032")  # for Win32
        return value

    @staticmethod
    def quote(buf: Optional[Union[float, int, Decimal, HexLiteral, bytes]]) -> bytes:
        """
        Quote the parameters for commands. General rules:
          o numbers are returns as bytes using ascii codec
          o None is returned as bytearray(b'NULL')
          o Everything else is single quoted '<buf>'

        Returns a bytearray object.
        """
        if isinstance(buf, NUMERIC_TYPES):
            return str(buf).encode("ascii")
        if isinstance(buf, type(None)):
            return bytearray(b"NULL")
        return bytearray(b"'" + buf + b"'")  # type: ignore[operator]

    def to_mysql(self, value: ToMysqlInputTypes) -> ToMysqlOutputTypes:
        """Convert Python data type to MySQL"""
        type_name = value.__class__.__name__.lower()
        try:
            converted: ToMysqlOutputTypes = getattr(self, f"_{type_name}_to_mysql")(
                value
            )
            return converted
        except AttributeError:
            if self.str_fallback:
                return str(value).encode()
            raise TypeError(
                f"Python '{type_name}' cannot be converted to a MySQL type"
            ) from None

    def to_python(
        self,
        vtype: DescriptionType,
        value: Optional[bytes],
    ) -> ToPythonOutputTypes:
        """Convert MySQL data type to Python"""
        # \x00
        if value == 0 and vtype[1] != FieldType.BIT:
            # Don't go further when we hit a NULL value
            return None
        if value is None:
            return None

        if not self._cache_field_types:
            self._cache_field_types = {}
            for name, info in FieldType.desc.items():
                try:
                    self._cache_field_types[info[0]] = getattr(
                        self, f"_{name.lower()}_to_python"
                    )
                except AttributeError:
                    # We ignore field types which has no method
                    pass

        try:
            return self._cache_field_types[vtype[1]](value, vtype)
        except KeyError:
            # If one type is not defined, we just return the value as str
            try:
                return value.decode("utf-8")
            except UnicodeDecodeError:
                return value
        except ValueError as err:
            raise ValueError(f"{err} (field {vtype[0]})") from err
        except TypeError as err:
            raise TypeError(f"{err} (field {vtype[0]})") from err

    @staticmethod
    def _int_to_mysql(value: int) -> int:
        """Convert value to int"""
        return int(value)

    @staticmethod
    def _long_to_mysql(value: int) -> int:
        """Convert value to int

        Note: There is no type "long" in Python 3 since integers are of unlimited size.
              Since Python 2 is no longer supported, this method should be deprecated.
        """
        return int(value)

    @staticmethod
    def _float_to_mysql(value: float) -> Optional[float]:
        """Convert value to float"""
        if math.isnan(value):
            return None
        return float(value)

    def _str_to_mysql(self, value: str) -> Union[bytes, HexLiteral]:
        """Convert value to string"""
        return self._unicode_to_mysql(value)

    def _unicode_to_mysql(self, value: str) -> Union[bytes, HexLiteral]:
        """Convert unicode"""
        charset = self.charset
        charset_id = self.charset_id
        if charset == "binary":
            charset = "utf8"
            charset_id = CharacterSet.get_charset_info(charset)[0]
        encoded = value.encode(charset)
        if charset_id in CharacterSet.slash_charsets:
            if b"\x5c" in encoded:
                return HexLiteral(value, charset)
        return encoded

    @staticmethod
    def _bytes_to_mysql(value: bytes) -> bytes:
        """Convert value to bytes"""
        return value

    @staticmethod
    def _bytearray_to_mysql(value: bytearray) -> bytes:
        """Convert value to bytes"""
        return bytes(value)

    @staticmethod
    def _bool_to_mysql(value: bool) -> int:
        """Convert value to boolean"""
        return 1 if value else 0

    @staticmethod
    def _nonetype_to_mysql(value: None) -> None:  # pylint: disable=unused-argument
        """
        This would return what None would be in MySQL, but instead we
        leave it None and return it right away. The actual conversion
        from None to NULL happens in the quoting functionality.

        Return None.
        """
        return None

    @staticmethod
    def _datetime_to_mysql(value: datetime.datetime) -> bytes:
        """
        Converts a datetime instance to a string suitable for MySQL.
        The returned string has format: %Y-%m-%d %H:%M:%S[.%f]

        If the instance isn't a datetime.datetime type, it return None.

        Returns a bytes.
        """
        if value.microsecond:
            fmt = "{0:04d}-{1:02d}-{2:02d} {3:02d}:{4:02d}:{5:02d}.{6:06d}"
            return fmt.format(
                value.year,
                value.month,
                value.day,
                value.hour,
                value.minute,
                value.second,
                value.microsecond,
            ).encode("ascii")

        fmt = "{0:04d}-{1:02d}-{2:02d} {3:02d}:{4:02d}:{5:02d}"
        return fmt.format(
            value.year,
            value.month,
            value.day,
            value.hour,
            value.minute,
            value.second,
        ).encode("ascii")

    @staticmethod
    def _date_to_mysql(value: datetime.date) -> bytes:
        """
        Converts a date instance to a string suitable for MySQL.
        The returned string has format: %Y-%m-%d

        If the instance isn't a datetime.date type, it return None.

        Returns a bytes.
        """
        return f"{value.year:04d}-{value.month:02d}-{value.day:02d}".encode("ascii")

    @staticmethod
    def _time_to_mysql(value: datetime.time) -> bytes:
        """
        Converts a time instance to a string suitable for MySQL.
        The returned string has format: %H:%M:%S[.%f]

        If the instance isn't a datetime.time type, it return None.

        Returns a bytes.
        """
        if value.microsecond:
            return value.strftime("%H:%M:%S.%f").encode("ascii")
        return value.strftime("%H:%M:%S").encode("ascii")

    @staticmethod
    def _struct_time_to_mysql(value: time.struct_time) -> bytes:
        """
        Converts a time.struct_time sequence to a string suitable
        for MySQL.
        The returned string has format: %Y-%m-%d %H:%M:%S

        Returns a bytes or None when not valid.
        """
        return time.strftime("%Y-%m-%d %H:%M:%S", value).encode("ascii")

    @staticmethod
    def _timedelta_to_mysql(value: datetime.timedelta) -> bytes:
        """
        Converts a timedelta instance to a string suitable for MySQL.
        The returned string has format: %H:%M:%S

        Returns a bytes.
        """
        seconds = abs(value.days * 86400 + value.seconds)

        if value.microseconds:
            fmt = "{0:02d}:{1:02d}:{2:02d}.{3:06d}"
            if value.days < 0:
                mcs = 1000000 - value.microseconds
                seconds -= 1
            else:
                mcs = value.microseconds
        else:
            fmt = "{0:02d}:{1:02d}:{2:02d}"

        if value.days < 0:
            fmt = "-" + fmt

        (hours, remainder) = divmod(seconds, 3600)
        (mins, secs) = divmod(remainder, 60)

        if value.microseconds:
            result = fmt.format(hours, mins, secs, mcs)
        else:
            result = fmt.format(hours, mins, secs)

        return result.encode("ascii")

    @staticmethod
    def _decimal_to_mysql(value: Decimal) -> Optional[bytes]:
        """
        Converts a decimal.Decimal instance to a string suitable for
        MySQL.

        Returns a bytes or None when not valid.
        """
        if isinstance(value, Decimal):
            return str(value).encode("ascii")

        return None

    def row_to_python(
        self, row: Tuple[bytes, ...], fields: List[DescriptionType]
    ) -> RowType:
        """Convert a MySQL text result row to Python types

        The row argument is a sequence containing text result returned
        by a MySQL server. Each value of the row is converted to the
        using the field type information in the fields argument.

        Returns a tuple.
        """
        i = 0
        result: List[ToPythonOutputTypes] = [None] * len(fields)

        if not self._cache_field_types:
            self._cache_field_types = {}
            for name, info in FieldType.desc.items():
                try:
                    self._cache_field_types[info[0]] = getattr(
                        self, f"_{name.lower()}_to_python"
                    )
                except AttributeError:
                    # We ignore field types which has no method
                    pass

        for field in fields:
            field_type = field[1]

            if (row[i] == 0 and field_type != FieldType.BIT) or row[i] is None:
                # Don't convert NULL value
                i += 1
                continue

            try:
                result[i] = self._cache_field_types[field_type](row[i], field)
            except KeyError:
                # If one type is not defined, we just return the value as str
                try:
                    result[i] = row[i].decode("utf-8")
                except UnicodeDecodeError:
                    result[i] = row[i]
            except (ValueError, TypeError) as err:
                # Item "ValueError" of "Union[ValueError, TypeError]" has no attribute "message"
                err.message = f"{err} (field {field[0]})"  # type: ignore[union-attr]
                raise

            i += 1

        return tuple(result)

    # pylint: disable=unused-argument
    @staticmethod
    def _float_to_python(value: bytes, desc: Optional[DescriptionType] = None) -> float:
        """
        Returns value as float type.
        """
        return float(value)

    _double_to_python = _float_to_python

    @staticmethod
    def _int_to_python(value: bytes, desc: Optional[DescriptionType] = None) -> int:
        """
        Returns value as int type.
        """
        return int(value)

    _tiny_to_python = _int_to_python
    _short_to_python = _int_to_python
    _int24_to_python = _int_to_python
    _long_to_python = _int_to_python
    _longlong_to_python = _int_to_python

    def _decimal_to_python(
        self, value: bytes, desc: Optional[DescriptionType] = None
    ) -> Decimal:
        """
        Returns value as a decimal.Decimal.
        """
        val = value.decode(self.charset)
        return Decimal(val)

    _newdecimal_to_python = _decimal_to_python

    @staticmethod
    def _str(value: bytes, desc: Optional[DescriptionType] = None) -> str:
        """
        Returns value as str type.
        """
        return str(value)

    @staticmethod
    def _bit_to_python(value: bytes, dsc: Optional[DescriptionType] = None) -> int:
        """Returns BIT columntype as integer"""
        int_val = value
        if len(int_val) < 8:
            int_val = b"\x00" * (8 - len(int_val)) + int_val
        return int(struct.unpack(">Q", int_val)[0])

    @staticmethod
    def _date_to_python(
        value: bytes, dsc: Optional[DescriptionType] = None
    ) -> Optional[datetime.date]:
        """Converts TIME column MySQL to a python datetime.datetime type.

        Raises ValueError if the value can not be converted.

        Returns DATE column type as datetime.date type.
        """
        if isinstance(value, datetime.date):
            return value
        try:
            parts = value.split(b"-")
            if len(parts) != 3:
                raise ValueError(f"invalid datetime format: {parts} len: {len(parts)}")
            try:
                return datetime.date(int(parts[0]), int(parts[1]), int(parts[2]))
            except ValueError:
                return None
        except (IndexError, ValueError):
            raise ValueError(
                f"Could not convert {repr(value)} to python datetime.timedelta"
            ) from None

    _NEWDATE_to_python = _date_to_python

    @staticmethod
    def _time_to_python(
        value: bytes, dsc: Optional[DescriptionType] = None
    ) -> datetime.timedelta:
        """Converts TIME column value to python datetime.time value type.

        Converts the TIME column MySQL type passed as bytes to a python
        datetime.datetime type.

        Raises ValueError if the value can not be converted.

        Returns datetime.timedelta type.
        """
        mcs: Optional[Union[int, bytes]] = None
        try:
            (hms, mcs) = value.split(b".")
            mcs = int(mcs.ljust(6, b"0"))
        except (TypeError, ValueError):
            hms = value
            mcs = 0
        try:
            (hours, mins, secs) = [int(d) for d in hms.split(b":")]
            if value[0] == 45 or value[0] == "-":
                mins, secs, mcs = (
                    -mins,
                    -secs,
                    -mcs,  # pylint: disable=invalid-unary-operand-type
                )
            return datetime.timedelta(
                hours=hours, minutes=mins, seconds=secs, microseconds=mcs
            )
        except (IndexError, TypeError, ValueError):
            raise ValueError(
                CONVERT_ERROR.format(value=value, pytype="datetime.timedelta")
            ) from None

    @staticmethod
    def _datetime_to_python(
        value: bytes, dsc: Optional[DescriptionType] = None
    ) -> Optional[datetime.datetime]:
        """Converts DATETIME column value to python datetime.time value type.

        Converts the DATETIME column MySQL type passed as bytes to a python
        datetime.datetime type.

        Returns: datetime.datetime type.
        """
        if isinstance(value, datetime.datetime):
            return value
        datetime_val = None
        mcs: Optional[Union[int, bytes]] = None
        try:
            (date_, time_) = value.split(b" ")
            if len(time_) > 8:
                (hms, mcs) = time_.split(b".")
                mcs = int(mcs.ljust(6, b"0"))
            else:
                hms = time_
                mcs = 0
            dtval = (
                [int(i) for i in date_.split(b"-")]
                + [int(i) for i in hms.split(b":")]
                + [
                    mcs,
                ]
            )
            if len(dtval) < 6:
                raise ValueError(f"invalid datetime format: {dtval} len: {len(dtval)}")
            # Note that by default MySQL accepts invalid timestamps
            # (this is also backward compatibility).
            # Traditionaly C/py returns None for this well formed but
            # invalid datetime for python like '0000-00-00 HH:MM:SS'.
            try:
                datetime_val = datetime.datetime(*dtval)  # type: ignore[arg-type]
            except ValueError:
                return None
        except (IndexError, TypeError):
            raise ValueError(
                CONVERT_ERROR.format(value=value, pytype="datetime.timedelta")
            ) from None

        return datetime_val

    _timestamp_to_python = _datetime_to_python

    @staticmethod
    def _year_to_python(value: bytes, dsc: Optional[DescriptionType] = None) -> int:
        """Returns YEAR column type as integer"""
        try:
            year = int(value)
        except ValueError as err:
            raise ValueError(f"Failed converting YEAR to int ({repr(value)})") from err

        return year

    def _set_to_python(
        self, value: bytes, dsc: Optional[DescriptionType] = None
    ) -> Set[str]:
        """Returns SET column type as set

        Actually, MySQL protocol sees a SET as a string type field. So this
        code isn't called directly, but used by STRING_to_python() method.

        Returns SET column type as a set.
        """
        set_type = None
        val = value.decode(self.charset)
        if not val:
            return set()
        try:
            set_type = set(val.split(","))
        except ValueError as err:
            raise ValueError(
                f"Could not convert set {repr(value)} to a sequence"
            ) from err
        return set_type

    def _string_to_python(
        self, value: bytes, dsc: Optional[DescriptionType] = None
    ) -> Union[StrOrBytes, Set[str]]:
        """
        Note that a SET is a string too, but using the FieldFlag we can see
        whether we have to split it.

        Returns string typed columns as string type.
        """
        if self.charset == "binary":
            return value
        if dsc is not None:
            if dsc[1] == FieldType.JSON and self.use_unicode:
                return value.decode(self.charset)
            if dsc[7] & FieldFlag.SET:
                return self._set_to_python(value, dsc)
            # 'binary' charset
            if dsc[8] == 63:
                return value
        if isinstance(value, (bytes, bytearray)) and self.use_unicode:
            try:
                return value.decode(self.charset)
            except UnicodeDecodeError:
                return value

        return value

    _var_string_to_python = _string_to_python
    _json_to_python = _string_to_python

    def _blob_to_python(
        self, value: bytes, dsc: Optional[DescriptionType] = None
    ) -> Union[StrOrBytes, Set[str]]:
        """Convert BLOB data type to Python."""
        if dsc is not None:
            if (
                dsc[7] & FieldFlag.BLOB
                and dsc[7] & FieldFlag.BINARY
                # 'binary' charset
                and dsc[8] == 63
            ):
                return bytes(value)
        return self._string_to_python(value, dsc)

    _long_blob_to_python = _blob_to_python
    _medium_blob_to_python = _blob_to_python
    _tiny_blob_to_python = _blob_to_python
    # pylint: enable=unused-argument

virt/lib/python3.9/site-packages/mysql/connector/conversion 2.py
PrinceDisant/LostInNL
# Copyright (c) 2009, 2023, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

"""Converting MySQL and Python types
"""

import datetime
import math
import struct
import time

from decimal import Decimal
from typing import Any, Callable, Dict, List, Optional, Set, Tuple, Union

from .constants import CharacterSet, FieldFlag, FieldType
from .custom_types import HexLiteral
from .types import (
    DescriptionType,
    RowType,
    StrOrBytes,
    ToMysqlInputTypes,
    ToMysqlOutputTypes,
    ToPythonOutputTypes,
)
from .utils import NUMERIC_TYPES

CONVERT_ERROR = "Could not convert '{value}' to python {pytype}"


class MySQLConverterBase:
    """Base class for conversion classes

    All class dealing with converting to and from MySQL data types must
    be a subclass of this class.
    """

    def __init__(
        self,
        charset: Optional[str] = "utf8",
        use_unicode: bool = True,
        str_fallback: bool = False,
    ) -> None:
        self.python_types: Optional[Tuple[Any, ...]] = None
        self.mysql_types: Optional[Tuple[Any, ...]] = None
        self.charset: Optional[str] = None
        self.charset_id: int = 0
        self.set_charset(charset)
        self.use_unicode: bool = use_unicode
        self.str_fallback: bool = str_fallback
        self._cache_field_types: Dict[
            int,
            Callable[[bytes, DescriptionType], ToPythonOutputTypes],
        ] = {}

    def set_charset(self, charset: Optional[str]) -> None:
        """Set character set"""
        if charset in ("utf8mb4", "utf8mb3"):
            charset = "utf8"
        if charset is not None:
            self.charset = charset
        else:
            # default to utf8
            self.charset = "utf8"
        self.charset_id = CharacterSet.get_charset_info(self.charset)[0]

    def set_unicode(self, value: bool = True) -> None:
        """Set whether to use Unicode"""
        self.use_unicode = value

    def to_mysql(
        self, value: ToMysqlInputTypes
    ) -> Union[ToMysqlInputTypes, HexLiteral]:
        """Convert Python data type to MySQL"""
        type_name = value.__class__.__name__.lower()
        try:
            converted: ToMysqlOutputTypes = getattr(self, f"_{type_name}_to_mysql")(
                value
            )
            return converted
        except AttributeError:
            return value

    def to_python(
        self, vtype: DescriptionType, value: Optional[bytes]
    ) -> ToPythonOutputTypes:
        """Convert MySQL data type to Python"""

        if (value == b"\x00" or value is None) and vtype[1] != FieldType.BIT:
            # Don't go further when we hit a NULL value
            return None

        if not self._cache_field_types:
            self._cache_field_types = {}
            for name, info in FieldType.desc.items():
                try:
                    self._cache_field_types[info[0]] = getattr(
                        self, f"_{name.lower()}_to_python"
                    )
                except AttributeError:
                    # We ignore field types which has no method
                    pass
        if value is None:
            return None
        try:
            return self._cache_field_types[vtype[1]](value, vtype)
        except KeyError:
            return value

    @staticmethod
    def escape(
        value: Any,
        sql_mode: Optional[str] = None,  # pylint: disable=unused-argument
    ) -> Any:
        """Escape buffer for sending to MySQL"""
        return value

    @staticmethod
    def quote(buf: Any) -> StrOrBytes:
        """Quote buffer for sending to MySQL"""
        return str(buf)


class MySQLConverter(MySQLConverterBase):
    """Default conversion class for MySQL Connector/Python.

     o escape method: for escaping values send to MySQL
     o quoting method: for quoting values send to MySQL in statements
     o conversion mapping: maps Python and MySQL data types to
       function for converting them.

    Whenever one needs to convert values differently, a converter_class
    argument can be given while instantiating a new connection like
    cnx.connect(converter_class=CustomMySQLConverterClass).

    """

    def __init__(
        self,
        charset: Optional[str] = None,
        use_unicode: bool = True,
        str_fallback: bool = False,
    ) -> None:
        MySQLConverterBase.__init__(self, charset, use_unicode, str_fallback)
        self._cache_field_types: Dict[
            int,
            Callable[[bytes, DescriptionType], ToPythonOutputTypes],
        ] = {}

    @staticmethod
    def escape(value: Any, sql_mode: Optional[str] = None) -> Any:
        """
        Escapes special characters as they are expected to by when MySQL
        receives them.
        As found in MySQL source mysys/charset.c

        Returns the value if not a string, or the escaped string.
        """
        if isinstance(value, (bytes, bytearray)):
            if sql_mode == "NO_BACKSLASH_ESCAPES":
                return value.replace(b"'", b"''")
            value = value.replace(b"\\", b"\\\\")
            value = value.replace(b"\n", b"\\n")
            value = value.replace(b"\r", b"\\r")
            value = value.replace(b"\047", b"\134\047")  # single quotes
            value = value.replace(b"\042", b"\134\042")  # double quotes
            value = value.replace(b"\032", b"\134\032")  # for Win32
        elif isinstance(value, str) and not isinstance(value, HexLiteral):
            if sql_mode == "NO_BACKSLASH_ESCAPES":
                return value.replace("'", "''")
            value = value.replace("\\", "\\\\")
            value = value.replace("\n", "\\n")
            value = value.replace("\r", "\\r")
            value = value.replace("\047", "\134\047")  # single quotes
            value = value.replace("\042", "\134\042")  # double quotes
            value = value.replace("\032", "\134\032")  # for Win32
        return value

    @staticmethod
    def quote(buf: Optional[Union[float, int, Decimal, HexLiteral, bytes]]) -> bytes:
        """
        Quote the parameters for commands. General rules:
          o numbers are returns as bytes using ascii codec
          o None is returned as bytearray(b'NULL')
          o Everything else is single quoted '<buf>'

        Returns a bytearray object.
        """
        if isinstance(buf, NUMERIC_TYPES):
            return str(buf).encode("ascii")
        if isinstance(buf, type(None)):
            return bytearray(b"NULL")
        return bytearray(b"'" + buf + b"'")  # type: ignore[operator]

    def to_mysql(self, value: ToMysqlInputTypes) -> ToMysqlOutputTypes:
        """Convert Python data type to MySQL"""
        type_name = value.__class__.__name__.lower()
        try:
            converted: ToMysqlOutputTypes = getattr(self, f"_{type_name}_to_mysql")(
                value
            )
            return converted
        except AttributeError:
            if self.str_fallback:
                return str(value).encode()
            raise TypeError(
                f"Python '{type_name}' cannot be converted to a MySQL type"
            ) from None

    def to_python(
        self,
        vtype: DescriptionType,
        value: Optional[bytes],
    ) -> ToPythonOutputTypes:
        """Convert MySQL data type to Python"""
        # \x00
        if value == 0 and vtype[1] != FieldType.BIT:
            # Don't go further when we hit a NULL value
            return None
        if value is None:
            return None

        if not self._cache_field_types:
            self._cache_field_types = {}
            for name, info in FieldType.desc.items():
                try:
                    self._cache_field_types[info[0]] = getattr(
                        self, f"_{name.lower()}_to_python"
                    )
                except AttributeError:
                    # We ignore field types which has no method
                    pass

        try:
            return self._cache_field_types[vtype[1]](value, vtype)
        except KeyError:
            # If one type is not defined, we just return the value as str
            try:
                return value.decode("utf-8")
            except UnicodeDecodeError:
                return value
        except ValueError as err:
            raise ValueError(f"{err} (field {vtype[0]})") from err
        except TypeError as err:
            raise TypeError(f"{err} (field {vtype[0]})") from err

    @staticmethod
    def _int_to_mysql(value: int) -> int:
        """Convert value to int"""
        return int(value)

    @staticmethod
    def _long_to_mysql(value: int) -> int:
        """Convert value to int

        Note: There is no type "long" in Python 3 since integers are of unlimited size.
              Since Python 2 is no longer supported, this method should be deprecated.
        """
        return int(value)

    @staticmethod
    def _float_to_mysql(value: float) -> Optional[float]:
        """Convert value to float"""
        if math.isnan(value):
            return None
        return float(value)

    def _str_to_mysql(self, value: str) -> Union[bytes, HexLiteral]:
        """Convert value to string"""
        return self._unicode_to_mysql(value)

    def _unicode_to_mysql(self, value: str) -> Union[bytes, HexLiteral]:
        """Convert unicode"""
        charset = self.charset
        charset_id = self.charset_id
        if charset == "binary":
            charset = "utf8"
            charset_id = CharacterSet.get_charset_info(charset)[0]
        encoded = value.encode(charset)
        if charset_id in CharacterSet.slash_charsets:
            if b"\x5c" in encoded:
                return HexLiteral(value, charset)
        return encoded

    @staticmethod
    def _bytes_to_mysql(value: bytes) -> bytes:
        """Convert value to bytes"""
        return value

    @staticmethod
    def _bytearray_to_mysql(value: bytearray) -> bytes:
        """Convert value to bytes"""
        return bytes(value)

    @staticmethod
    def _bool_to_mysql(value: bool) -> int:
        """Convert value to boolean"""
        return 1 if value else 0

    @staticmethod
    def _nonetype_to_mysql(value: None) -> None:  # pylint: disable=unused-argument
        """
        This would return what None would be in MySQL, but instead we
        leave it None and return it right away. The actual conversion
        from None to NULL happens in the quoting functionality.

        Return None.
        """
        return None

    @staticmethod
    def _datetime_to_mysql(value: datetime.datetime) -> bytes:
        """
        Converts a datetime instance to a string suitable for MySQL.
        The returned string has format: %Y-%m-%d %H:%M:%S[.%f]

        If the instance isn't a datetime.datetime type, it return None.

        Returns a bytes.
        """
        if value.microsecond:
            fmt = "{0:04d}-{1:02d}-{2:02d} {3:02d}:{4:02d}:{5:02d}.{6:06d}"
            return fmt.format(
                value.year,
                value.month,
                value.day,
                value.hour,
                value.minute,
                value.second,
                value.microsecond,
            ).encode("ascii")

        fmt = "{0:04d}-{1:02d}-{2:02d} {3:02d}:{4:02d}:{5:02d}"
        return fmt.format(
            value.year,
            value.month,
            value.day,
            value.hour,
            value.minute,
            value.second,
        ).encode("ascii")

    @staticmethod
    def _date_to_mysql(value: datetime.date) -> bytes:
        """
        Converts a date instance to a string suitable for MySQL.
        The returned string has format: %Y-%m-%d

        If the instance isn't a datetime.date type, it return None.

        Returns a bytes.
        """
        return f"{value.year:04d}-{value.month:02d}-{value.day:02d}".encode("ascii")

    @staticmethod
    def _time_to_mysql(value: datetime.time) -> bytes:
        """
        Converts a time instance to a string suitable for MySQL.
        The returned string has format: %H:%M:%S[.%f]

        If the instance isn't a datetime.time type, it return None.

        Returns a bytes.
        """
        if value.microsecond:
            return value.strftime("%H:%M:%S.%f").encode("ascii")
        return value.strftime("%H:%M:%S").encode("ascii")

    @staticmethod
    def _struct_time_to_mysql(value: time.struct_time) -> bytes:
        """
        Converts a time.struct_time sequence to a string suitable
        for MySQL.
        The returned string has format: %Y-%m-%d %H:%M:%S

        Returns a bytes or None when not valid.
        """
        return time.strftime("%Y-%m-%d %H:%M:%S", value).encode("ascii")

    @staticmethod
    def _timedelta_to_mysql(value: datetime.timedelta) -> bytes:
        """
        Converts a timedelta instance to a string suitable for MySQL.
        The returned string has format: %H:%M:%S

        Returns a bytes.
        """
        seconds = abs(value.days * 86400 + value.seconds)

        if value.microseconds:
            fmt = "{0:02d}:{1:02d}:{2:02d}.{3:06d}"
            if value.days < 0:
                mcs = 1000000 - value.microseconds
                seconds -= 1
            else:
                mcs = value.microseconds
        else:
            fmt = "{0:02d}:{1:02d}:{2:02d}"

        if value.days < 0:
            fmt = "-" + fmt

        (hours, remainder) = divmod(seconds, 3600)
        (mins, secs) = divmod(remainder, 60)

        if value.microseconds:
            result = fmt.format(hours, mins, secs, mcs)
        else:
            result = fmt.format(hours, mins, secs)

        return result.encode("ascii")

    @staticmethod
    def _decimal_to_mysql(value: Decimal) -> Optional[bytes]:
        """
        Converts a decimal.Decimal instance to a string suitable for
        MySQL.

        Returns a bytes or None when not valid.
        """
        if isinstance(value, Decimal):
            return str(value).encode("ascii")

        return None

    def row_to_python(
        self, row: Tuple[bytes, ...], fields: List[DescriptionType]
    ) -> RowType:
        """Convert a MySQL text result row to Python types

        The row argument is a sequence containing text result returned
        by a MySQL server. Each value of the row is converted to the
        using the field type information in the fields argument.

        Returns a tuple.
        """
        i = 0
        result: List[ToPythonOutputTypes] = [None] * len(fields)

        if not self._cache_field_types:
            self._cache_field_types = {}
            for name, info in FieldType.desc.items():
                try:
                    self._cache_field_types[info[0]] = getattr(
                        self, f"_{name.lower()}_to_python"
                    )
                except AttributeError:
                    # We ignore field types which has no method
                    pass

        for field in fields:
            field_type = field[1]

            if (row[i] == 0 and field_type != FieldType.BIT) or row[i] is None:
                # Don't convert NULL value
                i += 1
                continue

            try:
                result[i] = self._cache_field_types[field_type](row[i], field)
            except KeyError:
                # If one type is not defined, we just return the value as str
                try:
                    result[i] = row[i].decode("utf-8")
                except UnicodeDecodeError:
                    result[i] = row[i]
            except (ValueError, TypeError) as err:
                # Item "ValueError" of "Union[ValueError, TypeError]" has no attribute "message"
                err.message = f"{err} (field {field[0]})"  # type: ignore[union-attr]
                raise

            i += 1

        return tuple(result)

    # pylint: disable=unused-argument
    @staticmethod
    def _float_to_python(value: bytes, desc: Optional[DescriptionType] = None) -> float:
        """
        Returns value as float type.
        """
        return float(value)

    _double_to_python = _float_to_python

    @staticmethod
    def _int_to_python(value: bytes, desc: Optional[DescriptionType] = None) -> int:
        """
        Returns value as int type.
        """
        return int(value)

    _tiny_to_python = _int_to_python
    _short_to_python = _int_to_python
    _int24_to_python = _int_to_python
    _long_to_python = _int_to_python
    _longlong_to_python = _int_to_python

    def _decimal_to_python(
        self, value: bytes, desc: Optional[DescriptionType] = None
    ) -> Decimal:
        """
        Returns value as a decimal.Decimal.
        """
        val = value.decode(self.charset)
        return Decimal(val)

    _newdecimal_to_python = _decimal_to_python

    @staticmethod
    def _str(value: bytes, desc: Optional[DescriptionType] = None) -> str:
        """
        Returns value as str type.
        """
        return str(value)

    @staticmethod
    def _bit_to_python(value: bytes, dsc: Optional[DescriptionType] = None) -> int:
        """Returns BIT columntype as integer"""
        int_val = value
        if len(int_val) < 8:
            int_val = b"\x00" * (8 - len(int_val)) + int_val
        return int(struct.unpack(">Q", int_val)[0])

    @staticmethod
    def _date_to_python(
        value: bytes, dsc: Optional[DescriptionType] = None
    ) -> Optional[datetime.date]:
        """Converts TIME column MySQL to a python datetime.datetime type.

        Raises ValueError if the value can not be converted.

        Returns DATE column type as datetime.date type.
        """
        if isinstance(value, datetime.date):
            return value
        try:
            parts = value.split(b"-")
            if len(parts) != 3:
                raise ValueError(f"invalid datetime format: {parts} len: {len(parts)}")
            try:
                return datetime.date(int(parts[0]), int(parts[1]), int(parts[2]))
            except ValueError:
                return None
        except (IndexError, ValueError):
            raise ValueError(
                f"Could not convert {repr(value)} to python datetime.timedelta"
            ) from None

    _NEWDATE_to_python = _date_to_python

    @staticmethod
    def _time_to_python(
        value: bytes, dsc: Optional[DescriptionType] = None
    ) -> datetime.timedelta:
        """Converts TIME column value to python datetime.time value type.

        Converts the TIME column MySQL type passed as bytes to a python
        datetime.datetime type.

        Raises ValueError if the value can not be converted.

        Returns datetime.timedelta type.
        """
        mcs: Optional[Union[int, bytes]] = None
        try:
            (hms, mcs) = value.split(b".")
            mcs = int(mcs.ljust(6, b"0"))
        except (TypeError, ValueError):
            hms = value
            mcs = 0
        try:
            (hours, mins, secs) = [int(d) for d in hms.split(b":")]
            if value[0] == 45 or value[0] == "-":
                mins, secs, mcs = (
                    -mins,
                    -secs,
                    -mcs,  # pylint: disable=invalid-unary-operand-type
                )
            return datetime.timedelta(
                hours=hours, minutes=mins, seconds=secs, microseconds=mcs
            )
        except (IndexError, TypeError, ValueError):
            raise ValueError(
                CONVERT_ERROR.format(value=value, pytype="datetime.timedelta")
            ) from None

    @staticmethod
    def _datetime_to_python(
        value: bytes, dsc: Optional[DescriptionType] = None
    ) -> Optional[datetime.datetime]:
        """Converts DATETIME column value to python datetime.time value type.

        Converts the DATETIME column MySQL type passed as bytes to a python
        datetime.datetime type.

        Returns: datetime.datetime type.
        """
        if isinstance(value, datetime.datetime):
            return value
        datetime_val = None
        mcs: Optional[Union[int, bytes]] = None
        try:
            (date_, time_) = value.split(b" ")
            if len(time_) > 8:
                (hms, mcs) = time_.split(b".")
                mcs = int(mcs.ljust(6, b"0"))
            else:
                hms = time_
                mcs = 0
            dtval = (
                [int(i) for i in date_.split(b"-")]
                + [int(i) for i in hms.split(b":")]
                + [
                    mcs,
                ]
            )
            if len(dtval) < 6:
                raise ValueError(f"invalid datetime format: {dtval} len: {len(dtval)}")
            # Note that by default MySQL accepts invalid timestamps
            # (this is also backward compatibility).
            # Traditionaly C/py returns None for this well formed but
            # invalid datetime for python like '0000-00-00 HH:MM:SS'.
            try:
                datetime_val = datetime.datetime(*dtval)  # type: ignore[arg-type]
            except ValueError:
                return None
        except (IndexError, TypeError):
            raise ValueError(
                CONVERT_ERROR.format(value=value, pytype="datetime.timedelta")
            ) from None

        return datetime_val

    _timestamp_to_python = _datetime_to_python

    @staticmethod
    def _year_to_python(value: bytes, dsc: Optional[DescriptionType] = None) -> int:
        """Returns YEAR column type as integer"""
        try:
            year = int(value)
        except ValueError as err:
            raise ValueError(f"Failed converting YEAR to int ({repr(value)})") from err

        return year

    def _set_to_python(
        self, value: bytes, dsc: Optional[DescriptionType] = None
    ) -> Set[str]:
        """Returns SET column type as set

        Actually, MySQL protocol sees a SET as a string type field. So this
        code isn't called directly, but used by STRING_to_python() method.

        Returns SET column type as a set.
        """
        set_type = None
        val = value.decode(self.charset)
        if not val:
            return set()
        try:
            set_type = set(val.split(","))
        except ValueError as err:
            raise ValueError(
                f"Could not convert set {repr(value)} to a sequence"
            ) from err
        return set_type

    def _string_to_python(
        self, value: bytes, dsc: Optional[DescriptionType] = None
    ) -> Union[StrOrBytes, Set[str]]:
        """
        Note that a SET is a string too, but using the FieldFlag we can see
        whether we have to split it.

        Returns string typed columns as string type.
        """
        if self.charset == "binary":
            return value
        if dsc is not None:
            if dsc[1] == FieldType.JSON and self.use_unicode:
                return value.decode(self.charset)
            if dsc[7] & FieldFlag.SET:
                return self._set_to_python(value, dsc)
            # 'binary' charset
            if dsc[8] == 63:
                return value
        if isinstance(value, (bytes, bytearray)) and self.use_unicode:
            try:
                return value.decode(self.charset)
            except UnicodeDecodeError:
                return value

        return value

    _var_string_to_python = _string_to_python
    _json_to_python = _string_to_python

    def _blob_to_python(
        self, value: bytes, dsc: Optional[DescriptionType] = None
    ) -> Union[StrOrBytes, Set[str]]:
        """Convert BLOB data type to Python."""
        if dsc is not None:
            if (
                dsc[7] & FieldFlag.BLOB
                and dsc[7] & FieldFlag.BINARY
                # 'binary' charset
                and dsc[8] == 63
            ):
                return bytes(value)
        return self._string_to_python(value, dsc)

    _long_blob_to_python = _blob_to_python
    _medium_blob_to_python = _blob_to_python
    _tiny_blob_to_python = _blob_to_python
    # pylint: enable=unused-argument

virt/lib/python3.9/site-packages/mysql/connector/conversion 3.py
PrinceDisant/LostInNL
# Copyright (c) 2009, 2023, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

"""Converting MySQL and Python types
"""

import datetime
import math
import struct
import time

from decimal import Decimal
from typing import Any, Callable, Dict, List, Optional, Set, Tuple, Union

from .constants import CharacterSet, FieldFlag, FieldType
from .custom_types import HexLiteral
from .types import (
    DescriptionType,
    RowType,
    StrOrBytes,
    ToMysqlInputTypes,
    ToMysqlOutputTypes,
    ToPythonOutputTypes,
)
from .utils import NUMERIC_TYPES

CONVERT_ERROR = "Could not convert '{value}' to python {pytype}"


class MySQLConverterBase:
    """Base class for conversion classes

    All class dealing with converting to and from MySQL data types must
    be a subclass of this class.
    """

    def __init__(
        self,
        charset: Optional[str] = "utf8",
        use_unicode: bool = True,
        str_fallback: bool = False,
    ) -> None:
        self.python_types: Optional[Tuple[Any, ...]] = None
        self.mysql_types: Optional[Tuple[Any, ...]] = None
        self.charset: Optional[str] = None
        self.charset_id: int = 0
        self.set_charset(charset)
        self.use_unicode: bool = use_unicode
        self.str_fallback: bool = str_fallback
        self._cache_field_types: Dict[
            int,
            Callable[[bytes, DescriptionType], ToPythonOutputTypes],
        ] = {}

    def set_charset(self, charset: Optional[str]) -> None:
        """Set character set"""
        if charset in ("utf8mb4", "utf8mb3"):
            charset = "utf8"
        if charset is not None:
            self.charset = charset
        else:
            # default to utf8
            self.charset = "utf8"
        self.charset_id = CharacterSet.get_charset_info(self.charset)[0]

    def set_unicode(self, value: bool = True) -> None:
        """Set whether to use Unicode"""
        self.use_unicode = value

    def to_mysql(
        self, value: ToMysqlInputTypes
    ) -> Union[ToMysqlInputTypes, HexLiteral]:
        """Convert Python data type to MySQL"""
        type_name = value.__class__.__name__.lower()
        try:
            converted: ToMysqlOutputTypes = getattr(self, f"_{type_name}_to_mysql")(
                value
            )
            return converted
        except AttributeError:
            return value

    def to_python(
        self, vtype: DescriptionType, value: Optional[bytes]
    ) -> ToPythonOutputTypes:
        """Convert MySQL data type to Python"""

        if (value == b"\x00" or value is None) and vtype[1] != FieldType.BIT:
            # Don't go further when we hit a NULL value
            return None

        if not self._cache_field_types:
            self._cache_field_types = {}
            for name, info in FieldType.desc.items():
                try:
                    self._cache_field_types[info[0]] = getattr(
                        self, f"_{name.lower()}_to_python"
                    )
                except AttributeError:
                    # We ignore field types which has no method
                    pass
        if value is None:
            return None
        try:
            return self._cache_field_types[vtype[1]](value, vtype)
        except KeyError:
            return value

    @staticmethod
    def escape(
        value: Any,
        sql_mode: Optional[str] = None,  # pylint: disable=unused-argument
    ) -> Any:
        """Escape buffer for sending to MySQL"""
        return value

    @staticmethod
    def quote(buf: Any) -> StrOrBytes:
        """Quote buffer for sending to MySQL"""
        return str(buf)


class MySQLConverter(MySQLConverterBase):
    """Default conversion class for MySQL Connector/Python.

     o escape method: for escaping values send to MySQL
     o quoting method: for quoting values send to MySQL in statements
     o conversion mapping: maps Python and MySQL data types to
       function for converting them.

    Whenever one needs to convert values differently, a converter_class
    argument can be given while instantiating a new connection like
    cnx.connect(converter_class=CustomMySQLConverterClass).

    """

    def __init__(
        self,
        charset: Optional[str] = None,
        use_unicode: bool = True,
        str_fallback: bool = False,
    ) -> None:
        MySQLConverterBase.__init__(self, charset, use_unicode, str_fallback)
        self._cache_field_types: Dict[
            int,
            Callable[[bytes, DescriptionType], ToPythonOutputTypes],
        ] = {}

    @staticmethod
    def escape(value: Any, sql_mode: Optional[str] = None) -> Any:
        """
        Escapes special characters as they are expected to by when MySQL
        receives them.
        As found in MySQL source mysys/charset.c

        Returns the value if not a string, or the escaped string.
        """
        if isinstance(value, (bytes, bytearray)):
            if sql_mode == "NO_BACKSLASH_ESCAPES":
                return value.replace(b"'", b"''")
            value = value.replace(b"\\", b"\\\\")
            value = value.replace(b"\n", b"\\n")
            value = value.replace(b"\r", b"\\r")
            value = value.replace(b"\047", b"\134\047")  # single quotes
            value = value.replace(b"\042", b"\134\042")  # double quotes
            value = value.replace(b"\032", b"\134\032")  # for Win32
        elif isinstance(value, str) and not isinstance(value, HexLiteral):
            if sql_mode == "NO_BACKSLASH_ESCAPES":
                return value.replace("'", "''")
            value = value.replace("\\", "\\\\")
            value = value.replace("\n", "\\n")
            value = value.replace("\r", "\\r")
            value = value.replace("\047", "\134\047")  # single quotes
            value = value.replace("\042", "\134\042")  # double quotes
            value = value.replace("\032", "\134\032")  # for Win32
        return value

    @staticmethod
    def quote(buf: Optional[Union[float, int, Decimal, HexLiteral, bytes]]) -> bytes:
        """
        Quote the parameters for commands. General rules:
          o numbers are returns as bytes using ascii codec
          o None is returned as bytearray(b'NULL')
          o Everything else is single quoted '<buf>'

        Returns a bytearray object.
        """
        if isinstance(buf, NUMERIC_TYPES):
            return str(buf).encode("ascii")
        if isinstance(buf, type(None)):
            return bytearray(b"NULL")
        return bytearray(b"'" + buf + b"'")  # type: ignore[operator]

    def to_mysql(self, value: ToMysqlInputTypes) -> ToMysqlOutputTypes:
        """Convert Python data type to MySQL"""
        type_name = value.__class__.__name__.lower()
        try:
            converted: ToMysqlOutputTypes = getattr(self, f"_{type_name}_to_mysql")(
                value
            )
            return converted
        except AttributeError:
            if self.str_fallback:
                return str(value).encode()
            raise TypeError(
                f"Python '{type_name}' cannot be converted to a MySQL type"
            ) from None

    def to_python(
        self,
        vtype: DescriptionType,
        value: Optional[bytes],
    ) -> ToPythonOutputTypes:
        """Convert MySQL data type to Python"""
        # \x00
        if value == 0 and vtype[1] != FieldType.BIT:
            # Don't go further when we hit a NULL value
            return None
        if value is None:
            return None

        if not self._cache_field_types:
            self._cache_field_types = {}
            for name, info in FieldType.desc.items():
                try:
                    self._cache_field_types[info[0]] = getattr(
                        self, f"_{name.lower()}_to_python"
                    )
                except AttributeError:
                    # We ignore field types which has no method
                    pass

        try:
            return self._cache_field_types[vtype[1]](value, vtype)
        except KeyError:
            # If one type is not defined, we just return the value as str
            try:
                return value.decode("utf-8")
            except UnicodeDecodeError:
                return value
        except ValueError as err:
            raise ValueError(f"{err} (field {vtype[0]})") from err
        except TypeError as err:
            raise TypeError(f"{err} (field {vtype[0]})") from err

    @staticmethod
    def _int_to_mysql(value: int) -> int:
        """Convert value to int"""
        return int(value)

    @staticmethod
    def _long_to_mysql(value: int) -> int:
        """Convert value to int

        Note: There is no type "long" in Python 3 since integers are of unlimited size.
              Since Python 2 is no longer supported, this method should be deprecated.
        """
        return int(value)

    @staticmethod
    def _float_to_mysql(value: float) -> Optional[float]:
        """Convert value to float"""
        if math.isnan(value):
            return None
        return float(value)

    def _str_to_mysql(self, value: str) -> Union[bytes, HexLiteral]:
        """Convert value to string"""
        return self._unicode_to_mysql(value)

    def _unicode_to_mysql(self, value: str) -> Union[bytes, HexLiteral]:
        """Convert unicode"""
        charset = self.charset
        charset_id = self.charset_id
        if charset == "binary":
            charset = "utf8"
            charset_id = CharacterSet.get_charset_info(charset)[0]
        encoded = value.encode(charset)
        if charset_id in CharacterSet.slash_charsets:
            if b"\x5c" in encoded:
                return HexLiteral(value, charset)
        return encoded

    @staticmethod
    def _bytes_to_mysql(value: bytes) -> bytes:
        """Convert value to bytes"""
        return value

    @staticmethod
    def _bytearray_to_mysql(value: bytearray) -> bytes:
        """Convert value to bytes"""
        return bytes(value)

    @staticmethod
    def _bool_to_mysql(value: bool) -> int:
        """Convert value to boolean"""
        return 1 if value else 0

    @staticmethod
    def _nonetype_to_mysql(value: None) -> None:  # pylint: disable=unused-argument
        """
        This would return what None would be in MySQL, but instead we
        leave it None and return it right away. The actual conversion
        from None to NULL happens in the quoting functionality.

        Return None.
        """
        return None

    @staticmethod
    def _datetime_to_mysql(value: datetime.datetime) -> bytes:
        """
        Converts a datetime instance to a string suitable for MySQL.
        The returned string has format: %Y-%m-%d %H:%M:%S[.%f]

        If the instance isn't a datetime.datetime type, it return None.

        Returns a bytes.
        """
        if value.microsecond:
            fmt = "{0:04d}-{1:02d}-{2:02d} {3:02d}:{4:02d}:{5:02d}.{6:06d}"
            return fmt.format(
                value.year,
                value.month,
                value.day,
                value.hour,
                value.minute,
                value.second,
                value.microsecond,
            ).encode("ascii")

        fmt = "{0:04d}-{1:02d}-{2:02d} {3:02d}:{4:02d}:{5:02d}"
        return fmt.format(
            value.year,
            value.month,
            value.day,
            value.hour,
            value.minute,
            value.second,
        ).encode("ascii")

    @staticmethod
    def _date_to_mysql(value: datetime.date) -> bytes:
        """
        Converts a date instance to a string suitable for MySQL.
        The returned string has format: %Y-%m-%d

        If the instance isn't a datetime.date type, it return None.

        Returns a bytes.
        """
        return f"{value.year:04d}-{value.month:02d}-{value.day:02d}".encode("ascii")

    @staticmethod
    def _time_to_mysql(value: datetime.time) -> bytes:
        """
        Converts a time instance to a string suitable for MySQL.
        The returned string has format: %H:%M:%S[.%f]

        If the instance isn't a datetime.time type, it return None.

        Returns a bytes.
        """
        if value.microsecond:
            return value.strftime("%H:%M:%S.%f").encode("ascii")
        return value.strftime("%H:%M:%S").encode("ascii")

    @staticmethod
    def _struct_time_to_mysql(value: time.struct_time) -> bytes:
        """
        Converts a time.struct_time sequence to a string suitable
        for MySQL.
        The returned string has format: %Y-%m-%d %H:%M:%S

        Returns a bytes or None when not valid.
        """
        return time.strftime("%Y-%m-%d %H:%M:%S", value).encode("ascii")

    @staticmethod
    def _timedelta_to_mysql(value: datetime.timedelta) -> bytes:
        """
        Converts a timedelta instance to a string suitable for MySQL.
        The returned string has format: %H:%M:%S

        Returns a bytes.
        """
        seconds = abs(value.days * 86400 + value.seconds)

        if value.microseconds:
            fmt = "{0:02d}:{1:02d}:{2:02d}.{3:06d}"
            if value.days < 0:
                mcs = 1000000 - value.microseconds
                seconds -= 1
            else:
                mcs = value.microseconds
        else:
            fmt = "{0:02d}:{1:02d}:{2:02d}"

        if value.days < 0:
            fmt = "-" + fmt

        (hours, remainder) = divmod(seconds, 3600)
        (mins, secs) = divmod(remainder, 60)

        if value.microseconds:
            result = fmt.format(hours, mins, secs, mcs)
        else:
            result = fmt.format(hours, mins, secs)

        return result.encode("ascii")

    @staticmethod
    def _decimal_to_mysql(value: Decimal) -> Optional[bytes]:
        """
        Converts a decimal.Decimal instance to a string suitable for
        MySQL.

        Returns a bytes or None when not valid.
        """
        if isinstance(value, Decimal):
            return str(value).encode("ascii")

        return None

    def row_to_python(
        self, row: Tuple[bytes, ...], fields: List[DescriptionType]
    ) -> RowType:
        """Convert a MySQL text result row to Python types

        The row argument is a sequence containing text result returned
        by a MySQL server. Each value of the row is converted to the
        using the field type information in the fields argument.

        Returns a tuple.
        """
        i = 0
        result: List[ToPythonOutputTypes] = [None] * len(fields)

        if not self._cache_field_types:
            self._cache_field_types = {}
            for name, info in FieldType.desc.items():
                try:
                    self._cache_field_types[info[0]] = getattr(
                        self, f"_{name.lower()}_to_python"
                    )
                except AttributeError:
                    # We ignore field types which has no method
                    pass

        for field in fields:
            field_type = field[1]

            if (row[i] == 0 and field_type != FieldType.BIT) or row[i] is None:
                # Don't convert NULL value
                i += 1
                continue

            try:
                result[i] = self._cache_field_types[field_type](row[i], field)
            except KeyError:
                # If one type is not defined, we just return the value as str
                try:
                    result[i] = row[i].decode("utf-8")
                except UnicodeDecodeError:
                    result[i] = row[i]
            except (ValueError, TypeError) as err:
                # Item "ValueError" of "Union[ValueError, TypeError]" has no attribute "message"
                err.message = f"{err} (field {field[0]})"  # type: ignore[union-attr]
                raise

            i += 1

        return tuple(result)

    # pylint: disable=unused-argument
    @staticmethod
    def _float_to_python(value: bytes, desc: Optional[DescriptionType] = None) -> float:
        """
        Returns value as float type.
        """
        return float(value)

    _double_to_python = _float_to_python

    @staticmethod
    def _int_to_python(value: bytes, desc: Optional[DescriptionType] = None) -> int:
        """
        Returns value as int type.
        """
        return int(value)

    _tiny_to_python = _int_to_python
    _short_to_python = _int_to_python
    _int24_to_python = _int_to_python
    _long_to_python = _int_to_python
    _longlong_to_python = _int_to_python

    def _decimal_to_python(
        self, value: bytes, desc: Optional[DescriptionType] = None
    ) -> Decimal:
        """
        Returns value as a decimal.Decimal.
        """
        val = value.decode(self.charset)
        return Decimal(val)

    _newdecimal_to_python = _decimal_to_python

    @staticmethod
    def _str(value: bytes, desc: Optional[DescriptionType] = None) -> str:
        """
        Returns value as str type.
        """
        return str(value)

    @staticmethod
    def _bit_to_python(value: bytes, dsc: Optional[DescriptionType] = None) -> int:
        """Returns BIT columntype as integer"""
        int_val = value
        if len(int_val) < 8:
            int_val = b"\x00" * (8 - len(int_val)) + int_val
        return int(struct.unpack(">Q", int_val)[0])

    @staticmethod
    def _date_to_python(
        value: bytes, dsc: Optional[DescriptionType] = None
    ) -> Optional[datetime.date]:
        """Converts TIME column MySQL to a python datetime.datetime type.

        Raises ValueError if the value can not be converted.

        Returns DATE column type as datetime.date type.
        """
        if isinstance(value, datetime.date):
            return value
        try:
            parts = value.split(b"-")
            if len(parts) != 3:
                raise ValueError(f"invalid datetime format: {parts} len: {len(parts)}")
            try:
                return datetime.date(int(parts[0]), int(parts[1]), int(parts[2]))
            except ValueError:
                return None
        except (IndexError, ValueError):
            raise ValueError(
                f"Could not convert {repr(value)} to python datetime.timedelta"
            ) from None

    _NEWDATE_to_python = _date_to_python

    @staticmethod
    def _time_to_python(
        value: bytes, dsc: Optional[DescriptionType] = None
    ) -> datetime.timedelta:
        """Converts TIME column value to python datetime.time value type.

        Converts the TIME column MySQL type passed as bytes to a python
        datetime.datetime type.

        Raises ValueError if the value can not be converted.

        Returns datetime.timedelta type.
        """
        mcs: Optional[Union[int, bytes]] = None
        try:
            (hms, mcs) = value.split(b".")
            mcs = int(mcs.ljust(6, b"0"))
        except (TypeError, ValueError):
            hms = value
            mcs = 0
        try:
            (hours, mins, secs) = [int(d) for d in hms.split(b":")]
            if value[0] == 45 or value[0] == "-":
                mins, secs, mcs = (
                    -mins,
                    -secs,
                    -mcs,  # pylint: disable=invalid-unary-operand-type
                )
            return datetime.timedelta(
                hours=hours, minutes=mins, seconds=secs, microseconds=mcs
            )
        except (IndexError, TypeError, ValueError):
            raise ValueError(
                CONVERT_ERROR.format(value=value, pytype="datetime.timedelta")
            ) from None

    @staticmethod
    def _datetime_to_python(
        value: bytes, dsc: Optional[DescriptionType] = None
    ) -> Optional[datetime.datetime]:
        """Converts DATETIME column value to python datetime.time value type.

        Converts the DATETIME column MySQL type passed as bytes to a python
        datetime.datetime type.

        Returns: datetime.datetime type.
        """
        if isinstance(value, datetime.datetime):
            return value
        datetime_val = None
        mcs: Optional[Union[int, bytes]] = None
        try:
            (date_, time_) = value.split(b" ")
            if len(time_) > 8:
                (hms, mcs) = time_.split(b".")
                mcs = int(mcs.ljust(6, b"0"))
            else:
                hms = time_
                mcs = 0
            dtval = (
                [int(i) for i in date_.split(b"-")]
                + [int(i) for i in hms.split(b":")]
                + [
                    mcs,
                ]
            )
            if len(dtval) < 6:
                raise ValueError(f"invalid datetime format: {dtval} len: {len(dtval)}")
            # Note that by default MySQL accepts invalid timestamps
            # (this is also backward compatibility).
            # Traditionaly C/py returns None for this well formed but
            # invalid datetime for python like '0000-00-00 HH:MM:SS'.
            try:
                datetime_val = datetime.datetime(*dtval)  # type: ignore[arg-type]
            except ValueError:
                return None
        except (IndexError, TypeError):
            raise ValueError(
                CONVERT_ERROR.format(value=value, pytype="datetime.timedelta")
            ) from None

        return datetime_val

    _timestamp_to_python = _datetime_to_python

    @staticmethod
    def _year_to_python(value: bytes, dsc: Optional[DescriptionType] = None) -> int:
        """Returns YEAR column type as integer"""
        try:
            year = int(value)
        except ValueError as err:
            raise ValueError(f"Failed converting YEAR to int ({repr(value)})") from err

        return year

    def _set_to_python(
        self, value: bytes, dsc: Optional[DescriptionType] = None
    ) -> Set[str]:
        """Returns SET column type as set

        Actually, MySQL protocol sees a SET as a string type field. So this
        code isn't called directly, but used by STRING_to_python() method.

        Returns SET column type as a set.
        """
        set_type = None
        val = value.decode(self.charset)
        if not val:
            return set()
        try:
            set_type = set(val.split(","))
        except ValueError as err:
            raise ValueError(
                f"Could not convert set {repr(value)} to a sequence"
            ) from err
        return set_type

    def _string_to_python(
        self, value: bytes, dsc: Optional[DescriptionType] = None
    ) -> Union[StrOrBytes, Set[str]]:
        """
        Note that a SET is a string too, but using the FieldFlag we can see
        whether we have to split it.

        Returns string typed columns as string type.
        """
        if self.charset == "binary":
            return value
        if dsc is not None:
            if dsc[1] == FieldType.JSON and self.use_unicode:
                return value.decode(self.charset)
            if dsc[7] & FieldFlag.SET:
                return self._set_to_python(value, dsc)
            # 'binary' charset
            if dsc[8] == 63:
                return value
        if isinstance(value, (bytes, bytearray)) and self.use_unicode:
            try:
                return value.decode(self.charset)
            except UnicodeDecodeError:
                return value

        return value

    _var_string_to_python = _string_to_python
    _json_to_python = _string_to_python

    def _blob_to_python(
        self, value: bytes, dsc: Optional[DescriptionType] = None
    ) -> Union[StrOrBytes, Set[str]]:
        """Convert BLOB data type to Python."""
        if dsc is not None:
            if (
                dsc[7] & FieldFlag.BLOB
                and dsc[7] & FieldFlag.BINARY
                # 'binary' charset
                and dsc[8] == 63
            ):
                return bytes(value)
        return self._string_to_python(value, dsc)

    _long_blob_to_python = _blob_to_python
    _medium_blob_to_python = _blob_to_python
    _tiny_blob_to_python = _blob_to_python
    # pylint: enable=unused-argument

virt/lib/python3.9/site-packages/mysql/connector/conversion.py
PrinceDisant/LostInNL
# Copyright (c) 2009, 2023, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

# mypy: disable-error-code="assignment,arg-type,attr-defined,index,override,call-overload"

"""Cursor classes."""
from __future__ import annotations

import re
import warnings
import weakref

from collections import namedtuple
from decimal import Decimal
from typing import (
    Any,
    Dict,
    Generator,
    Iterator,
    List,
    NoReturn,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
)
from weakref import CallableProxyType

from .abstracts import NAMED_TUPLE_CACHE, MySQLConnectionAbstract, MySQLCursorAbstract
from .constants import ServerFlag
from .errors import (
    Error,
    InterfaceError,
    NotSupportedError,
    ProgrammingError,
    get_mysql_exception,
)
from .types import (
    DescriptionType,
    EofPacketType,
    ParamsDictType,
    ParamsSequenceOrDictType,
    ParamsSequenceType,
    ResultType,
    RowType,
    StrOrBytes,
    ToPythonOutputTypes,
    WarningType,
)

SQL_COMMENT = r"\/\*.*?\*\/"
RE_SQL_COMMENT = re.compile(
    rf"""({SQL_COMMENT})|(["'`][^"'`]*?({SQL_COMMENT})[^"'`]*?["'`])""",
    re.I | re.M | re.S,
)
RE_SQL_ON_DUPLICATE = re.compile(
    r"""\s*ON\s+DUPLICATE\s+KEY(?:[^"'`]*["'`][^"'`]*["'`])*[^"'`]*$""",
    re.I | re.M | re.S,
)
RE_SQL_INSERT_STMT = re.compile(
    rf"({SQL_COMMENT}|\s)*INSERT({SQL_COMMENT}|\s)"
    r"*(?:IGNORE\s+)?INTO\s+[`'\"]?.+[`'\"]?(?:\.[`'\"]?.+[`'\"]?)"
    r"{0,2}\s+VALUES\s*(\(.+\)).*",
    re.I | re.M | re.S,
)
RE_SQL_INSERT_VALUES = re.compile(r".*VALUES\s*(\(.+\)).*", re.I | re.M | re.S)
RE_PY_PARAM = re.compile(b"(%s)")
RE_PY_MAPPING_PARAM = re.compile(
    rb"""
    %
    \((?P<mapping_key>[^)]+)\)
    (?P<conversion_type>[diouxXeEfFgGcrs%])
    """,
    re.X,
)
RE_SQL_SPLIT_STMTS = re.compile(
    b""";(?=(?:[^"'`]*(?:"[^"]*"|'[^']*'|`[^`]*`))*[^"'`]*$)"""
)
RE_SQL_FIND_PARAM = re.compile(b"""%s(?=(?:[^"'`]*["'`][^"'`]*["'`])*[^"'`]*$)""")
RE_SQL_PYTHON_REPLACE_PARAM = re.compile(r"%\(.*?\)s")
RE_SQL_PYTHON_CAPTURE_PARAM_NAME = re.compile(r"%\((.*?)\)s")

ERR_NO_RESULT_TO_FETCH = "No result set to fetch from"

MAX_RESULTS = 4294967295


class _ParamSubstitutor:
    """
    Substitutes parameters into SQL statement.
    """

    def __init__(self, params: Sequence[bytes]) -> None:
        self.params: Sequence[bytes] = params
        self.index: int = 0

    def __call__(self, matchobj: re.Match) -> bytes:
        index = self.index
        self.index += 1
        try:
            return bytes(self.params[index])
        except IndexError:
            raise ProgrammingError(
                "Not enough parameters for the SQL statement"
            ) from None

    @property
    def remaining(self) -> int:
        """Returns number of parameters remaining to be substituted"""
        return len(self.params) - self.index


def _bytestr_format_dict(bytestr: bytes, value_dict: Dict[bytes, bytes]) -> bytes:
    """
    >>> _bytestr_format_dict(b'%(a)s', {b'a': b'foobar'})
    b'foobar
    >>> _bytestr_format_dict(b'%%(a)s', {b'a': b'foobar'})
    b'%%(a)s'
    >>> _bytestr_format_dict(b'%%%(a)s', {b'a': b'foobar'})
    b'%%foobar'
    >>> _bytestr_format_dict(b'%(x)s %(y)s',
    ...                      {b'x': b'x=%(y)s', b'y': b'y=%(x)s'})
    b'x=%(y)s y=%(x)s'
    """

    def replace(matchobj: re.Match) -> bytes:
        """Replace pattern."""
        value: Optional[bytes] = None
        groups = matchobj.groupdict()
        if groups["conversion_type"] == b"%":
            value = b"%"
        if groups["conversion_type"] == b"s":
            key = groups["mapping_key"]
            value = value_dict[key]
        if value is None:
            raise ValueError(
                f"Unsupported conversion_type: {groups['conversion_type']}"
            )
        return value

    stmt = RE_PY_MAPPING_PARAM.sub(replace, bytestr)
    return stmt


class CursorBase(MySQLCursorAbstract):
    """
    Base for defining MySQLCursor. This class is a skeleton and defines
    methods and members as required for the Python Database API
    Specification v2.0.

    It's better to inherite from MySQLCursor.
    """

    _raw: bool = False

    def __init__(self) -> None:
        self._description: Optional[List[DescriptionType]] = None
        self._rowcount: int = -1
        self.arraysize: int = 1
        super().__init__()

    def callproc(self, procname: str, args: Sequence[Any] = ()) -> Any:
        """Calls a stored procedue with the given arguments

        The arguments will be set during this session, meaning
        they will be called like  _<procname>__arg<nr> where
        <nr> is an enumeration (+1) of the arguments.

        Coding Example:
          1) Definining the Stored Routine in MySQL:
          CREATE PROCEDURE multiply(IN pFac1 INT, IN pFac2 INT, OUT pProd INT)
          BEGIN
            SET pProd := pFac1 * pFac2;
          END

          2) Executing in Python:
          args = (5,5,0) # 0 is to hold pprod
          cursor.callproc('multiply', args)
          print(cursor.fetchone())

        Does not return a value, but a result set will be
        available when the CALL-statement execute successfully.
        Raises exceptions when something is wrong.
        """

    def close(self) -> Any:
        """Close the cursor."""

    def execute(
        self,
        operation: Any,
        params: Union[Sequence[Any], Dict[str, Any]] = (),
        multi: bool = False,
    ) -> Any:
        """Executes the given operation

        Executes the given operation substituting any markers with
        the given parameters.

        For example, getting all rows where id is 5:
          cursor.execute("SELECT * FROM t1 WHERE id = %s", (5,))

        The multi argument should be set to True when executing multiple
        statements in one operation. If not set and multiple results are
        found, an InterfaceError will be raised.

        If warnings where generated, and connection.get_warnings is True, then
        self._warnings will be a list containing these warnings.

        Returns an iterator when multi is True, otherwise None.
        """

    def executemany(
        self, operation: Any, seq_params: Sequence[Union[Sequence[Any], Dict[str, Any]]]
    ) -> Any:
        """Execute the given operation multiple times

        The executemany() method will execute the operation iterating
        over the list of parameters in seq_params.

        Example: Inserting 3 new employees and their phone number

        data = [
            ('Jane','555-001'),
            ('Joe', '555-001'),
            ('John', '555-003')
            ]
        stmt = "INSERT INTO employees (name, phone) VALUES ('%s','%s')"
        cursor.executemany(stmt, data)

        INSERT statements are optimized by batching the data, that is
        using the MySQL multiple rows syntax.

        Results are discarded. If they are needed, consider looping over
        data using the execute() method.
        """

    def fetchone(self) -> Optional[Sequence[Any]]:
        """Return next row of a query result set.

        Returns:
            tuple or None: A row from query result set.
        """

    def fetchmany(self, size: int = 1) -> List[Sequence[Any]]:
        """Return the next set of rows of a query result set.

        When no more rows are available, it returns an empty list.
        The number of rows returned can be specified using the size argument,
        which defaults to one.

        Returns:
            list: The next set of rows of a query result set.
        """

    def fetchall(self) -> List[Sequence[Any]]:
        """Return all rows of a query result set.

        Returns:
            list: A list of tuples with all rows of a query result set.
        """

    def nextset(self) -> Any:
        """Not Implemented."""

    def setinputsizes(self, sizes: Any) -> Any:
        """Not Implemented."""

    def setoutputsize(self, size: Any, column: Any = None) -> Any:
        """Not Implemented."""

    def reset(self, free: bool = True) -> Any:
        """Reset the cursor to default"""

    @property
    def description(self) -> Optional[List[DescriptionType]]:
        """Returns description of columns in a result

        This property returns a list of tuples describing the columns in
        in a result set. A tuple is described as follows::

                (column_name,
                 type,
                 None,
                 None,
                 None,
                 None,
                 null_ok,
                 column_flags)  # Addition to PEP-249 specs

        Returns a list of tuples.
        """
        return self._description

    @property
    def rowcount(self) -> int:
        """Returns the number of rows produced or affected

        This property returns the number of rows produced by queries
        such as a SELECT, or affected rows when executing DML statements
        like INSERT or UPDATE.

        Note that for non-buffered cursors it is impossible to know the
        number of rows produced before having fetched them all. For those,
        the number of rows will be -1 right after execution, and
        incremented when fetching rows.

        Returns an integer.
        """
        return self._rowcount


class MySQLCursor(CursorBase):
    """Default cursor for interacting with MySQL

    This cursor will execute statements and handle the result. It will
    not automatically fetch all rows.

    MySQLCursor should be inherited whenever other functionallity is
    required. An example would to change the fetch* member functions
    to return dictionaries instead of lists of values.

    Implements the Python Database API Specification v2.0 (PEP-249)
    """

    def __init__(
        self, connection: Optional[Type[MySQLConnectionAbstract]] = None
    ) -> None:
        CursorBase.__init__(self)
        self._connection: CallableProxyType[Type[MySQLConnectionAbstract]] = None
        self._nextrow: Tuple[Optional[RowType], Optional[EofPacketType]] = (
            None,
            None,
        )
        self._binary: bool = False

        if connection is not None:
            self._set_connection(connection)

    def __iter__(self) -> Iterator[RowType]:
        """
        Iteration over the result set which calls self.fetchone()
        and returns the next row.
        """
        return iter(self.fetchone, None)

    def _set_connection(self, connection: Type[MySQLConnectionAbstract]) -> None:
        """Set the connection"""
        try:
            self._connection = weakref.proxy(connection)
            self._connection.is_connected()
        except (AttributeError, TypeError):
            raise InterfaceError(errno=2048) from None

    def _reset_result(self) -> None:
        """Reset the cursor to default"""
        self._rowcount: int = -1
        self._nextrow = (None, None)
        self._stored_results: List[MySQLCursor] = []
        self._warnings: Optional[List[WarningType]] = None
        self._warning_count: int = 0
        self._description: Optional[List[DescriptionType]] = None
        self._executed: Optional[StrOrBytes] = None
        self._executed_list: List[StrOrBytes] = []
        self.reset()

    def _have_unread_result(self) -> bool:
        """Check whether there is an unread result"""
        try:
            return self._connection.unread_result
        except AttributeError:
            return False

    def _check_executed(self) -> None:
        """Check if the statement has been executed.

        Raises an error if the statement has not been executed.
        """
        if self._executed is None:
            raise InterfaceError(ERR_NO_RESULT_TO_FETCH)

    def __next__(self) -> RowType:
        """
        Used for iterating over the result set. Calles self.fetchone()
        to get the next row.
        """
        try:
            row = self.fetchone()
        except InterfaceError:
            raise StopIteration from None
        if not row:
            raise StopIteration
        return row

    def close(self) -> bool:
        """Close the cursor

        Returns True when successful, otherwise False.
        """
        if self._connection is None:
            return False

        self._connection.handle_unread_result()
        self._reset_result()
        self._connection = None

        return True

    def _process_params_dict(
        self, params: ParamsDictType
    ) -> Dict[bytes, Union[bytes, Decimal]]:
        """Process query parameters given as dictionary"""
        res: Dict[bytes, Any] = {}
        try:
            sql_mode = self._connection.sql_mode
            to_mysql = self._connection.converter.to_mysql
            escape = self._connection.converter.escape
            quote = self._connection.converter.quote
            for key, value in params.items():
                conv = value
                conv = to_mysql(conv)
                conv = escape(conv, sql_mode)
                if not isinstance(value, Decimal):
                    conv = quote(conv)
                res[key.encode()] = conv
        except Exception as err:
            raise ProgrammingError(
                f"Failed processing pyformat-parameters; {err}"
            ) from err
        return res

    def _process_params(
        self, params: ParamsSequenceType
    ) -> Tuple[Union[bytes, Decimal], ...]:
        """Process query parameters."""
        res = params[:]
        try:
            sql_mode = self._connection.sql_mode
            to_mysql = self._connection.converter.to_mysql
            escape = self._connection.converter.escape
            quote = self._connection.converter.quote
            res = [to_mysql(value) for value in res]
            res = [escape(value, sql_mode) for value in res]
            res = [
                quote(value) if not isinstance(params[i], Decimal) else value
                for i, value in enumerate(res)
            ]
        except Exception as err:
            raise ProgrammingError(
                f"Failed processing format-parameters; {err}"
            ) from err
        return tuple(res)

    def _handle_noresultset(self, res: ResultType) -> None:
        """Handles result of execute() when there is no result set"""
        try:
            self._rowcount = res["affected_rows"]
            self._last_insert_id = res["insert_id"]
            self._warning_count = res["warning_count"]
        except (KeyError, TypeError) as err:
            raise ProgrammingError(f"Failed handling non-resultset; {err}") from None

        self._handle_warnings()

    def _handle_resultset(self) -> None:
        """Handles result set

        This method handles the result set and is called after reading
        and storing column information in _handle_result(). For non-buffering
        cursors, this method is usually doing nothing.
        """

    def _handle_result(self, result: ResultType) -> None:
        """
        Handle the result after a command was send. The result can be either
        an OK-packet or a dictionary containing column/eof information.

        Raises InterfaceError when result is not a dict() or result is
        invalid.
        """
        if not isinstance(result, dict):
            raise InterfaceError("Result was not a dict()")

        if "columns" in result:
            # Weak test, must be column/eof information
            self._description = result["columns"]
            self._connection.unread_result = True
            self._handle_resultset()
        elif "affected_rows" in result:
            # Weak test, must be an OK-packet
            self._connection.unread_result = False
            self._handle_noresultset(result)
        else:
            raise InterfaceError("Invalid result")

    def _execute_iter(
        self, query_iter: Generator[ResultType, None, None]
    ) -> Generator[MySQLCursor, None, None]:
        """Generator returns MySQLCursor objects for multiple statements

        This method is only used when multiple statements are executed
        by the execute() method. It uses zip() to make an iterator from the
        given query_iter (result of MySQLConnection.cmd_query_iter()) and
        the list of statements that were executed.
        """
        executed_list = RE_SQL_SPLIT_STMTS.split(self._executed)

        i = 0
        while True:
            try:
                result = next(query_iter)
                self._reset_result()
                self._handle_result(result)
                try:
                    self._executed = executed_list[i].strip()
                    i += 1
                except IndexError:
                    self._executed = executed_list[0]

                yield self
            except StopIteration:
                return

    def execute(
        self,
        operation: StrOrBytes,
        params: Optional[ParamsSequenceOrDictType] = None,
        multi: bool = False,
    ) -> Optional[Generator[MySQLCursor, None, None]]:
        """Executes the given operation

        Executes the given operation substituting any markers with
        the given parameters.

        For example, getting all rows where id is 5:
          cursor.execute("SELECT * FROM t1 WHERE id = %s", (5,))

        The multi argument should be set to True when executing multiple
        statements in one operation. If not set and multiple results are
        found, an InterfaceError will be raised.

        If warnings where generated, and connection.get_warnings is True, then
        self._warnings will be a list containing these warnings.

        Returns an iterator when multi is True, otherwise None.
        """
        if not operation:
            return None

        try:
            if not self._connection:
                raise ProgrammingError
        except (ProgrammingError, ReferenceError) as err:
            raise ProgrammingError("Cursor is not connected") from err

        self._connection.handle_unread_result()

        self._reset_result()
        stmt: StrOrBytes = ""

        try:
            if not isinstance(operation, (bytes, bytearray)):
                stmt = operation.encode(self._connection.python_charset)
            else:
                stmt = operation
        except (UnicodeDecodeError, UnicodeEncodeError) as err:
            raise ProgrammingError(str(err)) from err

        if params:
            if isinstance(params, dict):
                stmt = _bytestr_format_dict(stmt, self._process_params_dict(params))
            elif isinstance(params, (list, tuple)):
                psub = _ParamSubstitutor(self._process_params(params))
                stmt = RE_PY_PARAM.sub(psub, stmt)
                if psub.remaining != 0:
                    raise ProgrammingError(
                        "Not all parameters were used in the SQL statement"
                    )
            else:
                raise ProgrammingError(
                    f"Could not process parameters: {type(params).__name__}({params}),"
                    " it must be of type list, tuple or dict"
                )

        self._executed = stmt
        if multi:
            self._executed_list = []
            return self._execute_iter(self._connection.cmd_query_iter(stmt))

        try:
            self._handle_result(self._connection.cmd_query(stmt))
        except InterfaceError as err:
            if self._connection.have_next_result:
                raise InterfaceError(
                    "Use multi=True when executing multiple statements"
                ) from err
            raise
        return None

    def _batch_insert(
        self, operation: str, seq_params: Sequence[ParamsSequenceOrDictType]
    ) -> Optional[bytes]:
        """Implements multi row insert"""

        def remove_comments(match: re.Match) -> str:
            """Remove comments from INSERT statements.

            This function is used while removing comments from INSERT
            statements. If the matched string is a comment not enclosed
            by quotes, it returns an empty string, else the string itself.
            """
            if match.group(1):
                return ""
            return match.group(2)

        tmp = re.sub(
            RE_SQL_ON_DUPLICATE,
            "",
            re.sub(RE_SQL_COMMENT, remove_comments, operation),
        )

        matches = re.search(RE_SQL_INSERT_VALUES, tmp)
        if not matches:
            raise InterfaceError(
                "Failed rewriting statement for multi-row INSERT. Check SQL syntax"
            )
        fmt = matches.group(1).encode(self._connection.python_charset)
        values = []

        try:
            stmt = operation.encode(self._connection.python_charset)
            for params in seq_params:
                tmp = fmt
                if isinstance(params, dict):
                    tmp = _bytestr_format_dict(tmp, self._process_params_dict(params))
                else:
                    psub = _ParamSubstitutor(self._process_params(params))
                    tmp = RE_PY_PARAM.sub(psub, tmp)
                    if psub.remaining != 0:
                        raise ProgrammingError(
                            "Not all parameters were used in the SQL statement"
                        )
                values.append(tmp)
            if fmt in stmt:
                stmt = stmt.replace(fmt, b",".join(values), 1)
                self._executed = stmt
                return stmt
            return None
        except (UnicodeDecodeError, UnicodeEncodeError) as err:
            raise ProgrammingError(str(err)) from err
        except Error:
            raise
        except Exception as err:
            raise InterfaceError(f"Failed executing the operation; {err}") from None

    def executemany(
        self, operation: str, seq_params: Sequence[ParamsSequenceOrDictType]
    ) -> Optional[Generator[MySQLCursor, None, None]]:
        """Execute the given operation multiple times

        The executemany() method will execute the operation iterating
        over the list of parameters in seq_params.

        Example: Inserting 3 new employees and their phone number

        data = [
            ('Jane','555-001'),
            ('Joe', '555-001'),
            ('John', '555-003')
            ]
        stmt = "INSERT INTO employees (name, phone) VALUES ('%s','%s)"
        cursor.executemany(stmt, data)

        INSERT statements are optimized by batching the data, that is
        using the MySQL multiple rows syntax.

        Results are discarded. If they are needed, consider looping over
        data using the execute() method.
        """
        if not operation or not seq_params:
            return None
        self._connection.handle_unread_result()

        try:
            _ = iter(seq_params)
        except TypeError as err:
            raise ProgrammingError("Parameters for query must be an Iterable") from err

        # Optimize INSERTs by batching them
        if re.match(RE_SQL_INSERT_STMT, operation):
            if not seq_params:
                self._rowcount = 0
                return None
            stmt = self._batch_insert(operation, seq_params)
            if stmt is not None:
                self._executed = stmt
                return self.execute(stmt)

        rowcnt = 0
        try:
            for params in seq_params:
                self.execute(operation, params)
                if self.with_rows and self._have_unread_result():
                    self.fetchall()
                rowcnt += self._rowcount
        except (ValueError, TypeError) as err:
            raise InterfaceError(f"Failed executing the operation; {err}") from None
        self._rowcount = rowcnt
        return None

    def stored_results(self) -> Iterator[MySQLCursor]:
        """Returns an iterator for stored results

        This method returns an iterator over results which are stored when
        callproc() is called. The iterator will provide MySQLCursorBuffered
        instances.

        Returns a iterator.
        """
        return iter(self._stored_results)

    def callproc(
        self,
        procname: str,
        args: Sequence[Any] = (),
    ) -> Optional[Union[Dict[str, ToPythonOutputTypes], RowType]]:
        """Calls a stored procedure with the given arguments

        The arguments will be set during this session, meaning
        they will be called like  _<procname>__arg<nr> where
        <nr> is an enumeration (+1) of the arguments.

        Coding Example:
          1) Defining the Stored Routine in MySQL:
          CREATE PROCEDURE multiply(IN pFac1 INT, IN pFac2 INT, OUT pProd INT)
          BEGIN
            SET pProd := pFac1 * pFac2;
          END

          2) Executing in Python:
          args = (5, 5, 0)  # 0 is to hold pprod
          cursor.callproc('multiply', args)
          print(cursor.fetchone())

        For OUT and INOUT parameters the user should provide the
        type of the parameter as well. The argument should be a
        tuple with first item as the value of the parameter to pass
        and second argument the type of the argument.

        In the above example, one can call callproc method like:
          args = (5, 5, (0, 'INT'))
          cursor.callproc('multiply', args)

        The type of the argument given in the tuple will be used by
        the MySQL CAST function to convert the values in the corresponding
        MySQL type (See CAST in MySQL Reference for more information)

        Does not return a value, but a result set will be
        available when the CALL-statement execute successfully.
        Raises exceptions when something is wrong.
        """
        if not procname or not isinstance(procname, str):
            raise ValueError("procname must be a string")

        if not isinstance(args, (tuple, list)):
            raise ValueError("args must be a sequence")

        argfmt = "@_{name}_arg{index}"
        self._stored_results = []

        results = []
        try:
            argnames = []
            argtypes = []

            # MySQL itself does support calling procedures with their full
            # name <database>.<procedure_name>. It's necessary to split
            # by '.' and grab the procedure name from procname.
            procname_abs = procname.split(".")[-1]
            if args:
                argvalues = []
                for idx, arg in enumerate(args):
                    argname = argfmt.format(name=procname_abs, index=idx + 1)
                    argnames.append(argname)
                    if isinstance(arg, tuple):
                        argtypes.append(f" CAST({argname} AS {arg[1]})")
                        argvalues.append(arg[0])
                    else:
                        argtypes.append(argname)
                        argvalues.append(arg)

                placeholders = ",".join(f"{arg}=%s" for arg in argnames)
                self.execute(f"SET {placeholders}", argvalues)

            call = f"CALL {procname}({','.join(argnames)})"

            # We disable consuming results temporary to make sure we
            # getting all results
            can_consume_results = self._connection.can_consume_results
            for result in self._connection.cmd_query_iter(call):
                self._connection.can_consume_results = False
                if isinstance(self, (MySQLCursorDict, MySQLCursorBufferedDict)):
                    cursor_class = MySQLCursorBufferedDict
                elif isinstance(
                    self,
                    (MySQLCursorNamedTuple, MySQLCursorBufferedNamedTuple),
                ):
                    cursor_class = MySQLCursorBufferedNamedTuple
                elif self._raw:
                    cursor_class = MySQLCursorBufferedRaw
                else:
                    cursor_class = MySQLCursorBuffered
                # pylint: disable=protected-access
                cur = cursor_class(self._connection.get_self())
                cur._executed = f"(a result of {call})"
                cur._handle_result(result)
                # pylint: enable=protected-access
                if cur.warnings is not None:
                    self._warnings = cur.warnings
                if "columns" in result:
                    results.append(cur)
            self._connection.can_consume_results = can_consume_results

            if argnames:
                # Create names aliases to be compatible with namedtuples
                args = [
                    f"{name} AS {alias}"
                    for name, alias in zip(
                        argtypes, [arg.lstrip("@_") for arg in argnames]
                    )
                ]
                select = f"SELECT {','.join(args)}"
                self.execute(select)
                self._stored_results = results
                return self.fetchone()

            self._stored_results = results
            return tuple()

        except Error:
            raise
        except Exception as err:
            raise InterfaceError(f"Failed calling stored routine; {err}") from None

    def getlastrowid(self) -> Optional[int]:
        """Returns the value generated for an AUTO_INCREMENT column

        Returns the value generated for an AUTO_INCREMENT column by
        the previous INSERT or UPDATE statement.

        Returns a long value or None.
        """
        return self._last_insert_id

    def _fetch_warnings(self) -> Optional[List[WarningType]]:
        """
        Fetch warnings doing a SHOW WARNINGS. Can be called after getting
        the result.

        Returns a result set or None when there were no warnings.
        """
        res = []
        try:
            cur = self._connection.cursor(raw=False)
            cur.execute("SHOW WARNINGS")
            res = cur.fetchall()
            cur.close()
        except Exception as err:
            raise InterfaceError(f"Failed getting warnings; {err}") from None

        if res:
            return res

        return None

    def _handle_warnings(self) -> None:
        """Handle possible warnings after all results are consumed.

        Raises:
            Error: Also raises exceptions if raise_on_warnings is set.
        """
        if self._connection.get_warnings and self._warning_count:
            self._warnings = self._fetch_warnings()

        if not self._warnings:
            return

        err = get_mysql_exception(
            self._warnings[0][1],
            self._warnings[0][2],
            warning=not self._connection.raise_on_warnings,
        )

        if self._connection.raise_on_warnings:
            raise err

        warnings.warn(err, stacklevel=4)

    def _handle_eof(self, eof: EofPacketType) -> None:
        """Handle EOF packet"""
        self._connection.unread_result = False
        self._nextrow = (None, None)
        self._warning_count = eof["warning_count"]
        self._handle_warnings()

    def _fetch_row(self, raw: bool = False) -> Optional[RowType]:
        """Returns the next row in the result set

        Returns a tuple or None.
        """
        if not self._have_unread_result():
            return None
        row = None

        if self._nextrow == (None, None):
            (row, eof) = self._connection.get_row(
                binary=self._binary, columns=self.description, raw=raw
            )
        else:
            (row, eof) = self._nextrow

        if row:
            self._nextrow = self._connection.get_row(
                binary=self._binary, columns=self.description, raw=raw
            )
            eof = self._nextrow[1]
            if eof is not None:
                self._handle_eof(eof)
            if self._rowcount == -1:
                self._rowcount = 1
            else:
                self._rowcount += 1
        if eof:
            self._handle_eof(eof)

        return row

    def fetchone(self) -> Optional[RowType]:
        """Return next row of a query result set.

        Returns:
            tuple or None: A row from query result set.
        """
        self._check_executed()
        return self._fetch_row()

    def fetchmany(self, size: Optional[int] = None) -> List[RowType]:
        """Return the next set of rows of a query result set.

        When no more rows are available, it returns an empty list.
        The number of rows returned can be specified using the size argument,
        which defaults to one.

        Returns:
            list: The next set of rows of a query result set.
        """
        self._check_executed()
        res = []
        cnt = size or self.arraysize
        while cnt > 0 and self._have_unread_result():
            cnt -= 1
            row = self.fetchone()
            if row:
                res.append(row)
        return res

    def fetchall(self) -> List[RowType]:
        """Return all rows of a query result set.

        Returns:
            list: A list of tuples with all rows of a query result set.
        """
        self._check_executed()
        if not self._have_unread_result():
            return []

        (rows, eof) = self._connection.get_rows()
        if self._nextrow[0]:
            rows.insert(0, self._nextrow[0])

        self._handle_eof(eof)
        rowcount = len(rows)
        if rowcount >= 0 and self._rowcount == -1:
            self._rowcount = 0
        self._rowcount += rowcount
        return rows

    @property
    def column_names(self) -> Tuple[str, ...]:
        """Returns column names

        This property returns the columns names as a tuple.

        Returns a tuple.
        """
        if not self.description:
            return tuple()
        return tuple(d[0] for d in self.description)

    @property
    def statement(self) -> Optional[str]:
        """Returns the executed statement

        This property returns the executed statement. When multiple
        statements were executed, the current statement in the iterator
        will be returned.
        """
        if self._executed is None:
            return None
        try:
            return self._executed.strip().decode("utf-8")  # type: ignore[union-attr]
        except (AttributeError, UnicodeDecodeError):
            return self._executed.strip()  # type: ignore[return-value]

    @property
    def with_rows(self) -> bool:
        """Returns whether the cursor could have rows returned

        This property returns True when column descriptions are available
        and possibly also rows, which will need to be fetched.

        Returns True or False.
        """
        if not self.description:
            return False
        return True

    def __str__(self) -> str:
        fmt = "{class_name}: {stmt}"
        if self._executed:
            try:
                executed = self._executed.decode("utf-8")  # type: ignore[union-attr]
            except AttributeError:
                executed = self._executed
            if len(executed) > 40:
                executed = executed[:40] + ".."
        else:
            executed = "(Nothing executed yet)"
        return fmt.format(class_name=self.__class__.__name__, stmt=executed)


class MySQLCursorBuffered(MySQLCursor):
    """Cursor which fetches rows within execute()"""

    def __init__(
        self, connection: Optional[Type[MySQLConnectionAbstract]] = None
    ) -> None:
        super().__init__(connection)
        self._rows: Optional[List[RowType]] = None
        self._next_row: int = 0

    def _handle_resultset(self) -> None:
        (self._rows, eof) = self._connection.get_rows()
        self._rowcount = len(self._rows)
        self._handle_eof(eof)
        self._next_row = 0
        try:
            self._connection.unread_result = False
        except AttributeError:
            pass

    def reset(self, free: bool = True) -> None:
        self._rows = None

    def _fetch_row(self, raw: bool = False) -> Optional[RowType]:
        row = None
        try:
            row = self._rows[self._next_row]
        except (IndexError, TypeError):
            return None
        self._next_row += 1
        return row

    def fetchone(self) -> Optional[RowType]:
        """Return next row of a query result set.

        Returns:
            tuple or None: A row from query result set.
        """
        self._check_executed()
        return self._fetch_row()

    def fetchall(self) -> List[RowType]:
        """Return all rows of a query result set.

        Returns:
            list: A list of tuples with all rows of a query result set.
        """
        if self._executed is None or self._rows is None:
            raise InterfaceError(ERR_NO_RESULT_TO_FETCH)
        res = []
        res = self._rows[self._next_row :]
        self._next_row = len(self._rows)
        return res

    def fetchmany(self, size: Optional[int] = None) -> List[RowType]:
        """Return the next set of rows of a query result set.

        When no more rows are available, it returns an empty list.
        The number of rows returned can be specified using the size argument,
        which defaults to one.

        Returns:
            list: The next set of rows of a query result set.
        """
        self._check_executed()
        res = []
        cnt = size or self.arraysize
        while cnt > 0:
            cnt -= 1
            row = self.fetchone()
            if row:
                res.append(row)

        return res

    @property
    def with_rows(self) -> bool:
        return self._rows is not None


class MySQLCursorRaw(MySQLCursor):
    """
    Skips conversion from MySQL datatypes to Python types when fetching rows.
    """

    _raw: bool = True

    def fetchone(self) -> Optional[RowType]:
        """Return next row of a query result set.

        Returns:
            tuple or None: A row from query result set.
        """
        self._check_executed()
        return self._fetch_row(raw=True)

    def fetchall(self) -> List[RowType]:
        """Return all rows of a query result set.

        Returns:
            list: A list of tuples with all rows of a query result set.
        """
        self._check_executed()
        if not self._have_unread_result():
            return []
        (rows, eof) = self._connection.get_rows(raw=True)
        if self._nextrow[0]:
            rows.insert(0, self._nextrow[0])
        self._handle_eof(eof)
        rowcount = len(rows)
        if rowcount >= 0 and self._rowcount == -1:
            self._rowcount = 0
        self._rowcount += rowcount
        return rows


class MySQLCursorBufferedRaw(MySQLCursorBuffered):
    """
    Cursor which skips conversion from MySQL datatypes to Python types when
    fetching rows and fetches rows within execute().
    """

    _raw: bool = True

    def _handle_resultset(self) -> None:
        (self._rows, eof) = self._connection.get_rows(raw=self._raw)
        self._rowcount = len(self._rows)
        self._handle_eof(eof)
        self._next_row = 0
        try:
            self._connection.unread_result = False
        except AttributeError:
            pass

    def fetchone(self) -> Optional[RowType]:
        """Return next row of a query result set.

        Returns:
            tuple or None: A row from query result set.
        """
        self._check_executed()
        return self._fetch_row()

    def fetchall(self) -> List[RowType]:
        """Return all rows of a query result set.

        Returns:
            list: A list of tuples with all rows of a query result set.
        """
        self._check_executed()
        return list(self._rows[self._next_row :])

    @property
    def with_rows(self) -> bool:
        return self._rows is not None


class MySQLCursorPrepared(MySQLCursor):
    """Cursor using MySQL Prepared Statements"""

    def __init__(self, connection: Optional[Type[MySQLConnectionAbstract]] = None):
        super().__init__(connection)
        self._rows: Optional[List[RowType]] = None
        self._next_row: int = 0
        self._prepared: Optional[Dict[str, Union[int, List[DescriptionType]]]] = None
        self._binary: bool = True
        self._have_result: Optional[bool] = None
        self._last_row_sent: bool = False
        self._cursor_exists: bool = False

    def reset(self, free: bool = True) -> None:
        if self._prepared:
            try:
                self._connection.cmd_stmt_close(self._prepared["statement_id"])
            except Error:
                # We tried to deallocate, but it's OK when we fail.
                pass
            self._prepared = None
        self._last_row_sent = False
        self._cursor_exists = False

    def _handle_noresultset(self, res: ResultType) -> None:
        self._handle_server_status(res.get("status_flag", res.get("server_status", 0)))
        super()._handle_noresultset(res)

    def _handle_server_status(self, flags: int) -> None:
        """Check for SERVER_STATUS_CURSOR_EXISTS and
        SERVER_STATUS_LAST_ROW_SENT flags set by the server.
        """
        self._cursor_exists = flags & ServerFlag.STATUS_CURSOR_EXISTS != 0
        self._last_row_sent = flags & ServerFlag.STATUS_LAST_ROW_SENT != 0

    def _handle_eof(self, eof: EofPacketType) -> None:
        self._handle_server_status(eof.get("status_flag", eof.get("server_status", 0)))
        super()._handle_eof(eof)

    def callproc(self, procname: Any, args: Any = ()) -> NoReturn:
        """Calls a stored procedue

        Not supported with MySQLCursorPrepared.
        """
        raise NotSupportedError()

    def close(self) -> None:
        """Close the cursor

        This method will try to deallocate the prepared statement and close
        the cursor.
        """
        self.reset()
        super().close()

    def _row_to_python(self, rowdata: Any, desc: Any = None) -> Any:
        """Convert row data from MySQL to Python types

        The conversion is done while reading binary data in the
        protocol module.
        """

    def _handle_result(self, result: ResultType) -> None:
        """Handle result after execution"""
        if isinstance(result, dict):
            self._connection.unread_result = False
            self._have_result = False
            self._handle_noresultset(result)
        else:
            self._description = result[1]
            self._connection.unread_result = True
            self._have_result = True

            if "status_flag" in result[2]:  # type: ignore[operator]
                self._handle_server_status(result[2]["status_flag"])
            elif "server_status" in result[2]:  # type: ignore[operator]
                self._handle_server_status(result[2]["server_status"])

    def execute(
        self,
        operation: StrOrBytes,
        params: Optional[ParamsSequenceOrDictType] = None,
        multi: bool = False,
    ) -> None:  # multi is unused
        """Prepare and execute a MySQL Prepared Statement

        This method will prepare the given operation and execute it using
        the optionally given parameters.

        If the cursor instance already had a prepared statement, it is
        first closed.

        Note: argument "multi" is unused.
        """
        charset = self._connection.charset
        if charset == "utf8mb4":
            charset = "utf8"

        if not isinstance(operation, str):
            try:
                operation = operation.decode(charset)
            except UnicodeDecodeError as err:
                raise ProgrammingError(str(err)) from err

        if isinstance(params, dict):
            replacement_keys = re.findall(RE_SQL_PYTHON_CAPTURE_PARAM_NAME, operation)
            try:
                # Replace params dict with params tuple in correct order.
                params = tuple(params[key] for key in replacement_keys)
            except KeyError as err:
                raise ProgrammingError(
                    "Not all placeholders were found in the parameters dict"
                ) from err
            # Convert %(name)s to ? before sending it to MySQL
            operation = re.sub(RE_SQL_PYTHON_REPLACE_PARAM, "?", operation)

        if operation is not self._executed:
            if self._prepared:
                self._connection.cmd_stmt_close(self._prepared["statement_id"])
            self._executed = operation

            try:
                operation = operation.encode(charset)
            except UnicodeEncodeError as err:
                raise ProgrammingError(str(err)) from err

            if b"%s" in operation:
                # Convert %s to ? before sending it to MySQL
                operation = re.sub(RE_SQL_FIND_PARAM, b"?", operation)

            try:
                self._prepared = self._connection.cmd_stmt_prepare(operation)
            except Error:
                self._executed = None
                raise

        self._connection.cmd_stmt_reset(self._prepared["statement_id"])

        if self._prepared["parameters"] and not params:
            return
        if params:
            if not isinstance(params, (tuple, list)):
                raise ProgrammingError(
                    errno=1210,
                    msg=f"Incorrect type of argument: {type(params).__name__}({params})"
                    ", it must be of type tuple or list the argument given to "
                    "the prepared statement",
                )
            if len(self._prepared["parameters"]) != len(params):
                raise ProgrammingError(
                    errno=1210,
                    msg="Incorrect number of arguments executing prepared statement",
                )

        if params is None:
            params = ()
        res = self._connection.cmd_stmt_execute(
            self._prepared["statement_id"],
            data=params,
            parameters=self._prepared["parameters"],
        )
        self._handle_result(res)

    def executemany(
        self,
        operation: str,
        seq_params: Sequence[ParamsSequenceType],
    ) -> None:
        """Prepare and execute a MySQL Prepared Statement many times

        This method will prepare the given operation and execute with each
        tuple found the list seq_params.

        If the cursor instance already had a prepared statement, it is
        first closed.

        executemany() simply calls execute().
        """
        rowcnt = 0
        try:
            for params in seq_params:
                self.execute(operation, params)
                if self.with_rows and self._have_unread_result():
                    self.fetchall()
                rowcnt += self._rowcount
        except (ValueError, TypeError) as err:
            raise InterfaceError(f"Failed executing the operation; {err}") from None
        self._rowcount = rowcnt

    def fetchone(self) -> Optional[RowType]:
        """Return next row of a query result set.

        Returns:
            tuple or None: A row from query result set.
        """
        self._check_executed()
        if self._cursor_exists:
            self._connection.cmd_stmt_fetch(self._prepared["statement_id"])
        return self._fetch_row() or None

    def fetchmany(self, size: Optional[int] = None) -> List[RowType]:
        """Return the next set of rows of a query result set.

        When no more rows are available, it returns an empty list.
        The number of rows returned can be specified using the size argument,
        which defaults to one.

        Returns:
            list: The next set of rows of a query result set.
        """
        self._check_executed()
        res = []
        cnt = size or self.arraysize
        while cnt > 0 and self._have_unread_result():
            cnt -= 1
            row = self._fetch_row()
            if row:
                res.append(row)
        return res

    def fetchall(self) -> List[RowType]:
        """Return all rows of a query result set.

        Returns:
            list: A list of tuples with all rows of a query result set.
        """
        self._check_executed()
        rows = []
        if self._nextrow[0]:
            rows.append(self._nextrow[0])
        while self._have_unread_result():
            if self._cursor_exists:
                self._connection.cmd_stmt_fetch(
                    self._prepared["statement_id"], MAX_RESULTS
                )
            (tmp, eof) = self._connection.get_rows(
                binary=self._binary, columns=self.description
            )
            rows.extend(tmp)
            self._handle_eof(eof)
        self._rowcount = len(rows)
        return rows


class MySQLCursorDict(MySQLCursor):
    """
    Cursor fetching rows as dictionaries.

    The fetch methods of this class will return dictionaries instead of tuples.
    Each row is a dictionary that looks like:
        row = {
            "col1": value1,
            "col2": value2
        }
    """

    def _row_to_python(
        self,
        rowdata: RowType,
        desc: Optional[List[DescriptionType]] = None,  # pylint: disable=unused-argument
    ) -> Optional[Dict[str, ToPythonOutputTypes]]:
        """Convert a MySQL text result row to Python types

        Returns a dictionary.
        """
        return dict(zip(self.column_names, rowdata)) if rowdata else None

    def fetchone(self) -> Optional[Dict[str, ToPythonOutputTypes]]:
        """Return next row of a query result set.

        Returns:
            dict or None: A dict from query result set.
        """
        return self._row_to_python(super().fetchone(), self.description)

    def fetchall(self) -> List[Optional[Dict[str, ToPythonOutputTypes]]]:
        """Return all rows of a query result set.

        Returns:
            list: A list of dictionaries with all rows of a query
                  result set where column names are used as keys.
        """
        return [
            self._row_to_python(row, self.description)
            for row in super().fetchall()
            if row
        ]


class MySQLCursorNamedTuple(MySQLCursor):
    """
    Cursor fetching rows as named tuple.

    The fetch methods of this class will return namedtuples instead of tuples.
    Each row is returned as a namedtuple and the values can be accessed as:
    row.col1, row.col2
    """

    def _row_to_python(
        self,
        rowdata: RowType,
        desc: Optional[List[DescriptionType]] = None,  # pylint: disable=unused-argument
    ) -> Optional[RowType]:
        """Convert a MySQL text result row to Python types

        Returns a named tuple.
        """
        row = rowdata

        if row:
            columns = tuple(self.column_names)
            try:
                named_tuple = NAMED_TUPLE_CACHE[columns]
            except KeyError:
                named_tuple = namedtuple("Row", columns)  # type:ignore[no-redef, misc]
                NAMED_TUPLE_CACHE[columns] = named_tuple
            return named_tuple(*row)
        return None

    def fetchone(self) -> Optional[RowType]:
        """Return next row of a query result set.

        Returns:
            tuple or None: A row from query result set.
        """
        row = super().fetchone()
        if not row:
            return None
        return (
            self._row_to_python(row, self.description)
            if hasattr(self._connection, "converter")
            else row
        )

    def fetchall(self) -> List[Optional[RowType]]:
        """Return all rows of a query result set.

        Returns:
            list: A list of tuples with all rows of a query result set.
        """
        return [
            self._row_to_python(row, self.description)
            for row in super().fetchall()
            if row
        ]


class MySQLCursorBufferedDict(MySQLCursorDict, MySQLCursorBuffered):
    """
    Buffered Cursor fetching rows as dictionaries.
    """

    def fetchone(self) -> Optional[Dict[str, ToPythonOutputTypes]]:
        """Return next row of a query result set.

        Returns:
            tuple or None: A row from query result set.
        """
        self._check_executed()
        row = self._fetch_row()
        if row:
            return self._row_to_python(row, self.description)
        return None

    def fetchall(self) -> List[Optional[Dict[str, ToPythonOutputTypes]]]:
        """Return all rows of a query result set.

        Returns:
            list: A list of tuples with all rows of a query result set.
        """
        if self._executed is None or self._rows is None:
            raise InterfaceError(ERR_NO_RESULT_TO_FETCH)
        res = []
        for row in self._rows[self._next_row :]:
            res.append(self._row_to_python(row, self.description))
        self._next_row = len(self._rows)
        return res


class MySQLCursorBufferedNamedTuple(MySQLCursorNamedTuple, MySQLCursorBuffered):
    """
    Buffered Cursor fetching rows as named tuple.
    """

    def fetchone(self) -> Optional[RowType]:
        """Return next row of a query result set.

        Returns:
            tuple or None: A row from query result set.
        """
        self._check_executed()
        row = self._fetch_row()
        if row:
            return self._row_to_python(row, self.description)
        return None

    def fetchall(self) -> List[Optional[RowType]]:
        """Return all rows of a query result set.

        Returns:
            list: A list of tuples with all rows of a query result set.
        """
        if self._executed is None or self._rows is None:
            raise InterfaceError(ERR_NO_RESULT_TO_FETCH)
        res = []
        for row in self._rows[self._next_row :]:
            res.append(self._row_to_python(row, self.description))
        self._next_row = len(self._rows)
        return res


class MySQLCursorPreparedDict(MySQLCursorDict, MySQLCursorPrepared):  # type: ignore[misc]
    """
    This class is a blend of features from MySQLCursorDict and MySQLCursorPrepared

    Multiple inheritance in python is allowed but care must be taken
    when assuming methods resolution. In the case of multiple
    inheritance, a given attribute is first searched in the current
    class if it's not found then it's searched in the parent classes.
    The parent classes are searched in a left-right fashion and each
    class is searched once.
    Based on python's attribute resolution, in this case, attributes
    are searched as follows:
    1. MySQLCursorPreparedDict (current class)
    2. MySQLCursorDict (left parent class)
    3. MySQLCursorPrepared (right parent class)
    4. MySQLCursor (base class)
    """

    def fetchmany(
        self, size: Optional[int] = None
    ) -> List[Dict[str, ToPythonOutputTypes]]:
        """Return the next set of rows of a query result set.

        When no more rows are available, it returns an empty list.
        The number of rows returned can be specified using the size argument,
        which defaults to one.

        Returns:
            list: The next set of rows of a query result set represented
                  as a list of dictionaries where column names are used as keys.
        """
        return [
            self._row_to_python(row, self.description)
            for row in super().fetchmany(size=size)
            if row
        ]


class MySQLCursorPreparedNamedTuple(MySQLCursorNamedTuple, MySQLCursorPrepared):
    """
    This class is a blend of features from MySQLCursorNamedTuple and MySQLCursorPrepared
    """

    def fetchmany(self, size: Optional[int] = None) -> List[RowType]:
        """Return the next set of rows of a query result set.

        When no more rows are available, it returns an empty list.
        The number of rows returned can be specified using the size argument,
        which defaults to one.

        Returns:
            list: The next set of rows of a query result set represented
                  as a list of named tuples where column names are used as names.
        """
        return [
            self._row_to_python(row, self.description)
            for row in super().fetchmany(size=size)
            if row
        ]


class MySQLCursorPreparedRaw(MySQLCursorPrepared):
    """
    This class is a blend of features from MySQLCursorRaw and MySQLCursorPrepared
    """

    _raw: bool = True

    def fetchone(self) -> Optional[RowType]:
        """Return next row of a query result set.

        Returns:
            tuple or None: A row from query result set.
        """
        self._check_executed()
        if self._cursor_exists:
            self._connection.cmd_stmt_fetch(self._prepared["statement_id"])
        return self._fetch_row(raw=self._raw) or None

    def fetchmany(self, size: Optional[int] = None) -> List[RowType]:
        """Return the next set of rows of a query result set.

        When no more rows are available, it returns an empty list.
        The number of rows returned can be specified using the size argument,
        which defaults to one.

        Returns:
            list: The next set of rows of a query result set.
        """
        self._check_executed()
        res = []
        cnt = size or self.arraysize
        while cnt > 0 and self._have_unread_result():
            cnt -= 1
            row = self._fetch_row(raw=self._raw)
            if row:
                res.append(row)
        return res

    def fetchall(self) -> List[RowType]:
        """Return all rows of a query result set.

        Returns:
            list: A list of tuples with all rows of a query result set.
        """
        self._check_executed()
        rows = []
        if self._nextrow[0]:
            rows.append(self._nextrow[0])
        while self._have_unread_result():
            if self._cursor_exists:
                self._connection.cmd_stmt_fetch(
                    self._prepared["statement_id"], MAX_RESULTS
                )
            (tmp, eof) = self._connection.get_rows(
                raw=self._raw, binary=self._binary, columns=self.description
            )
            rows.extend(tmp)
            self._handle_eof(eof)
        self._rowcount = len(rows)
        return rows

virt/lib/python3.9/site-packages/mysql/connector/cursor 2.py
PrinceDisant/LostInNL
# Copyright (c) 2009, 2023, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

# mypy: disable-error-code="assignment,arg-type,attr-defined,index,override,call-overload"

"""Cursor classes."""
from __future__ import annotations

import re
import warnings
import weakref

from collections import namedtuple
from decimal import Decimal
from typing import (
    Any,
    Dict,
    Generator,
    Iterator,
    List,
    NoReturn,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
)
from weakref import CallableProxyType

from .abstracts import NAMED_TUPLE_CACHE, MySQLConnectionAbstract, MySQLCursorAbstract
from .constants import ServerFlag
from .errors import (
    Error,
    InterfaceError,
    NotSupportedError,
    ProgrammingError,
    get_mysql_exception,
)
from .types import (
    DescriptionType,
    EofPacketType,
    ParamsDictType,
    ParamsSequenceOrDictType,
    ParamsSequenceType,
    ResultType,
    RowType,
    StrOrBytes,
    ToPythonOutputTypes,
    WarningType,
)

SQL_COMMENT = r"\/\*.*?\*\/"
RE_SQL_COMMENT = re.compile(
    rf"""({SQL_COMMENT})|(["'`][^"'`]*?({SQL_COMMENT})[^"'`]*?["'`])""",
    re.I | re.M | re.S,
)
RE_SQL_ON_DUPLICATE = re.compile(
    r"""\s*ON\s+DUPLICATE\s+KEY(?:[^"'`]*["'`][^"'`]*["'`])*[^"'`]*$""",
    re.I | re.M | re.S,
)
RE_SQL_INSERT_STMT = re.compile(
    rf"({SQL_COMMENT}|\s)*INSERT({SQL_COMMENT}|\s)"
    r"*(?:IGNORE\s+)?INTO\s+[`'\"]?.+[`'\"]?(?:\.[`'\"]?.+[`'\"]?)"
    r"{0,2}\s+VALUES\s*(\(.+\)).*",
    re.I | re.M | re.S,
)
RE_SQL_INSERT_VALUES = re.compile(r".*VALUES\s*(\(.+\)).*", re.I | re.M | re.S)
RE_PY_PARAM = re.compile(b"(%s)")
RE_PY_MAPPING_PARAM = re.compile(
    rb"""
    %
    \((?P<mapping_key>[^)]+)\)
    (?P<conversion_type>[diouxXeEfFgGcrs%])
    """,
    re.X,
)
RE_SQL_SPLIT_STMTS = re.compile(
    b""";(?=(?:[^"'`]*(?:"[^"]*"|'[^']*'|`[^`]*`))*[^"'`]*$)"""
)
RE_SQL_FIND_PARAM = re.compile(b"""%s(?=(?:[^"'`]*["'`][^"'`]*["'`])*[^"'`]*$)""")
RE_SQL_PYTHON_REPLACE_PARAM = re.compile(r"%\(.*?\)s")
RE_SQL_PYTHON_CAPTURE_PARAM_NAME = re.compile(r"%\((.*?)\)s")

ERR_NO_RESULT_TO_FETCH = "No result set to fetch from"

MAX_RESULTS = 4294967295


class _ParamSubstitutor:
    """
    Substitutes parameters into SQL statement.
    """

    def __init__(self, params: Sequence[bytes]) -> None:
        self.params: Sequence[bytes] = params
        self.index: int = 0

    def __call__(self, matchobj: re.Match) -> bytes:
        index = self.index
        self.index += 1
        try:
            return bytes(self.params[index])
        except IndexError:
            raise ProgrammingError(
                "Not enough parameters for the SQL statement"
            ) from None

    @property
    def remaining(self) -> int:
        """Returns number of parameters remaining to be substituted"""
        return len(self.params) - self.index


def _bytestr_format_dict(bytestr: bytes, value_dict: Dict[bytes, bytes]) -> bytes:
    """
    >>> _bytestr_format_dict(b'%(a)s', {b'a': b'foobar'})
    b'foobar
    >>> _bytestr_format_dict(b'%%(a)s', {b'a': b'foobar'})
    b'%%(a)s'
    >>> _bytestr_format_dict(b'%%%(a)s', {b'a': b'foobar'})
    b'%%foobar'
    >>> _bytestr_format_dict(b'%(x)s %(y)s',
    ...                      {b'x': b'x=%(y)s', b'y': b'y=%(x)s'})
    b'x=%(y)s y=%(x)s'
    """

    def replace(matchobj: re.Match) -> bytes:
        """Replace pattern."""
        value: Optional[bytes] = None
        groups = matchobj.groupdict()
        if groups["conversion_type"] == b"%":
            value = b"%"
        if groups["conversion_type"] == b"s":
            key = groups["mapping_key"]
            value = value_dict[key]
        if value is None:
            raise ValueError(
                f"Unsupported conversion_type: {groups['conversion_type']}"
            )
        return value

    stmt = RE_PY_MAPPING_PARAM.sub(replace, bytestr)
    return stmt


class CursorBase(MySQLCursorAbstract):
    """
    Base for defining MySQLCursor. This class is a skeleton and defines
    methods and members as required for the Python Database API
    Specification v2.0.

    It's better to inherite from MySQLCursor.
    """

    _raw: bool = False

    def __init__(self) -> None:
        self._description: Optional[List[DescriptionType]] = None
        self._rowcount: int = -1
        self.arraysize: int = 1
        super().__init__()

    def callproc(self, procname: str, args: Sequence[Any] = ()) -> Any:
        """Calls a stored procedue with the given arguments

        The arguments will be set during this session, meaning
        they will be called like  _<procname>__arg<nr> where
        <nr> is an enumeration (+1) of the arguments.

        Coding Example:
          1) Definining the Stored Routine in MySQL:
          CREATE PROCEDURE multiply(IN pFac1 INT, IN pFac2 INT, OUT pProd INT)
          BEGIN
            SET pProd := pFac1 * pFac2;
          END

          2) Executing in Python:
          args = (5,5,0) # 0 is to hold pprod
          cursor.callproc('multiply', args)
          print(cursor.fetchone())

        Does not return a value, but a result set will be
        available when the CALL-statement execute successfully.
        Raises exceptions when something is wrong.
        """

    def close(self) -> Any:
        """Close the cursor."""

    def execute(
        self,
        operation: Any,
        params: Union[Sequence[Any], Dict[str, Any]] = (),
        multi: bool = False,
    ) -> Any:
        """Executes the given operation

        Executes the given operation substituting any markers with
        the given parameters.

        For example, getting all rows where id is 5:
          cursor.execute("SELECT * FROM t1 WHERE id = %s", (5,))

        The multi argument should be set to True when executing multiple
        statements in one operation. If not set and multiple results are
        found, an InterfaceError will be raised.

        If warnings where generated, and connection.get_warnings is True, then
        self._warnings will be a list containing these warnings.

        Returns an iterator when multi is True, otherwise None.
        """

    def executemany(
        self, operation: Any, seq_params: Sequence[Union[Sequence[Any], Dict[str, Any]]]
    ) -> Any:
        """Execute the given operation multiple times

        The executemany() method will execute the operation iterating
        over the list of parameters in seq_params.

        Example: Inserting 3 new employees and their phone number

        data = [
            ('Jane','555-001'),
            ('Joe', '555-001'),
            ('John', '555-003')
            ]
        stmt = "INSERT INTO employees (name, phone) VALUES ('%s','%s')"
        cursor.executemany(stmt, data)

        INSERT statements are optimized by batching the data, that is
        using the MySQL multiple rows syntax.

        Results are discarded. If they are needed, consider looping over
        data using the execute() method.
        """

    def fetchone(self) -> Optional[Sequence[Any]]:
        """Return next row of a query result set.

        Returns:
            tuple or None: A row from query result set.
        """

    def fetchmany(self, size: int = 1) -> List[Sequence[Any]]:
        """Return the next set of rows of a query result set.

        When no more rows are available, it returns an empty list.
        The number of rows returned can be specified using the size argument,
        which defaults to one.

        Returns:
            list: The next set of rows of a query result set.
        """

    def fetchall(self) -> List[Sequence[Any]]:
        """Return all rows of a query result set.

        Returns:
            list: A list of tuples with all rows of a query result set.
        """

    def nextset(self) -> Any:
        """Not Implemented."""

    def setinputsizes(self, sizes: Any) -> Any:
        """Not Implemented."""

    def setoutputsize(self, size: Any, column: Any = None) -> Any:
        """Not Implemented."""

    def reset(self, free: bool = True) -> Any:
        """Reset the cursor to default"""

    @property
    def description(self) -> Optional[List[DescriptionType]]:
        """Returns description of columns in a result

        This property returns a list of tuples describing the columns in
        in a result set. A tuple is described as follows::

                (column_name,
                 type,
                 None,
                 None,
                 None,
                 None,
                 null_ok,
                 column_flags)  # Addition to PEP-249 specs

        Returns a list of tuples.
        """
        return self._description

    @property
    def rowcount(self) -> int:
        """Returns the number of rows produced or affected

        This property returns the number of rows produced by queries
        such as a SELECT, or affected rows when executing DML statements
        like INSERT or UPDATE.

        Note that for non-buffered cursors it is impossible to know the
        number of rows produced before having fetched them all. For those,
        the number of rows will be -1 right after execution, and
        incremented when fetching rows.

        Returns an integer.
        """
        return self._rowcount


class MySQLCursor(CursorBase):
    """Default cursor for interacting with MySQL

    This cursor will execute statements and handle the result. It will
    not automatically fetch all rows.

    MySQLCursor should be inherited whenever other functionallity is
    required. An example would to change the fetch* member functions
    to return dictionaries instead of lists of values.

    Implements the Python Database API Specification v2.0 (PEP-249)
    """

    def __init__(
        self, connection: Optional[Type[MySQLConnectionAbstract]] = None
    ) -> None:
        CursorBase.__init__(self)
        self._connection: CallableProxyType[Type[MySQLConnectionAbstract]] = None
        self._nextrow: Tuple[Optional[RowType], Optional[EofPacketType]] = (
            None,
            None,
        )
        self._binary: bool = False

        if connection is not None:
            self._set_connection(connection)

    def __iter__(self) -> Iterator[RowType]:
        """
        Iteration over the result set which calls self.fetchone()
        and returns the next row.
        """
        return iter(self.fetchone, None)

    def _set_connection(self, connection: Type[MySQLConnectionAbstract]) -> None:
        """Set the connection"""
        try:
            self._connection = weakref.proxy(connection)
            self._connection.is_connected()
        except (AttributeError, TypeError):
            raise InterfaceError(errno=2048) from None

    def _reset_result(self) -> None:
        """Reset the cursor to default"""
        self._rowcount: int = -1
        self._nextrow = (None, None)
        self._stored_results: List[MySQLCursor] = []
        self._warnings: Optional[List[WarningType]] = None
        self._warning_count: int = 0
        self._description: Optional[List[DescriptionType]] = None
        self._executed: Optional[StrOrBytes] = None
        self._executed_list: List[StrOrBytes] = []
        self.reset()

    def _have_unread_result(self) -> bool:
        """Check whether there is an unread result"""
        try:
            return self._connection.unread_result
        except AttributeError:
            return False

    def _check_executed(self) -> None:
        """Check if the statement has been executed.

        Raises an error if the statement has not been executed.
        """
        if self._executed is None:
            raise InterfaceError(ERR_NO_RESULT_TO_FETCH)

    def __next__(self) -> RowType:
        """
        Used for iterating over the result set. Calles self.fetchone()
        to get the next row.
        """
        try:
            row = self.fetchone()
        except InterfaceError:
            raise StopIteration from None
        if not row:
            raise StopIteration
        return row

    def close(self) -> bool:
        """Close the cursor

        Returns True when successful, otherwise False.
        """
        if self._connection is None:
            return False

        self._connection.handle_unread_result()
        self._reset_result()
        self._connection = None

        return True

    def _process_params_dict(
        self, params: ParamsDictType
    ) -> Dict[bytes, Union[bytes, Decimal]]:
        """Process query parameters given as dictionary"""
        res: Dict[bytes, Any] = {}
        try:
            sql_mode = self._connection.sql_mode
            to_mysql = self._connection.converter.to_mysql
            escape = self._connection.converter.escape
            quote = self._connection.converter.quote
            for key, value in params.items():
                conv = value
                conv = to_mysql(conv)
                conv = escape(conv, sql_mode)
                if not isinstance(value, Decimal):
                    conv = quote(conv)
                res[key.encode()] = conv
        except Exception as err:
            raise ProgrammingError(
                f"Failed processing pyformat-parameters; {err}"
            ) from err
        return res

    def _process_params(
        self, params: ParamsSequenceType
    ) -> Tuple[Union[bytes, Decimal], ...]:
        """Process query parameters."""
        res = params[:]
        try:
            sql_mode = self._connection.sql_mode
            to_mysql = self._connection.converter.to_mysql
            escape = self._connection.converter.escape
            quote = self._connection.converter.quote
            res = [to_mysql(value) for value in res]
            res = [escape(value, sql_mode) for value in res]
            res = [
                quote(value) if not isinstance(params[i], Decimal) else value
                for i, value in enumerate(res)
            ]
        except Exception as err:
            raise ProgrammingError(
                f"Failed processing format-parameters; {err}"
            ) from err
        return tuple(res)

    def _handle_noresultset(self, res: ResultType) -> None:
        """Handles result of execute() when there is no result set"""
        try:
            self._rowcount = res["affected_rows"]
            self._last_insert_id = res["insert_id"]
            self._warning_count = res["warning_count"]
        except (KeyError, TypeError) as err:
            raise ProgrammingError(f"Failed handling non-resultset; {err}") from None

        self._handle_warnings()

    def _handle_resultset(self) -> None:
        """Handles result set

        This method handles the result set and is called after reading
        and storing column information in _handle_result(). For non-buffering
        cursors, this method is usually doing nothing.
        """

    def _handle_result(self, result: ResultType) -> None:
        """
        Handle the result after a command was send. The result can be either
        an OK-packet or a dictionary containing column/eof information.

        Raises InterfaceError when result is not a dict() or result is
        invalid.
        """
        if not isinstance(result, dict):
            raise InterfaceError("Result was not a dict()")

        if "columns" in result:
            # Weak test, must be column/eof information
            self._description = result["columns"]
            self._connection.unread_result = True
            self._handle_resultset()
        elif "affected_rows" in result:
            # Weak test, must be an OK-packet
            self._connection.unread_result = False
            self._handle_noresultset(result)
        else:
            raise InterfaceError("Invalid result")

    def _execute_iter(
        self, query_iter: Generator[ResultType, None, None]
    ) -> Generator[MySQLCursor, None, None]:
        """Generator returns MySQLCursor objects for multiple statements

        This method is only used when multiple statements are executed
        by the execute() method. It uses zip() to make an iterator from the
        given query_iter (result of MySQLConnection.cmd_query_iter()) and
        the list of statements that were executed.
        """
        executed_list = RE_SQL_SPLIT_STMTS.split(self._executed)

        i = 0
        while True:
            try:
                result = next(query_iter)
                self._reset_result()
                self._handle_result(result)
                try:
                    self._executed = executed_list[i].strip()
                    i += 1
                except IndexError:
                    self._executed = executed_list[0]

                yield self
            except StopIteration:
                return

    def execute(
        self,
        operation: StrOrBytes,
        params: Optional[ParamsSequenceOrDictType] = None,
        multi: bool = False,
    ) -> Optional[Generator[MySQLCursor, None, None]]:
        """Executes the given operation

        Executes the given operation substituting any markers with
        the given parameters.

        For example, getting all rows where id is 5:
          cursor.execute("SELECT * FROM t1 WHERE id = %s", (5,))

        The multi argument should be set to True when executing multiple
        statements in one operation. If not set and multiple results are
        found, an InterfaceError will be raised.

        If warnings where generated, and connection.get_warnings is True, then
        self._warnings will be a list containing these warnings.

        Returns an iterator when multi is True, otherwise None.
        """
        if not operation:
            return None

        try:
            if not self._connection:
                raise ProgrammingError
        except (ProgrammingError, ReferenceError) as err:
            raise ProgrammingError("Cursor is not connected") from err

        self._connection.handle_unread_result()

        self._reset_result()
        stmt: StrOrBytes = ""

        try:
            if not isinstance(operation, (bytes, bytearray)):
                stmt = operation.encode(self._connection.python_charset)
            else:
                stmt = operation
        except (UnicodeDecodeError, UnicodeEncodeError) as err:
            raise ProgrammingError(str(err)) from err

        if params:
            if isinstance(params, dict):
                stmt = _bytestr_format_dict(stmt, self._process_params_dict(params))
            elif isinstance(params, (list, tuple)):
                psub = _ParamSubstitutor(self._process_params(params))
                stmt = RE_PY_PARAM.sub(psub, stmt)
                if psub.remaining != 0:
                    raise ProgrammingError(
                        "Not all parameters were used in the SQL statement"
                    )
            else:
                raise ProgrammingError(
                    f"Could not process parameters: {type(params).__name__}({params}),"
                    " it must be of type list, tuple or dict"
                )

        self._executed = stmt
        if multi:
            self._executed_list = []
            return self._execute_iter(self._connection.cmd_query_iter(stmt))

        try:
            self._handle_result(self._connection.cmd_query(stmt))
        except InterfaceError as err:
            if self._connection.have_next_result:
                raise InterfaceError(
                    "Use multi=True when executing multiple statements"
                ) from err
            raise
        return None

    def _batch_insert(
        self, operation: str, seq_params: Sequence[ParamsSequenceOrDictType]
    ) -> Optional[bytes]:
        """Implements multi row insert"""

        def remove_comments(match: re.Match) -> str:
            """Remove comments from INSERT statements.

            This function is used while removing comments from INSERT
            statements. If the matched string is a comment not enclosed
            by quotes, it returns an empty string, else the string itself.
            """
            if match.group(1):
                return ""
            return match.group(2)

        tmp = re.sub(
            RE_SQL_ON_DUPLICATE,
            "",
            re.sub(RE_SQL_COMMENT, remove_comments, operation),
        )

        matches = re.search(RE_SQL_INSERT_VALUES, tmp)
        if not matches:
            raise InterfaceError(
                "Failed rewriting statement for multi-row INSERT. Check SQL syntax"
            )
        fmt = matches.group(1).encode(self._connection.python_charset)
        values = []

        try:
            stmt = operation.encode(self._connection.python_charset)
            for params in seq_params:
                tmp = fmt
                if isinstance(params, dict):
                    tmp = _bytestr_format_dict(tmp, self._process_params_dict(params))
                else:
                    psub = _ParamSubstitutor(self._process_params(params))
                    tmp = RE_PY_PARAM.sub(psub, tmp)
                    if psub.remaining != 0:
                        raise ProgrammingError(
                            "Not all parameters were used in the SQL statement"
                        )
                values.append(tmp)
            if fmt in stmt:
                stmt = stmt.replace(fmt, b",".join(values), 1)
                self._executed = stmt
                return stmt
            return None
        except (UnicodeDecodeError, UnicodeEncodeError) as err:
            raise ProgrammingError(str(err)) from err
        except Error:
            raise
        except Exception as err:
            raise InterfaceError(f"Failed executing the operation; {err}") from None

    def executemany(
        self, operation: str, seq_params: Sequence[ParamsSequenceOrDictType]
    ) -> Optional[Generator[MySQLCursor, None, None]]:
        """Execute the given operation multiple times

        The executemany() method will execute the operation iterating
        over the list of parameters in seq_params.

        Example: Inserting 3 new employees and their phone number

        data = [
            ('Jane','555-001'),
            ('Joe', '555-001'),
            ('John', '555-003')
            ]
        stmt = "INSERT INTO employees (name, phone) VALUES ('%s','%s)"
        cursor.executemany(stmt, data)

        INSERT statements are optimized by batching the data, that is
        using the MySQL multiple rows syntax.

        Results are discarded. If they are needed, consider looping over
        data using the execute() method.
        """
        if not operation or not seq_params:
            return None
        self._connection.handle_unread_result()

        try:
            _ = iter(seq_params)
        except TypeError as err:
            raise ProgrammingError("Parameters for query must be an Iterable") from err

        # Optimize INSERTs by batching them
        if re.match(RE_SQL_INSERT_STMT, operation):
            if not seq_params:
                self._rowcount = 0
                return None
            stmt = self._batch_insert(operation, seq_params)
            if stmt is not None:
                self._executed = stmt
                return self.execute(stmt)

        rowcnt = 0
        try:
            for params in seq_params:
                self.execute(operation, params)
                if self.with_rows and self._have_unread_result():
                    self.fetchall()
                rowcnt += self._rowcount
        except (ValueError, TypeError) as err:
            raise InterfaceError(f"Failed executing the operation; {err}") from None
        self._rowcount = rowcnt
        return None

    def stored_results(self) -> Iterator[MySQLCursor]:
        """Returns an iterator for stored results

        This method returns an iterator over results which are stored when
        callproc() is called. The iterator will provide MySQLCursorBuffered
        instances.

        Returns a iterator.
        """
        return iter(self._stored_results)

    def callproc(
        self,
        procname: str,
        args: Sequence[Any] = (),
    ) -> Optional[Union[Dict[str, ToPythonOutputTypes], RowType]]:
        """Calls a stored procedure with the given arguments

        The arguments will be set during this session, meaning
        they will be called like  _<procname>__arg<nr> where
        <nr> is an enumeration (+1) of the arguments.

        Coding Example:
          1) Defining the Stored Routine in MySQL:
          CREATE PROCEDURE multiply(IN pFac1 INT, IN pFac2 INT, OUT pProd INT)
          BEGIN
            SET pProd := pFac1 * pFac2;
          END

          2) Executing in Python:
          args = (5, 5, 0)  # 0 is to hold pprod
          cursor.callproc('multiply', args)
          print(cursor.fetchone())

        For OUT and INOUT parameters the user should provide the
        type of the parameter as well. The argument should be a
        tuple with first item as the value of the parameter to pass
        and second argument the type of the argument.

        In the above example, one can call callproc method like:
          args = (5, 5, (0, 'INT'))
          cursor.callproc('multiply', args)

        The type of the argument given in the tuple will be used by
        the MySQL CAST function to convert the values in the corresponding
        MySQL type (See CAST in MySQL Reference for more information)

        Does not return a value, but a result set will be
        available when the CALL-statement execute successfully.
        Raises exceptions when something is wrong.
        """
        if not procname or not isinstance(procname, str):
            raise ValueError("procname must be a string")

        if not isinstance(args, (tuple, list)):
            raise ValueError("args must be a sequence")

        argfmt = "@_{name}_arg{index}"
        self._stored_results = []

        results = []
        try:
            argnames = []
            argtypes = []

            # MySQL itself does support calling procedures with their full
            # name <database>.<procedure_name>. It's necessary to split
            # by '.' and grab the procedure name from procname.
            procname_abs = procname.split(".")[-1]
            if args:
                argvalues = []
                for idx, arg in enumerate(args):
                    argname = argfmt.format(name=procname_abs, index=idx + 1)
                    argnames.append(argname)
                    if isinstance(arg, tuple):
                        argtypes.append(f" CAST({argname} AS {arg[1]})")
                        argvalues.append(arg[0])
                    else:
                        argtypes.append(argname)
                        argvalues.append(arg)

                placeholders = ",".join(f"{arg}=%s" for arg in argnames)
                self.execute(f"SET {placeholders}", argvalues)

            call = f"CALL {procname}({','.join(argnames)})"

            # We disable consuming results temporary to make sure we
            # getting all results
            can_consume_results = self._connection.can_consume_results
            for result in self._connection.cmd_query_iter(call):
                self._connection.can_consume_results = False
                if isinstance(self, (MySQLCursorDict, MySQLCursorBufferedDict)):
                    cursor_class = MySQLCursorBufferedDict
                elif isinstance(
                    self,
                    (MySQLCursorNamedTuple, MySQLCursorBufferedNamedTuple),
                ):
                    cursor_class = MySQLCursorBufferedNamedTuple
                elif self._raw:
                    cursor_class = MySQLCursorBufferedRaw
                else:
                    cursor_class = MySQLCursorBuffered
                # pylint: disable=protected-access
                cur = cursor_class(self._connection.get_self())
                cur._executed = f"(a result of {call})"
                cur._handle_result(result)
                # pylint: enable=protected-access
                if cur.warnings is not None:
                    self._warnings = cur.warnings
                if "columns" in result:
                    results.append(cur)
            self._connection.can_consume_results = can_consume_results

            if argnames:
                # Create names aliases to be compatible with namedtuples
                args = [
                    f"{name} AS {alias}"
                    for name, alias in zip(
                        argtypes, [arg.lstrip("@_") for arg in argnames]
                    )
                ]
                select = f"SELECT {','.join(args)}"
                self.execute(select)
                self._stored_results = results
                return self.fetchone()

            self._stored_results = results
            return tuple()

        except Error:
            raise
        except Exception as err:
            raise InterfaceError(f"Failed calling stored routine; {err}") from None

    def getlastrowid(self) -> Optional[int]:
        """Returns the value generated for an AUTO_INCREMENT column

        Returns the value generated for an AUTO_INCREMENT column by
        the previous INSERT or UPDATE statement.

        Returns a long value or None.
        """
        return self._last_insert_id

    def _fetch_warnings(self) -> Optional[List[WarningType]]:
        """
        Fetch warnings doing a SHOW WARNINGS. Can be called after getting
        the result.

        Returns a result set or None when there were no warnings.
        """
        res = []
        try:
            cur = self._connection.cursor(raw=False)
            cur.execute("SHOW WARNINGS")
            res = cur.fetchall()
            cur.close()
        except Exception as err:
            raise InterfaceError(f"Failed getting warnings; {err}") from None

        if res:
            return res

        return None

    def _handle_warnings(self) -> None:
        """Handle possible warnings after all results are consumed.

        Raises:
            Error: Also raises exceptions if raise_on_warnings is set.
        """
        if self._connection.get_warnings and self._warning_count:
            self._warnings = self._fetch_warnings()

        if not self._warnings:
            return

        err = get_mysql_exception(
            self._warnings[0][1],
            self._warnings[0][2],
            warning=not self._connection.raise_on_warnings,
        )

        if self._connection.raise_on_warnings:
            raise err

        warnings.warn(err, stacklevel=4)

    def _handle_eof(self, eof: EofPacketType) -> None:
        """Handle EOF packet"""
        self._connection.unread_result = False
        self._nextrow = (None, None)
        self._warning_count = eof["warning_count"]
        self._handle_warnings()

    def _fetch_row(self, raw: bool = False) -> Optional[RowType]:
        """Returns the next row in the result set

        Returns a tuple or None.
        """
        if not self._have_unread_result():
            return None
        row = None

        if self._nextrow == (None, None):
            (row, eof) = self._connection.get_row(
                binary=self._binary, columns=self.description, raw=raw
            )
        else:
            (row, eof) = self._nextrow

        if row:
            self._nextrow = self._connection.get_row(
                binary=self._binary, columns=self.description, raw=raw
            )
            eof = self._nextrow[1]
            if eof is not None:
                self._handle_eof(eof)
            if self._rowcount == -1:
                self._rowcount = 1
            else:
                self._rowcount += 1
        if eof:
            self._handle_eof(eof)

        return row

    def fetchone(self) -> Optional[RowType]:
        """Return next row of a query result set.

        Returns:
            tuple or None: A row from query result set.
        """
        self._check_executed()
        return self._fetch_row()

    def fetchmany(self, size: Optional[int] = None) -> List[RowType]:
        """Return the next set of rows of a query result set.

        When no more rows are available, it returns an empty list.
        The number of rows returned can be specified using the size argument,
        which defaults to one.

        Returns:
            list: The next set of rows of a query result set.
        """
        self._check_executed()
        res = []
        cnt = size or self.arraysize
        while cnt > 0 and self._have_unread_result():
            cnt -= 1
            row = self.fetchone()
            if row:
                res.append(row)
        return res

    def fetchall(self) -> List[RowType]:
        """Return all rows of a query result set.

        Returns:
            list: A list of tuples with all rows of a query result set.
        """
        self._check_executed()
        if not self._have_unread_result():
            return []

        (rows, eof) = self._connection.get_rows()
        if self._nextrow[0]:
            rows.insert(0, self._nextrow[0])

        self._handle_eof(eof)
        rowcount = len(rows)
        if rowcount >= 0 and self._rowcount == -1:
            self._rowcount = 0
        self._rowcount += rowcount
        return rows

    @property
    def column_names(self) -> Tuple[str, ...]:
        """Returns column names

        This property returns the columns names as a tuple.

        Returns a tuple.
        """
        if not self.description:
            return tuple()
        return tuple(d[0] for d in self.description)

    @property
    def statement(self) -> Optional[str]:
        """Returns the executed statement

        This property returns the executed statement. When multiple
        statements were executed, the current statement in the iterator
        will be returned.
        """
        if self._executed is None:
            return None
        try:
            return self._executed.strip().decode("utf-8")  # type: ignore[union-attr]
        except (AttributeError, UnicodeDecodeError):
            return self._executed.strip()  # type: ignore[return-value]

    @property
    def with_rows(self) -> bool:
        """Returns whether the cursor could have rows returned

        This property returns True when column descriptions are available
        and possibly also rows, which will need to be fetched.

        Returns True or False.
        """
        if not self.description:
            return False
        return True

    def __str__(self) -> str:
        fmt = "{class_name}: {stmt}"
        if self._executed:
            try:
                executed = self._executed.decode("utf-8")  # type: ignore[union-attr]
            except AttributeError:
                executed = self._executed
            if len(executed) > 40:
                executed = executed[:40] + ".."
        else:
            executed = "(Nothing executed yet)"
        return fmt.format(class_name=self.__class__.__name__, stmt=executed)


class MySQLCursorBuffered(MySQLCursor):
    """Cursor which fetches rows within execute()"""

    def __init__(
        self, connection: Optional[Type[MySQLConnectionAbstract]] = None
    ) -> None:
        super().__init__(connection)
        self._rows: Optional[List[RowType]] = None
        self._next_row: int = 0

    def _handle_resultset(self) -> None:
        (self._rows, eof) = self._connection.get_rows()
        self._rowcount = len(self._rows)
        self._handle_eof(eof)
        self._next_row = 0
        try:
            self._connection.unread_result = False
        except AttributeError:
            pass

    def reset(self, free: bool = True) -> None:
        self._rows = None

    def _fetch_row(self, raw: bool = False) -> Optional[RowType]:
        row = None
        try:
            row = self._rows[self._next_row]
        except (IndexError, TypeError):
            return None
        self._next_row += 1
        return row

    def fetchone(self) -> Optional[RowType]:
        """Return next row of a query result set.

        Returns:
            tuple or None: A row from query result set.
        """
        self._check_executed()
        return self._fetch_row()

    def fetchall(self) -> List[RowType]:
        """Return all rows of a query result set.

        Returns:
            list: A list of tuples with all rows of a query result set.
        """
        if self._executed is None or self._rows is None:
            raise InterfaceError(ERR_NO_RESULT_TO_FETCH)
        res = []
        res = self._rows[self._next_row :]
        self._next_row = len(self._rows)
        return res

    def fetchmany(self, size: Optional[int] = None) -> List[RowType]:
        """Return the next set of rows of a query result set.

        When no more rows are available, it returns an empty list.
        The number of rows returned can be specified using the size argument,
        which defaults to one.

        Returns:
            list: The next set of rows of a query result set.
        """
        self._check_executed()
        res = []
        cnt = size or self.arraysize
        while cnt > 0:
            cnt -= 1
            row = self.fetchone()
            if row:
                res.append(row)

        return res

    @property
    def with_rows(self) -> bool:
        return self._rows is not None


class MySQLCursorRaw(MySQLCursor):
    """
    Skips conversion from MySQL datatypes to Python types when fetching rows.
    """

    _raw: bool = True

    def fetchone(self) -> Optional[RowType]:
        """Return next row of a query result set.

        Returns:
            tuple or None: A row from query result set.
        """
        self._check_executed()
        return self._fetch_row(raw=True)

    def fetchall(self) -> List[RowType]:
        """Return all rows of a query result set.

        Returns:
            list: A list of tuples with all rows of a query result set.
        """
        self._check_executed()
        if not self._have_unread_result():
            return []
        (rows, eof) = self._connection.get_rows(raw=True)
        if self._nextrow[0]:
            rows.insert(0, self._nextrow[0])
        self._handle_eof(eof)
        rowcount = len(rows)
        if rowcount >= 0 and self._rowcount == -1:
            self._rowcount = 0
        self._rowcount += rowcount
        return rows


class MySQLCursorBufferedRaw(MySQLCursorBuffered):
    """
    Cursor which skips conversion from MySQL datatypes to Python types when
    fetching rows and fetches rows within execute().
    """

    _raw: bool = True

    def _handle_resultset(self) -> None:
        (self._rows, eof) = self._connection.get_rows(raw=self._raw)
        self._rowcount = len(self._rows)
        self._handle_eof(eof)
        self._next_row = 0
        try:
            self._connection.unread_result = False
        except AttributeError:
            pass

    def fetchone(self) -> Optional[RowType]:
        """Return next row of a query result set.

        Returns:
            tuple or None: A row from query result set.
        """
        self._check_executed()
        return self._fetch_row()

    def fetchall(self) -> List[RowType]:
        """Return all rows of a query result set.

        Returns:
            list: A list of tuples with all rows of a query result set.
        """
        self._check_executed()
        return list(self._rows[self._next_row :])

    @property
    def with_rows(self) -> bool:
        return self._rows is not None


class MySQLCursorPrepared(MySQLCursor):
    """Cursor using MySQL Prepared Statements"""

    def __init__(self, connection: Optional[Type[MySQLConnectionAbstract]] = None):
        super().__init__(connection)
        self._rows: Optional[List[RowType]] = None
        self._next_row: int = 0
        self._prepared: Optional[Dict[str, Union[int, List[DescriptionType]]]] = None
        self._binary: bool = True
        self._have_result: Optional[bool] = None
        self._last_row_sent: bool = False
        self._cursor_exists: bool = False

    def reset(self, free: bool = True) -> None:
        if self._prepared:
            try:
                self._connection.cmd_stmt_close(self._prepared["statement_id"])
            except Error:
                # We tried to deallocate, but it's OK when we fail.
                pass
            self._prepared = None
        self._last_row_sent = False
        self._cursor_exists = False

    def _handle_noresultset(self, res: ResultType) -> None:
        self._handle_server_status(res.get("status_flag", res.get("server_status", 0)))
        super()._handle_noresultset(res)

    def _handle_server_status(self, flags: int) -> None:
        """Check for SERVER_STATUS_CURSOR_EXISTS and
        SERVER_STATUS_LAST_ROW_SENT flags set by the server.
        """
        self._cursor_exists = flags & ServerFlag.STATUS_CURSOR_EXISTS != 0
        self._last_row_sent = flags & ServerFlag.STATUS_LAST_ROW_SENT != 0

    def _handle_eof(self, eof: EofPacketType) -> None:
        self._handle_server_status(eof.get("status_flag", eof.get("server_status", 0)))
        super()._handle_eof(eof)

    def callproc(self, procname: Any, args: Any = ()) -> NoReturn:
        """Calls a stored procedue

        Not supported with MySQLCursorPrepared.
        """
        raise NotSupportedError()

    def close(self) -> None:
        """Close the cursor

        This method will try to deallocate the prepared statement and close
        the cursor.
        """
        self.reset()
        super().close()

    def _row_to_python(self, rowdata: Any, desc: Any = None) -> Any:
        """Convert row data from MySQL to Python types

        The conversion is done while reading binary data in the
        protocol module.
        """

    def _handle_result(self, result: ResultType) -> None:
        """Handle result after execution"""
        if isinstance(result, dict):
            self._connection.unread_result = False
            self._have_result = False
            self._handle_noresultset(result)
        else:
            self._description = result[1]
            self._connection.unread_result = True
            self._have_result = True

            if "status_flag" in result[2]:  # type: ignore[operator]
                self._handle_server_status(result[2]["status_flag"])
            elif "server_status" in result[2]:  # type: ignore[operator]
                self._handle_server_status(result[2]["server_status"])

    def execute(
        self,
        operation: StrOrBytes,
        params: Optional[ParamsSequenceOrDictType] = None,
        multi: bool = False,
    ) -> None:  # multi is unused
        """Prepare and execute a MySQL Prepared Statement

        This method will prepare the given operation and execute it using
        the optionally given parameters.

        If the cursor instance already had a prepared statement, it is
        first closed.

        Note: argument "multi" is unused.
        """
        charset = self._connection.charset
        if charset == "utf8mb4":
            charset = "utf8"

        if not isinstance(operation, str):
            try:
                operation = operation.decode(charset)
            except UnicodeDecodeError as err:
                raise ProgrammingError(str(err)) from err

        if isinstance(params, dict):
            replacement_keys = re.findall(RE_SQL_PYTHON_CAPTURE_PARAM_NAME, operation)
            try:
                # Replace params dict with params tuple in correct order.
                params = tuple(params[key] for key in replacement_keys)
            except KeyError as err:
                raise ProgrammingError(
                    "Not all placeholders were found in the parameters dict"
                ) from err
            # Convert %(name)s to ? before sending it to MySQL
            operation = re.sub(RE_SQL_PYTHON_REPLACE_PARAM, "?", operation)

        if operation is not self._executed:
            if self._prepared:
                self._connection.cmd_stmt_close(self._prepared["statement_id"])
            self._executed = operation

            try:
                operation = operation.encode(charset)
            except UnicodeEncodeError as err:
                raise ProgrammingError(str(err)) from err

            if b"%s" in operation:
                # Convert %s to ? before sending it to MySQL
                operation = re.sub(RE_SQL_FIND_PARAM, b"?", operation)

            try:
                self._prepared = self._connection.cmd_stmt_prepare(operation)
            except Error:
                self._executed = None
                raise

        self._connection.cmd_stmt_reset(self._prepared["statement_id"])

        if self._prepared["parameters"] and not params:
            return
        if params:
            if not isinstance(params, (tuple, list)):
                raise ProgrammingError(
                    errno=1210,
                    msg=f"Incorrect type of argument: {type(params).__name__}({params})"
                    ", it must be of type tuple or list the argument given to "
                    "the prepared statement",
                )
            if len(self._prepared["parameters"]) != len(params):
                raise ProgrammingError(
                    errno=1210,
                    msg="Incorrect number of arguments executing prepared statement",
                )

        if params is None:
            params = ()
        res = self._connection.cmd_stmt_execute(
            self._prepared["statement_id"],
            data=params,
            parameters=self._prepared["parameters"],
        )
        self._handle_result(res)

    def executemany(
        self,
        operation: str,
        seq_params: Sequence[ParamsSequenceType],
    ) -> None:
        """Prepare and execute a MySQL Prepared Statement many times

        This method will prepare the given operation and execute with each
        tuple found the list seq_params.

        If the cursor instance already had a prepared statement, it is
        first closed.

        executemany() simply calls execute().
        """
        rowcnt = 0
        try:
            for params in seq_params:
                self.execute(operation, params)
                if self.with_rows and self._have_unread_result():
                    self.fetchall()
                rowcnt += self._rowcount
        except (ValueError, TypeError) as err:
            raise InterfaceError(f"Failed executing the operation; {err}") from None
        self._rowcount = rowcnt

    def fetchone(self) -> Optional[RowType]:
        """Return next row of a query result set.

        Returns:
            tuple or None: A row from query result set.
        """
        self._check_executed()
        if self._cursor_exists:
            self._connection.cmd_stmt_fetch(self._prepared["statement_id"])
        return self._fetch_row() or None

    def fetchmany(self, size: Optional[int] = None) -> List[RowType]:
        """Return the next set of rows of a query result set.

        When no more rows are available, it returns an empty list.
        The number of rows returned can be specified using the size argument,
        which defaults to one.

        Returns:
            list: The next set of rows of a query result set.
        """
        self._check_executed()
        res = []
        cnt = size or self.arraysize
        while cnt > 0 and self._have_unread_result():
            cnt -= 1
            row = self._fetch_row()
            if row:
                res.append(row)
        return res

    def fetchall(self) -> List[RowType]:
        """Return all rows of a query result set.

        Returns:
            list: A list of tuples with all rows of a query result set.
        """
        self._check_executed()
        rows = []
        if self._nextrow[0]:
            rows.append(self._nextrow[0])
        while self._have_unread_result():
            if self._cursor_exists:
                self._connection.cmd_stmt_fetch(
                    self._prepared["statement_id"], MAX_RESULTS
                )
            (tmp, eof) = self._connection.get_rows(
                binary=self._binary, columns=self.description
            )
            rows.extend(tmp)
            self._handle_eof(eof)
        self._rowcount = len(rows)
        return rows


class MySQLCursorDict(MySQLCursor):
    """
    Cursor fetching rows as dictionaries.

    The fetch methods of this class will return dictionaries instead of tuples.
    Each row is a dictionary that looks like:
        row = {
            "col1": value1,
            "col2": value2
        }
    """

    def _row_to_python(
        self,
        rowdata: RowType,
        desc: Optional[List[DescriptionType]] = None,  # pylint: disable=unused-argument
    ) -> Optional[Dict[str, ToPythonOutputTypes]]:
        """Convert a MySQL text result row to Python types

        Returns a dictionary.
        """
        return dict(zip(self.column_names, rowdata)) if rowdata else None

    def fetchone(self) -> Optional[Dict[str, ToPythonOutputTypes]]:
        """Return next row of a query result set.

        Returns:
            dict or None: A dict from query result set.
        """
        return self._row_to_python(super().fetchone(), self.description)

    def fetchall(self) -> List[Optional[Dict[str, ToPythonOutputTypes]]]:
        """Return all rows of a query result set.

        Returns:
            list: A list of dictionaries with all rows of a query
                  result set where column names are used as keys.
        """
        return [
            self._row_to_python(row, self.description)
            for row in super().fetchall()
            if row
        ]


class MySQLCursorNamedTuple(MySQLCursor):
    """
    Cursor fetching rows as named tuple.

    The fetch methods of this class will return namedtuples instead of tuples.
    Each row is returned as a namedtuple and the values can be accessed as:
    row.col1, row.col2
    """

    def _row_to_python(
        self,
        rowdata: RowType,
        desc: Optional[List[DescriptionType]] = None,  # pylint: disable=unused-argument
    ) -> Optional[RowType]:
        """Convert a MySQL text result row to Python types

        Returns a named tuple.
        """
        row = rowdata

        if row:
            columns = tuple(self.column_names)
            try:
                named_tuple = NAMED_TUPLE_CACHE[columns]
            except KeyError:
                named_tuple = namedtuple("Row", columns)  # type:ignore[no-redef, misc]
                NAMED_TUPLE_CACHE[columns] = named_tuple
            return named_tuple(*row)
        return None

    def fetchone(self) -> Optional[RowType]:
        """Return next row of a query result set.

        Returns:
            tuple or None: A row from query result set.
        """
        row = super().fetchone()
        if not row:
            return None
        return (
            self._row_to_python(row, self.description)
            if hasattr(self._connection, "converter")
            else row
        )

    def fetchall(self) -> List[Optional[RowType]]:
        """Return all rows of a query result set.

        Returns:
            list: A list of tuples with all rows of a query result set.
        """
        return [
            self._row_to_python(row, self.description)
            for row in super().fetchall()
            if row
        ]


class MySQLCursorBufferedDict(MySQLCursorDict, MySQLCursorBuffered):
    """
    Buffered Cursor fetching rows as dictionaries.
    """

    def fetchone(self) -> Optional[Dict[str, ToPythonOutputTypes]]:
        """Return next row of a query result set.

        Returns:
            tuple or None: A row from query result set.
        """
        self._check_executed()
        row = self._fetch_row()
        if row:
            return self._row_to_python(row, self.description)
        return None

    def fetchall(self) -> List[Optional[Dict[str, ToPythonOutputTypes]]]:
        """Return all rows of a query result set.

        Returns:
            list: A list of tuples with all rows of a query result set.
        """
        if self._executed is None or self._rows is None:
            raise InterfaceError(ERR_NO_RESULT_TO_FETCH)
        res = []
        for row in self._rows[self._next_row :]:
            res.append(self._row_to_python(row, self.description))
        self._next_row = len(self._rows)
        return res


class MySQLCursorBufferedNamedTuple(MySQLCursorNamedTuple, MySQLCursorBuffered):
    """
    Buffered Cursor fetching rows as named tuple.
    """

    def fetchone(self) -> Optional[RowType]:
        """Return next row of a query result set.

        Returns:
            tuple or None: A row from query result set.
        """
        self._check_executed()
        row = self._fetch_row()
        if row:
            return self._row_to_python(row, self.description)
        return None

    def fetchall(self) -> List[Optional[RowType]]:
        """Return all rows of a query result set.

        Returns:
            list: A list of tuples with all rows of a query result set.
        """
        if self._executed is None or self._rows is None:
            raise InterfaceError(ERR_NO_RESULT_TO_FETCH)
        res = []
        for row in self._rows[self._next_row :]:
            res.append(self._row_to_python(row, self.description))
        self._next_row = len(self._rows)
        return res


class MySQLCursorPreparedDict(MySQLCursorDict, MySQLCursorPrepared):  # type: ignore[misc]
    """
    This class is a blend of features from MySQLCursorDict and MySQLCursorPrepared

    Multiple inheritance in python is allowed but care must be taken
    when assuming methods resolution. In the case of multiple
    inheritance, a given attribute is first searched in the current
    class if it's not found then it's searched in the parent classes.
    The parent classes are searched in a left-right fashion and each
    class is searched once.
    Based on python's attribute resolution, in this case, attributes
    are searched as follows:
    1. MySQLCursorPreparedDict (current class)
    2. MySQLCursorDict (left parent class)
    3. MySQLCursorPrepared (right parent class)
    4. MySQLCursor (base class)
    """

    def fetchmany(
        self, size: Optional[int] = None
    ) -> List[Dict[str, ToPythonOutputTypes]]:
        """Return the next set of rows of a query result set.

        When no more rows are available, it returns an empty list.
        The number of rows returned can be specified using the size argument,
        which defaults to one.

        Returns:
            list: The next set of rows of a query result set represented
                  as a list of dictionaries where column names are used as keys.
        """
        return [
            self._row_to_python(row, self.description)
            for row in super().fetchmany(size=size)
            if row
        ]


class MySQLCursorPreparedNamedTuple(MySQLCursorNamedTuple, MySQLCursorPrepared):
    """
    This class is a blend of features from MySQLCursorNamedTuple and MySQLCursorPrepared
    """

    def fetchmany(self, size: Optional[int] = None) -> List[RowType]:
        """Return the next set of rows of a query result set.

        When no more rows are available, it returns an empty list.
        The number of rows returned can be specified using the size argument,
        which defaults to one.

        Returns:
            list: The next set of rows of a query result set represented
                  as a list of named tuples where column names are used as names.
        """
        return [
            self._row_to_python(row, self.description)
            for row in super().fetchmany(size=size)
            if row
        ]


class MySQLCursorPreparedRaw(MySQLCursorPrepared):
    """
    This class is a blend of features from MySQLCursorRaw and MySQLCursorPrepared
    """

    _raw: bool = True

    def fetchone(self) -> Optional[RowType]:
        """Return next row of a query result set.

        Returns:
            tuple or None: A row from query result set.
        """
        self._check_executed()
        if self._cursor_exists:
            self._connection.cmd_stmt_fetch(self._prepared["statement_id"])
        return self._fetch_row(raw=self._raw) or None

    def fetchmany(self, size: Optional[int] = None) -> List[RowType]:
        """Return the next set of rows of a query result set.

        When no more rows are available, it returns an empty list.
        The number of rows returned can be specified using the size argument,
        which defaults to one.

        Returns:
            list: The next set of rows of a query result set.
        """
        self._check_executed()
        res = []
        cnt = size or self.arraysize
        while cnt > 0 and self._have_unread_result():
            cnt -= 1
            row = self._fetch_row(raw=self._raw)
            if row:
                res.append(row)
        return res

    def fetchall(self) -> List[RowType]:
        """Return all rows of a query result set.

        Returns:
            list: A list of tuples with all rows of a query result set.
        """
        self._check_executed()
        rows = []
        if self._nextrow[0]:
            rows.append(self._nextrow[0])
        while self._have_unread_result():
            if self._cursor_exists:
                self._connection.cmd_stmt_fetch(
                    self._prepared["statement_id"], MAX_RESULTS
                )
            (tmp, eof) = self._connection.get_rows(
                raw=self._raw, binary=self._binary, columns=self.description
            )
            rows.extend(tmp)
            self._handle_eof(eof)
        self._rowcount = len(rows)
        return rows

virt/lib/python3.9/site-packages/mysql/connector/cursor 3.py
PrinceDisant/LostInNL
# Copyright (c) 2009, 2023, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

# mypy: disable-error-code="assignment,arg-type,attr-defined,index,override,call-overload"

"""Cursor classes."""
from __future__ import annotations

import re
import warnings
import weakref

from collections import namedtuple
from decimal import Decimal
from typing import (
    Any,
    Dict,
    Generator,
    Iterator,
    List,
    NoReturn,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
)
from weakref import CallableProxyType

from .abstracts import NAMED_TUPLE_CACHE, MySQLConnectionAbstract, MySQLCursorAbstract
from .constants import ServerFlag
from .errors import (
    Error,
    InterfaceError,
    NotSupportedError,
    ProgrammingError,
    get_mysql_exception,
)
from .types import (
    DescriptionType,
    EofPacketType,
    ParamsDictType,
    ParamsSequenceOrDictType,
    ParamsSequenceType,
    ResultType,
    RowType,
    StrOrBytes,
    ToPythonOutputTypes,
    WarningType,
)

SQL_COMMENT = r"\/\*.*?\*\/"
RE_SQL_COMMENT = re.compile(
    rf"""({SQL_COMMENT})|(["'`][^"'`]*?({SQL_COMMENT})[^"'`]*?["'`])""",
    re.I | re.M | re.S,
)
RE_SQL_ON_DUPLICATE = re.compile(
    r"""\s*ON\s+DUPLICATE\s+KEY(?:[^"'`]*["'`][^"'`]*["'`])*[^"'`]*$""",
    re.I | re.M | re.S,
)
RE_SQL_INSERT_STMT = re.compile(
    rf"({SQL_COMMENT}|\s)*INSERT({SQL_COMMENT}|\s)"
    r"*(?:IGNORE\s+)?INTO\s+[`'\"]?.+[`'\"]?(?:\.[`'\"]?.+[`'\"]?)"
    r"{0,2}\s+VALUES\s*(\(.+\)).*",
    re.I | re.M | re.S,
)
RE_SQL_INSERT_VALUES = re.compile(r".*VALUES\s*(\(.+\)).*", re.I | re.M | re.S)
RE_PY_PARAM = re.compile(b"(%s)")
RE_PY_MAPPING_PARAM = re.compile(
    rb"""
    %
    \((?P<mapping_key>[^)]+)\)
    (?P<conversion_type>[diouxXeEfFgGcrs%])
    """,
    re.X,
)
RE_SQL_SPLIT_STMTS = re.compile(
    b""";(?=(?:[^"'`]*(?:"[^"]*"|'[^']*'|`[^`]*`))*[^"'`]*$)"""
)
RE_SQL_FIND_PARAM = re.compile(b"""%s(?=(?:[^"'`]*["'`][^"'`]*["'`])*[^"'`]*$)""")
RE_SQL_PYTHON_REPLACE_PARAM = re.compile(r"%\(.*?\)s")
RE_SQL_PYTHON_CAPTURE_PARAM_NAME = re.compile(r"%\((.*?)\)s")

ERR_NO_RESULT_TO_FETCH = "No result set to fetch from"

MAX_RESULTS = 4294967295


class _ParamSubstitutor:
    """
    Substitutes parameters into SQL statement.
    """

    def __init__(self, params: Sequence[bytes]) -> None:
        self.params: Sequence[bytes] = params
        self.index: int = 0

    def __call__(self, matchobj: re.Match) -> bytes:
        index = self.index
        self.index += 1
        try:
            return bytes(self.params[index])
        except IndexError:
            raise ProgrammingError(
                "Not enough parameters for the SQL statement"
            ) from None

    @property
    def remaining(self) -> int:
        """Returns number of parameters remaining to be substituted"""
        return len(self.params) - self.index


def _bytestr_format_dict(bytestr: bytes, value_dict: Dict[bytes, bytes]) -> bytes:
    """
    >>> _bytestr_format_dict(b'%(a)s', {b'a': b'foobar'})
    b'foobar
    >>> _bytestr_format_dict(b'%%(a)s', {b'a': b'foobar'})
    b'%%(a)s'
    >>> _bytestr_format_dict(b'%%%(a)s', {b'a': b'foobar'})
    b'%%foobar'
    >>> _bytestr_format_dict(b'%(x)s %(y)s',
    ...                      {b'x': b'x=%(y)s', b'y': b'y=%(x)s'})
    b'x=%(y)s y=%(x)s'
    """

    def replace(matchobj: re.Match) -> bytes:
        """Replace pattern."""
        value: Optional[bytes] = None
        groups = matchobj.groupdict()
        if groups["conversion_type"] == b"%":
            value = b"%"
        if groups["conversion_type"] == b"s":
            key = groups["mapping_key"]
            value = value_dict[key]
        if value is None:
            raise ValueError(
                f"Unsupported conversion_type: {groups['conversion_type']}"
            )
        return value

    stmt = RE_PY_MAPPING_PARAM.sub(replace, bytestr)
    return stmt


class CursorBase(MySQLCursorAbstract):
    """
    Base for defining MySQLCursor. This class is a skeleton and defines
    methods and members as required for the Python Database API
    Specification v2.0.

    It's better to inherite from MySQLCursor.
    """

    _raw: bool = False

    def __init__(self) -> None:
        self._description: Optional[List[DescriptionType]] = None
        self._rowcount: int = -1
        self.arraysize: int = 1
        super().__init__()

    def callproc(self, procname: str, args: Sequence[Any] = ()) -> Any:
        """Calls a stored procedue with the given arguments

        The arguments will be set during this session, meaning
        they will be called like  _<procname>__arg<nr> where
        <nr> is an enumeration (+1) of the arguments.

        Coding Example:
          1) Definining the Stored Routine in MySQL:
          CREATE PROCEDURE multiply(IN pFac1 INT, IN pFac2 INT, OUT pProd INT)
          BEGIN
            SET pProd := pFac1 * pFac2;
          END

          2) Executing in Python:
          args = (5,5,0) # 0 is to hold pprod
          cursor.callproc('multiply', args)
          print(cursor.fetchone())

        Does not return a value, but a result set will be
        available when the CALL-statement execute successfully.
        Raises exceptions when something is wrong.
        """

    def close(self) -> Any:
        """Close the cursor."""

    def execute(
        self,
        operation: Any,
        params: Union[Sequence[Any], Dict[str, Any]] = (),
        multi: bool = False,
    ) -> Any:
        """Executes the given operation

        Executes the given operation substituting any markers with
        the given parameters.

        For example, getting all rows where id is 5:
          cursor.execute("SELECT * FROM t1 WHERE id = %s", (5,))

        The multi argument should be set to True when executing multiple
        statements in one operation. If not set and multiple results are
        found, an InterfaceError will be raised.

        If warnings where generated, and connection.get_warnings is True, then
        self._warnings will be a list containing these warnings.

        Returns an iterator when multi is True, otherwise None.
        """

    def executemany(
        self, operation: Any, seq_params: Sequence[Union[Sequence[Any], Dict[str, Any]]]
    ) -> Any:
        """Execute the given operation multiple times

        The executemany() method will execute the operation iterating
        over the list of parameters in seq_params.

        Example: Inserting 3 new employees and their phone number

        data = [
            ('Jane','555-001'),
            ('Joe', '555-001'),
            ('John', '555-003')
            ]
        stmt = "INSERT INTO employees (name, phone) VALUES ('%s','%s')"
        cursor.executemany(stmt, data)

        INSERT statements are optimized by batching the data, that is
        using the MySQL multiple rows syntax.

        Results are discarded. If they are needed, consider looping over
        data using the execute() method.
        """

    def fetchone(self) -> Optional[Sequence[Any]]:
        """Return next row of a query result set.

        Returns:
            tuple or None: A row from query result set.
        """

    def fetchmany(self, size: int = 1) -> List[Sequence[Any]]:
        """Return the next set of rows of a query result set.

        When no more rows are available, it returns an empty list.
        The number of rows returned can be specified using the size argument,
        which defaults to one.

        Returns:
            list: The next set of rows of a query result set.
        """

    def fetchall(self) -> List[Sequence[Any]]:
        """Return all rows of a query result set.

        Returns:
            list: A list of tuples with all rows of a query result set.
        """

    def nextset(self) -> Any:
        """Not Implemented."""

    def setinputsizes(self, sizes: Any) -> Any:
        """Not Implemented."""

    def setoutputsize(self, size: Any, column: Any = None) -> Any:
        """Not Implemented."""

    def reset(self, free: bool = True) -> Any:
        """Reset the cursor to default"""

    @property
    def description(self) -> Optional[List[DescriptionType]]:
        """Returns description of columns in a result

        This property returns a list of tuples describing the columns in
        in a result set. A tuple is described as follows::

                (column_name,
                 type,
                 None,
                 None,
                 None,
                 None,
                 null_ok,
                 column_flags)  # Addition to PEP-249 specs

        Returns a list of tuples.
        """
        return self._description

    @property
    def rowcount(self) -> int:
        """Returns the number of rows produced or affected

        This property returns the number of rows produced by queries
        such as a SELECT, or affected rows when executing DML statements
        like INSERT or UPDATE.

        Note that for non-buffered cursors it is impossible to know the
        number of rows produced before having fetched them all. For those,
        the number of rows will be -1 right after execution, and
        incremented when fetching rows.

        Returns an integer.
        """
        return self._rowcount


class MySQLCursor(CursorBase):
    """Default cursor for interacting with MySQL

    This cursor will execute statements and handle the result. It will
    not automatically fetch all rows.

    MySQLCursor should be inherited whenever other functionallity is
    required. An example would to change the fetch* member functions
    to return dictionaries instead of lists of values.

    Implements the Python Database API Specification v2.0 (PEP-249)
    """

    def __init__(
        self, connection: Optional[Type[MySQLConnectionAbstract]] = None
    ) -> None:
        CursorBase.__init__(self)
        self._connection: CallableProxyType[Type[MySQLConnectionAbstract]] = None
        self._nextrow: Tuple[Optional[RowType], Optional[EofPacketType]] = (
            None,
            None,
        )
        self._binary: bool = False

        if connection is not None:
            self._set_connection(connection)

    def __iter__(self) -> Iterator[RowType]:
        """
        Iteration over the result set which calls self.fetchone()
        and returns the next row.
        """
        return iter(self.fetchone, None)

    def _set_connection(self, connection: Type[MySQLConnectionAbstract]) -> None:
        """Set the connection"""
        try:
            self._connection = weakref.proxy(connection)
            self._connection.is_connected()
        except (AttributeError, TypeError):
            raise InterfaceError(errno=2048) from None

    def _reset_result(self) -> None:
        """Reset the cursor to default"""
        self._rowcount: int = -1
        self._nextrow = (None, None)
        self._stored_results: List[MySQLCursor] = []
        self._warnings: Optional[List[WarningType]] = None
        self._warning_count: int = 0
        self._description: Optional[List[DescriptionType]] = None
        self._executed: Optional[StrOrBytes] = None
        self._executed_list: List[StrOrBytes] = []
        self.reset()

    def _have_unread_result(self) -> bool:
        """Check whether there is an unread result"""
        try:
            return self._connection.unread_result
        except AttributeError:
            return False

    def _check_executed(self) -> None:
        """Check if the statement has been executed.

        Raises an error if the statement has not been executed.
        """
        if self._executed is None:
            raise InterfaceError(ERR_NO_RESULT_TO_FETCH)

    def __next__(self) -> RowType:
        """
        Used for iterating over the result set. Calles self.fetchone()
        to get the next row.
        """
        try:
            row = self.fetchone()
        except InterfaceError:
            raise StopIteration from None
        if not row:
            raise StopIteration
        return row

    def close(self) -> bool:
        """Close the cursor

        Returns True when successful, otherwise False.
        """
        if self._connection is None:
            return False

        self._connection.handle_unread_result()
        self._reset_result()
        self._connection = None

        return True

    def _process_params_dict(
        self, params: ParamsDictType
    ) -> Dict[bytes, Union[bytes, Decimal]]:
        """Process query parameters given as dictionary"""
        res: Dict[bytes, Any] = {}
        try:
            sql_mode = self._connection.sql_mode
            to_mysql = self._connection.converter.to_mysql
            escape = self._connection.converter.escape
            quote = self._connection.converter.quote
            for key, value in params.items():
                conv = value
                conv = to_mysql(conv)
                conv = escape(conv, sql_mode)
                if not isinstance(value, Decimal):
                    conv = quote(conv)
                res[key.encode()] = conv
        except Exception as err:
            raise ProgrammingError(
                f"Failed processing pyformat-parameters; {err}"
            ) from err
        return res

    def _process_params(
        self, params: ParamsSequenceType
    ) -> Tuple[Union[bytes, Decimal], ...]:
        """Process query parameters."""
        res = params[:]
        try:
            sql_mode = self._connection.sql_mode
            to_mysql = self._connection.converter.to_mysql
            escape = self._connection.converter.escape
            quote = self._connection.converter.quote
            res = [to_mysql(value) for value in res]
            res = [escape(value, sql_mode) for value in res]
            res = [
                quote(value) if not isinstance(params[i], Decimal) else value
                for i, value in enumerate(res)
            ]
        except Exception as err:
            raise ProgrammingError(
                f"Failed processing format-parameters; {err}"
            ) from err
        return tuple(res)

    def _handle_noresultset(self, res: ResultType) -> None:
        """Handles result of execute() when there is no result set"""
        try:
            self._rowcount = res["affected_rows"]
            self._last_insert_id = res["insert_id"]
            self._warning_count = res["warning_count"]
        except (KeyError, TypeError) as err:
            raise ProgrammingError(f"Failed handling non-resultset; {err}") from None

        self._handle_warnings()

    def _handle_resultset(self) -> None:
        """Handles result set

        This method handles the result set and is called after reading
        and storing column information in _handle_result(). For non-buffering
        cursors, this method is usually doing nothing.
        """

    def _handle_result(self, result: ResultType) -> None:
        """
        Handle the result after a command was send. The result can be either
        an OK-packet or a dictionary containing column/eof information.

        Raises InterfaceError when result is not a dict() or result is
        invalid.
        """
        if not isinstance(result, dict):
            raise InterfaceError("Result was not a dict()")

        if "columns" in result:
            # Weak test, must be column/eof information
            self._description = result["columns"]
            self._connection.unread_result = True
            self._handle_resultset()
        elif "affected_rows" in result:
            # Weak test, must be an OK-packet
            self._connection.unread_result = False
            self._handle_noresultset(result)
        else:
            raise InterfaceError("Invalid result")

    def _execute_iter(
        self, query_iter: Generator[ResultType, None, None]
    ) -> Generator[MySQLCursor, None, None]:
        """Generator returns MySQLCursor objects for multiple statements

        This method is only used when multiple statements are executed
        by the execute() method. It uses zip() to make an iterator from the
        given query_iter (result of MySQLConnection.cmd_query_iter()) and
        the list of statements that were executed.
        """
        executed_list = RE_SQL_SPLIT_STMTS.split(self._executed)

        i = 0
        while True:
            try:
                result = next(query_iter)
                self._reset_result()
                self._handle_result(result)
                try:
                    self._executed = executed_list[i].strip()
                    i += 1
                except IndexError:
                    self._executed = executed_list[0]

                yield self
            except StopIteration:
                return

    def execute(
        self,
        operation: StrOrBytes,
        params: Optional[ParamsSequenceOrDictType] = None,
        multi: bool = False,
    ) -> Optional[Generator[MySQLCursor, None, None]]:
        """Executes the given operation

        Executes the given operation substituting any markers with
        the given parameters.

        For example, getting all rows where id is 5:
          cursor.execute("SELECT * FROM t1 WHERE id = %s", (5,))

        The multi argument should be set to True when executing multiple
        statements in one operation. If not set and multiple results are
        found, an InterfaceError will be raised.

        If warnings where generated, and connection.get_warnings is True, then
        self._warnings will be a list containing these warnings.

        Returns an iterator when multi is True, otherwise None.
        """
        if not operation:
            return None

        try:
            if not self._connection:
                raise ProgrammingError
        except (ProgrammingError, ReferenceError) as err:
            raise ProgrammingError("Cursor is not connected") from err

        self._connection.handle_unread_result()

        self._reset_result()
        stmt: StrOrBytes = ""

        try:
            if not isinstance(operation, (bytes, bytearray)):
                stmt = operation.encode(self._connection.python_charset)
            else:
                stmt = operation
        except (UnicodeDecodeError, UnicodeEncodeError) as err:
            raise ProgrammingError(str(err)) from err

        if params:
            if isinstance(params, dict):
                stmt = _bytestr_format_dict(stmt, self._process_params_dict(params))
            elif isinstance(params, (list, tuple)):
                psub = _ParamSubstitutor(self._process_params(params))
                stmt = RE_PY_PARAM.sub(psub, stmt)
                if psub.remaining != 0:
                    raise ProgrammingError(
                        "Not all parameters were used in the SQL statement"
                    )
            else:
                raise ProgrammingError(
                    f"Could not process parameters: {type(params).__name__}({params}),"
                    " it must be of type list, tuple or dict"
                )

        self._executed = stmt
        if multi:
            self._executed_list = []
            return self._execute_iter(self._connection.cmd_query_iter(stmt))

        try:
            self._handle_result(self._connection.cmd_query(stmt))
        except InterfaceError as err:
            if self._connection.have_next_result:
                raise InterfaceError(
                    "Use multi=True when executing multiple statements"
                ) from err
            raise
        return None

    def _batch_insert(
        self, operation: str, seq_params: Sequence[ParamsSequenceOrDictType]
    ) -> Optional[bytes]:
        """Implements multi row insert"""

        def remove_comments(match: re.Match) -> str:
            """Remove comments from INSERT statements.

            This function is used while removing comments from INSERT
            statements. If the matched string is a comment not enclosed
            by quotes, it returns an empty string, else the string itself.
            """
            if match.group(1):
                return ""
            return match.group(2)

        tmp = re.sub(
            RE_SQL_ON_DUPLICATE,
            "",
            re.sub(RE_SQL_COMMENT, remove_comments, operation),
        )

        matches = re.search(RE_SQL_INSERT_VALUES, tmp)
        if not matches:
            raise InterfaceError(
                "Failed rewriting statement for multi-row INSERT. Check SQL syntax"
            )
        fmt = matches.group(1).encode(self._connection.python_charset)
        values = []

        try:
            stmt = operation.encode(self._connection.python_charset)
            for params in seq_params:
                tmp = fmt
                if isinstance(params, dict):
                    tmp = _bytestr_format_dict(tmp, self._process_params_dict(params))
                else:
                    psub = _ParamSubstitutor(self._process_params(params))
                    tmp = RE_PY_PARAM.sub(psub, tmp)
                    if psub.remaining != 0:
                        raise ProgrammingError(
                            "Not all parameters were used in the SQL statement"
                        )
                values.append(tmp)
            if fmt in stmt:
                stmt = stmt.replace(fmt, b",".join(values), 1)
                self._executed = stmt
                return stmt
            return None
        except (UnicodeDecodeError, UnicodeEncodeError) as err:
            raise ProgrammingError(str(err)) from err
        except Error:
            raise
        except Exception as err:
            raise InterfaceError(f"Failed executing the operation; {err}") from None

    def executemany(
        self, operation: str, seq_params: Sequence[ParamsSequenceOrDictType]
    ) -> Optional[Generator[MySQLCursor, None, None]]:
        """Execute the given operation multiple times

        The executemany() method will execute the operation iterating
        over the list of parameters in seq_params.

        Example: Inserting 3 new employees and their phone number

        data = [
            ('Jane','555-001'),
            ('Joe', '555-001'),
            ('John', '555-003')
            ]
        stmt = "INSERT INTO employees (name, phone) VALUES ('%s','%s)"
        cursor.executemany(stmt, data)

        INSERT statements are optimized by batching the data, that is
        using the MySQL multiple rows syntax.

        Results are discarded. If they are needed, consider looping over
        data using the execute() method.
        """
        if not operation or not seq_params:
            return None
        self._connection.handle_unread_result()

        try:
            _ = iter(seq_params)
        except TypeError as err:
            raise ProgrammingError("Parameters for query must be an Iterable") from err

        # Optimize INSERTs by batching them
        if re.match(RE_SQL_INSERT_STMT, operation):
            if not seq_params:
                self._rowcount = 0
                return None
            stmt = self._batch_insert(operation, seq_params)
            if stmt is not None:
                self._executed = stmt
                return self.execute(stmt)

        rowcnt = 0
        try:
            for params in seq_params:
                self.execute(operation, params)
                if self.with_rows and self._have_unread_result():
                    self.fetchall()
                rowcnt += self._rowcount
        except (ValueError, TypeError) as err:
            raise InterfaceError(f"Failed executing the operation; {err}") from None
        self._rowcount = rowcnt
        return None

    def stored_results(self) -> Iterator[MySQLCursor]:
        """Returns an iterator for stored results

        This method returns an iterator over results which are stored when
        callproc() is called. The iterator will provide MySQLCursorBuffered
        instances.

        Returns a iterator.
        """
        return iter(self._stored_results)

    def callproc(
        self,
        procname: str,
        args: Sequence[Any] = (),
    ) -> Optional[Union[Dict[str, ToPythonOutputTypes], RowType]]:
        """Calls a stored procedure with the given arguments

        The arguments will be set during this session, meaning
        they will be called like  _<procname>__arg<nr> where
        <nr> is an enumeration (+1) of the arguments.

        Coding Example:
          1) Defining the Stored Routine in MySQL:
          CREATE PROCEDURE multiply(IN pFac1 INT, IN pFac2 INT, OUT pProd INT)
          BEGIN
            SET pProd := pFac1 * pFac2;
          END

          2) Executing in Python:
          args = (5, 5, 0)  # 0 is to hold pprod
          cursor.callproc('multiply', args)
          print(cursor.fetchone())

        For OUT and INOUT parameters the user should provide the
        type of the parameter as well. The argument should be a
        tuple with first item as the value of the parameter to pass
        and second argument the type of the argument.

        In the above example, one can call callproc method like:
          args = (5, 5, (0, 'INT'))
          cursor.callproc('multiply', args)

        The type of the argument given in the tuple will be used by
        the MySQL CAST function to convert the values in the corresponding
        MySQL type (See CAST in MySQL Reference for more information)

        Does not return a value, but a result set will be
        available when the CALL-statement execute successfully.
        Raises exceptions when something is wrong.
        """
        if not procname or not isinstance(procname, str):
            raise ValueError("procname must be a string")

        if not isinstance(args, (tuple, list)):
            raise ValueError("args must be a sequence")

        argfmt = "@_{name}_arg{index}"
        self._stored_results = []

        results = []
        try:
            argnames = []
            argtypes = []

            # MySQL itself does support calling procedures with their full
            # name <database>.<procedure_name>. It's necessary to split
            # by '.' and grab the procedure name from procname.
            procname_abs = procname.split(".")[-1]
            if args:
                argvalues = []
                for idx, arg in enumerate(args):
                    argname = argfmt.format(name=procname_abs, index=idx + 1)
                    argnames.append(argname)
                    if isinstance(arg, tuple):
                        argtypes.append(f" CAST({argname} AS {arg[1]})")
                        argvalues.append(arg[0])
                    else:
                        argtypes.append(argname)
                        argvalues.append(arg)

                placeholders = ",".join(f"{arg}=%s" for arg in argnames)
                self.execute(f"SET {placeholders}", argvalues)

            call = f"CALL {procname}({','.join(argnames)})"

            # We disable consuming results temporary to make sure we
            # getting all results
            can_consume_results = self._connection.can_consume_results
            for result in self._connection.cmd_query_iter(call):
                self._connection.can_consume_results = False
                if isinstance(self, (MySQLCursorDict, MySQLCursorBufferedDict)):
                    cursor_class = MySQLCursorBufferedDict
                elif isinstance(
                    self,
                    (MySQLCursorNamedTuple, MySQLCursorBufferedNamedTuple),
                ):
                    cursor_class = MySQLCursorBufferedNamedTuple
                elif self._raw:
                    cursor_class = MySQLCursorBufferedRaw
                else:
                    cursor_class = MySQLCursorBuffered
                # pylint: disable=protected-access
                cur = cursor_class(self._connection.get_self())
                cur._executed = f"(a result of {call})"
                cur._handle_result(result)
                # pylint: enable=protected-access
                if cur.warnings is not None:
                    self._warnings = cur.warnings
                if "columns" in result:
                    results.append(cur)
            self._connection.can_consume_results = can_consume_results

            if argnames:
                # Create names aliases to be compatible with namedtuples
                args = [
                    f"{name} AS {alias}"
                    for name, alias in zip(
                        argtypes, [arg.lstrip("@_") for arg in argnames]
                    )
                ]
                select = f"SELECT {','.join(args)}"
                self.execute(select)
                self._stored_results = results
                return self.fetchone()

            self._stored_results = results
            return tuple()

        except Error:
            raise
        except Exception as err:
            raise InterfaceError(f"Failed calling stored routine; {err}") from None

    def getlastrowid(self) -> Optional[int]:
        """Returns the value generated for an AUTO_INCREMENT column

        Returns the value generated for an AUTO_INCREMENT column by
        the previous INSERT or UPDATE statement.

        Returns a long value or None.
        """
        return self._last_insert_id

    def _fetch_warnings(self) -> Optional[List[WarningType]]:
        """
        Fetch warnings doing a SHOW WARNINGS. Can be called after getting
        the result.

        Returns a result set or None when there were no warnings.
        """
        res = []
        try:
            cur = self._connection.cursor(raw=False)
            cur.execute("SHOW WARNINGS")
            res = cur.fetchall()
            cur.close()
        except Exception as err:
            raise InterfaceError(f"Failed getting warnings; {err}") from None

        if res:
            return res

        return None

    def _handle_warnings(self) -> None:
        """Handle possible warnings after all results are consumed.

        Raises:
            Error: Also raises exceptions if raise_on_warnings is set.
        """
        if self._connection.get_warnings and self._warning_count:
            self._warnings = self._fetch_warnings()

        if not self._warnings:
            return

        err = get_mysql_exception(
            self._warnings[0][1],
            self._warnings[0][2],
            warning=not self._connection.raise_on_warnings,
        )

        if self._connection.raise_on_warnings:
            raise err

        warnings.warn(err, stacklevel=4)

    def _handle_eof(self, eof: EofPacketType) -> None:
        """Handle EOF packet"""
        self._connection.unread_result = False
        self._nextrow = (None, None)
        self._warning_count = eof["warning_count"]
        self._handle_warnings()

    def _fetch_row(self, raw: bool = False) -> Optional[RowType]:
        """Returns the next row in the result set

        Returns a tuple or None.
        """
        if not self._have_unread_result():
            return None
        row = None

        if self._nextrow == (None, None):
            (row, eof) = self._connection.get_row(
                binary=self._binary, columns=self.description, raw=raw
            )
        else:
            (row, eof) = self._nextrow

        if row:
            self._nextrow = self._connection.get_row(
                binary=self._binary, columns=self.description, raw=raw
            )
            eof = self._nextrow[1]
            if eof is not None:
                self._handle_eof(eof)
            if self._rowcount == -1:
                self._rowcount = 1
            else:
                self._rowcount += 1
        if eof:
            self._handle_eof(eof)

        return row

    def fetchone(self) -> Optional[RowType]:
        """Return next row of a query result set.

        Returns:
            tuple or None: A row from query result set.
        """
        self._check_executed()
        return self._fetch_row()

    def fetchmany(self, size: Optional[int] = None) -> List[RowType]:
        """Return the next set of rows of a query result set.

        When no more rows are available, it returns an empty list.
        The number of rows returned can be specified using the size argument,
        which defaults to one.

        Returns:
            list: The next set of rows of a query result set.
        """
        self._check_executed()
        res = []
        cnt = size or self.arraysize
        while cnt > 0 and self._have_unread_result():
            cnt -= 1
            row = self.fetchone()
            if row:
                res.append(row)
        return res

    def fetchall(self) -> List[RowType]:
        """Return all rows of a query result set.

        Returns:
            list: A list of tuples with all rows of a query result set.
        """
        self._check_executed()
        if not self._have_unread_result():
            return []

        (rows, eof) = self._connection.get_rows()
        if self._nextrow[0]:
            rows.insert(0, self._nextrow[0])

        self._handle_eof(eof)
        rowcount = len(rows)
        if rowcount >= 0 and self._rowcount == -1:
            self._rowcount = 0
        self._rowcount += rowcount
        return rows

    @property
    def column_names(self) -> Tuple[str, ...]:
        """Returns column names

        This property returns the columns names as a tuple.

        Returns a tuple.
        """
        if not self.description:
            return tuple()
        return tuple(d[0] for d in self.description)

    @property
    def statement(self) -> Optional[str]:
        """Returns the executed statement

        This property returns the executed statement. When multiple
        statements were executed, the current statement in the iterator
        will be returned.
        """
        if self._executed is None:
            return None
        try:
            return self._executed.strip().decode("utf-8")  # type: ignore[union-attr]
        except (AttributeError, UnicodeDecodeError):
            return self._executed.strip()  # type: ignore[return-value]

    @property
    def with_rows(self) -> bool:
        """Returns whether the cursor could have rows returned

        This property returns True when column descriptions are available
        and possibly also rows, which will need to be fetched.

        Returns True or False.
        """
        if not self.description:
            return False
        return True

    def __str__(self) -> str:
        fmt = "{class_name}: {stmt}"
        if self._executed:
            try:
                executed = self._executed.decode("utf-8")  # type: ignore[union-attr]
            except AttributeError:
                executed = self._executed
            if len(executed) > 40:
                executed = executed[:40] + ".."
        else:
            executed = "(Nothing executed yet)"
        return fmt.format(class_name=self.__class__.__name__, stmt=executed)


class MySQLCursorBuffered(MySQLCursor):
    """Cursor which fetches rows within execute()"""

    def __init__(
        self, connection: Optional[Type[MySQLConnectionAbstract]] = None
    ) -> None:
        super().__init__(connection)
        self._rows: Optional[List[RowType]] = None
        self._next_row: int = 0

    def _handle_resultset(self) -> None:
        (self._rows, eof) = self._connection.get_rows()
        self._rowcount = len(self._rows)
        self._handle_eof(eof)
        self._next_row = 0
        try:
            self._connection.unread_result = False
        except AttributeError:
            pass

    def reset(self, free: bool = True) -> None:
        self._rows = None

    def _fetch_row(self, raw: bool = False) -> Optional[RowType]:
        row = None
        try:
            row = self._rows[self._next_row]
        except (IndexError, TypeError):
            return None
        self._next_row += 1
        return row

    def fetchone(self) -> Optional[RowType]:
        """Return next row of a query result set.

        Returns:
            tuple or None: A row from query result set.
        """
        self._check_executed()
        return self._fetch_row()

    def fetchall(self) -> List[RowType]:
        """Return all rows of a query result set.

        Returns:
            list: A list of tuples with all rows of a query result set.
        """
        if self._executed is None or self._rows is None:
            raise InterfaceError(ERR_NO_RESULT_TO_FETCH)
        res = []
        res = self._rows[self._next_row :]
        self._next_row = len(self._rows)
        return res

    def fetchmany(self, size: Optional[int] = None) -> List[RowType]:
        """Return the next set of rows of a query result set.

        When no more rows are available, it returns an empty list.
        The number of rows returned can be specified using the size argument,
        which defaults to one.

        Returns:
            list: The next set of rows of a query result set.
        """
        self._check_executed()
        res = []
        cnt = size or self.arraysize
        while cnt > 0:
            cnt -= 1
            row = self.fetchone()
            if row:
                res.append(row)

        return res

    @property
    def with_rows(self) -> bool:
        return self._rows is not None


class MySQLCursorRaw(MySQLCursor):
    """
    Skips conversion from MySQL datatypes to Python types when fetching rows.
    """

    _raw: bool = True

    def fetchone(self) -> Optional[RowType]:
        """Return next row of a query result set.

        Returns:
            tuple or None: A row from query result set.
        """
        self._check_executed()
        return self._fetch_row(raw=True)

    def fetchall(self) -> List[RowType]:
        """Return all rows of a query result set.

        Returns:
            list: A list of tuples with all rows of a query result set.
        """
        self._check_executed()
        if not self._have_unread_result():
            return []
        (rows, eof) = self._connection.get_rows(raw=True)
        if self._nextrow[0]:
            rows.insert(0, self._nextrow[0])
        self._handle_eof(eof)
        rowcount = len(rows)
        if rowcount >= 0 and self._rowcount == -1:
            self._rowcount = 0
        self._rowcount += rowcount
        return rows


class MySQLCursorBufferedRaw(MySQLCursorBuffered):
    """
    Cursor which skips conversion from MySQL datatypes to Python types when
    fetching rows and fetches rows within execute().
    """

    _raw: bool = True

    def _handle_resultset(self) -> None:
        (self._rows, eof) = self._connection.get_rows(raw=self._raw)
        self._rowcount = len(self._rows)
        self._handle_eof(eof)
        self._next_row = 0
        try:
            self._connection.unread_result = False
        except AttributeError:
            pass

    def fetchone(self) -> Optional[RowType]:
        """Return next row of a query result set.

        Returns:
            tuple or None: A row from query result set.
        """
        self._check_executed()
        return self._fetch_row()

    def fetchall(self) -> List[RowType]:
        """Return all rows of a query result set.

        Returns:
            list: A list of tuples with all rows of a query result set.
        """
        self._check_executed()
        return list(self._rows[self._next_row :])

    @property
    def with_rows(self) -> bool:
        return self._rows is not None


class MySQLCursorPrepared(MySQLCursor):
    """Cursor using MySQL Prepared Statements"""

    def __init__(self, connection: Optional[Type[MySQLConnectionAbstract]] = None):
        super().__init__(connection)
        self._rows: Optional[List[RowType]] = None
        self._next_row: int = 0
        self._prepared: Optional[Dict[str, Union[int, List[DescriptionType]]]] = None
        self._binary: bool = True
        self._have_result: Optional[bool] = None
        self._last_row_sent: bool = False
        self._cursor_exists: bool = False

    def reset(self, free: bool = True) -> None:
        if self._prepared:
            try:
                self._connection.cmd_stmt_close(self._prepared["statement_id"])
            except Error:
                # We tried to deallocate, but it's OK when we fail.
                pass
            self._prepared = None
        self._last_row_sent = False
        self._cursor_exists = False

    def _handle_noresultset(self, res: ResultType) -> None:
        self._handle_server_status(res.get("status_flag", res.get("server_status", 0)))
        super()._handle_noresultset(res)

    def _handle_server_status(self, flags: int) -> None:
        """Check for SERVER_STATUS_CURSOR_EXISTS and
        SERVER_STATUS_LAST_ROW_SENT flags set by the server.
        """
        self._cursor_exists = flags & ServerFlag.STATUS_CURSOR_EXISTS != 0
        self._last_row_sent = flags & ServerFlag.STATUS_LAST_ROW_SENT != 0

    def _handle_eof(self, eof: EofPacketType) -> None:
        self._handle_server_status(eof.get("status_flag", eof.get("server_status", 0)))
        super()._handle_eof(eof)

    def callproc(self, procname: Any, args: Any = ()) -> NoReturn:
        """Calls a stored procedue

        Not supported with MySQLCursorPrepared.
        """
        raise NotSupportedError()

    def close(self) -> None:
        """Close the cursor

        This method will try to deallocate the prepared statement and close
        the cursor.
        """
        self.reset()
        super().close()

    def _row_to_python(self, rowdata: Any, desc: Any = None) -> Any:
        """Convert row data from MySQL to Python types

        The conversion is done while reading binary data in the
        protocol module.
        """

    def _handle_result(self, result: ResultType) -> None:
        """Handle result after execution"""
        if isinstance(result, dict):
            self._connection.unread_result = False
            self._have_result = False
            self._handle_noresultset(result)
        else:
            self._description = result[1]
            self._connection.unread_result = True
            self._have_result = True

            if "status_flag" in result[2]:  # type: ignore[operator]
                self._handle_server_status(result[2]["status_flag"])
            elif "server_status" in result[2]:  # type: ignore[operator]
                self._handle_server_status(result[2]["server_status"])

    def execute(
        self,
        operation: StrOrBytes,
        params: Optional[ParamsSequenceOrDictType] = None,
        multi: bool = False,
    ) -> None:  # multi is unused
        """Prepare and execute a MySQL Prepared Statement

        This method will prepare the given operation and execute it using
        the optionally given parameters.

        If the cursor instance already had a prepared statement, it is
        first closed.

        Note: argument "multi" is unused.
        """
        charset = self._connection.charset
        if charset == "utf8mb4":
            charset = "utf8"

        if not isinstance(operation, str):
            try:
                operation = operation.decode(charset)
            except UnicodeDecodeError as err:
                raise ProgrammingError(str(err)) from err

        if isinstance(params, dict):
            replacement_keys = re.findall(RE_SQL_PYTHON_CAPTURE_PARAM_NAME, operation)
            try:
                # Replace params dict with params tuple in correct order.
                params = tuple(params[key] for key in replacement_keys)
            except KeyError as err:
                raise ProgrammingError(
                    "Not all placeholders were found in the parameters dict"
                ) from err
            # Convert %(name)s to ? before sending it to MySQL
            operation = re.sub(RE_SQL_PYTHON_REPLACE_PARAM, "?", operation)

        if operation is not self._executed:
            if self._prepared:
                self._connection.cmd_stmt_close(self._prepared["statement_id"])
            self._executed = operation

            try:
                operation = operation.encode(charset)
            except UnicodeEncodeError as err:
                raise ProgrammingError(str(err)) from err

            if b"%s" in operation:
                # Convert %s to ? before sending it to MySQL
                operation = re.sub(RE_SQL_FIND_PARAM, b"?", operation)

            try:
                self._prepared = self._connection.cmd_stmt_prepare(operation)
            except Error:
                self._executed = None
                raise

        self._connection.cmd_stmt_reset(self._prepared["statement_id"])

        if self._prepared["parameters"] and not params:
            return
        if params:
            if not isinstance(params, (tuple, list)):
                raise ProgrammingError(
                    errno=1210,
                    msg=f"Incorrect type of argument: {type(params).__name__}({params})"
                    ", it must be of type tuple or list the argument given to "
                    "the prepared statement",
                )
            if len(self._prepared["parameters"]) != len(params):
                raise ProgrammingError(
                    errno=1210,
                    msg="Incorrect number of arguments executing prepared statement",
                )

        if params is None:
            params = ()
        res = self._connection.cmd_stmt_execute(
            self._prepared["statement_id"],
            data=params,
            parameters=self._prepared["parameters"],
        )
        self._handle_result(res)

    def executemany(
        self,
        operation: str,
        seq_params: Sequence[ParamsSequenceType],
    ) -> None:
        """Prepare and execute a MySQL Prepared Statement many times

        This method will prepare the given operation and execute with each
        tuple found the list seq_params.

        If the cursor instance already had a prepared statement, it is
        first closed.

        executemany() simply calls execute().
        """
        rowcnt = 0
        try:
            for params in seq_params:
                self.execute(operation, params)
                if self.with_rows and self._have_unread_result():
                    self.fetchall()
                rowcnt += self._rowcount
        except (ValueError, TypeError) as err:
            raise InterfaceError(f"Failed executing the operation; {err}") from None
        self._rowcount = rowcnt

    def fetchone(self) -> Optional[RowType]:
        """Return next row of a query result set.

        Returns:
            tuple or None: A row from query result set.
        """
        self._check_executed()
        if self._cursor_exists:
            self._connection.cmd_stmt_fetch(self._prepared["statement_id"])
        return self._fetch_row() or None

    def fetchmany(self, size: Optional[int] = None) -> List[RowType]:
        """Return the next set of rows of a query result set.

        When no more rows are available, it returns an empty list.
        The number of rows returned can be specified using the size argument,
        which defaults to one.

        Returns:
            list: The next set of rows of a query result set.
        """
        self._check_executed()
        res = []
        cnt = size or self.arraysize
        while cnt > 0 and self._have_unread_result():
            cnt -= 1
            row = self._fetch_row()
            if row:
                res.append(row)
        return res

    def fetchall(self) -> List[RowType]:
        """Return all rows of a query result set.

        Returns:
            list: A list of tuples with all rows of a query result set.
        """
        self._check_executed()
        rows = []
        if self._nextrow[0]:
            rows.append(self._nextrow[0])
        while self._have_unread_result():
            if self._cursor_exists:
                self._connection.cmd_stmt_fetch(
                    self._prepared["statement_id"], MAX_RESULTS
                )
            (tmp, eof) = self._connection.get_rows(
                binary=self._binary, columns=self.description
            )
            rows.extend(tmp)
            self._handle_eof(eof)
        self._rowcount = len(rows)
        return rows


class MySQLCursorDict(MySQLCursor):
    """
    Cursor fetching rows as dictionaries.

    The fetch methods of this class will return dictionaries instead of tuples.
    Each row is a dictionary that looks like:
        row = {
            "col1": value1,
            "col2": value2
        }
    """

    def _row_to_python(
        self,
        rowdata: RowType,
        desc: Optional[List[DescriptionType]] = None,  # pylint: disable=unused-argument
    ) -> Optional[Dict[str, ToPythonOutputTypes]]:
        """Convert a MySQL text result row to Python types

        Returns a dictionary.
        """
        return dict(zip(self.column_names, rowdata)) if rowdata else None

    def fetchone(self) -> Optional[Dict[str, ToPythonOutputTypes]]:
        """Return next row of a query result set.

        Returns:
            dict or None: A dict from query result set.
        """
        return self._row_to_python(super().fetchone(), self.description)

    def fetchall(self) -> List[Optional[Dict[str, ToPythonOutputTypes]]]:
        """Return all rows of a query result set.

        Returns:
            list: A list of dictionaries with all rows of a query
                  result set where column names are used as keys.
        """
        return [
            self._row_to_python(row, self.description)
            for row in super().fetchall()
            if row
        ]


class MySQLCursorNamedTuple(MySQLCursor):
    """
    Cursor fetching rows as named tuple.

    The fetch methods of this class will return namedtuples instead of tuples.
    Each row is returned as a namedtuple and the values can be accessed as:
    row.col1, row.col2
    """

    def _row_to_python(
        self,
        rowdata: RowType,
        desc: Optional[List[DescriptionType]] = None,  # pylint: disable=unused-argument
    ) -> Optional[RowType]:
        """Convert a MySQL text result row to Python types

        Returns a named tuple.
        """
        row = rowdata

        if row:
            columns = tuple(self.column_names)
            try:
                named_tuple = NAMED_TUPLE_CACHE[columns]
            except KeyError:
                named_tuple = namedtuple("Row", columns)  # type:ignore[no-redef, misc]
                NAMED_TUPLE_CACHE[columns] = named_tuple
            return named_tuple(*row)
        return None

    def fetchone(self) -> Optional[RowType]:
        """Return next row of a query result set.

        Returns:
            tuple or None: A row from query result set.
        """
        row = super().fetchone()
        if not row:
            return None
        return (
            self._row_to_python(row, self.description)
            if hasattr(self._connection, "converter")
            else row
        )

    def fetchall(self) -> List[Optional[RowType]]:
        """Return all rows of a query result set.

        Returns:
            list: A list of tuples with all rows of a query result set.
        """
        return [
            self._row_to_python(row, self.description)
            for row in super().fetchall()
            if row
        ]


class MySQLCursorBufferedDict(MySQLCursorDict, MySQLCursorBuffered):
    """
    Buffered Cursor fetching rows as dictionaries.
    """

    def fetchone(self) -> Optional[Dict[str, ToPythonOutputTypes]]:
        """Return next row of a query result set.

        Returns:
            tuple or None: A row from query result set.
        """
        self._check_executed()
        row = self._fetch_row()
        if row:
            return self._row_to_python(row, self.description)
        return None

    def fetchall(self) -> List[Optional[Dict[str, ToPythonOutputTypes]]]:
        """Return all rows of a query result set.

        Returns:
            list: A list of tuples with all rows of a query result set.
        """
        if self._executed is None or self._rows is None:
            raise InterfaceError(ERR_NO_RESULT_TO_FETCH)
        res = []
        for row in self._rows[self._next_row :]:
            res.append(self._row_to_python(row, self.description))
        self._next_row = len(self._rows)
        return res


class MySQLCursorBufferedNamedTuple(MySQLCursorNamedTuple, MySQLCursorBuffered):
    """
    Buffered Cursor fetching rows as named tuple.
    """

    def fetchone(self) -> Optional[RowType]:
        """Return next row of a query result set.

        Returns:
            tuple or None: A row from query result set.
        """
        self._check_executed()
        row = self._fetch_row()
        if row:
            return self._row_to_python(row, self.description)
        return None

    def fetchall(self) -> List[Optional[RowType]]:
        """Return all rows of a query result set.

        Returns:
            list: A list of tuples with all rows of a query result set.
        """
        if self._executed is None or self._rows is None:
            raise InterfaceError(ERR_NO_RESULT_TO_FETCH)
        res = []
        for row in self._rows[self._next_row :]:
            res.append(self._row_to_python(row, self.description))
        self._next_row = len(self._rows)
        return res


class MySQLCursorPreparedDict(MySQLCursorDict, MySQLCursorPrepared):  # type: ignore[misc]
    """
    This class is a blend of features from MySQLCursorDict and MySQLCursorPrepared

    Multiple inheritance in python is allowed but care must be taken
    when assuming methods resolution. In the case of multiple
    inheritance, a given attribute is first searched in the current
    class if it's not found then it's searched in the parent classes.
    The parent classes are searched in a left-right fashion and each
    class is searched once.
    Based on python's attribute resolution, in this case, attributes
    are searched as follows:
    1. MySQLCursorPreparedDict (current class)
    2. MySQLCursorDict (left parent class)
    3. MySQLCursorPrepared (right parent class)
    4. MySQLCursor (base class)
    """

    def fetchmany(
        self, size: Optional[int] = None
    ) -> List[Dict[str, ToPythonOutputTypes]]:
        """Return the next set of rows of a query result set.

        When no more rows are available, it returns an empty list.
        The number of rows returned can be specified using the size argument,
        which defaults to one.

        Returns:
            list: The next set of rows of a query result set represented
                  as a list of dictionaries where column names are used as keys.
        """
        return [
            self._row_to_python(row, self.description)
            for row in super().fetchmany(size=size)
            if row
        ]


class MySQLCursorPreparedNamedTuple(MySQLCursorNamedTuple, MySQLCursorPrepared):
    """
    This class is a blend of features from MySQLCursorNamedTuple and MySQLCursorPrepared
    """

    def fetchmany(self, size: Optional[int] = None) -> List[RowType]:
        """Return the next set of rows of a query result set.

        When no more rows are available, it returns an empty list.
        The number of rows returned can be specified using the size argument,
        which defaults to one.

        Returns:
            list: The next set of rows of a query result set represented
                  as a list of named tuples where column names are used as names.
        """
        return [
            self._row_to_python(row, self.description)
            for row in super().fetchmany(size=size)
            if row
        ]


class MySQLCursorPreparedRaw(MySQLCursorPrepared):
    """
    This class is a blend of features from MySQLCursorRaw and MySQLCursorPrepared
    """

    _raw: bool = True

    def fetchone(self) -> Optional[RowType]:
        """Return next row of a query result set.

        Returns:
            tuple or None: A row from query result set.
        """
        self._check_executed()
        if self._cursor_exists:
            self._connection.cmd_stmt_fetch(self._prepared["statement_id"])
        return self._fetch_row(raw=self._raw) or None

    def fetchmany(self, size: Optional[int] = None) -> List[RowType]:
        """Return the next set of rows of a query result set.

        When no more rows are available, it returns an empty list.
        The number of rows returned can be specified using the size argument,
        which defaults to one.

        Returns:
            list: The next set of rows of a query result set.
        """
        self._check_executed()
        res = []
        cnt = size or self.arraysize
        while cnt > 0 and self._have_unread_result():
            cnt -= 1
            row = self._fetch_row(raw=self._raw)
            if row:
                res.append(row)
        return res

    def fetchall(self) -> List[RowType]:
        """Return all rows of a query result set.

        Returns:
            list: A list of tuples with all rows of a query result set.
        """
        self._check_executed()
        rows = []
        if self._nextrow[0]:
            rows.append(self._nextrow[0])
        while self._have_unread_result():
            if self._cursor_exists:
                self._connection.cmd_stmt_fetch(
                    self._prepared["statement_id"], MAX_RESULTS
                )
            (tmp, eof) = self._connection.get_rows(
                raw=self._raw, binary=self._binary, columns=self.description
            )
            rows.extend(tmp)
            self._handle_eof(eof)
        self._rowcount = len(rows)
        return rows

virt/lib/python3.9/site-packages/mysql/connector/cursor.py
PrinceDisant/LostInNL
# Copyright (c) 2014, 2023, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

# mypy: disable-error-code="assignment,arg-type,override,union-attr"

"""Cursor classes using the C Extension."""
from __future__ import annotations

import re
import warnings
import weakref

from collections import namedtuple
from typing import (
    Any,
    Dict,
    Generator,
    Iterator,
    List,
    NoReturn,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
)
from weakref import CallableProxyType

# pylint: disable=import-error,no-name-in-module
from _mysql_connector import MySQLInterfaceError, MySQLPrepStmt

from .types import (
    CextEofPacketType,
    CextResultType,
    DescriptionType,
    ParamsSequenceOrDictType,
    ParamsSequenceType,
    RowType,
    StrOrBytes,
    ToPythonOutputTypes,
    WarningType,
)

# pylint: enable=import-error,no-name-in-module
# isort: split

from .abstracts import NAMED_TUPLE_CACHE, MySQLConnectionAbstract, MySQLCursorAbstract
from .cursor import (
    RE_PY_PARAM,
    RE_SQL_COMMENT,
    RE_SQL_FIND_PARAM,
    RE_SQL_INSERT_STMT,
    RE_SQL_INSERT_VALUES,
    RE_SQL_ON_DUPLICATE,
    RE_SQL_PYTHON_CAPTURE_PARAM_NAME,
    RE_SQL_PYTHON_REPLACE_PARAM,
    RE_SQL_SPLIT_STMTS,
)
from .errorcode import CR_NO_RESULT_SET
from .errors import (
    Error,
    InterfaceError,
    NotSupportedError,
    ProgrammingError,
    get_mysql_exception,
)

ERR_NO_RESULT_TO_FETCH = "No result set to fetch from"


class _ParamSubstitutor:

    """
    Substitutes parameters into SQL statement.
    """

    def __init__(self, params: Sequence[bytes]) -> None:
        self.params: Sequence[bytes] = params
        self.index: int = 0

    def __call__(self, matchobj: object) -> bytes:
        index = self.index
        self.index += 1
        try:
            return self.params[index]
        except IndexError:
            raise ProgrammingError(
                "Not enough parameters for the SQL statement"
            ) from None

    @property
    def remaining(self) -> int:
        """Returns number of parameters remaining to be substituted"""
        return len(self.params) - self.index


class CMySQLCursor(MySQLCursorAbstract):

    """Default cursor for interacting with MySQL using C Extension"""

    _raw: bool = False
    _buffered: bool = False
    _raw_as_string: bool = False

    def __init__(self, connection: Type[MySQLConnectionAbstract]) -> None:
        """Initialize"""
        MySQLCursorAbstract.__init__(self)

        self._affected_rows: int = -1
        self._rowcount: int = -1
        self._nextrow: Tuple[Optional[RowType], Optional[CextEofPacketType]] = (
            None,
            None,
        )

        if not isinstance(connection, MySQLConnectionAbstract):
            raise InterfaceError(errno=2048)
        self._cnx: CallableProxyType[Type[MySQLConnectionAbstract]] = weakref.proxy(
            connection
        )

    def reset(self, free: bool = True) -> None:
        """Reset the cursor

        When free is True (default) the result will be freed.
        """
        self._rowcount = -1
        self._nextrow = None
        self._affected_rows = -1
        self._last_insert_id: int = 0
        self._warning_count: int = 0
        self._warnings: Optional[List[WarningType]] = None
        self._warnings = None
        self._warning_count = 0
        self._description: Optional[List[DescriptionType]] = None
        self._executed_list: List[StrOrBytes] = []
        if free and self._cnx:
            self._cnx.free_result()
        super().reset()

    def _check_executed(self) -> None:
        """Check if the statement has been executed.

        Raises an error if the statement has not been executed.
        """
        if self._executed is None:
            raise InterfaceError(ERR_NO_RESULT_TO_FETCH)

    def _fetch_warnings(self) -> Optional[List[WarningType]]:
        """Fetch warnings

        Fetch warnings doing a SHOW WARNINGS. Can be called after getting
        the result.

        Returns a result set or None when there were no warnings.

        Raises Error (or subclass) on errors.

        Returns list of tuples or None.
        """
        warns = []
        try:
            # force freeing result
            self._cnx.consume_results()
            _ = self._cnx.cmd_query("SHOW WARNINGS")
            warns = self._cnx.get_rows()[0]
            self._cnx.consume_results()
        except MySQLInterfaceError as err:
            raise get_mysql_exception(
                msg=err.msg, errno=err.errno, sqlstate=err.sqlstate
            ) from err
        except Exception as err:
            raise InterfaceError(f"Failed getting warnings; {err}") from None

        if warns:
            return warns

        return None

    def _handle_warnings(self) -> None:
        """Handle possible warnings after all results are consumed.

        Raises:
            Error: Also raises exceptions if raise_on_warnings is set.
        """
        if self._cnx.get_warnings and self._warning_count:
            self._warnings = self._fetch_warnings()

        if not self._warnings:
            return

        err = get_mysql_exception(
            *self._warnings[0][1:3], warning=not self._cnx.raise_on_warnings
        )
        if self._cnx.raise_on_warnings:
            raise err

        warnings.warn(str(err), stacklevel=4)

    def _handle_result(self, result: Union[CextEofPacketType, CextResultType]) -> None:
        """Handles the result after statement execution"""
        if "columns" in result:
            self._description = result["columns"]
            self._rowcount = 0
            self._handle_resultset()
        else:
            self._last_insert_id = result["insert_id"]
            self._warning_count = result["warning_count"]
            self._affected_rows = result["affected_rows"]
            self._rowcount = -1
            self._handle_warnings()

    def _handle_resultset(self) -> None:
        """Handle a result set"""

    def _handle_eof(self) -> None:
        """Handle end of reading the result

        Raises an Error on errors.
        """
        self._warning_count = self._cnx.warning_count
        self._handle_warnings()
        if not self._cnx.more_results:
            self._cnx.free_result()

    def _execute_iter(self) -> Generator[CMySQLCursor, None, None]:
        """Generator returns MySQLCursor objects for multiple statements

        Deprecated: use nextset() method directly.

        This method is only used when multiple statements are executed
        by the execute() method. It uses zip() to make an iterator from the
        given query_iter (result of MySQLConnection.cmd_query_iter()) and
        the list of statements that were executed.
        """
        executed_list = RE_SQL_SPLIT_STMTS.split(self._executed)
        i = 0
        self._executed = executed_list[i]
        yield self

        while True:
            try:
                if not self.nextset():
                    raise StopIteration
            except InterfaceError as err:
                # Result without result set
                if err.errno != CR_NO_RESULT_SET:
                    raise
            except StopIteration:
                return
            i += 1
            try:
                self._executed = executed_list[i].strip()
            except IndexError:
                self._executed = executed_list[0]
            yield self
        return

    def execute(
        self,
        operation: StrOrBytes,
        params: ParamsSequenceOrDictType = (),
        multi: bool = False,
    ) -> Optional[Generator[CMySQLCursor, None, None]]:
        """Execute given statement using given parameters

        Deprecated: The multi argument is not needed and nextset() should
        be used to handle multiple result sets.
        """
        if not operation:
            return None

        try:
            if not self._cnx or self._cnx.is_closed():
                raise ProgrammingError
        except (ProgrammingError, ReferenceError) as err:
            raise ProgrammingError("Cursor is not connected", 2055) from err
        self._cnx.handle_unread_result()

        stmt = ""
        self.reset()

        try:
            if isinstance(operation, str):
                stmt = operation.encode(self._cnx.python_charset)
            else:
                stmt = operation
        except (UnicodeDecodeError, UnicodeEncodeError) as err:
            raise ProgrammingError(str(err)) from err

        if params:
            prepared = self._cnx.prepare_for_mysql(params)
            if isinstance(prepared, dict):
                for key, value in prepared.items():
                    stmt = stmt.replace(f"%({key})s".encode(), value)
            elif isinstance(prepared, (list, tuple)):
                psub = _ParamSubstitutor(prepared)
                stmt = RE_PY_PARAM.sub(psub, stmt)  # type: ignore[call-overload]
                if psub.remaining != 0:
                    raise ProgrammingError(
                        "Not all parameters were used in the SQL statement"
                    )

        try:
            result = self._cnx.cmd_query(
                stmt,
                raw=self._raw,
                buffered=self._buffered,
                raw_as_string=self._raw_as_string,
            )
        except MySQLInterfaceError as err:
            raise get_mysql_exception(
                msg=err.msg, errno=err.errno, sqlstate=err.sqlstate
            ) from err

        self._executed = stmt
        self._handle_result(result)

        if multi:
            return self._execute_iter()

        return None

    def _batch_insert(
        self,
        operation: str,
        seq_params: Sequence[ParamsSequenceOrDictType],
    ) -> Optional[bytes]:
        """Implements multi row insert"""

        def remove_comments(match: re.Match) -> str:
            """Remove comments from INSERT statements.

            This function is used while removing comments from INSERT
            statements. If the matched string is a comment not enclosed
            by quotes, it returns an empty string, else the string itself.
            """
            if match.group(1):
                return ""
            return match.group(2)

        tmp = re.sub(
            RE_SQL_ON_DUPLICATE,
            "",
            re.sub(RE_SQL_COMMENT, remove_comments, operation),
        )

        matches = re.search(RE_SQL_INSERT_VALUES, tmp)
        if not matches:
            raise InterfaceError(
                "Failed rewriting statement for multi-row INSERT. Check SQL syntax"
            )
        fmt = matches.group(1).encode(self._cnx.python_charset)
        values = []

        try:
            stmt = operation.encode(self._cnx.python_charset)
            for params in seq_params:
                tmp = fmt
                prepared = self._cnx.prepare_for_mysql(params)
                if isinstance(prepared, dict):
                    for key, value in prepared.items():
                        tmp = tmp.replace(f"%({key})s".encode(), value)
                elif isinstance(prepared, (list, tuple)):
                    psub = _ParamSubstitutor(prepared)
                    tmp = RE_PY_PARAM.sub(psub, tmp)  # type: ignore[call-overload]
                    if psub.remaining != 0:
                        raise ProgrammingError(
                            "Not all parameters were used in the SQL statement"
                        )
                values.append(tmp)

            if fmt in stmt:
                stmt = stmt.replace(fmt, b",".join(values), 1)
                self._executed = stmt
                return stmt
            return None
        except (UnicodeDecodeError, UnicodeEncodeError) as err:
            raise ProgrammingError(str(err)) from err
        except Exception as err:
            raise InterfaceError(f"Failed executing the operation; {err}") from None

    def executemany(
        self,
        operation: str,
        seq_params: Sequence[ParamsSequenceOrDictType],
    ) -> Optional[Generator[CMySQLCursor, None, None]]:
        """Execute the given operation multiple times

        The executemany() method will execute the operation iterating
        over the list of parameters in seq_params.

        Example: Inserting 3 new employees and their phone number

        data = [
            ('Jane','555-001'),
            ('Joe', '555-001'),
            ('John', '555-003')
            ]
        stmt = "INSERT INTO employees (name, phone) VALUES ('%s','%s)"
        cursor.executemany(stmt, data)

        INSERT statements are optimized by batching the data, that is
        using the MySQL multiple rows syntax.

        Results are discarded! If they are needed, consider looping over
        data using the execute() method.
        """
        if not operation or not seq_params:
            return None

        try:
            if not self._cnx:
                raise ProgrammingError
        except (ProgrammingError, ReferenceError) as err:
            raise ProgrammingError("Cursor is not connected") from err
        self._cnx.handle_unread_result()

        if not isinstance(seq_params, (list, tuple)):
            raise ProgrammingError("Parameters for query must be list or tuple.")

        # Optimize INSERTs by batching them
        if re.match(RE_SQL_INSERT_STMT, operation):
            if not seq_params:
                self._rowcount = 0
                return None
            stmt = self._batch_insert(operation, seq_params)
            if stmt is not None:
                self._executed = stmt
                return self.execute(stmt)

        rowcnt = 0
        try:
            # When processing read ops (e.g., SELECT), rowcnt is updated
            # based on self._rowcount. For write ops (e.g., INSERT) is
            # updated based on self._affected_rows.
            # The variable self._description is None for write ops, that's
            # why we use it as indicator for updating rowcnt.
            for params in seq_params:
                self.execute(operation, params)
                if self.with_rows and self._cnx.unread_result:
                    self.fetchall()
                rowcnt += self._rowcount if self.description else self._affected_rows
        except (ValueError, TypeError) as err:
            raise InterfaceError(f"Failed executing the operation; {err}") from None

        self._rowcount = rowcnt
        return None

    @property
    def description(self) -> Optional[List[DescriptionType]]:
        """Returns description of columns in a result"""
        return self._description

    @property
    def rowcount(self) -> int:
        """Returns the number of rows produced or affected"""
        if self._rowcount == -1:
            return self._affected_rows
        return self._rowcount

    def close(self) -> bool:
        """Close the cursor

        The result will be freed.
        """
        if not self._cnx:
            return False

        self._cnx.handle_unread_result()
        self._warnings = None
        self._cnx = None
        return True

    def callproc(
        self,
        procname: str,
        args: Sequence[Any] = (),
    ) -> Optional[Union[Dict[str, ToPythonOutputTypes], RowType]]:
        """Calls a stored procedure with the given arguments"""
        if not procname or not isinstance(procname, str):
            raise ValueError("procname must be a string")

        if not isinstance(args, (tuple, list)):
            raise ValueError("args must be a sequence")

        argfmt = "@_{name}_arg{index}"
        self._stored_results = []

        try:
            argnames = []
            argtypes = []

            # MySQL itself does support calling procedures with their full
            # name <database>.<procedure_name>. It's necessary to split
            # by '.' and grab the procedure name from procname.
            procname_abs = procname.split(".")[-1]
            if args:
                argvalues = []
                for idx, arg in enumerate(args):
                    argname = argfmt.format(name=procname_abs, index=idx + 1)
                    argnames.append(argname)
                    if isinstance(arg, tuple):
                        argtypes.append(f" CAST({argname} AS {arg[1]})")
                        argvalues.append(arg[0])
                    else:
                        argtypes.append(argname)
                        argvalues.append(arg)

                placeholders = ",".join(f"{arg}=%s" for arg in argnames)
                self.execute(f"SET {placeholders}", argvalues)

            call = f"CALL {procname}({','.join(argnames)})"

            result = self._cnx.cmd_query(
                call, raw=self._raw, raw_as_string=self._raw_as_string
            )

            results = []
            while self._cnx.result_set_available:
                result = self._cnx.fetch_eof_columns()
                if isinstance(self, (CMySQLCursorDict, CMySQLCursorBufferedDict)):
                    cursor_class = CMySQLCursorBufferedDict
                elif isinstance(
                    self,
                    (CMySQLCursorNamedTuple, CMySQLCursorBufferedNamedTuple),
                ):
                    cursor_class = CMySQLCursorBufferedNamedTuple
                elif self._raw:
                    cursor_class = CMySQLCursorBufferedRaw
                else:
                    cursor_class = CMySQLCursorBuffered
                # pylint: disable=protected-access
                cur = cursor_class(self._cnx.get_self())
                cur._executed = f"(a result of {call})"
                cur._handle_result(result)
                # pylint: enable=protected-access
                results.append(cur)
                self._cnx.next_result()
            self._stored_results = results
            self._handle_eof()

            if argnames:
                self.reset()
                # Create names aliases to be compatible with namedtuples
                args = [
                    f"{name} AS {alias}"
                    for name, alias in zip(
                        argtypes, [arg.lstrip("@_") for arg in argnames]
                    )
                ]
                select = f"SELECT {','.join(args)}"
                self.execute(select)

                return self.fetchone()
            return tuple()

        except Error:
            raise
        except Exception as err:
            raise InterfaceError(f"Failed calling stored routine; {err}") from None

    def nextset(self) -> Optional[bool]:
        """Skip to the next available result set"""
        if not self._cnx.next_result():
            self.reset(free=True)
            return None
        self.reset(free=False)

        if not self._cnx.result_set_available:
            eof = self._cnx.fetch_eof_status()
            self._handle_result(eof)
            raise InterfaceError(errno=CR_NO_RESULT_SET)

        self._handle_result(self._cnx.fetch_eof_columns())
        return True

    def fetchall(self) -> List[RowType]:
        """Return all rows of a query result set.

        Returns:
            list: A list of tuples with all rows of a query result set.
        """
        self._check_executed()
        if not self._cnx.unread_result:
            return []

        rows: Tuple[List[RowType], Optional[CextEofPacketType]] = self._cnx.get_rows()
        if self._nextrow and self._nextrow[0]:
            rows[0].insert(0, self._nextrow[0])

        if not rows[0]:
            self._handle_eof()
            return []

        self._rowcount += len(rows[0])
        self._handle_eof()
        # self._cnx.handle_unread_result()
        return rows[0]

    def fetchmany(self, size: int = 1) -> List[RowType]:
        """Return the next set of rows of a query result set.

        When no more rows are available, it returns an empty list.
        The number of rows returned can be specified using the size argument,
        which defaults to one.

        Returns:
            list: The next set of rows of a query result set.
        """
        self._check_executed()
        if self._nextrow and self._nextrow[0]:
            rows = [self._nextrow[0]]
            size -= 1
        else:
            rows = []

        if size and self._cnx.unread_result:
            rows.extend(self._cnx.get_rows(size)[0])

        if size:
            if self._cnx.unread_result:
                self._nextrow = self._cnx.get_row()
                if (
                    self._nextrow
                    and not self._nextrow[0]
                    and not self._cnx.more_results
                ):
                    self._cnx.free_result()
            else:
                self._nextrow = (None, None)

        if not rows:
            self._handle_eof()
            return []

        self._rowcount += len(rows)
        return rows

    def fetchone(self) -> Optional[RowType]:
        """Return next row of a query result set.

        Returns:
            tuple or None: A row from query result set.
        """
        self._check_executed()
        row = self._nextrow
        if not row and self._cnx.unread_result:
            row = self._cnx.get_row()

        if row and row[0]:
            self._nextrow = self._cnx.get_row()
            if not self._nextrow[0] and not self._cnx.more_results:
                self._cnx.free_result()
        else:
            self._handle_eof()
            return None
        self._rowcount += 1
        return row[0]

    def __iter__(self) -> Iterator[RowType]:
        """Iteration over the result set

        Iteration over the result set which calls self.fetchone()
        and returns the next row.
        """
        return iter(self.fetchone, None)

    def stored_results(self) -> Generator[CMySQLCursor, None, None]:
        """Returns an iterator for stored results

        This method returns an iterator over results which are stored when
        callproc() is called. The iterator will provide MySQLCursorBuffered
        instances.

        Returns a iterator.
        """
        for result in self._stored_results:
            yield result
        self._stored_results = []

    def __next__(self) -> RowType:
        """Iteration over the result set
        Used for iterating over the result set. Calls self.fetchone()
        to get the next row.

        Raises StopIteration when no more rows are available.
        """
        try:
            row = self.fetchone()
        except InterfaceError:
            raise StopIteration from None
        if not row:
            raise StopIteration from None
        return row

    @property
    def column_names(self) -> Tuple[str, ...]:
        """Returns column names

        This property returns the columns names as a tuple.

        Returns a tuple.
        """
        if not self.description:
            return ()
        return tuple(d[0] for d in self.description)

    @property
    def statement(self) -> str:
        """Returns the executed statement

        This property returns the executed statement. When multiple
        statements were executed, the current statement in the iterator
        will be returned.
        """
        try:
            return self._executed.strip().decode("utf8")
        except AttributeError:
            return self._executed.strip()  # type: ignore[return-value]

    @property
    def with_rows(self) -> bool:
        """Returns whether the cursor could have rows returned

        This property returns True when column descriptions are available
        and possibly also rows, which will need to be fetched.

        Returns True or False.
        """
        if self.description:
            return True
        return False

    def __str__(self) -> str:
        fmt = "{class_name}: {stmt}"
        if self._executed:
            try:
                executed = self._executed.decode("utf-8")
            except AttributeError:
                executed = self._executed
            if len(executed) > 40:
                executed = executed[:40] + ".."
        else:
            executed = "(Nothing executed yet)"

        return fmt.format(class_name=self.__class__.__name__, stmt=executed)


class CMySQLCursorBuffered(CMySQLCursor):

    """Cursor using C Extension buffering results"""

    def __init__(self, connection: Type[MySQLConnectionAbstract]):
        """Initialize"""
        super().__init__(connection)

        self._rows: Optional[List[RowType]] = None
        self._next_row: int = 0

    def _handle_resultset(self) -> None:
        """Handle a result set"""
        self._rows = self._cnx.get_rows()[0]
        self._next_row = 0
        self._rowcount: int = len(self._rows)
        self._handle_eof()

    def reset(self, free: bool = True) -> None:
        """Reset the cursor to default"""
        self._rows = None
        self._next_row = 0
        super().reset(free=free)

    def _fetch_row(self) -> Optional[RowType]:
        """Returns the next row in the result set

        Returns a tuple or None.
        """
        row = None
        try:
            row = self._rows[self._next_row]
        except IndexError:
            return None
        self._next_row += 1
        return row

    def fetchall(self) -> List[RowType]:
        """Return all rows of a query result set.

        Returns:
            list: A list of tuples with all rows of a query result set.
        """
        self._check_executed()
        res = self._rows[self._next_row :]
        self._next_row = len(self._rows)
        return res

    def fetchmany(self, size: int = 1) -> List[RowType]:
        """Return the next set of rows of a query result set.

        When no more rows are available, it returns an empty list.
        The number of rows returned can be specified using the size argument,
        which defaults to one.

        Returns:
            list: The next set of rows of a query result set.
        """
        self._check_executed()
        res = []
        cnt = size or self.arraysize
        while cnt > 0:
            cnt -= 1
            row = self._fetch_row()
            if row:
                res.append(row)
            else:
                break
        return res

    def fetchone(self) -> Optional[RowType]:
        """Return next row of a query result set.

        Returns:
            tuple or None: A row from query result set.
        """
        self._check_executed()
        return self._fetch_row()

    @property
    def with_rows(self) -> bool:
        """Returns whether the cursor could have rows returned

        This property returns True when rows are available,
        which will need to be fetched.

        Returns True or False.
        """
        return self._rows is not None


class CMySQLCursorRaw(CMySQLCursor):
    """Cursor using C Extension return raw results"""

    _raw: bool = True


class CMySQLCursorBufferedRaw(CMySQLCursorBuffered):
    """Cursor using C Extension buffering raw results"""

    _raw: bool = True


class CMySQLCursorDict(CMySQLCursor):
    """Cursor using C Extension returning rows as dictionaries"""

    _raw: bool = False

    def fetchone(self) -> Optional[Dict[str, ToPythonOutputTypes]]:
        """Return next row of a query result set.

        Returns:
            dict or None: A dict from query result set.
        """
        row = super().fetchone()
        return dict(zip(self.column_names, row)) if row else None

    def fetchmany(self, size: int = 1) -> List[Dict[str, ToPythonOutputTypes]]:
        """Return the next set of rows of a query result set.

        When no more rows are available, it returns an empty list.
        The number of rows returned can be specified using the size argument,
        which defaults to one.

        Returns:
            list: The next set of rows of a query result set represented
                  as a list of dictionaries where column names are used as keys.
        """
        res = super().fetchmany(size=size)
        return [dict(zip(self.column_names, row)) for row in res]

    def fetchall(self) -> List[Dict[str, ToPythonOutputTypes]]:
        """Return all rows of a query result set.

        Returns:
            list: A list of dictionaries with all rows of a query
                  result set where column names are used as keys.
        """
        res = super().fetchall()
        return [dict(zip(self.column_names, row)) for row in res]


class CMySQLCursorBufferedDict(CMySQLCursorBuffered):
    """Cursor using C Extension buffering and returning rows as dictionaries"""

    _raw = False

    def _fetch_row(self) -> Optional[Dict[str, ToPythonOutputTypes]]:
        row = super()._fetch_row()
        if row:
            return dict(zip(self.column_names, row))
        return None

    def fetchall(self) -> List[Dict[str, ToPythonOutputTypes]]:
        """Return all rows of a query result set.

        Returns:
            list: A list of tuples with all rows of a query result set.
        """
        res = super().fetchall()
        return [dict(zip(self.column_names, row)) for row in res]


class CMySQLCursorNamedTuple(CMySQLCursor):
    """Cursor using C Extension returning rows as named tuples"""

    named_tuple: Any = None

    def _handle_resultset(self) -> None:
        """Handle a result set"""
        super()._handle_resultset()
        columns = tuple(self.column_names)
        try:
            self.named_tuple = NAMED_TUPLE_CACHE[columns]
        except KeyError:
            self.named_tuple = namedtuple("Row", columns)  # type: ignore[misc]
            NAMED_TUPLE_CACHE[columns] = self.named_tuple

    def fetchone(self) -> Optional[RowType]:
        """Return next row of a query result set.

        Returns:
            tuple or None: A row from query result set.
        """
        row = super().fetchone()
        if row:
            return self.named_tuple(*row)
        return None

    def fetchmany(self, size: int = 1) -> List[RowType]:
        """Return the next set of rows of a query result set.

        When no more rows are available, it returns an empty list.
        The number of rows returned can be specified using the size argument,
        which defaults to one.

        Returns:
            list: The next set of rows of a query result set.
        """
        res = super().fetchmany(size=size)
        if not res:
            return []
        return [self.named_tuple(*row) for row in res]

    def fetchall(self) -> List[RowType]:
        """Return all rows of a query result set.

        Returns:
            list: A list of tuples with all rows of a query result set.
        """
        res = super().fetchall()
        return [self.named_tuple(*row) for row in res]


class CMySQLCursorBufferedNamedTuple(CMySQLCursorBuffered):
    """Cursor using C Extension buffering and returning rows as named tuples"""

    named_tuple: Any = None

    def _handle_resultset(self) -> None:
        super()._handle_resultset()
        self.named_tuple = namedtuple("Row", self.column_names)  # type: ignore[misc]

    def _fetch_row(self) -> Optional[RowType]:
        row = super()._fetch_row()
        if row:
            return self.named_tuple(*row)
        return None

    def fetchall(self) -> List[RowType]:
        """Return all rows of a query result set.

        Returns:
            list: A list of tuples with all rows of a query result set.
        """
        res = super().fetchall()
        return [self.named_tuple(*row) for row in res]


class CMySQLCursorPrepared(CMySQLCursor):
    """Cursor using MySQL Prepared Statements"""

    def __init__(self, connection: Type[MySQLConnectionAbstract]):
        super().__init__(connection)
        self._rows: Optional[List[RowType]] = None
        self._rowcount: int = 0
        self._next_row: int = 0
        self._binary: bool = True
        self._stmt: Optional[MySQLPrepStmt] = None

    def _handle_eof(self) -> None:
        """Handle EOF packet"""
        self._nextrow = (None, None)
        self._handle_warnings()

    def _fetch_row(self, raw: bool = False) -> Optional[RowType]:
        """Returns the next row in the result set

        Returns a tuple or None.
        """
        if not self._stmt or not self._stmt.have_result_set:
            return None
        row = None

        if self._nextrow == (None, None):
            (row, eof) = self._cnx.get_row(
                binary=self._binary,
                columns=self.description,
                raw=raw,
                prep_stmt=self._stmt,
            )
        else:
            (row, eof) = self._nextrow

        if row:
            self._nextrow = self._cnx.get_row(
                binary=self._binary,
                columns=self.description,
                raw=raw,
                prep_stmt=self._stmt,
            )
            eof = self._nextrow[1]
            if eof is not None:
                self._warning_count = eof["warning_count"]
                self._handle_eof()
            if self._rowcount == -1:
                self._rowcount = 1
            else:
                self._rowcount += 1
        if eof:
            self._warning_count = eof["warning_count"]
            self._handle_eof()

        return row

    def callproc(self, procname: Any, args: Any = None) -> NoReturn:
        """Calls a stored procedue

        Not supported with CMySQLCursorPrepared.
        """
        raise NotSupportedError()

    def close(self) -> None:
        """Close the cursor

        This method will try to deallocate the prepared statement and close
        the cursor.
        """
        if self._stmt:
            self.reset()
            self._cnx.cmd_stmt_close(self._stmt)
            self._stmt = None
        super().close()

    def reset(self, free: bool = True) -> None:
        """Resets the prepared statement."""
        if self._stmt:
            self._cnx.cmd_stmt_reset(self._stmt)
        super().reset(free=free)

    def execute(
        self,
        operation: StrOrBytes,
        params: Optional[ParamsSequenceOrDictType] = None,
        multi: bool = False,
    ) -> None:  # multi is unused
        """Prepare and execute a MySQL Prepared Statement

        This method will prepare the given operation and execute it using
        the given parameters.

        If the cursor instance already had a prepared statement, it is
        first closed.

        Note: argument "multi" is unused.
        """
        if not operation:
            return

        try:
            if not self._cnx or self._cnx.is_closed():
                raise ProgrammingError
        except (ProgrammingError, ReferenceError) as err:
            raise ProgrammingError("Cursor is not connected", 2055) from err

        self._cnx.handle_unread_result(prepared=True)

        charset = self._cnx.charset
        if charset == "utf8mb4":
            charset = "utf8"

        if not isinstance(operation, str):
            try:
                operation = operation.decode(charset)
            except UnicodeDecodeError as err:
                raise ProgrammingError(str(err)) from err

        if isinstance(params, dict):
            replacement_keys = re.findall(RE_SQL_PYTHON_CAPTURE_PARAM_NAME, operation)
            try:
                # Replace params dict with params tuple in correct order.
                params = tuple(params[key] for key in replacement_keys)
            except KeyError as err:
                raise ProgrammingError(
                    "Not all placeholders were found in the parameters dict"
                ) from err
            # Convert %(name)s to ? before sending it to MySQL
            operation = re.sub(RE_SQL_PYTHON_REPLACE_PARAM, "?", operation)

        if operation is not self._executed:
            if self._stmt:
                self._cnx.cmd_stmt_close(self._stmt)
            self._executed = operation

            try:
                operation = operation.encode(charset)
            except UnicodeEncodeError as err:
                raise ProgrammingError(str(err)) from err

            if b"%s" in operation:
                # Convert %s to ? before sending it to MySQL
                operation = re.sub(RE_SQL_FIND_PARAM, b"?", operation)

            try:
                self._stmt = self._cnx.cmd_stmt_prepare(operation)
            except Error:
                self._executed = None
                self._stmt = None
                raise

        self._cnx.cmd_stmt_reset(self._stmt)

        if self._stmt.param_count > 0 and not params:
            return
        if params:
            if not isinstance(params, (tuple, list)):
                raise ProgrammingError(
                    errno=1210,
                    msg=f"Incorrect type of argument: {type(params).__name__}({params})"
                    ", it must be of type tuple or list the argument given to "
                    "the prepared statement",
                )
            if self._stmt.param_count != len(params):
                raise ProgrammingError(
                    errno=1210,
                    msg="Incorrect number of arguments executing prepared statement",
                )

        if params is None:
            params = ()
        res = self._cnx.cmd_stmt_execute(self._stmt, *params)
        if res:
            self._handle_result(res)

    def executemany(
        self, operation: str, seq_params: Sequence[ParamsSequenceType]
    ) -> None:
        """Prepare and execute a MySQL Prepared Statement many times

        This method will prepare the given operation and execute with each
        tuple found the list seq_params.

        If the cursor instance already had a prepared statement, it is
        first closed.
        """
        rowcnt = 0
        try:
            for params in seq_params:
                self.execute(operation, params)
                if self.with_rows:
                    self.fetchall()
                rowcnt += self._rowcount
        except (ValueError, TypeError) as err:
            raise InterfaceError(f"Failed executing the operation; {err}") from err
        self._rowcount = rowcnt

    def fetchone(self) -> Optional[RowType]:
        """Return next row of a query result set.

        Returns:
            tuple or None: A row from query result set.
        """
        self._check_executed()
        return self._fetch_row() or None

    def fetchmany(self, size: Optional[int] = None) -> List[RowType]:
        """Return the next set of rows of a query result set.

        When no more rows are available, it returns an empty list.
        The number of rows returned can be specified using the size argument,
        which defaults to one.

        Returns:
            list: The next set of rows of a query result set.
        """
        self._check_executed()
        res = []
        cnt = size or self.arraysize
        while cnt > 0 and self._stmt.have_result_set:
            cnt -= 1
            row = self._fetch_row()
            if row:
                res.append(row)
        return res

    def fetchall(self) -> List[RowType]:
        """Return all rows of a query result set.

        Returns:
            list: A list of tuples with all rows of a query result set.
        """
        self._check_executed()
        if not self._stmt.have_result_set:
            return []

        rows = self._cnx.get_rows(prep_stmt=self._stmt)
        if self._nextrow and self._nextrow[0]:
            rows[0].insert(0, self._nextrow[0])

        if not rows[0]:
            self._handle_eof()
            return []

        self._rowcount += len(rows[0])
        self._handle_eof()
        return rows[0]


class CMySQLCursorPreparedDict(CMySQLCursorDict, CMySQLCursorPrepared):  # type: ignore[misc]
    """This class is a blend of features from CMySQLCursorDict and CMySQLCursorPrepared

    Multiple inheritance in python is allowed but care must be taken
    when assuming methods resolution. In the case of multiple
    inheritance, a given attribute is first searched in the current
    class if it's not found then it's searched in the parent classes.
    The parent classes are searched in a left-right fashion and each
    class is searched once.
    Based on python's attribute resolution, in this case, attributes
    are searched as follows:
    1. CMySQLCursorPreparedDict (current class)
    2. CMySQLCursorDict (left parent class)
    3. CMySQLCursorPrepared (right parent class)
    4. CMySQLCursor (base class)
    """


class CMySQLCursorPreparedNamedTuple(CMySQLCursorNamedTuple, CMySQLCursorPrepared):
    """This class is a blend of features from CMySQLCursorNamedTuple and CMySQLCursorPrepared"""


class CMySQLCursorPreparedRaw(CMySQLCursorPrepared):
    """This class is a blend of features from CMySQLCursorRaw and CMySQLCursorPrepared"""

    _raw: bool = True

virt/lib/python3.9/site-packages/mysql/connector/cursor_cext 2.py
PrinceDisant/LostInNL
# Copyright (c) 2014, 2023, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

# mypy: disable-error-code="assignment,arg-type,override,union-attr"

"""Cursor classes using the C Extension."""
from __future__ import annotations

import re
import warnings
import weakref

from collections import namedtuple
from typing import (
    Any,
    Dict,
    Generator,
    Iterator,
    List,
    NoReturn,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
)
from weakref import CallableProxyType

# pylint: disable=import-error,no-name-in-module
from _mysql_connector import MySQLInterfaceError, MySQLPrepStmt

from .types import (
    CextEofPacketType,
    CextResultType,
    DescriptionType,
    ParamsSequenceOrDictType,
    ParamsSequenceType,
    RowType,
    StrOrBytes,
    ToPythonOutputTypes,
    WarningType,
)

# pylint: enable=import-error,no-name-in-module
# isort: split

from .abstracts import NAMED_TUPLE_CACHE, MySQLConnectionAbstract, MySQLCursorAbstract
from .cursor import (
    RE_PY_PARAM,
    RE_SQL_COMMENT,
    RE_SQL_FIND_PARAM,
    RE_SQL_INSERT_STMT,
    RE_SQL_INSERT_VALUES,
    RE_SQL_ON_DUPLICATE,
    RE_SQL_PYTHON_CAPTURE_PARAM_NAME,
    RE_SQL_PYTHON_REPLACE_PARAM,
    RE_SQL_SPLIT_STMTS,
)
from .errorcode import CR_NO_RESULT_SET
from .errors import (
    Error,
    InterfaceError,
    NotSupportedError,
    ProgrammingError,
    get_mysql_exception,
)

ERR_NO_RESULT_TO_FETCH = "No result set to fetch from"


class _ParamSubstitutor:

    """
    Substitutes parameters into SQL statement.
    """

    def __init__(self, params: Sequence[bytes]) -> None:
        self.params: Sequence[bytes] = params
        self.index: int = 0

    def __call__(self, matchobj: object) -> bytes:
        index = self.index
        self.index += 1
        try:
            return self.params[index]
        except IndexError:
            raise ProgrammingError(
                "Not enough parameters for the SQL statement"
            ) from None

    @property
    def remaining(self) -> int:
        """Returns number of parameters remaining to be substituted"""
        return len(self.params) - self.index


class CMySQLCursor(MySQLCursorAbstract):

    """Default cursor for interacting with MySQL using C Extension"""

    _raw: bool = False
    _buffered: bool = False
    _raw_as_string: bool = False

    def __init__(self, connection: Type[MySQLConnectionAbstract]) -> None:
        """Initialize"""
        MySQLCursorAbstract.__init__(self)

        self._affected_rows: int = -1
        self._rowcount: int = -1
        self._nextrow: Tuple[Optional[RowType], Optional[CextEofPacketType]] = (
            None,
            None,
        )

        if not isinstance(connection, MySQLConnectionAbstract):
            raise InterfaceError(errno=2048)
        self._cnx: CallableProxyType[Type[MySQLConnectionAbstract]] = weakref.proxy(
            connection
        )

    def reset(self, free: bool = True) -> None:
        """Reset the cursor

        When free is True (default) the result will be freed.
        """
        self._rowcount = -1
        self._nextrow = None
        self._affected_rows = -1
        self._last_insert_id: int = 0
        self._warning_count: int = 0
        self._warnings: Optional[List[WarningType]] = None
        self._warnings = None
        self._warning_count = 0
        self._description: Optional[List[DescriptionType]] = None
        self._executed_list: List[StrOrBytes] = []
        if free and self._cnx:
            self._cnx.free_result()
        super().reset()

    def _check_executed(self) -> None:
        """Check if the statement has been executed.

        Raises an error if the statement has not been executed.
        """
        if self._executed is None:
            raise InterfaceError(ERR_NO_RESULT_TO_FETCH)

    def _fetch_warnings(self) -> Optional[List[WarningType]]:
        """Fetch warnings

        Fetch warnings doing a SHOW WARNINGS. Can be called after getting
        the result.

        Returns a result set or None when there were no warnings.

        Raises Error (or subclass) on errors.

        Returns list of tuples or None.
        """
        warns = []
        try:
            # force freeing result
            self._cnx.consume_results()
            _ = self._cnx.cmd_query("SHOW WARNINGS")
            warns = self._cnx.get_rows()[0]
            self._cnx.consume_results()
        except MySQLInterfaceError as err:
            raise get_mysql_exception(
                msg=err.msg, errno=err.errno, sqlstate=err.sqlstate
            ) from err
        except Exception as err:
            raise InterfaceError(f"Failed getting warnings; {err}") from None

        if warns:
            return warns

        return None

    def _handle_warnings(self) -> None:
        """Handle possible warnings after all results are consumed.

        Raises:
            Error: Also raises exceptions if raise_on_warnings is set.
        """
        if self._cnx.get_warnings and self._warning_count:
            self._warnings = self._fetch_warnings()

        if not self._warnings:
            return

        err = get_mysql_exception(
            *self._warnings[0][1:3], warning=not self._cnx.raise_on_warnings
        )
        if self._cnx.raise_on_warnings:
            raise err

        warnings.warn(str(err), stacklevel=4)

    def _handle_result(self, result: Union[CextEofPacketType, CextResultType]) -> None:
        """Handles the result after statement execution"""
        if "columns" in result:
            self._description = result["columns"]
            self._rowcount = 0
            self._handle_resultset()
        else:
            self._last_insert_id = result["insert_id"]
            self._warning_count = result["warning_count"]
            self._affected_rows = result["affected_rows"]
            self._rowcount = -1
            self._handle_warnings()

    def _handle_resultset(self) -> None:
        """Handle a result set"""

    def _handle_eof(self) -> None:
        """Handle end of reading the result

        Raises an Error on errors.
        """
        self._warning_count = self._cnx.warning_count
        self._handle_warnings()
        if not self._cnx.more_results:
            self._cnx.free_result()

    def _execute_iter(self) -> Generator[CMySQLCursor, None, None]:
        """Generator returns MySQLCursor objects for multiple statements

        Deprecated: use nextset() method directly.

        This method is only used when multiple statements are executed
        by the execute() method. It uses zip() to make an iterator from the
        given query_iter (result of MySQLConnection.cmd_query_iter()) and
        the list of statements that were executed.
        """
        executed_list = RE_SQL_SPLIT_STMTS.split(self._executed)
        i = 0
        self._executed = executed_list[i]
        yield self

        while True:
            try:
                if not self.nextset():
                    raise StopIteration
            except InterfaceError as err:
                # Result without result set
                if err.errno != CR_NO_RESULT_SET:
                    raise
            except StopIteration:
                return
            i += 1
            try:
                self._executed = executed_list[i].strip()
            except IndexError:
                self._executed = executed_list[0]
            yield self
        return

    def execute(
        self,
        operation: StrOrBytes,
        params: ParamsSequenceOrDictType = (),
        multi: bool = False,
    ) -> Optional[Generator[CMySQLCursor, None, None]]:
        """Execute given statement using given parameters

        Deprecated: The multi argument is not needed and nextset() should
        be used to handle multiple result sets.
        """
        if not operation:
            return None

        try:
            if not self._cnx or self._cnx.is_closed():
                raise ProgrammingError
        except (ProgrammingError, ReferenceError) as err:
            raise ProgrammingError("Cursor is not connected", 2055) from err
        self._cnx.handle_unread_result()

        stmt = ""
        self.reset()

        try:
            if isinstance(operation, str):
                stmt = operation.encode(self._cnx.python_charset)
            else:
                stmt = operation
        except (UnicodeDecodeError, UnicodeEncodeError) as err:
            raise ProgrammingError(str(err)) from err

        if params:
            prepared = self._cnx.prepare_for_mysql(params)
            if isinstance(prepared, dict):
                for key, value in prepared.items():
                    stmt = stmt.replace(f"%({key})s".encode(), value)
            elif isinstance(prepared, (list, tuple)):
                psub = _ParamSubstitutor(prepared)
                stmt = RE_PY_PARAM.sub(psub, stmt)  # type: ignore[call-overload]
                if psub.remaining != 0:
                    raise ProgrammingError(
                        "Not all parameters were used in the SQL statement"
                    )

        try:
            result = self._cnx.cmd_query(
                stmt,
                raw=self._raw,
                buffered=self._buffered,
                raw_as_string=self._raw_as_string,
            )
        except MySQLInterfaceError as err:
            raise get_mysql_exception(
                msg=err.msg, errno=err.errno, sqlstate=err.sqlstate
            ) from err

        self._executed = stmt
        self._handle_result(result)

        if multi:
            return self._execute_iter()

        return None

    def _batch_insert(
        self,
        operation: str,
        seq_params: Sequence[ParamsSequenceOrDictType],
    ) -> Optional[bytes]:
        """Implements multi row insert"""

        def remove_comments(match: re.Match) -> str:
            """Remove comments from INSERT statements.

            This function is used while removing comments from INSERT
            statements. If the matched string is a comment not enclosed
            by quotes, it returns an empty string, else the string itself.
            """
            if match.group(1):
                return ""
            return match.group(2)

        tmp = re.sub(
            RE_SQL_ON_DUPLICATE,
            "",
            re.sub(RE_SQL_COMMENT, remove_comments, operation),
        )

        matches = re.search(RE_SQL_INSERT_VALUES, tmp)
        if not matches:
            raise InterfaceError(
                "Failed rewriting statement for multi-row INSERT. Check SQL syntax"
            )
        fmt = matches.group(1).encode(self._cnx.python_charset)
        values = []

        try:
            stmt = operation.encode(self._cnx.python_charset)
            for params in seq_params:
                tmp = fmt
                prepared = self._cnx.prepare_for_mysql(params)
                if isinstance(prepared, dict):
                    for key, value in prepared.items():
                        tmp = tmp.replace(f"%({key})s".encode(), value)
                elif isinstance(prepared, (list, tuple)):
                    psub = _ParamSubstitutor(prepared)
                    tmp = RE_PY_PARAM.sub(psub, tmp)  # type: ignore[call-overload]
                    if psub.remaining != 0:
                        raise ProgrammingError(
                            "Not all parameters were used in the SQL statement"
                        )
                values.append(tmp)

            if fmt in stmt:
                stmt = stmt.replace(fmt, b",".join(values), 1)
                self._executed = stmt
                return stmt
            return None
        except (UnicodeDecodeError, UnicodeEncodeError) as err:
            raise ProgrammingError(str(err)) from err
        except Exception as err:
            raise InterfaceError(f"Failed executing the operation; {err}") from None

    def executemany(
        self,
        operation: str,
        seq_params: Sequence[ParamsSequenceOrDictType],
    ) -> Optional[Generator[CMySQLCursor, None, None]]:
        """Execute the given operation multiple times

        The executemany() method will execute the operation iterating
        over the list of parameters in seq_params.

        Example: Inserting 3 new employees and their phone number

        data = [
            ('Jane','555-001'),
            ('Joe', '555-001'),
            ('John', '555-003')
            ]
        stmt = "INSERT INTO employees (name, phone) VALUES ('%s','%s)"
        cursor.executemany(stmt, data)

        INSERT statements are optimized by batching the data, that is
        using the MySQL multiple rows syntax.

        Results are discarded! If they are needed, consider looping over
        data using the execute() method.
        """
        if not operation or not seq_params:
            return None

        try:
            if not self._cnx:
                raise ProgrammingError
        except (ProgrammingError, ReferenceError) as err:
            raise ProgrammingError("Cursor is not connected") from err
        self._cnx.handle_unread_result()

        if not isinstance(seq_params, (list, tuple)):
            raise ProgrammingError("Parameters for query must be list or tuple.")

        # Optimize INSERTs by batching them
        if re.match(RE_SQL_INSERT_STMT, operation):
            if not seq_params:
                self._rowcount = 0
                return None
            stmt = self._batch_insert(operation, seq_params)
            if stmt is not None:
                self._executed = stmt
                return self.execute(stmt)

        rowcnt = 0
        try:
            # When processing read ops (e.g., SELECT), rowcnt is updated
            # based on self._rowcount. For write ops (e.g., INSERT) is
            # updated based on self._affected_rows.
            # The variable self._description is None for write ops, that's
            # why we use it as indicator for updating rowcnt.
            for params in seq_params:
                self.execute(operation, params)
                if self.with_rows and self._cnx.unread_result:
                    self.fetchall()
                rowcnt += self._rowcount if self.description else self._affected_rows
        except (ValueError, TypeError) as err:
            raise InterfaceError(f"Failed executing the operation; {err}") from None

        self._rowcount = rowcnt
        return None

    @property
    def description(self) -> Optional[List[DescriptionType]]:
        """Returns description of columns in a result"""
        return self._description

    @property
    def rowcount(self) -> int:
        """Returns the number of rows produced or affected"""
        if self._rowcount == -1:
            return self._affected_rows
        return self._rowcount

    def close(self) -> bool:
        """Close the cursor

        The result will be freed.
        """
        if not self._cnx:
            return False

        self._cnx.handle_unread_result()
        self._warnings = None
        self._cnx = None
        return True

    def callproc(
        self,
        procname: str,
        args: Sequence[Any] = (),
    ) -> Optional[Union[Dict[str, ToPythonOutputTypes], RowType]]:
        """Calls a stored procedure with the given arguments"""
        if not procname or not isinstance(procname, str):
            raise ValueError("procname must be a string")

        if not isinstance(args, (tuple, list)):
            raise ValueError("args must be a sequence")

        argfmt = "@_{name}_arg{index}"
        self._stored_results = []

        try:
            argnames = []
            argtypes = []

            # MySQL itself does support calling procedures with their full
            # name <database>.<procedure_name>. It's necessary to split
            # by '.' and grab the procedure name from procname.
            procname_abs = procname.split(".")[-1]
            if args:
                argvalues = []
                for idx, arg in enumerate(args):
                    argname = argfmt.format(name=procname_abs, index=idx + 1)
                    argnames.append(argname)
                    if isinstance(arg, tuple):
                        argtypes.append(f" CAST({argname} AS {arg[1]})")
                        argvalues.append(arg[0])
                    else:
                        argtypes.append(argname)
                        argvalues.append(arg)

                placeholders = ",".join(f"{arg}=%s" for arg in argnames)
                self.execute(f"SET {placeholders}", argvalues)

            call = f"CALL {procname}({','.join(argnames)})"

            result = self._cnx.cmd_query(
                call, raw=self._raw, raw_as_string=self._raw_as_string
            )

            results = []
            while self._cnx.result_set_available:
                result = self._cnx.fetch_eof_columns()
                if isinstance(self, (CMySQLCursorDict, CMySQLCursorBufferedDict)):
                    cursor_class = CMySQLCursorBufferedDict
                elif isinstance(
                    self,
                    (CMySQLCursorNamedTuple, CMySQLCursorBufferedNamedTuple),
                ):
                    cursor_class = CMySQLCursorBufferedNamedTuple
                elif self._raw:
                    cursor_class = CMySQLCursorBufferedRaw
                else:
                    cursor_class = CMySQLCursorBuffered
                # pylint: disable=protected-access
                cur = cursor_class(self._cnx.get_self())
                cur._executed = f"(a result of {call})"
                cur._handle_result(result)
                # pylint: enable=protected-access
                results.append(cur)
                self._cnx.next_result()
            self._stored_results = results
            self._handle_eof()

            if argnames:
                self.reset()
                # Create names aliases to be compatible with namedtuples
                args = [
                    f"{name} AS {alias}"
                    for name, alias in zip(
                        argtypes, [arg.lstrip("@_") for arg in argnames]
                    )
                ]
                select = f"SELECT {','.join(args)}"
                self.execute(select)

                return self.fetchone()
            return tuple()

        except Error:
            raise
        except Exception as err:
            raise InterfaceError(f"Failed calling stored routine; {err}") from None

    def nextset(self) -> Optional[bool]:
        """Skip to the next available result set"""
        if not self._cnx.next_result():
            self.reset(free=True)
            return None
        self.reset(free=False)

        if not self._cnx.result_set_available:
            eof = self._cnx.fetch_eof_status()
            self._handle_result(eof)
            raise InterfaceError(errno=CR_NO_RESULT_SET)

        self._handle_result(self._cnx.fetch_eof_columns())
        return True

    def fetchall(self) -> List[RowType]:
        """Return all rows of a query result set.

        Returns:
            list: A list of tuples with all rows of a query result set.
        """
        self._check_executed()
        if not self._cnx.unread_result:
            return []

        rows: Tuple[List[RowType], Optional[CextEofPacketType]] = self._cnx.get_rows()
        if self._nextrow and self._nextrow[0]:
            rows[0].insert(0, self._nextrow[0])

        if not rows[0]:
            self._handle_eof()
            return []

        self._rowcount += len(rows[0])
        self._handle_eof()
        # self._cnx.handle_unread_result()
        return rows[0]

    def fetchmany(self, size: int = 1) -> List[RowType]:
        """Return the next set of rows of a query result set.

        When no more rows are available, it returns an empty list.
        The number of rows returned can be specified using the size argument,
        which defaults to one.

        Returns:
            list: The next set of rows of a query result set.
        """
        self._check_executed()
        if self._nextrow and self._nextrow[0]:
            rows = [self._nextrow[0]]
            size -= 1
        else:
            rows = []

        if size and self._cnx.unread_result:
            rows.extend(self._cnx.get_rows(size)[0])

        if size:
            if self._cnx.unread_result:
                self._nextrow = self._cnx.get_row()
                if (
                    self._nextrow
                    and not self._nextrow[0]
                    and not self._cnx.more_results
                ):
                    self._cnx.free_result()
            else:
                self._nextrow = (None, None)

        if not rows:
            self._handle_eof()
            return []

        self._rowcount += len(rows)
        return rows

    def fetchone(self) -> Optional[RowType]:
        """Return next row of a query result set.

        Returns:
            tuple or None: A row from query result set.
        """
        self._check_executed()
        row = self._nextrow
        if not row and self._cnx.unread_result:
            row = self._cnx.get_row()

        if row and row[0]:
            self._nextrow = self._cnx.get_row()
            if not self._nextrow[0] and not self._cnx.more_results:
                self._cnx.free_result()
        else:
            self._handle_eof()
            return None
        self._rowcount += 1
        return row[0]

    def __iter__(self) -> Iterator[RowType]:
        """Iteration over the result set

        Iteration over the result set which calls self.fetchone()
        and returns the next row.
        """
        return iter(self.fetchone, None)

    def stored_results(self) -> Generator[CMySQLCursor, None, None]:
        """Returns an iterator for stored results

        This method returns an iterator over results which are stored when
        callproc() is called. The iterator will provide MySQLCursorBuffered
        instances.

        Returns a iterator.
        """
        for result in self._stored_results:
            yield result
        self._stored_results = []

    def __next__(self) -> RowType:
        """Iteration over the result set
        Used for iterating over the result set. Calls self.fetchone()
        to get the next row.

        Raises StopIteration when no more rows are available.
        """
        try:
            row = self.fetchone()
        except InterfaceError:
            raise StopIteration from None
        if not row:
            raise StopIteration from None
        return row

    @property
    def column_names(self) -> Tuple[str, ...]:
        """Returns column names

        This property returns the columns names as a tuple.

        Returns a tuple.
        """
        if not self.description:
            return ()
        return tuple(d[0] for d in self.description)

    @property
    def statement(self) -> str:
        """Returns the executed statement

        This property returns the executed statement. When multiple
        statements were executed, the current statement in the iterator
        will be returned.
        """
        try:
            return self._executed.strip().decode("utf8")
        except AttributeError:
            return self._executed.strip()  # type: ignore[return-value]

    @property
    def with_rows(self) -> bool:
        """Returns whether the cursor could have rows returned

        This property returns True when column descriptions are available
        and possibly also rows, which will need to be fetched.

        Returns True or False.
        """
        if self.description:
            return True
        return False

    def __str__(self) -> str:
        fmt = "{class_name}: {stmt}"
        if self._executed:
            try:
                executed = self._executed.decode("utf-8")
            except AttributeError:
                executed = self._executed
            if len(executed) > 40:
                executed = executed[:40] + ".."
        else:
            executed = "(Nothing executed yet)"

        return fmt.format(class_name=self.__class__.__name__, stmt=executed)


class CMySQLCursorBuffered(CMySQLCursor):

    """Cursor using C Extension buffering results"""

    def __init__(self, connection: Type[MySQLConnectionAbstract]):
        """Initialize"""
        super().__init__(connection)

        self._rows: Optional[List[RowType]] = None
        self._next_row: int = 0

    def _handle_resultset(self) -> None:
        """Handle a result set"""
        self._rows = self._cnx.get_rows()[0]
        self._next_row = 0
        self._rowcount: int = len(self._rows)
        self._handle_eof()

    def reset(self, free: bool = True) -> None:
        """Reset the cursor to default"""
        self._rows = None
        self._next_row = 0
        super().reset(free=free)

    def _fetch_row(self) -> Optional[RowType]:
        """Returns the next row in the result set

        Returns a tuple or None.
        """
        row = None
        try:
            row = self._rows[self._next_row]
        except IndexError:
            return None
        self._next_row += 1
        return row

    def fetchall(self) -> List[RowType]:
        """Return all rows of a query result set.

        Returns:
            list: A list of tuples with all rows of a query result set.
        """
        self._check_executed()
        res = self._rows[self._next_row :]
        self._next_row = len(self._rows)
        return res

    def fetchmany(self, size: int = 1) -> List[RowType]:
        """Return the next set of rows of a query result set.

        When no more rows are available, it returns an empty list.
        The number of rows returned can be specified using the size argument,
        which defaults to one.

        Returns:
            list: The next set of rows of a query result set.
        """
        self._check_executed()
        res = []
        cnt = size or self.arraysize
        while cnt > 0:
            cnt -= 1
            row = self._fetch_row()
            if row:
                res.append(row)
            else:
                break
        return res

    def fetchone(self) -> Optional[RowType]:
        """Return next row of a query result set.

        Returns:
            tuple or None: A row from query result set.
        """
        self._check_executed()
        return self._fetch_row()

    @property
    def with_rows(self) -> bool:
        """Returns whether the cursor could have rows returned

        This property returns True when rows are available,
        which will need to be fetched.

        Returns True or False.
        """
        return self._rows is not None


class CMySQLCursorRaw(CMySQLCursor):
    """Cursor using C Extension return raw results"""

    _raw: bool = True


class CMySQLCursorBufferedRaw(CMySQLCursorBuffered):
    """Cursor using C Extension buffering raw results"""

    _raw: bool = True


class CMySQLCursorDict(CMySQLCursor):
    """Cursor using C Extension returning rows as dictionaries"""

    _raw: bool = False

    def fetchone(self) -> Optional[Dict[str, ToPythonOutputTypes]]:
        """Return next row of a query result set.

        Returns:
            dict or None: A dict from query result set.
        """
        row = super().fetchone()
        return dict(zip(self.column_names, row)) if row else None

    def fetchmany(self, size: int = 1) -> List[Dict[str, ToPythonOutputTypes]]:
        """Return the next set of rows of a query result set.

        When no more rows are available, it returns an empty list.
        The number of rows returned can be specified using the size argument,
        which defaults to one.

        Returns:
            list: The next set of rows of a query result set represented
                  as a list of dictionaries where column names are used as keys.
        """
        res = super().fetchmany(size=size)
        return [dict(zip(self.column_names, row)) for row in res]

    def fetchall(self) -> List[Dict[str, ToPythonOutputTypes]]:
        """Return all rows of a query result set.

        Returns:
            list: A list of dictionaries with all rows of a query
                  result set where column names are used as keys.
        """
        res = super().fetchall()
        return [dict(zip(self.column_names, row)) for row in res]


class CMySQLCursorBufferedDict(CMySQLCursorBuffered):
    """Cursor using C Extension buffering and returning rows as dictionaries"""

    _raw = False

    def _fetch_row(self) -> Optional[Dict[str, ToPythonOutputTypes]]:
        row = super()._fetch_row()
        if row:
            return dict(zip(self.column_names, row))
        return None

    def fetchall(self) -> List[Dict[str, ToPythonOutputTypes]]:
        """Return all rows of a query result set.

        Returns:
            list: A list of tuples with all rows of a query result set.
        """
        res = super().fetchall()
        return [dict(zip(self.column_names, row)) for row in res]


class CMySQLCursorNamedTuple(CMySQLCursor):
    """Cursor using C Extension returning rows as named tuples"""

    named_tuple: Any = None

    def _handle_resultset(self) -> None:
        """Handle a result set"""
        super()._handle_resultset()
        columns = tuple(self.column_names)
        try:
            self.named_tuple = NAMED_TUPLE_CACHE[columns]
        except KeyError:
            self.named_tuple = namedtuple("Row", columns)  # type: ignore[misc]
            NAMED_TUPLE_CACHE[columns] = self.named_tuple

    def fetchone(self) -> Optional[RowType]:
        """Return next row of a query result set.

        Returns:
            tuple or None: A row from query result set.
        """
        row = super().fetchone()
        if row:
            return self.named_tuple(*row)
        return None

    def fetchmany(self, size: int = 1) -> List[RowType]:
        """Return the next set of rows of a query result set.

        When no more rows are available, it returns an empty list.
        The number of rows returned can be specified using the size argument,
        which defaults to one.

        Returns:
            list: The next set of rows of a query result set.
        """
        res = super().fetchmany(size=size)
        if not res:
            return []
        return [self.named_tuple(*row) for row in res]

    def fetchall(self) -> List[RowType]:
        """Return all rows of a query result set.

        Returns:
            list: A list of tuples with all rows of a query result set.
        """
        res = super().fetchall()
        return [self.named_tuple(*row) for row in res]


class CMySQLCursorBufferedNamedTuple(CMySQLCursorBuffered):
    """Cursor using C Extension buffering and returning rows as named tuples"""

    named_tuple: Any = None

    def _handle_resultset(self) -> None:
        super()._handle_resultset()
        self.named_tuple = namedtuple("Row", self.column_names)  # type: ignore[misc]

    def _fetch_row(self) -> Optional[RowType]:
        row = super()._fetch_row()
        if row:
            return self.named_tuple(*row)
        return None

    def fetchall(self) -> List[RowType]:
        """Return all rows of a query result set.

        Returns:
            list: A list of tuples with all rows of a query result set.
        """
        res = super().fetchall()
        return [self.named_tuple(*row) for row in res]


class CMySQLCursorPrepared(CMySQLCursor):
    """Cursor using MySQL Prepared Statements"""

    def __init__(self, connection: Type[MySQLConnectionAbstract]):
        super().__init__(connection)
        self._rows: Optional[List[RowType]] = None
        self._rowcount: int = 0
        self._next_row: int = 0
        self._binary: bool = True
        self._stmt: Optional[MySQLPrepStmt] = None

    def _handle_eof(self) -> None:
        """Handle EOF packet"""
        self._nextrow = (None, None)
        self._handle_warnings()

    def _fetch_row(self, raw: bool = False) -> Optional[RowType]:
        """Returns the next row in the result set

        Returns a tuple or None.
        """
        if not self._stmt or not self._stmt.have_result_set:
            return None
        row = None

        if self._nextrow == (None, None):
            (row, eof) = self._cnx.get_row(
                binary=self._binary,
                columns=self.description,
                raw=raw,
                prep_stmt=self._stmt,
            )
        else:
            (row, eof) = self._nextrow

        if row:
            self._nextrow = self._cnx.get_row(
                binary=self._binary,
                columns=self.description,
                raw=raw,
                prep_stmt=self._stmt,
            )
            eof = self._nextrow[1]
            if eof is not None:
                self._warning_count = eof["warning_count"]
                self._handle_eof()
            if self._rowcount == -1:
                self._rowcount = 1
            else:
                self._rowcount += 1
        if eof:
            self._warning_count = eof["warning_count"]
            self._handle_eof()

        return row

    def callproc(self, procname: Any, args: Any = None) -> NoReturn:
        """Calls a stored procedue

        Not supported with CMySQLCursorPrepared.
        """
        raise NotSupportedError()

    def close(self) -> None:
        """Close the cursor

        This method will try to deallocate the prepared statement and close
        the cursor.
        """
        if self._stmt:
            self.reset()
            self._cnx.cmd_stmt_close(self._stmt)
            self._stmt = None
        super().close()

    def reset(self, free: bool = True) -> None:
        """Resets the prepared statement."""
        if self._stmt:
            self._cnx.cmd_stmt_reset(self._stmt)
        super().reset(free=free)

    def execute(
        self,
        operation: StrOrBytes,
        params: Optional[ParamsSequenceOrDictType] = None,
        multi: bool = False,
    ) -> None:  # multi is unused
        """Prepare and execute a MySQL Prepared Statement

        This method will prepare the given operation and execute it using
        the given parameters.

        If the cursor instance already had a prepared statement, it is
        first closed.

        Note: argument "multi" is unused.
        """
        if not operation:
            return

        try:
            if not self._cnx or self._cnx.is_closed():
                raise ProgrammingError
        except (ProgrammingError, ReferenceError) as err:
            raise ProgrammingError("Cursor is not connected", 2055) from err

        self._cnx.handle_unread_result(prepared=True)

        charset = self._cnx.charset
        if charset == "utf8mb4":
            charset = "utf8"

        if not isinstance(operation, str):
            try:
                operation = operation.decode(charset)
            except UnicodeDecodeError as err:
                raise ProgrammingError(str(err)) from err

        if isinstance(params, dict):
            replacement_keys = re.findall(RE_SQL_PYTHON_CAPTURE_PARAM_NAME, operation)
            try:
                # Replace params dict with params tuple in correct order.
                params = tuple(params[key] for key in replacement_keys)
            except KeyError as err:
                raise ProgrammingError(
                    "Not all placeholders were found in the parameters dict"
                ) from err
            # Convert %(name)s to ? before sending it to MySQL
            operation = re.sub(RE_SQL_PYTHON_REPLACE_PARAM, "?", operation)

        if operation is not self._executed:
            if self._stmt:
                self._cnx.cmd_stmt_close(self._stmt)
            self._executed = operation

            try:
                operation = operation.encode(charset)
            except UnicodeEncodeError as err:
                raise ProgrammingError(str(err)) from err

            if b"%s" in operation:
                # Convert %s to ? before sending it to MySQL
                operation = re.sub(RE_SQL_FIND_PARAM, b"?", operation)

            try:
                self._stmt = self._cnx.cmd_stmt_prepare(operation)
            except Error:
                self._executed = None
                self._stmt = None
                raise

        self._cnx.cmd_stmt_reset(self._stmt)

        if self._stmt.param_count > 0 and not params:
            return
        if params:
            if not isinstance(params, (tuple, list)):
                raise ProgrammingError(
                    errno=1210,
                    msg=f"Incorrect type of argument: {type(params).__name__}({params})"
                    ", it must be of type tuple or list the argument given to "
                    "the prepared statement",
                )
            if self._stmt.param_count != len(params):
                raise ProgrammingError(
                    errno=1210,
                    msg="Incorrect number of arguments executing prepared statement",
                )

        if params is None:
            params = ()
        res = self._cnx.cmd_stmt_execute(self._stmt, *params)
        if res:
            self._handle_result(res)

    def executemany(
        self, operation: str, seq_params: Sequence[ParamsSequenceType]
    ) -> None:
        """Prepare and execute a MySQL Prepared Statement many times

        This method will prepare the given operation and execute with each
        tuple found the list seq_params.

        If the cursor instance already had a prepared statement, it is
        first closed.
        """
        rowcnt = 0
        try:
            for params in seq_params:
                self.execute(operation, params)
                if self.with_rows:
                    self.fetchall()
                rowcnt += self._rowcount
        except (ValueError, TypeError) as err:
            raise InterfaceError(f"Failed executing the operation; {err}") from err
        self._rowcount = rowcnt

    def fetchone(self) -> Optional[RowType]:
        """Return next row of a query result set.

        Returns:
            tuple or None: A row from query result set.
        """
        self._check_executed()
        return self._fetch_row() or None

    def fetchmany(self, size: Optional[int] = None) -> List[RowType]:
        """Return the next set of rows of a query result set.

        When no more rows are available, it returns an empty list.
        The number of rows returned can be specified using the size argument,
        which defaults to one.

        Returns:
            list: The next set of rows of a query result set.
        """
        self._check_executed()
        res = []
        cnt = size or self.arraysize
        while cnt > 0 and self._stmt.have_result_set:
            cnt -= 1
            row = self._fetch_row()
            if row:
                res.append(row)
        return res

    def fetchall(self) -> List[RowType]:
        """Return all rows of a query result set.

        Returns:
            list: A list of tuples with all rows of a query result set.
        """
        self._check_executed()
        if not self._stmt.have_result_set:
            return []

        rows = self._cnx.get_rows(prep_stmt=self._stmt)
        if self._nextrow and self._nextrow[0]:
            rows[0].insert(0, self._nextrow[0])

        if not rows[0]:
            self._handle_eof()
            return []

        self._rowcount += len(rows[0])
        self._handle_eof()
        return rows[0]


class CMySQLCursorPreparedDict(CMySQLCursorDict, CMySQLCursorPrepared):  # type: ignore[misc]
    """This class is a blend of features from CMySQLCursorDict and CMySQLCursorPrepared

    Multiple inheritance in python is allowed but care must be taken
    when assuming methods resolution. In the case of multiple
    inheritance, a given attribute is first searched in the current
    class if it's not found then it's searched in the parent classes.
    The parent classes are searched in a left-right fashion and each
    class is searched once.
    Based on python's attribute resolution, in this case, attributes
    are searched as follows:
    1. CMySQLCursorPreparedDict (current class)
    2. CMySQLCursorDict (left parent class)
    3. CMySQLCursorPrepared (right parent class)
    4. CMySQLCursor (base class)
    """


class CMySQLCursorPreparedNamedTuple(CMySQLCursorNamedTuple, CMySQLCursorPrepared):
    """This class is a blend of features from CMySQLCursorNamedTuple and CMySQLCursorPrepared"""


class CMySQLCursorPreparedRaw(CMySQLCursorPrepared):
    """This class is a blend of features from CMySQLCursorRaw and CMySQLCursorPrepared"""

    _raw: bool = True

virt/lib/python3.9/site-packages/mysql/connector/cursor_cext 3.py
PrinceDisant/LostInNL
# Copyright (c) 2014, 2023, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

# mypy: disable-error-code="assignment,arg-type,override,union-attr"

"""Cursor classes using the C Extension."""
from __future__ import annotations

import re
import warnings
import weakref

from collections import namedtuple
from typing import (
    Any,
    Dict,
    Generator,
    Iterator,
    List,
    NoReturn,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
)
from weakref import CallableProxyType

# pylint: disable=import-error,no-name-in-module
from _mysql_connector import MySQLInterfaceError, MySQLPrepStmt

from .types import (
    CextEofPacketType,
    CextResultType,
    DescriptionType,
    ParamsSequenceOrDictType,
    ParamsSequenceType,
    RowType,
    StrOrBytes,
    ToPythonOutputTypes,
    WarningType,
)

# pylint: enable=import-error,no-name-in-module
# isort: split

from .abstracts import NAMED_TUPLE_CACHE, MySQLConnectionAbstract, MySQLCursorAbstract
from .cursor import (
    RE_PY_PARAM,
    RE_SQL_COMMENT,
    RE_SQL_FIND_PARAM,
    RE_SQL_INSERT_STMT,
    RE_SQL_INSERT_VALUES,
    RE_SQL_ON_DUPLICATE,
    RE_SQL_PYTHON_CAPTURE_PARAM_NAME,
    RE_SQL_PYTHON_REPLACE_PARAM,
    RE_SQL_SPLIT_STMTS,
)
from .errorcode import CR_NO_RESULT_SET
from .errors import (
    Error,
    InterfaceError,
    NotSupportedError,
    ProgrammingError,
    get_mysql_exception,
)

ERR_NO_RESULT_TO_FETCH = "No result set to fetch from"


class _ParamSubstitutor:

    """
    Substitutes parameters into SQL statement.
    """

    def __init__(self, params: Sequence[bytes]) -> None:
        self.params: Sequence[bytes] = params
        self.index: int = 0

    def __call__(self, matchobj: object) -> bytes:
        index = self.index
        self.index += 1
        try:
            return self.params[index]
        except IndexError:
            raise ProgrammingError(
                "Not enough parameters for the SQL statement"
            ) from None

    @property
    def remaining(self) -> int:
        """Returns number of parameters remaining to be substituted"""
        return len(self.params) - self.index


class CMySQLCursor(MySQLCursorAbstract):

    """Default cursor for interacting with MySQL using C Extension"""

    _raw: bool = False
    _buffered: bool = False
    _raw_as_string: bool = False

    def __init__(self, connection: Type[MySQLConnectionAbstract]) -> None:
        """Initialize"""
        MySQLCursorAbstract.__init__(self)

        self._affected_rows: int = -1
        self._rowcount: int = -1
        self._nextrow: Tuple[Optional[RowType], Optional[CextEofPacketType]] = (
            None,
            None,
        )

        if not isinstance(connection, MySQLConnectionAbstract):
            raise InterfaceError(errno=2048)
        self._cnx: CallableProxyType[Type[MySQLConnectionAbstract]] = weakref.proxy(
            connection
        )

    def reset(self, free: bool = True) -> None:
        """Reset the cursor

        When free is True (default) the result will be freed.
        """
        self._rowcount = -1
        self._nextrow = None
        self._affected_rows = -1
        self._last_insert_id: int = 0
        self._warning_count: int = 0
        self._warnings: Optional[List[WarningType]] = None
        self._warnings = None
        self._warning_count = 0
        self._description: Optional[List[DescriptionType]] = None
        self._executed_list: List[StrOrBytes] = []
        if free and self._cnx:
            self._cnx.free_result()
        super().reset()

    def _check_executed(self) -> None:
        """Check if the statement has been executed.

        Raises an error if the statement has not been executed.
        """
        if self._executed is None:
            raise InterfaceError(ERR_NO_RESULT_TO_FETCH)

    def _fetch_warnings(self) -> Optional[List[WarningType]]:
        """Fetch warnings

        Fetch warnings doing a SHOW WARNINGS. Can be called after getting
        the result.

        Returns a result set or None when there were no warnings.

        Raises Error (or subclass) on errors.

        Returns list of tuples or None.
        """
        warns = []
        try:
            # force freeing result
            self._cnx.consume_results()
            _ = self._cnx.cmd_query("SHOW WARNINGS")
            warns = self._cnx.get_rows()[0]
            self._cnx.consume_results()
        except MySQLInterfaceError as err:
            raise get_mysql_exception(
                msg=err.msg, errno=err.errno, sqlstate=err.sqlstate
            ) from err
        except Exception as err:
            raise InterfaceError(f"Failed getting warnings; {err}") from None

        if warns:
            return warns

        return None

    def _handle_warnings(self) -> None:
        """Handle possible warnings after all results are consumed.

        Raises:
            Error: Also raises exceptions if raise_on_warnings is set.
        """
        if self._cnx.get_warnings and self._warning_count:
            self._warnings = self._fetch_warnings()

        if not self._warnings:
            return

        err = get_mysql_exception(
            *self._warnings[0][1:3], warning=not self._cnx.raise_on_warnings
        )
        if self._cnx.raise_on_warnings:
            raise err

        warnings.warn(str(err), stacklevel=4)

    def _handle_result(self, result: Union[CextEofPacketType, CextResultType]) -> None:
        """Handles the result after statement execution"""
        if "columns" in result:
            self._description = result["columns"]
            self._rowcount = 0
            self._handle_resultset()
        else:
            self._last_insert_id = result["insert_id"]
            self._warning_count = result["warning_count"]
            self._affected_rows = result["affected_rows"]
            self._rowcount = -1
            self._handle_warnings()

    def _handle_resultset(self) -> None:
        """Handle a result set"""

    def _handle_eof(self) -> None:
        """Handle end of reading the result

        Raises an Error on errors.
        """
        self._warning_count = self._cnx.warning_count
        self._handle_warnings()
        if not self._cnx.more_results:
            self._cnx.free_result()

    def _execute_iter(self) -> Generator[CMySQLCursor, None, None]:
        """Generator returns MySQLCursor objects for multiple statements

        Deprecated: use nextset() method directly.

        This method is only used when multiple statements are executed
        by the execute() method. It uses zip() to make an iterator from the
        given query_iter (result of MySQLConnection.cmd_query_iter()) and
        the list of statements that were executed.
        """
        executed_list = RE_SQL_SPLIT_STMTS.split(self._executed)
        i = 0
        self._executed = executed_list[i]
        yield self

        while True:
            try:
                if not self.nextset():
                    raise StopIteration
            except InterfaceError as err:
                # Result without result set
                if err.errno != CR_NO_RESULT_SET:
                    raise
            except StopIteration:
                return
            i += 1
            try:
                self._executed = executed_list[i].strip()
            except IndexError:
                self._executed = executed_list[0]
            yield self
        return

    def execute(
        self,
        operation: StrOrBytes,
        params: ParamsSequenceOrDictType = (),
        multi: bool = False,
    ) -> Optional[Generator[CMySQLCursor, None, None]]:
        """Execute given statement using given parameters

        Deprecated: The multi argument is not needed and nextset() should
        be used to handle multiple result sets.
        """
        if not operation:
            return None

        try:
            if not self._cnx or self._cnx.is_closed():
                raise ProgrammingError
        except (ProgrammingError, ReferenceError) as err:
            raise ProgrammingError("Cursor is not connected", 2055) from err
        self._cnx.handle_unread_result()

        stmt = ""
        self.reset()

        try:
            if isinstance(operation, str):
                stmt = operation.encode(self._cnx.python_charset)
            else:
                stmt = operation
        except (UnicodeDecodeError, UnicodeEncodeError) as err:
            raise ProgrammingError(str(err)) from err

        if params:
            prepared = self._cnx.prepare_for_mysql(params)
            if isinstance(prepared, dict):
                for key, value in prepared.items():
                    stmt = stmt.replace(f"%({key})s".encode(), value)
            elif isinstance(prepared, (list, tuple)):
                psub = _ParamSubstitutor(prepared)
                stmt = RE_PY_PARAM.sub(psub, stmt)  # type: ignore[call-overload]
                if psub.remaining != 0:
                    raise ProgrammingError(
                        "Not all parameters were used in the SQL statement"
                    )

        try:
            result = self._cnx.cmd_query(
                stmt,
                raw=self._raw,
                buffered=self._buffered,
                raw_as_string=self._raw_as_string,
            )
        except MySQLInterfaceError as err:
            raise get_mysql_exception(
                msg=err.msg, errno=err.errno, sqlstate=err.sqlstate
            ) from err

        self._executed = stmt
        self._handle_result(result)

        if multi:
            return self._execute_iter()

        return None

    def _batch_insert(
        self,
        operation: str,
        seq_params: Sequence[ParamsSequenceOrDictType],
    ) -> Optional[bytes]:
        """Implements multi row insert"""

        def remove_comments(match: re.Match) -> str:
            """Remove comments from INSERT statements.

            This function is used while removing comments from INSERT
            statements. If the matched string is a comment not enclosed
            by quotes, it returns an empty string, else the string itself.
            """
            if match.group(1):
                return ""
            return match.group(2)

        tmp = re.sub(
            RE_SQL_ON_DUPLICATE,
            "",
            re.sub(RE_SQL_COMMENT, remove_comments, operation),
        )

        matches = re.search(RE_SQL_INSERT_VALUES, tmp)
        if not matches:
            raise InterfaceError(
                "Failed rewriting statement for multi-row INSERT. Check SQL syntax"
            )
        fmt = matches.group(1).encode(self._cnx.python_charset)
        values = []

        try:
            stmt = operation.encode(self._cnx.python_charset)
            for params in seq_params:
                tmp = fmt
                prepared = self._cnx.prepare_for_mysql(params)
                if isinstance(prepared, dict):
                    for key, value in prepared.items():
                        tmp = tmp.replace(f"%({key})s".encode(), value)
                elif isinstance(prepared, (list, tuple)):
                    psub = _ParamSubstitutor(prepared)
                    tmp = RE_PY_PARAM.sub(psub, tmp)  # type: ignore[call-overload]
                    if psub.remaining != 0:
                        raise ProgrammingError(
                            "Not all parameters were used in the SQL statement"
                        )
                values.append(tmp)

            if fmt in stmt:
                stmt = stmt.replace(fmt, b",".join(values), 1)
                self._executed = stmt
                return stmt
            return None
        except (UnicodeDecodeError, UnicodeEncodeError) as err:
            raise ProgrammingError(str(err)) from err
        except Exception as err:
            raise InterfaceError(f"Failed executing the operation; {err}") from None

    def executemany(
        self,
        operation: str,
        seq_params: Sequence[ParamsSequenceOrDictType],
    ) -> Optional[Generator[CMySQLCursor, None, None]]:
        """Execute the given operation multiple times

        The executemany() method will execute the operation iterating
        over the list of parameters in seq_params.

        Example: Inserting 3 new employees and their phone number

        data = [
            ('Jane','555-001'),
            ('Joe', '555-001'),
            ('John', '555-003')
            ]
        stmt = "INSERT INTO employees (name, phone) VALUES ('%s','%s)"
        cursor.executemany(stmt, data)

        INSERT statements are optimized by batching the data, that is
        using the MySQL multiple rows syntax.

        Results are discarded! If they are needed, consider looping over
        data using the execute() method.
        """
        if not operation or not seq_params:
            return None

        try:
            if not self._cnx:
                raise ProgrammingError
        except (ProgrammingError, ReferenceError) as err:
            raise ProgrammingError("Cursor is not connected") from err
        self._cnx.handle_unread_result()

        if not isinstance(seq_params, (list, tuple)):
            raise ProgrammingError("Parameters for query must be list or tuple.")

        # Optimize INSERTs by batching them
        if re.match(RE_SQL_INSERT_STMT, operation):
            if not seq_params:
                self._rowcount = 0
                return None
            stmt = self._batch_insert(operation, seq_params)
            if stmt is not None:
                self._executed = stmt
                return self.execute(stmt)

        rowcnt = 0
        try:
            # When processing read ops (e.g., SELECT), rowcnt is updated
            # based on self._rowcount. For write ops (e.g., INSERT) is
            # updated based on self._affected_rows.
            # The variable self._description is None for write ops, that's
            # why we use it as indicator for updating rowcnt.
            for params in seq_params:
                self.execute(operation, params)
                if self.with_rows and self._cnx.unread_result:
                    self.fetchall()
                rowcnt += self._rowcount if self.description else self._affected_rows
        except (ValueError, TypeError) as err:
            raise InterfaceError(f"Failed executing the operation; {err}") from None

        self._rowcount = rowcnt
        return None

    @property
    def description(self) -> Optional[List[DescriptionType]]:
        """Returns description of columns in a result"""
        return self._description

    @property
    def rowcount(self) -> int:
        """Returns the number of rows produced or affected"""
        if self._rowcount == -1:
            return self._affected_rows
        return self._rowcount

    def close(self) -> bool:
        """Close the cursor

        The result will be freed.
        """
        if not self._cnx:
            return False

        self._cnx.handle_unread_result()
        self._warnings = None
        self._cnx = None
        return True

    def callproc(
        self,
        procname: str,
        args: Sequence[Any] = (),
    ) -> Optional[Union[Dict[str, ToPythonOutputTypes], RowType]]:
        """Calls a stored procedure with the given arguments"""
        if not procname or not isinstance(procname, str):
            raise ValueError("procname must be a string")

        if not isinstance(args, (tuple, list)):
            raise ValueError("args must be a sequence")

        argfmt = "@_{name}_arg{index}"
        self._stored_results = []

        try:
            argnames = []
            argtypes = []

            # MySQL itself does support calling procedures with their full
            # name <database>.<procedure_name>. It's necessary to split
            # by '.' and grab the procedure name from procname.
            procname_abs = procname.split(".")[-1]
            if args:
                argvalues = []
                for idx, arg in enumerate(args):
                    argname = argfmt.format(name=procname_abs, index=idx + 1)
                    argnames.append(argname)
                    if isinstance(arg, tuple):
                        argtypes.append(f" CAST({argname} AS {arg[1]})")
                        argvalues.append(arg[0])
                    else:
                        argtypes.append(argname)
                        argvalues.append(arg)

                placeholders = ",".join(f"{arg}=%s" for arg in argnames)
                self.execute(f"SET {placeholders}", argvalues)

            call = f"CALL {procname}({','.join(argnames)})"

            result = self._cnx.cmd_query(
                call, raw=self._raw, raw_as_string=self._raw_as_string
            )

            results = []
            while self._cnx.result_set_available:
                result = self._cnx.fetch_eof_columns()
                if isinstance(self, (CMySQLCursorDict, CMySQLCursorBufferedDict)):
                    cursor_class = CMySQLCursorBufferedDict
                elif isinstance(
                    self,
                    (CMySQLCursorNamedTuple, CMySQLCursorBufferedNamedTuple),
                ):
                    cursor_class = CMySQLCursorBufferedNamedTuple
                elif self._raw:
                    cursor_class = CMySQLCursorBufferedRaw
                else:
                    cursor_class = CMySQLCursorBuffered
                # pylint: disable=protected-access
                cur = cursor_class(self._cnx.get_self())
                cur._executed = f"(a result of {call})"
                cur._handle_result(result)
                # pylint: enable=protected-access
                results.append(cur)
                self._cnx.next_result()
            self._stored_results = results
            self._handle_eof()

            if argnames:
                self.reset()
                # Create names aliases to be compatible with namedtuples
                args = [
                    f"{name} AS {alias}"
                    for name, alias in zip(
                        argtypes, [arg.lstrip("@_") for arg in argnames]
                    )
                ]
                select = f"SELECT {','.join(args)}"
                self.execute(select)

                return self.fetchone()
            return tuple()

        except Error:
            raise
        except Exception as err:
            raise InterfaceError(f"Failed calling stored routine; {err}") from None

    def nextset(self) -> Optional[bool]:
        """Skip to the next available result set"""
        if not self._cnx.next_result():
            self.reset(free=True)
            return None
        self.reset(free=False)

        if not self._cnx.result_set_available:
            eof = self._cnx.fetch_eof_status()
            self._handle_result(eof)
            raise InterfaceError(errno=CR_NO_RESULT_SET)

        self._handle_result(self._cnx.fetch_eof_columns())
        return True

    def fetchall(self) -> List[RowType]:
        """Return all rows of a query result set.

        Returns:
            list: A list of tuples with all rows of a query result set.
        """
        self._check_executed()
        if not self._cnx.unread_result:
            return []

        rows: Tuple[List[RowType], Optional[CextEofPacketType]] = self._cnx.get_rows()
        if self._nextrow and self._nextrow[0]:
            rows[0].insert(0, self._nextrow[0])

        if not rows[0]:
            self._handle_eof()
            return []

        self._rowcount += len(rows[0])
        self._handle_eof()
        # self._cnx.handle_unread_result()
        return rows[0]

    def fetchmany(self, size: int = 1) -> List[RowType]:
        """Return the next set of rows of a query result set.

        When no more rows are available, it returns an empty list.
        The number of rows returned can be specified using the size argument,
        which defaults to one.

        Returns:
            list: The next set of rows of a query result set.
        """
        self._check_executed()
        if self._nextrow and self._nextrow[0]:
            rows = [self._nextrow[0]]
            size -= 1
        else:
            rows = []

        if size and self._cnx.unread_result:
            rows.extend(self._cnx.get_rows(size)[0])

        if size:
            if self._cnx.unread_result:
                self._nextrow = self._cnx.get_row()
                if (
                    self._nextrow
                    and not self._nextrow[0]
                    and not self._cnx.more_results
                ):
                    self._cnx.free_result()
            else:
                self._nextrow = (None, None)

        if not rows:
            self._handle_eof()
            return []

        self._rowcount += len(rows)
        return rows

    def fetchone(self) -> Optional[RowType]:
        """Return next row of a query result set.

        Returns:
            tuple or None: A row from query result set.
        """
        self._check_executed()
        row = self._nextrow
        if not row and self._cnx.unread_result:
            row = self._cnx.get_row()

        if row and row[0]:
            self._nextrow = self._cnx.get_row()
            if not self._nextrow[0] and not self._cnx.more_results:
                self._cnx.free_result()
        else:
            self._handle_eof()
            return None
        self._rowcount += 1
        return row[0]

    def __iter__(self) -> Iterator[RowType]:
        """Iteration over the result set

        Iteration over the result set which calls self.fetchone()
        and returns the next row.
        """
        return iter(self.fetchone, None)

    def stored_results(self) -> Generator[CMySQLCursor, None, None]:
        """Returns an iterator for stored results

        This method returns an iterator over results which are stored when
        callproc() is called. The iterator will provide MySQLCursorBuffered
        instances.

        Returns a iterator.
        """
        for result in self._stored_results:
            yield result
        self._stored_results = []

    def __next__(self) -> RowType:
        """Iteration over the result set
        Used for iterating over the result set. Calls self.fetchone()
        to get the next row.

        Raises StopIteration when no more rows are available.
        """
        try:
            row = self.fetchone()
        except InterfaceError:
            raise StopIteration from None
        if not row:
            raise StopIteration from None
        return row

    @property
    def column_names(self) -> Tuple[str, ...]:
        """Returns column names

        This property returns the columns names as a tuple.

        Returns a tuple.
        """
        if not self.description:
            return ()
        return tuple(d[0] for d in self.description)

    @property
    def statement(self) -> str:
        """Returns the executed statement

        This property returns the executed statement. When multiple
        statements were executed, the current statement in the iterator
        will be returned.
        """
        try:
            return self._executed.strip().decode("utf8")
        except AttributeError:
            return self._executed.strip()  # type: ignore[return-value]

    @property
    def with_rows(self) -> bool:
        """Returns whether the cursor could have rows returned

        This property returns True when column descriptions are available
        and possibly also rows, which will need to be fetched.

        Returns True or False.
        """
        if self.description:
            return True
        return False

    def __str__(self) -> str:
        fmt = "{class_name}: {stmt}"
        if self._executed:
            try:
                executed = self._executed.decode("utf-8")
            except AttributeError:
                executed = self._executed
            if len(executed) > 40:
                executed = executed[:40] + ".."
        else:
            executed = "(Nothing executed yet)"

        return fmt.format(class_name=self.__class__.__name__, stmt=executed)


class CMySQLCursorBuffered(CMySQLCursor):

    """Cursor using C Extension buffering results"""

    def __init__(self, connection: Type[MySQLConnectionAbstract]):
        """Initialize"""
        super().__init__(connection)

        self._rows: Optional[List[RowType]] = None
        self._next_row: int = 0

    def _handle_resultset(self) -> None:
        """Handle a result set"""
        self._rows = self._cnx.get_rows()[0]
        self._next_row = 0
        self._rowcount: int = len(self._rows)
        self._handle_eof()

    def reset(self, free: bool = True) -> None:
        """Reset the cursor to default"""
        self._rows = None
        self._next_row = 0
        super().reset(free=free)

    def _fetch_row(self) -> Optional[RowType]:
        """Returns the next row in the result set

        Returns a tuple or None.
        """
        row = None
        try:
            row = self._rows[self._next_row]
        except IndexError:
            return None
        self._next_row += 1
        return row

    def fetchall(self) -> List[RowType]:
        """Return all rows of a query result set.

        Returns:
            list: A list of tuples with all rows of a query result set.
        """
        self._check_executed()
        res = self._rows[self._next_row :]
        self._next_row = len(self._rows)
        return res

    def fetchmany(self, size: int = 1) -> List[RowType]:
        """Return the next set of rows of a query result set.

        When no more rows are available, it returns an empty list.
        The number of rows returned can be specified using the size argument,
        which defaults to one.

        Returns:
            list: The next set of rows of a query result set.
        """
        self._check_executed()
        res = []
        cnt = size or self.arraysize
        while cnt > 0:
            cnt -= 1
            row = self._fetch_row()
            if row:
                res.append(row)
            else:
                break
        return res

    def fetchone(self) -> Optional[RowType]:
        """Return next row of a query result set.

        Returns:
            tuple or None: A row from query result set.
        """
        self._check_executed()
        return self._fetch_row()

    @property
    def with_rows(self) -> bool:
        """Returns whether the cursor could have rows returned

        This property returns True when rows are available,
        which will need to be fetched.

        Returns True or False.
        """
        return self._rows is not None


class CMySQLCursorRaw(CMySQLCursor):
    """Cursor using C Extension return raw results"""

    _raw: bool = True


class CMySQLCursorBufferedRaw(CMySQLCursorBuffered):
    """Cursor using C Extension buffering raw results"""

    _raw: bool = True


class CMySQLCursorDict(CMySQLCursor):
    """Cursor using C Extension returning rows as dictionaries"""

    _raw: bool = False

    def fetchone(self) -> Optional[Dict[str, ToPythonOutputTypes]]:
        """Return next row of a query result set.

        Returns:
            dict or None: A dict from query result set.
        """
        row = super().fetchone()
        return dict(zip(self.column_names, row)) if row else None

    def fetchmany(self, size: int = 1) -> List[Dict[str, ToPythonOutputTypes]]:
        """Return the next set of rows of a query result set.

        When no more rows are available, it returns an empty list.
        The number of rows returned can be specified using the size argument,
        which defaults to one.

        Returns:
            list: The next set of rows of a query result set represented
                  as a list of dictionaries where column names are used as keys.
        """
        res = super().fetchmany(size=size)
        return [dict(zip(self.column_names, row)) for row in res]

    def fetchall(self) -> List[Dict[str, ToPythonOutputTypes]]:
        """Return all rows of a query result set.

        Returns:
            list: A list of dictionaries with all rows of a query
                  result set where column names are used as keys.
        """
        res = super().fetchall()
        return [dict(zip(self.column_names, row)) for row in res]


class CMySQLCursorBufferedDict(CMySQLCursorBuffered):
    """Cursor using C Extension buffering and returning rows as dictionaries"""

    _raw = False

    def _fetch_row(self) -> Optional[Dict[str, ToPythonOutputTypes]]:
        row = super()._fetch_row()
        if row:
            return dict(zip(self.column_names, row))
        return None

    def fetchall(self) -> List[Dict[str, ToPythonOutputTypes]]:
        """Return all rows of a query result set.

        Returns:
            list: A list of tuples with all rows of a query result set.
        """
        res = super().fetchall()
        return [dict(zip(self.column_names, row)) for row in res]


class CMySQLCursorNamedTuple(CMySQLCursor):
    """Cursor using C Extension returning rows as named tuples"""

    named_tuple: Any = None

    def _handle_resultset(self) -> None:
        """Handle a result set"""
        super()._handle_resultset()
        columns = tuple(self.column_names)
        try:
            self.named_tuple = NAMED_TUPLE_CACHE[columns]
        except KeyError:
            self.named_tuple = namedtuple("Row", columns)  # type: ignore[misc]
            NAMED_TUPLE_CACHE[columns] = self.named_tuple

    def fetchone(self) -> Optional[RowType]:
        """Return next row of a query result set.

        Returns:
            tuple or None: A row from query result set.
        """
        row = super().fetchone()
        if row:
            return self.named_tuple(*row)
        return None

    def fetchmany(self, size: int = 1) -> List[RowType]:
        """Return the next set of rows of a query result set.

        When no more rows are available, it returns an empty list.
        The number of rows returned can be specified using the size argument,
        which defaults to one.

        Returns:
            list: The next set of rows of a query result set.
        """
        res = super().fetchmany(size=size)
        if not res:
            return []
        return [self.named_tuple(*row) for row in res]

    def fetchall(self) -> List[RowType]:
        """Return all rows of a query result set.

        Returns:
            list: A list of tuples with all rows of a query result set.
        """
        res = super().fetchall()
        return [self.named_tuple(*row) for row in res]


class CMySQLCursorBufferedNamedTuple(CMySQLCursorBuffered):
    """Cursor using C Extension buffering and returning rows as named tuples"""

    named_tuple: Any = None

    def _handle_resultset(self) -> None:
        super()._handle_resultset()
        self.named_tuple = namedtuple("Row", self.column_names)  # type: ignore[misc]

    def _fetch_row(self) -> Optional[RowType]:
        row = super()._fetch_row()
        if row:
            return self.named_tuple(*row)
        return None

    def fetchall(self) -> List[RowType]:
        """Return all rows of a query result set.

        Returns:
            list: A list of tuples with all rows of a query result set.
        """
        res = super().fetchall()
        return [self.named_tuple(*row) for row in res]


class CMySQLCursorPrepared(CMySQLCursor):
    """Cursor using MySQL Prepared Statements"""

    def __init__(self, connection: Type[MySQLConnectionAbstract]):
        super().__init__(connection)
        self._rows: Optional[List[RowType]] = None
        self._rowcount: int = 0
        self._next_row: int = 0
        self._binary: bool = True
        self._stmt: Optional[MySQLPrepStmt] = None

    def _handle_eof(self) -> None:
        """Handle EOF packet"""
        self._nextrow = (None, None)
        self._handle_warnings()

    def _fetch_row(self, raw: bool = False) -> Optional[RowType]:
        """Returns the next row in the result set

        Returns a tuple or None.
        """
        if not self._stmt or not self._stmt.have_result_set:
            return None
        row = None

        if self._nextrow == (None, None):
            (row, eof) = self._cnx.get_row(
                binary=self._binary,
                columns=self.description,
                raw=raw,
                prep_stmt=self._stmt,
            )
        else:
            (row, eof) = self._nextrow

        if row:
            self._nextrow = self._cnx.get_row(
                binary=self._binary,
                columns=self.description,
                raw=raw,
                prep_stmt=self._stmt,
            )
            eof = self._nextrow[1]
            if eof is not None:
                self._warning_count = eof["warning_count"]
                self._handle_eof()
            if self._rowcount == -1:
                self._rowcount = 1
            else:
                self._rowcount += 1
        if eof:
            self._warning_count = eof["warning_count"]
            self._handle_eof()

        return row

    def callproc(self, procname: Any, args: Any = None) -> NoReturn:
        """Calls a stored procedue

        Not supported with CMySQLCursorPrepared.
        """
        raise NotSupportedError()

    def close(self) -> None:
        """Close the cursor

        This method will try to deallocate the prepared statement and close
        the cursor.
        """
        if self._stmt:
            self.reset()
            self._cnx.cmd_stmt_close(self._stmt)
            self._stmt = None
        super().close()

    def reset(self, free: bool = True) -> None:
        """Resets the prepared statement."""
        if self._stmt:
            self._cnx.cmd_stmt_reset(self._stmt)
        super().reset(free=free)

    def execute(
        self,
        operation: StrOrBytes,
        params: Optional[ParamsSequenceOrDictType] = None,
        multi: bool = False,
    ) -> None:  # multi is unused
        """Prepare and execute a MySQL Prepared Statement

        This method will prepare the given operation and execute it using
        the given parameters.

        If the cursor instance already had a prepared statement, it is
        first closed.

        Note: argument "multi" is unused.
        """
        if not operation:
            return

        try:
            if not self._cnx or self._cnx.is_closed():
                raise ProgrammingError
        except (ProgrammingError, ReferenceError) as err:
            raise ProgrammingError("Cursor is not connected", 2055) from err

        self._cnx.handle_unread_result(prepared=True)

        charset = self._cnx.charset
        if charset == "utf8mb4":
            charset = "utf8"

        if not isinstance(operation, str):
            try:
                operation = operation.decode(charset)
            except UnicodeDecodeError as err:
                raise ProgrammingError(str(err)) from err

        if isinstance(params, dict):
            replacement_keys = re.findall(RE_SQL_PYTHON_CAPTURE_PARAM_NAME, operation)
            try:
                # Replace params dict with params tuple in correct order.
                params = tuple(params[key] for key in replacement_keys)
            except KeyError as err:
                raise ProgrammingError(
                    "Not all placeholders were found in the parameters dict"
                ) from err
            # Convert %(name)s to ? before sending it to MySQL
            operation = re.sub(RE_SQL_PYTHON_REPLACE_PARAM, "?", operation)

        if operation is not self._executed:
            if self._stmt:
                self._cnx.cmd_stmt_close(self._stmt)
            self._executed = operation

            try:
                operation = operation.encode(charset)
            except UnicodeEncodeError as err:
                raise ProgrammingError(str(err)) from err

            if b"%s" in operation:
                # Convert %s to ? before sending it to MySQL
                operation = re.sub(RE_SQL_FIND_PARAM, b"?", operation)

            try:
                self._stmt = self._cnx.cmd_stmt_prepare(operation)
            except Error:
                self._executed = None
                self._stmt = None
                raise

        self._cnx.cmd_stmt_reset(self._stmt)

        if self._stmt.param_count > 0 and not params:
            return
        if params:
            if not isinstance(params, (tuple, list)):
                raise ProgrammingError(
                    errno=1210,
                    msg=f"Incorrect type of argument: {type(params).__name__}({params})"
                    ", it must be of type tuple or list the argument given to "
                    "the prepared statement",
                )
            if self._stmt.param_count != len(params):
                raise ProgrammingError(
                    errno=1210,
                    msg="Incorrect number of arguments executing prepared statement",
                )

        if params is None:
            params = ()
        res = self._cnx.cmd_stmt_execute(self._stmt, *params)
        if res:
            self._handle_result(res)

    def executemany(
        self, operation: str, seq_params: Sequence[ParamsSequenceType]
    ) -> None:
        """Prepare and execute a MySQL Prepared Statement many times

        This method will prepare the given operation and execute with each
        tuple found the list seq_params.

        If the cursor instance already had a prepared statement, it is
        first closed.
        """
        rowcnt = 0
        try:
            for params in seq_params:
                self.execute(operation, params)
                if self.with_rows:
                    self.fetchall()
                rowcnt += self._rowcount
        except (ValueError, TypeError) as err:
            raise InterfaceError(f"Failed executing the operation; {err}") from err
        self._rowcount = rowcnt

    def fetchone(self) -> Optional[RowType]:
        """Return next row of a query result set.

        Returns:
            tuple or None: A row from query result set.
        """
        self._check_executed()
        return self._fetch_row() or None

    def fetchmany(self, size: Optional[int] = None) -> List[RowType]:
        """Return the next set of rows of a query result set.

        When no more rows are available, it returns an empty list.
        The number of rows returned can be specified using the size argument,
        which defaults to one.

        Returns:
            list: The next set of rows of a query result set.
        """
        self._check_executed()
        res = []
        cnt = size or self.arraysize
        while cnt > 0 and self._stmt.have_result_set:
            cnt -= 1
            row = self._fetch_row()
            if row:
                res.append(row)
        return res

    def fetchall(self) -> List[RowType]:
        """Return all rows of a query result set.

        Returns:
            list: A list of tuples with all rows of a query result set.
        """
        self._check_executed()
        if not self._stmt.have_result_set:
            return []

        rows = self._cnx.get_rows(prep_stmt=self._stmt)
        if self._nextrow and self._nextrow[0]:
            rows[0].insert(0, self._nextrow[0])

        if not rows[0]:
            self._handle_eof()
            return []

        self._rowcount += len(rows[0])
        self._handle_eof()
        return rows[0]


class CMySQLCursorPreparedDict(CMySQLCursorDict, CMySQLCursorPrepared):  # type: ignore[misc]
    """This class is a blend of features from CMySQLCursorDict and CMySQLCursorPrepared

    Multiple inheritance in python is allowed but care must be taken
    when assuming methods resolution. In the case of multiple
    inheritance, a given attribute is first searched in the current
    class if it's not found then it's searched in the parent classes.
    The parent classes are searched in a left-right fashion and each
    class is searched once.
    Based on python's attribute resolution, in this case, attributes
    are searched as follows:
    1. CMySQLCursorPreparedDict (current class)
    2. CMySQLCursorDict (left parent class)
    3. CMySQLCursorPrepared (right parent class)
    4. CMySQLCursor (base class)
    """


class CMySQLCursorPreparedNamedTuple(CMySQLCursorNamedTuple, CMySQLCursorPrepared):
    """This class is a blend of features from CMySQLCursorNamedTuple and CMySQLCursorPrepared"""


class CMySQLCursorPreparedRaw(CMySQLCursorPrepared):
    """This class is a blend of features from CMySQLCursorRaw and CMySQLCursorPrepared"""

    _raw: bool = True

virt/lib/python3.9/site-packages/mysql/connector/cursor_cext.py
PrinceDisant/LostInNL
# Copyright (c) 2014, 2022, Oracle and/or its affiliates. All rights reserved.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

"""Custom Python types used by MySQL Connector/Python"""
from __future__ import annotations

from typing import Type


class HexLiteral(str):

    """Class holding MySQL hex literals"""

    charset: str = ""
    original: str = ""

    def __new__(cls: Type[HexLiteral], str_: str, charset: str = "utf8") -> HexLiteral:
        hexed = [f"{i:02x}" for i in str_.encode(charset)]
        obj = str.__new__(cls, "".join(hexed))
        obj.charset = charset
        obj.original = str_
        return obj

    def __str__(self) -> str:
        return "0x" + self

virt/lib/python3.9/site-packages/mysql/connector/custom_types.py
PrinceDisant/LostInNL
# Copyright (c) 2009, 2022, Oracle and/or its affiliates. All rights reserved.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

"""
This module implements some constructors and singletons as required by the
DB API v2.0 (PEP-249).
"""

# Python Db API v2
# pylint: disable=invalid-name
apilevel: str = "2.0"
threadsafety: int = 1
paramstyle: str = "pyformat"

import datetime
import time

from typing import Tuple

from . import constants


class _DBAPITypeObject:
    def __init__(self, *values: int) -> None:
        self.values: Tuple[int, ...] = values

    def __eq__(self, other: object) -> bool:
        return other in self.values

    def __ne__(self, other: object) -> bool:
        return other not in self.values


Date = datetime.date
Time = datetime.time
Timestamp = datetime.datetime


def DateFromTicks(ticks: int) -> datetime.date:
    """Construct an object holding a date value from the given ticks value."""
    return Date(*time.localtime(ticks)[:3])


def TimeFromTicks(ticks: int) -> datetime.time:
    """Construct an object holding a time value from the given ticks value."""
    return Time(*time.localtime(ticks)[3:6])


def TimestampFromTicks(ticks: int) -> datetime.datetime:
    """Construct an object holding a time stamp from the given ticks value."""
    return Timestamp(*time.localtime(ticks)[:6])


Binary = bytes

STRING = _DBAPITypeObject(*constants.FieldType.get_string_types())
BINARY = _DBAPITypeObject(*constants.FieldType.get_binary_types())
NUMBER = _DBAPITypeObject(*constants.FieldType.get_number_types())
DATETIME = _DBAPITypeObject(*constants.FieldType.get_timestamp_types())
ROWID = _DBAPITypeObject()

virt/lib/python3.9/site-packages/mysql/connector/dbapi.py
PrinceDisant/LostInNL
# Copyright (c) 2020, 2023, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

# mypy: disable-error-code="override"

"""Django database Backend using MySQL Connector/Python.

This Django database backend is heavily based on the MySQL backend from Django.

Changes include:
* Support for microseconds (MySQL 5.6.3 and later)
* Using INFORMATION_SCHEMA where possible
* Using new defaults for, for example SQL_AUTO_IS_NULL

Requires and comes with MySQL Connector/Python v8.0.22 and later:
    http://dev.mysql.com/downloads/connector/python/
"""

import warnings

from datetime import datetime, time
from typing import Any, Dict, Generator, Iterator, List, Optional, Set, Tuple, Union

from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
from django.db import IntegrityError
from django.db.backends.base.base import BaseDatabaseWrapper
from django.utils import dateparse, timezone
from django.utils.functional import cached_property

try:
    import mysql.connector

    from mysql.connector.connection import MySQLConnection
    from mysql.connector.connection_cext import CMySQLConnection
    from mysql.connector.conversion import MySQLConverter
    from mysql.connector.cursor import MySQLCursor
    from mysql.connector.cursor_cext import CMySQLCursor
    from mysql.connector.custom_types import HexLiteral
    from mysql.connector.pooling import PooledMySQLConnection
    from mysql.connector.types import (
        ParamsDictType,
        ParamsSequenceOrDictType,
        ParamsSequenceType,
        RowType,
        StrOrBytes,
    )
except ImportError as err:
    raise ImproperlyConfigured(f"Error loading mysql.connector module: {err}") from err

try:
    from _mysql_connector import datetime_to_mysql
except ImportError:
    HAVE_CEXT = False
else:
    HAVE_CEXT = True

from .client import DatabaseClient
from .creation import DatabaseCreation
from .features import DatabaseFeatures
from .introspection import DatabaseIntrospection
from .operations import DatabaseOperations
from .schema import DatabaseSchemaEditor
from .validation import DatabaseValidation

Error = mysql.connector.Error
DatabaseError = mysql.connector.DatabaseError
NotSupportedError = mysql.connector.NotSupportedError
OperationalError = mysql.connector.OperationalError
ProgrammingError = mysql.connector.ProgrammingError


def adapt_datetime_with_timezone_support(value: datetime) -> StrOrBytes:
    """Equivalent to DateTimeField.get_db_prep_value. Used only by raw SQL."""
    if settings.USE_TZ:
        if timezone.is_naive(value):
            warnings.warn(
                f"MySQL received a naive datetime ({value})"
                " while time zone support is active.",
                RuntimeWarning,
            )
            default_timezone = timezone.get_default_timezone()
            value = timezone.make_aware(value, default_timezone)
        value = value.astimezone(timezone.utc).replace(tzinfo=None)
    if HAVE_CEXT:
        mysql_datetime: bytes = datetime_to_mysql(value)
        return mysql_datetime
    return value.strftime("%Y-%m-%d %H:%M:%S.%f")


class CursorWrapper:
    """Wrapper around MySQL Connector/Python's cursor class.

    The cursor class is defined by the options passed to MySQL
    Connector/Python. If buffered option is True in those options,
    MySQLCursorBuffered will be used.
    """

    codes_for_integrityerror = (
        1048,  # Column cannot be null
        1690,  # BIGINT UNSIGNED value is out of range
        3819,  # CHECK constraint is violated
        4025,  # CHECK constraint failed
    )

    def __init__(self, cursor: Union[MySQLCursor, CMySQLCursor]) -> None:
        self.cursor: Union[MySQLCursor, CMySQLCursor] = cursor

    @staticmethod
    def _adapt_execute_args_dict(args: ParamsDictType) -> ParamsDictType:
        if not args:
            return args
        new_args = dict(args)
        for key, value in args.items():
            if isinstance(value, datetime):
                new_args[key] = adapt_datetime_with_timezone_support(value)

        return new_args

    @staticmethod
    def _adapt_execute_args(
        args: Optional[ParamsSequenceType],
    ) -> Optional[ParamsSequenceType]:
        if not args:
            return args
        new_args = list(args)
        for i, arg in enumerate(args):
            if isinstance(arg, datetime):
                new_args[i] = adapt_datetime_with_timezone_support(arg)

        return tuple(new_args)

    def execute(
        self, query: str, args: Optional[ParamsSequenceOrDictType] = None
    ) -> Optional[Generator[Union[MySQLCursor, CMySQLCursor], None, None]]:
        """Executes the given operation

        This wrapper method around the execute()-method of the cursor is
        mainly needed to re-raise using different exceptions.
        """
        new_args: Optional[ParamsSequenceOrDictType] = None
        if isinstance(args, dict):
            new_args = self._adapt_execute_args_dict(args)
        else:
            new_args = self._adapt_execute_args(args)
        try:
            return self.cursor.execute(query, new_args)
        except mysql.connector.OperationalError as exc:
            if exc.args[0] in self.codes_for_integrityerror:
                raise IntegrityError(*tuple(exc.args)) from None
            raise

    def executemany(
        self,
        query: str,
        args: Union[
            Tuple[ParamsSequenceOrDictType, ...],
            List[ParamsSequenceOrDictType],
        ],
    ) -> Optional[Generator[Union[MySQLCursor, CMySQLCursor], None, None]]:
        """Executes the given operation

        This wrapper method around the executemany()-method of the cursor is
        mainly needed to re-raise using different exceptions.
        """
        try:
            return self.cursor.executemany(query, args)
        except mysql.connector.OperationalError as exc:
            if exc.args[0] in self.codes_for_integrityerror:
                raise IntegrityError(*tuple(exc.args)) from None
            raise

    def __getattr__(self, attr: Any) -> Any:
        """Return an attribute of wrapped cursor"""
        return getattr(self.cursor, attr)

    def __iter__(self) -> Iterator[RowType]:
        """Return an iterator over wrapped cursor"""
        return iter(self.cursor)


class DatabaseWrapper(BaseDatabaseWrapper):  # pylint: disable=abstract-method
    """Represent a database connection."""

    vendor = "mysql"
    # This dictionary maps Field objects to their associated MySQL column
    # types, as strings. Column-type strings can contain format strings; they'll
    # be interpolated against the values of Field.__dict__ before being output.
    # If a column type is set to None, it won't be included in the output.
    data_types = {
        "AutoField": "integer AUTO_INCREMENT",
        "BigAutoField": "bigint AUTO_INCREMENT",
        "BinaryField": "longblob",
        "BooleanField": "bool",
        "CharField": "varchar(%(max_length)s)",
        "DateField": "date",
        "DateTimeField": "datetime(6)",
        "DecimalField": "numeric(%(max_digits)s, %(decimal_places)s)",
        "DurationField": "bigint",
        "FileField": "varchar(%(max_length)s)",
        "FilePathField": "varchar(%(max_length)s)",
        "FloatField": "double precision",
        "IntegerField": "integer",
        "BigIntegerField": "bigint",
        "IPAddressField": "char(15)",
        "GenericIPAddressField": "char(39)",
        "JSONField": "json",
        "NullBooleanField": "bool",
        "OneToOneField": "integer",
        "PositiveBigIntegerField": "bigint UNSIGNED",
        "PositiveIntegerField": "integer UNSIGNED",
        "PositiveSmallIntegerField": "smallint UNSIGNED",
        "SlugField": "varchar(%(max_length)s)",
        "SmallAutoField": "smallint AUTO_INCREMENT",
        "SmallIntegerField": "smallint",
        "TextField": "longtext",
        "TimeField": "time(6)",
        "UUIDField": "char(32)",
    }

    # For these data types:
    # - MySQL < 8.0.13 doesn't accept default values and
    #   implicitly treat them as nullable
    # - all versions of MySQL doesn't support full width database
    #   indexes
    _limited_data_types = (
        "tinyblob",
        "blob",
        "mediumblob",
        "longblob",
        "tinytext",
        "text",
        "mediumtext",
        "longtext",
        "json",
    )

    operators = {
        "exact": "= %s",
        "iexact": "LIKE %s",
        "contains": "LIKE BINARY %s",
        "icontains": "LIKE %s",
        "regex": "REGEXP BINARY %s",
        "iregex": "REGEXP %s",
        "gt": "> %s",
        "gte": ">= %s",
        "lt": "< %s",
        "lte": "<= %s",
        "startswith": "LIKE BINARY %s",
        "endswith": "LIKE BINARY %s",
        "istartswith": "LIKE %s",
        "iendswith": "LIKE %s",
    }

    # The patterns below are used to generate SQL pattern lookup clauses when
    # the right-hand side of the lookup isn't a raw string (it might be an expression
    # or the result of a bilateral transformation).
    # In those cases, special characters for LIKE operators (e.g. \, *, _) should be
    # escaped on database side.
    #
    # Note: we use str.format() here for readability as '%' is used as a wildcard for
    # the LIKE operator.
    pattern_esc = r"REPLACE(REPLACE(REPLACE({}, '\\', '\\\\'), '%%', '\%%'), '_', '\_')"
    pattern_ops = {
        "contains": "LIKE BINARY CONCAT('%%', {}, '%%')",
        "icontains": "LIKE CONCAT('%%', {}, '%%')",
        "startswith": "LIKE BINARY CONCAT({}, '%%')",
        "istartswith": "LIKE CONCAT({}, '%%')",
        "endswith": "LIKE BINARY CONCAT('%%', {})",
        "iendswith": "LIKE CONCAT('%%', {})",
    }

    isolation_level: Optional[str] = None
    isolation_levels = {
        "read uncommitted",
        "read committed",
        "repeatable read",
        "serializable",
    }

    Database = mysql.connector
    SchemaEditorClass = DatabaseSchemaEditor
    # Classes instantiated in __init__().
    client_class = DatabaseClient
    creation_class = DatabaseCreation
    features_class = DatabaseFeatures
    introspection_class = DatabaseIntrospection
    ops_class = DatabaseOperations
    validation_class = DatabaseValidation

    def __init__(self, *args: Any, **kwargs: Any) -> None:
        super().__init__(*args, **kwargs)

        options = self.settings_dict.get("OPTIONS")
        if options:
            self._use_pure = options.get("use_pure", not HAVE_CEXT)
            converter_class = options.get(
                "converter_class",
                DjangoMySQLConverter,
            )
            if not issubclass(converter_class, DjangoMySQLConverter):
                raise ProgrammingError(
                    "Converter class should be a subclass of "
                    "mysql.connector.django.base.DjangoMySQLConverter"
                )
            self.converter = converter_class()
        else:
            self.converter = DjangoMySQLConverter()
            self._use_pure = not HAVE_CEXT

    def __getattr__(self, attr: str) -> bool:
        if attr.startswith("mysql_is"):
            return False
        raise AttributeError

    def get_connection_params(self) -> Dict[str, Any]:
        kwargs = {
            "charset": "utf8",
            "use_unicode": True,
            "buffered": False,
            "consume_results": True,
        }

        settings_dict = self.settings_dict

        if settings_dict["USER"]:
            kwargs["user"] = settings_dict["USER"]
        if settings_dict["NAME"]:
            kwargs["database"] = settings_dict["NAME"]
        if settings_dict["PASSWORD"]:
            kwargs["passwd"] = settings_dict["PASSWORD"]
        if settings_dict["HOST"].startswith("/"):
            kwargs["unix_socket"] = settings_dict["HOST"]
        elif settings_dict["HOST"]:
            kwargs["host"] = settings_dict["HOST"]
        if settings_dict["PORT"]:
            kwargs["port"] = int(settings_dict["PORT"])
        if settings_dict.get("OPTIONS", {}).get("init_command"):
            kwargs["init_command"] = settings_dict["OPTIONS"]["init_command"]

        # Raise exceptions for database warnings if DEBUG is on
        kwargs["raise_on_warnings"] = settings.DEBUG

        kwargs["client_flags"] = [
            # Need potentially affected rows on UPDATE
            mysql.connector.constants.ClientFlag.FOUND_ROWS,
        ]

        try:
            options = settings_dict["OPTIONS"].copy()
            isolation_level = options.pop("isolation_level")
            if isolation_level:
                isolation_level = isolation_level.lower()
                if isolation_level not in self.isolation_levels:
                    valid_levels = ", ".join(
                        f"'{level}'" for level in sorted(self.isolation_levels)
                    )
                    raise ImproperlyConfigured(
                        f"Invalid transaction isolation level '{isolation_level}' "
                        f"specified.\nUse one of {valid_levels}, or None."
                    )
            self.isolation_level = isolation_level
            kwargs.update(options)
        except KeyError:
            # OPTIONS missing is OK
            pass
        return kwargs

    def get_new_connection(
        self, conn_params: Dict[str, Any]
    ) -> Union[PooledMySQLConnection, MySQLConnection, CMySQLConnection]:
        if "converter_class" not in conn_params:
            conn_params["converter_class"] = DjangoMySQLConverter
        cnx = mysql.connector.connect(**conn_params)

        return cnx

    def init_connection_state(self) -> None:
        assignments = []
        if self.features.is_sql_auto_is_null_enabled:  # type: ignore[attr-defined]
            # SQL_AUTO_IS_NULL controls whether an AUTO_INCREMENT column on
            # a recently inserted row will return when the field is tested
            # for NULL. Disabling this brings this aspect of MySQL in line
            # with SQL standards.
            assignments.append("SET SQL_AUTO_IS_NULL = 0")

        if self.isolation_level:
            assignments.append(
                "SET SESSION TRANSACTION ISOLATION LEVEL "
                f"{self.isolation_level.upper()}"
            )

        if assignments:
            with self.cursor() as cursor:
                cursor.execute("; ".join(assignments))

        if "AUTOCOMMIT" in self.settings_dict:
            try:
                self.set_autocommit(self.settings_dict["AUTOCOMMIT"])
            except AttributeError:
                self._set_autocommit(self.settings_dict["AUTOCOMMIT"])

    def create_cursor(self, name: Any = None) -> CursorWrapper:
        cursor = self.connection.cursor()
        return CursorWrapper(cursor)

    def _rollback(self) -> None:
        try:
            BaseDatabaseWrapper._rollback(self)  # type: ignore[attr-defined]
        except NotSupportedError:
            pass

    def _set_autocommit(self, autocommit: bool) -> None:
        with self.wrap_database_errors:
            self.connection.autocommit = autocommit

    def disable_constraint_checking(self) -> bool:
        """
        Disable foreign key checks, primarily for use in adding rows with
        forward references. Always return True to indicate constraint checks
        need to be re-enabled.
        """
        with self.cursor() as cursor:
            cursor.execute("SET foreign_key_checks=0")
        return True

    def enable_constraint_checking(self) -> None:
        """
        Re-enable foreign key checks after they have been disabled.
        """
        # Override needs_rollback in case constraint_checks_disabled is
        # nested inside transaction.atomic.
        self.needs_rollback, needs_rollback = False, self.needs_rollback
        try:
            with self.cursor() as cursor:
                cursor.execute("SET foreign_key_checks=1")
        finally:
            self.needs_rollback = needs_rollback

    def check_constraints(self, table_names: Optional[List[str]] = None) -> None:
        """
        Check each table name in `table_names` for rows with invalid foreign
        key references. This method is intended to be used in conjunction with
        `disable_constraint_checking()` and `enable_constraint_checking()`, to
        determine if rows with invalid references were entered while constraint
        checks were off.
        """
        with self.cursor() as cursor:
            if table_names is None:
                table_names = self.introspection.table_names(cursor)
            for table_name in table_names:
                primary_key_column_name = self.introspection.get_primary_key_column(
                    cursor, table_name
                )
                if not primary_key_column_name:
                    continue
                key_columns = self.introspection.get_key_columns(cursor, table_name)
                for (
                    column_name,
                    referenced_table_name,
                    referenced_column_name,
                ) in key_columns:
                    cursor.execute(
                        f"""
                        SELECT REFERRING.`{primary_key_column_name}`,
                        REFERRING.`{column_name}`
                        FROM `{table_name}` as REFERRING
                        LEFT JOIN `{referenced_table_name}` as REFERRED
                        ON (
                            REFERRING.`{column_name}` =
                            REFERRED.`{referenced_column_name}`
                        )
                        WHERE REFERRING.`{column_name}` IS NOT NULL
                        AND REFERRED.`{referenced_column_name}` IS NULL
                        """
                    )
                    for bad_row in cursor.fetchall():
                        raise IntegrityError(
                            f"The row in table '{table_name}' with primary "
                            f"key '{bad_row[0]}' has an invalid foreign key: "
                            f"{table_name}.{column_name} contains a value "
                            f"'{bad_row[1]}' that does not have a "
                            f"corresponding value in "
                            f"{referenced_table_name}."
                            f"{referenced_column_name}."
                        )

    def is_usable(self) -> bool:
        try:
            self.connection.ping()
        except Error:
            return False
        return True

    @cached_property
    @staticmethod
    def display_name() -> str:
        """Display name."""
        return "MySQL"

    @cached_property
    def data_type_check_constraints(self) -> Dict[str, str]:
        """Mapping of Field objects to their SQL for CHECK constraints."""
        if self.features.supports_column_check_constraints:
            check_constraints = {
                "PositiveBigIntegerField": "`%(column)s` >= 0",
                "PositiveIntegerField": "`%(column)s` >= 0",
                "PositiveSmallIntegerField": "`%(column)s` >= 0",
            }
            return check_constraints
        return {}

    @cached_property
    def mysql_server_data(self) -> Dict[str, Any]:
        """Return MySQL server data."""
        with self.temporary_connection() as cursor:
            # Select some server variables and test if the time zone
            # definitions are installed. CONVERT_TZ returns NULL if 'UTC'
            # timezone isn't loaded into the mysql.time_zone table.
            cursor.execute(
                """
                SELECT VERSION(),
                       @@sql_mode,
                       @@default_storage_engine,
                       @@sql_auto_is_null,
                       @@lower_case_table_names,
                       CONVERT_TZ('2001-01-01 01:00:00', 'UTC', 'UTC') IS NOT NULL
            """
            )
            row = cursor.fetchone()
        return {
            "version": row[0],
            "sql_mode": row[1],
            "default_storage_engine": row[2],
            "sql_auto_is_null": bool(row[3]),
            "lower_case_table_names": bool(row[4]),
            "has_zoneinfo_database": bool(row[5]),
        }

    @cached_property
    def mysql_server_info(self) -> Any:
        """Return MySQL version."""
        with self.temporary_connection() as cursor:
            cursor.execute("SELECT VERSION()")
            return cursor.fetchone()[0]

    @cached_property
    def mysql_version(self) -> Tuple[int, ...]:
        """Return MySQL version."""
        config = self.get_connection_params()
        with mysql.connector.connect(**config) as conn:
            server_version: Tuple[int, ...] = conn.get_server_version()
        return server_version

    @cached_property
    def sql_mode(self) -> Set[str]:
        """Return SQL mode."""
        with self.cursor() as cursor:
            cursor.execute("SELECT @@sql_mode")
            sql_mode = cursor.fetchone()
        return set(sql_mode[0].split(",") if sql_mode else ())

    @property
    def use_pure(self) -> bool:
        """Return True if pure Python version is being used."""
        ans: bool = self._use_pure
        return ans


class DjangoMySQLConverter(MySQLConverter):
    """Custom converter for Django."""

    # pylint: disable=unused-argument

    @staticmethod
    def _time_to_python(value: bytes, dsc: Any = None) -> Optional[time]:
        """Return MySQL TIME data type as datetime.time()

        Returns datetime.time()
        """
        return dateparse.parse_time(value.decode("utf-8"))

    @staticmethod
    def _datetime_to_python(value: bytes, dsc: Any = None) -> Optional[datetime]:
        """Connector/Python always returns naive datetime.datetime

        Connector/Python always returns naive timestamps since MySQL has
        no time zone support.

        - A naive datetime is a datetime that doesn't know its own timezone.

        Django needs a non-naive datetime, but in this method we don't need
        to make a datetime value time zone aware since Django itself at some
        point will make it aware (at least in versions 3.2.16 and 4.1.2) when
        USE_TZ=True. This may change in a future release, we need to keep an
        eye on this behaviour.

        Returns datetime.datetime()
        """
        return MySQLConverter._datetime_to_python(value) if value else None

    # pylint: enable=unused-argument

    def _safestring_to_mysql(self, value: str) -> Union[bytes, HexLiteral]:
        return self._str_to_mysql(value)

    def _safetext_to_mysql(self, value: str) -> Union[bytes, HexLiteral]:
        return self._str_to_mysql(value)

    def _safebytes_to_mysql(self, value: bytes) -> bytes:
        return self._bytes_to_mysql(value)

virt/lib/python3.9/site-packages/mysql/connector/django/base 2.py
PrinceDisant/LostInNL
# Copyright (c) 2020, 2023, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

# mypy: disable-error-code="override"

"""Django database Backend using MySQL Connector/Python.

This Django database backend is heavily based on the MySQL backend from Django.

Changes include:
* Support for microseconds (MySQL 5.6.3 and later)
* Using INFORMATION_SCHEMA where possible
* Using new defaults for, for example SQL_AUTO_IS_NULL

Requires and comes with MySQL Connector/Python v8.0.22 and later:
    http://dev.mysql.com/downloads/connector/python/
"""

import warnings

from datetime import datetime, time
from typing import Any, Dict, Generator, Iterator, List, Optional, Set, Tuple, Union

from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
from django.db import IntegrityError
from django.db.backends.base.base import BaseDatabaseWrapper
from django.utils import dateparse, timezone
from django.utils.functional import cached_property

try:
    import mysql.connector

    from mysql.connector.connection import MySQLConnection
    from mysql.connector.connection_cext import CMySQLConnection
    from mysql.connector.conversion import MySQLConverter
    from mysql.connector.cursor import MySQLCursor
    from mysql.connector.cursor_cext import CMySQLCursor
    from mysql.connector.custom_types import HexLiteral
    from mysql.connector.pooling import PooledMySQLConnection
    from mysql.connector.types import (
        ParamsDictType,
        ParamsSequenceOrDictType,
        ParamsSequenceType,
        RowType,
        StrOrBytes,
    )
except ImportError as err:
    raise ImproperlyConfigured(f"Error loading mysql.connector module: {err}") from err

try:
    from _mysql_connector import datetime_to_mysql
except ImportError:
    HAVE_CEXT = False
else:
    HAVE_CEXT = True

from .client import DatabaseClient
from .creation import DatabaseCreation
from .features import DatabaseFeatures
from .introspection import DatabaseIntrospection
from .operations import DatabaseOperations
from .schema import DatabaseSchemaEditor
from .validation import DatabaseValidation

Error = mysql.connector.Error
DatabaseError = mysql.connector.DatabaseError
NotSupportedError = mysql.connector.NotSupportedError
OperationalError = mysql.connector.OperationalError
ProgrammingError = mysql.connector.ProgrammingError


def adapt_datetime_with_timezone_support(value: datetime) -> StrOrBytes:
    """Equivalent to DateTimeField.get_db_prep_value. Used only by raw SQL."""
    if settings.USE_TZ:
        if timezone.is_naive(value):
            warnings.warn(
                f"MySQL received a naive datetime ({value})"
                " while time zone support is active.",
                RuntimeWarning,
            )
            default_timezone = timezone.get_default_timezone()
            value = timezone.make_aware(value, default_timezone)
        value = value.astimezone(timezone.utc).replace(tzinfo=None)
    if HAVE_CEXT:
        mysql_datetime: bytes = datetime_to_mysql(value)
        return mysql_datetime
    return value.strftime("%Y-%m-%d %H:%M:%S.%f")


class CursorWrapper:
    """Wrapper around MySQL Connector/Python's cursor class.

    The cursor class is defined by the options passed to MySQL
    Connector/Python. If buffered option is True in those options,
    MySQLCursorBuffered will be used.
    """

    codes_for_integrityerror = (
        1048,  # Column cannot be null
        1690,  # BIGINT UNSIGNED value is out of range
        3819,  # CHECK constraint is violated
        4025,  # CHECK constraint failed
    )

    def __init__(self, cursor: Union[MySQLCursor, CMySQLCursor]) -> None:
        self.cursor: Union[MySQLCursor, CMySQLCursor] = cursor

    @staticmethod
    def _adapt_execute_args_dict(args: ParamsDictType) -> ParamsDictType:
        if not args:
            return args
        new_args = dict(args)
        for key, value in args.items():
            if isinstance(value, datetime):
                new_args[key] = adapt_datetime_with_timezone_support(value)

        return new_args

    @staticmethod
    def _adapt_execute_args(
        args: Optional[ParamsSequenceType],
    ) -> Optional[ParamsSequenceType]:
        if not args:
            return args
        new_args = list(args)
        for i, arg in enumerate(args):
            if isinstance(arg, datetime):
                new_args[i] = adapt_datetime_with_timezone_support(arg)

        return tuple(new_args)

    def execute(
        self, query: str, args: Optional[ParamsSequenceOrDictType] = None
    ) -> Optional[Generator[Union[MySQLCursor, CMySQLCursor], None, None]]:
        """Executes the given operation

        This wrapper method around the execute()-method of the cursor is
        mainly needed to re-raise using different exceptions.
        """
        new_args: Optional[ParamsSequenceOrDictType] = None
        if isinstance(args, dict):
            new_args = self._adapt_execute_args_dict(args)
        else:
            new_args = self._adapt_execute_args(args)
        try:
            return self.cursor.execute(query, new_args)
        except mysql.connector.OperationalError as exc:
            if exc.args[0] in self.codes_for_integrityerror:
                raise IntegrityError(*tuple(exc.args)) from None
            raise

    def executemany(
        self,
        query: str,
        args: Union[
            Tuple[ParamsSequenceOrDictType, ...],
            List[ParamsSequenceOrDictType],
        ],
    ) -> Optional[Generator[Union[MySQLCursor, CMySQLCursor], None, None]]:
        """Executes the given operation

        This wrapper method around the executemany()-method of the cursor is
        mainly needed to re-raise using different exceptions.
        """
        try:
            return self.cursor.executemany(query, args)
        except mysql.connector.OperationalError as exc:
            if exc.args[0] in self.codes_for_integrityerror:
                raise IntegrityError(*tuple(exc.args)) from None
            raise

    def __getattr__(self, attr: Any) -> Any:
        """Return an attribute of wrapped cursor"""
        return getattr(self.cursor, attr)

    def __iter__(self) -> Iterator[RowType]:
        """Return an iterator over wrapped cursor"""
        return iter(self.cursor)


class DatabaseWrapper(BaseDatabaseWrapper):  # pylint: disable=abstract-method
    """Represent a database connection."""

    vendor = "mysql"
    # This dictionary maps Field objects to their associated MySQL column
    # types, as strings. Column-type strings can contain format strings; they'll
    # be interpolated against the values of Field.__dict__ before being output.
    # If a column type is set to None, it won't be included in the output.
    data_types = {
        "AutoField": "integer AUTO_INCREMENT",
        "BigAutoField": "bigint AUTO_INCREMENT",
        "BinaryField": "longblob",
        "BooleanField": "bool",
        "CharField": "varchar(%(max_length)s)",
        "DateField": "date",
        "DateTimeField": "datetime(6)",
        "DecimalField": "numeric(%(max_digits)s, %(decimal_places)s)",
        "DurationField": "bigint",
        "FileField": "varchar(%(max_length)s)",
        "FilePathField": "varchar(%(max_length)s)",
        "FloatField": "double precision",
        "IntegerField": "integer",
        "BigIntegerField": "bigint",
        "IPAddressField": "char(15)",
        "GenericIPAddressField": "char(39)",
        "JSONField": "json",
        "NullBooleanField": "bool",
        "OneToOneField": "integer",
        "PositiveBigIntegerField": "bigint UNSIGNED",
        "PositiveIntegerField": "integer UNSIGNED",
        "PositiveSmallIntegerField": "smallint UNSIGNED",
        "SlugField": "varchar(%(max_length)s)",
        "SmallAutoField": "smallint AUTO_INCREMENT",
        "SmallIntegerField": "smallint",
        "TextField": "longtext",
        "TimeField": "time(6)",
        "UUIDField": "char(32)",
    }

    # For these data types:
    # - MySQL < 8.0.13 doesn't accept default values and
    #   implicitly treat them as nullable
    # - all versions of MySQL doesn't support full width database
    #   indexes
    _limited_data_types = (
        "tinyblob",
        "blob",
        "mediumblob",
        "longblob",
        "tinytext",
        "text",
        "mediumtext",
        "longtext",
        "json",
    )

    operators = {
        "exact": "= %s",
        "iexact": "LIKE %s",
        "contains": "LIKE BINARY %s",
        "icontains": "LIKE %s",
        "regex": "REGEXP BINARY %s",
        "iregex": "REGEXP %s",
        "gt": "> %s",
        "gte": ">= %s",
        "lt": "< %s",
        "lte": "<= %s",
        "startswith": "LIKE BINARY %s",
        "endswith": "LIKE BINARY %s",
        "istartswith": "LIKE %s",
        "iendswith": "LIKE %s",
    }

    # The patterns below are used to generate SQL pattern lookup clauses when
    # the right-hand side of the lookup isn't a raw string (it might be an expression
    # or the result of a bilateral transformation).
    # In those cases, special characters for LIKE operators (e.g. \, *, _) should be
    # escaped on database side.
    #
    # Note: we use str.format() here for readability as '%' is used as a wildcard for
    # the LIKE operator.
    pattern_esc = r"REPLACE(REPLACE(REPLACE({}, '\\', '\\\\'), '%%', '\%%'), '_', '\_')"
    pattern_ops = {
        "contains": "LIKE BINARY CONCAT('%%', {}, '%%')",
        "icontains": "LIKE CONCAT('%%', {}, '%%')",
        "startswith": "LIKE BINARY CONCAT({}, '%%')",
        "istartswith": "LIKE CONCAT({}, '%%')",
        "endswith": "LIKE BINARY CONCAT('%%', {})",
        "iendswith": "LIKE CONCAT('%%', {})",
    }

    isolation_level: Optional[str] = None
    isolation_levels = {
        "read uncommitted",
        "read committed",
        "repeatable read",
        "serializable",
    }

    Database = mysql.connector
    SchemaEditorClass = DatabaseSchemaEditor
    # Classes instantiated in __init__().
    client_class = DatabaseClient
    creation_class = DatabaseCreation
    features_class = DatabaseFeatures
    introspection_class = DatabaseIntrospection
    ops_class = DatabaseOperations
    validation_class = DatabaseValidation

    def __init__(self, *args: Any, **kwargs: Any) -> None:
        super().__init__(*args, **kwargs)

        options = self.settings_dict.get("OPTIONS")
        if options:
            self._use_pure = options.get("use_pure", not HAVE_CEXT)
            converter_class = options.get(
                "converter_class",
                DjangoMySQLConverter,
            )
            if not issubclass(converter_class, DjangoMySQLConverter):
                raise ProgrammingError(
                    "Converter class should be a subclass of "
                    "mysql.connector.django.base.DjangoMySQLConverter"
                )
            self.converter = converter_class()
        else:
            self.converter = DjangoMySQLConverter()
            self._use_pure = not HAVE_CEXT

    def __getattr__(self, attr: str) -> bool:
        if attr.startswith("mysql_is"):
            return False
        raise AttributeError

    def get_connection_params(self) -> Dict[str, Any]:
        kwargs = {
            "charset": "utf8",
            "use_unicode": True,
            "buffered": False,
            "consume_results": True,
        }

        settings_dict = self.settings_dict

        if settings_dict["USER"]:
            kwargs["user"] = settings_dict["USER"]
        if settings_dict["NAME"]:
            kwargs["database"] = settings_dict["NAME"]
        if settings_dict["PASSWORD"]:
            kwargs["passwd"] = settings_dict["PASSWORD"]
        if settings_dict["HOST"].startswith("/"):
            kwargs["unix_socket"] = settings_dict["HOST"]
        elif settings_dict["HOST"]:
            kwargs["host"] = settings_dict["HOST"]
        if settings_dict["PORT"]:
            kwargs["port"] = int(settings_dict["PORT"])
        if settings_dict.get("OPTIONS", {}).get("init_command"):
            kwargs["init_command"] = settings_dict["OPTIONS"]["init_command"]

        # Raise exceptions for database warnings if DEBUG is on
        kwargs["raise_on_warnings"] = settings.DEBUG

        kwargs["client_flags"] = [
            # Need potentially affected rows on UPDATE
            mysql.connector.constants.ClientFlag.FOUND_ROWS,
        ]

        try:
            options = settings_dict["OPTIONS"].copy()
            isolation_level = options.pop("isolation_level")
            if isolation_level:
                isolation_level = isolation_level.lower()
                if isolation_level not in self.isolation_levels:
                    valid_levels = ", ".join(
                        f"'{level}'" for level in sorted(self.isolation_levels)
                    )
                    raise ImproperlyConfigured(
                        f"Invalid transaction isolation level '{isolation_level}' "
                        f"specified.\nUse one of {valid_levels}, or None."
                    )
            self.isolation_level = isolation_level
            kwargs.update(options)
        except KeyError:
            # OPTIONS missing is OK
            pass
        return kwargs

    def get_new_connection(
        self, conn_params: Dict[str, Any]
    ) -> Union[PooledMySQLConnection, MySQLConnection, CMySQLConnection]:
        if "converter_class" not in conn_params:
            conn_params["converter_class"] = DjangoMySQLConverter
        cnx = mysql.connector.connect(**conn_params)

        return cnx

    def init_connection_state(self) -> None:
        assignments = []
        if self.features.is_sql_auto_is_null_enabled:  # type: ignore[attr-defined]
            # SQL_AUTO_IS_NULL controls whether an AUTO_INCREMENT column on
            # a recently inserted row will return when the field is tested
            # for NULL. Disabling this brings this aspect of MySQL in line
            # with SQL standards.
            assignments.append("SET SQL_AUTO_IS_NULL = 0")

        if self.isolation_level:
            assignments.append(
                "SET SESSION TRANSACTION ISOLATION LEVEL "
                f"{self.isolation_level.upper()}"
            )

        if assignments:
            with self.cursor() as cursor:
                cursor.execute("; ".join(assignments))

        if "AUTOCOMMIT" in self.settings_dict:
            try:
                self.set_autocommit(self.settings_dict["AUTOCOMMIT"])
            except AttributeError:
                self._set_autocommit(self.settings_dict["AUTOCOMMIT"])

    def create_cursor(self, name: Any = None) -> CursorWrapper:
        cursor = self.connection.cursor()
        return CursorWrapper(cursor)

    def _rollback(self) -> None:
        try:
            BaseDatabaseWrapper._rollback(self)  # type: ignore[attr-defined]
        except NotSupportedError:
            pass

    def _set_autocommit(self, autocommit: bool) -> None:
        with self.wrap_database_errors:
            self.connection.autocommit = autocommit

    def disable_constraint_checking(self) -> bool:
        """
        Disable foreign key checks, primarily for use in adding rows with
        forward references. Always return True to indicate constraint checks
        need to be re-enabled.
        """
        with self.cursor() as cursor:
            cursor.execute("SET foreign_key_checks=0")
        return True

    def enable_constraint_checking(self) -> None:
        """
        Re-enable foreign key checks after they have been disabled.
        """
        # Override needs_rollback in case constraint_checks_disabled is
        # nested inside transaction.atomic.
        self.needs_rollback, needs_rollback = False, self.needs_rollback
        try:
            with self.cursor() as cursor:
                cursor.execute("SET foreign_key_checks=1")
        finally:
            self.needs_rollback = needs_rollback

    def check_constraints(self, table_names: Optional[List[str]] = None) -> None:
        """
        Check each table name in `table_names` for rows with invalid foreign
        key references. This method is intended to be used in conjunction with
        `disable_constraint_checking()` and `enable_constraint_checking()`, to
        determine if rows with invalid references were entered while constraint
        checks were off.
        """
        with self.cursor() as cursor:
            if table_names is None:
                table_names = self.introspection.table_names(cursor)
            for table_name in table_names:
                primary_key_column_name = self.introspection.get_primary_key_column(
                    cursor, table_name
                )
                if not primary_key_column_name:
                    continue
                key_columns = self.introspection.get_key_columns(cursor, table_name)
                for (
                    column_name,
                    referenced_table_name,
                    referenced_column_name,
                ) in key_columns:
                    cursor.execute(
                        f"""
                        SELECT REFERRING.`{primary_key_column_name}`,
                        REFERRING.`{column_name}`
                        FROM `{table_name}` as REFERRING
                        LEFT JOIN `{referenced_table_name}` as REFERRED
                        ON (
                            REFERRING.`{column_name}` =
                            REFERRED.`{referenced_column_name}`
                        )
                        WHERE REFERRING.`{column_name}` IS NOT NULL
                        AND REFERRED.`{referenced_column_name}` IS NULL
                        """
                    )
                    for bad_row in cursor.fetchall():
                        raise IntegrityError(
                            f"The row in table '{table_name}' with primary "
                            f"key '{bad_row[0]}' has an invalid foreign key: "
                            f"{table_name}.{column_name} contains a value "
                            f"'{bad_row[1]}' that does not have a "
                            f"corresponding value in "
                            f"{referenced_table_name}."
                            f"{referenced_column_name}."
                        )

    def is_usable(self) -> bool:
        try:
            self.connection.ping()
        except Error:
            return False
        return True

    @cached_property
    @staticmethod
    def display_name() -> str:
        """Display name."""
        return "MySQL"

    @cached_property
    def data_type_check_constraints(self) -> Dict[str, str]:
        """Mapping of Field objects to their SQL for CHECK constraints."""
        if self.features.supports_column_check_constraints:
            check_constraints = {
                "PositiveBigIntegerField": "`%(column)s` >= 0",
                "PositiveIntegerField": "`%(column)s` >= 0",
                "PositiveSmallIntegerField": "`%(column)s` >= 0",
            }
            return check_constraints
        return {}

    @cached_property
    def mysql_server_data(self) -> Dict[str, Any]:
        """Return MySQL server data."""
        with self.temporary_connection() as cursor:
            # Select some server variables and test if the time zone
            # definitions are installed. CONVERT_TZ returns NULL if 'UTC'
            # timezone isn't loaded into the mysql.time_zone table.
            cursor.execute(
                """
                SELECT VERSION(),
                       @@sql_mode,
                       @@default_storage_engine,
                       @@sql_auto_is_null,
                       @@lower_case_table_names,
                       CONVERT_TZ('2001-01-01 01:00:00', 'UTC', 'UTC') IS NOT NULL
            """
            )
            row = cursor.fetchone()
        return {
            "version": row[0],
            "sql_mode": row[1],
            "default_storage_engine": row[2],
            "sql_auto_is_null": bool(row[3]),
            "lower_case_table_names": bool(row[4]),
            "has_zoneinfo_database": bool(row[5]),
        }

    @cached_property
    def mysql_server_info(self) -> Any:
        """Return MySQL version."""
        with self.temporary_connection() as cursor:
            cursor.execute("SELECT VERSION()")
            return cursor.fetchone()[0]

    @cached_property
    def mysql_version(self) -> Tuple[int, ...]:
        """Return MySQL version."""
        config = self.get_connection_params()
        with mysql.connector.connect(**config) as conn:
            server_version: Tuple[int, ...] = conn.get_server_version()
        return server_version

    @cached_property
    def sql_mode(self) -> Set[str]:
        """Return SQL mode."""
        with self.cursor() as cursor:
            cursor.execute("SELECT @@sql_mode")
            sql_mode = cursor.fetchone()
        return set(sql_mode[0].split(",") if sql_mode else ())

    @property
    def use_pure(self) -> bool:
        """Return True if pure Python version is being used."""
        ans: bool = self._use_pure
        return ans


class DjangoMySQLConverter(MySQLConverter):
    """Custom converter for Django."""

    # pylint: disable=unused-argument

    @staticmethod
    def _time_to_python(value: bytes, dsc: Any = None) -> Optional[time]:
        """Return MySQL TIME data type as datetime.time()

        Returns datetime.time()
        """
        return dateparse.parse_time(value.decode("utf-8"))

    @staticmethod
    def _datetime_to_python(value: bytes, dsc: Any = None) -> Optional[datetime]:
        """Connector/Python always returns naive datetime.datetime

        Connector/Python always returns naive timestamps since MySQL has
        no time zone support.

        - A naive datetime is a datetime that doesn't know its own timezone.

        Django needs a non-naive datetime, but in this method we don't need
        to make a datetime value time zone aware since Django itself at some
        point will make it aware (at least in versions 3.2.16 and 4.1.2) when
        USE_TZ=True. This may change in a future release, we need to keep an
        eye on this behaviour.

        Returns datetime.datetime()
        """
        return MySQLConverter._datetime_to_python(value) if value else None

    # pylint: enable=unused-argument

    def _safestring_to_mysql(self, value: str) -> Union[bytes, HexLiteral]:
        return self._str_to_mysql(value)

    def _safetext_to_mysql(self, value: str) -> Union[bytes, HexLiteral]:
        return self._str_to_mysql(value)

    def _safebytes_to_mysql(self, value: bytes) -> bytes:
        return self._bytes_to_mysql(value)

virt/lib/python3.9/site-packages/mysql/connector/django/base 3.py
PrinceDisant/LostInNL
# Copyright (c) 2020, 2023, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

# mypy: disable-error-code="override"

"""Django database Backend using MySQL Connector/Python.

This Django database backend is heavily based on the MySQL backend from Django.

Changes include:
* Support for microseconds (MySQL 5.6.3 and later)
* Using INFORMATION_SCHEMA where possible
* Using new defaults for, for example SQL_AUTO_IS_NULL

Requires and comes with MySQL Connector/Python v8.0.22 and later:
    http://dev.mysql.com/downloads/connector/python/
"""

import warnings

from datetime import datetime, time
from typing import Any, Dict, Generator, Iterator, List, Optional, Set, Tuple, Union

from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
from django.db import IntegrityError
from django.db.backends.base.base import BaseDatabaseWrapper
from django.utils import dateparse, timezone
from django.utils.functional import cached_property

try:
    import mysql.connector

    from mysql.connector.connection import MySQLConnection
    from mysql.connector.connection_cext import CMySQLConnection
    from mysql.connector.conversion import MySQLConverter
    from mysql.connector.cursor import MySQLCursor
    from mysql.connector.cursor_cext import CMySQLCursor
    from mysql.connector.custom_types import HexLiteral
    from mysql.connector.pooling import PooledMySQLConnection
    from mysql.connector.types import (
        ParamsDictType,
        ParamsSequenceOrDictType,
        ParamsSequenceType,
        RowType,
        StrOrBytes,
    )
except ImportError as err:
    raise ImproperlyConfigured(f"Error loading mysql.connector module: {err}") from err

try:
    from _mysql_connector import datetime_to_mysql
except ImportError:
    HAVE_CEXT = False
else:
    HAVE_CEXT = True

from .client import DatabaseClient
from .creation import DatabaseCreation
from .features import DatabaseFeatures
from .introspection import DatabaseIntrospection
from .operations import DatabaseOperations
from .schema import DatabaseSchemaEditor
from .validation import DatabaseValidation

Error = mysql.connector.Error
DatabaseError = mysql.connector.DatabaseError
NotSupportedError = mysql.connector.NotSupportedError
OperationalError = mysql.connector.OperationalError
ProgrammingError = mysql.connector.ProgrammingError


def adapt_datetime_with_timezone_support(value: datetime) -> StrOrBytes:
    """Equivalent to DateTimeField.get_db_prep_value. Used only by raw SQL."""
    if settings.USE_TZ:
        if timezone.is_naive(value):
            warnings.warn(
                f"MySQL received a naive datetime ({value})"
                " while time zone support is active.",
                RuntimeWarning,
            )
            default_timezone = timezone.get_default_timezone()
            value = timezone.make_aware(value, default_timezone)
        value = value.astimezone(timezone.utc).replace(tzinfo=None)
    if HAVE_CEXT:
        mysql_datetime: bytes = datetime_to_mysql(value)
        return mysql_datetime
    return value.strftime("%Y-%m-%d %H:%M:%S.%f")


class CursorWrapper:
    """Wrapper around MySQL Connector/Python's cursor class.

    The cursor class is defined by the options passed to MySQL
    Connector/Python. If buffered option is True in those options,
    MySQLCursorBuffered will be used.
    """

    codes_for_integrityerror = (
        1048,  # Column cannot be null
        1690,  # BIGINT UNSIGNED value is out of range
        3819,  # CHECK constraint is violated
        4025,  # CHECK constraint failed
    )

    def __init__(self, cursor: Union[MySQLCursor, CMySQLCursor]) -> None:
        self.cursor: Union[MySQLCursor, CMySQLCursor] = cursor

    @staticmethod
    def _adapt_execute_args_dict(args: ParamsDictType) -> ParamsDictType:
        if not args:
            return args
        new_args = dict(args)
        for key, value in args.items():
            if isinstance(value, datetime):
                new_args[key] = adapt_datetime_with_timezone_support(value)

        return new_args

    @staticmethod
    def _adapt_execute_args(
        args: Optional[ParamsSequenceType],
    ) -> Optional[ParamsSequenceType]:
        if not args:
            return args
        new_args = list(args)
        for i, arg in enumerate(args):
            if isinstance(arg, datetime):
                new_args[i] = adapt_datetime_with_timezone_support(arg)

        return tuple(new_args)

    def execute(
        self, query: str, args: Optional[ParamsSequenceOrDictType] = None
    ) -> Optional[Generator[Union[MySQLCursor, CMySQLCursor], None, None]]:
        """Executes the given operation

        This wrapper method around the execute()-method of the cursor is
        mainly needed to re-raise using different exceptions.
        """
        new_args: Optional[ParamsSequenceOrDictType] = None
        if isinstance(args, dict):
            new_args = self._adapt_execute_args_dict(args)
        else:
            new_args = self._adapt_execute_args(args)
        try:
            return self.cursor.execute(query, new_args)
        except mysql.connector.OperationalError as exc:
            if exc.args[0] in self.codes_for_integrityerror:
                raise IntegrityError(*tuple(exc.args)) from None
            raise

    def executemany(
        self,
        query: str,
        args: Union[
            Tuple[ParamsSequenceOrDictType, ...],
            List[ParamsSequenceOrDictType],
        ],
    ) -> Optional[Generator[Union[MySQLCursor, CMySQLCursor], None, None]]:
        """Executes the given operation

        This wrapper method around the executemany()-method of the cursor is
        mainly needed to re-raise using different exceptions.
        """
        try:
            return self.cursor.executemany(query, args)
        except mysql.connector.OperationalError as exc:
            if exc.args[0] in self.codes_for_integrityerror:
                raise IntegrityError(*tuple(exc.args)) from None
            raise

    def __getattr__(self, attr: Any) -> Any:
        """Return an attribute of wrapped cursor"""
        return getattr(self.cursor, attr)

    def __iter__(self) -> Iterator[RowType]:
        """Return an iterator over wrapped cursor"""
        return iter(self.cursor)


class DatabaseWrapper(BaseDatabaseWrapper):  # pylint: disable=abstract-method
    """Represent a database connection."""

    vendor = "mysql"
    # This dictionary maps Field objects to their associated MySQL column
    # types, as strings. Column-type strings can contain format strings; they'll
    # be interpolated against the values of Field.__dict__ before being output.
    # If a column type is set to None, it won't be included in the output.
    data_types = {
        "AutoField": "integer AUTO_INCREMENT",
        "BigAutoField": "bigint AUTO_INCREMENT",
        "BinaryField": "longblob",
        "BooleanField": "bool",
        "CharField": "varchar(%(max_length)s)",
        "DateField": "date",
        "DateTimeField": "datetime(6)",
        "DecimalField": "numeric(%(max_digits)s, %(decimal_places)s)",
        "DurationField": "bigint",
        "FileField": "varchar(%(max_length)s)",
        "FilePathField": "varchar(%(max_length)s)",
        "FloatField": "double precision",
        "IntegerField": "integer",
        "BigIntegerField": "bigint",
        "IPAddressField": "char(15)",
        "GenericIPAddressField": "char(39)",
        "JSONField": "json",
        "NullBooleanField": "bool",
        "OneToOneField": "integer",
        "PositiveBigIntegerField": "bigint UNSIGNED",
        "PositiveIntegerField": "integer UNSIGNED",
        "PositiveSmallIntegerField": "smallint UNSIGNED",
        "SlugField": "varchar(%(max_length)s)",
        "SmallAutoField": "smallint AUTO_INCREMENT",
        "SmallIntegerField": "smallint",
        "TextField": "longtext",
        "TimeField": "time(6)",
        "UUIDField": "char(32)",
    }

    # For these data types:
    # - MySQL < 8.0.13 doesn't accept default values and
    #   implicitly treat them as nullable
    # - all versions of MySQL doesn't support full width database
    #   indexes
    _limited_data_types = (
        "tinyblob",
        "blob",
        "mediumblob",
        "longblob",
        "tinytext",
        "text",
        "mediumtext",
        "longtext",
        "json",
    )

    operators = {
        "exact": "= %s",
        "iexact": "LIKE %s",
        "contains": "LIKE BINARY %s",
        "icontains": "LIKE %s",
        "regex": "REGEXP BINARY %s",
        "iregex": "REGEXP %s",
        "gt": "> %s",
        "gte": ">= %s",
        "lt": "< %s",
        "lte": "<= %s",
        "startswith": "LIKE BINARY %s",
        "endswith": "LIKE BINARY %s",
        "istartswith": "LIKE %s",
        "iendswith": "LIKE %s",
    }

    # The patterns below are used to generate SQL pattern lookup clauses when
    # the right-hand side of the lookup isn't a raw string (it might be an expression
    # or the result of a bilateral transformation).
    # In those cases, special characters for LIKE operators (e.g. \, *, _) should be
    # escaped on database side.
    #
    # Note: we use str.format() here for readability as '%' is used as a wildcard for
    # the LIKE operator.
    pattern_esc = r"REPLACE(REPLACE(REPLACE({}, '\\', '\\\\'), '%%', '\%%'), '_', '\_')"
    pattern_ops = {
        "contains": "LIKE BINARY CONCAT('%%', {}, '%%')",
        "icontains": "LIKE CONCAT('%%', {}, '%%')",
        "startswith": "LIKE BINARY CONCAT({}, '%%')",
        "istartswith": "LIKE CONCAT({}, '%%')",
        "endswith": "LIKE BINARY CONCAT('%%', {})",
        "iendswith": "LIKE CONCAT('%%', {})",
    }

    isolation_level: Optional[str] = None
    isolation_levels = {
        "read uncommitted",
        "read committed",
        "repeatable read",
        "serializable",
    }

    Database = mysql.connector
    SchemaEditorClass = DatabaseSchemaEditor
    # Classes instantiated in __init__().
    client_class = DatabaseClient
    creation_class = DatabaseCreation
    features_class = DatabaseFeatures
    introspection_class = DatabaseIntrospection
    ops_class = DatabaseOperations
    validation_class = DatabaseValidation

    def __init__(self, *args: Any, **kwargs: Any) -> None:
        super().__init__(*args, **kwargs)

        options = self.settings_dict.get("OPTIONS")
        if options:
            self._use_pure = options.get("use_pure", not HAVE_CEXT)
            converter_class = options.get(
                "converter_class",
                DjangoMySQLConverter,
            )
            if not issubclass(converter_class, DjangoMySQLConverter):
                raise ProgrammingError(
                    "Converter class should be a subclass of "
                    "mysql.connector.django.base.DjangoMySQLConverter"
                )
            self.converter = converter_class()
        else:
            self.converter = DjangoMySQLConverter()
            self._use_pure = not HAVE_CEXT

    def __getattr__(self, attr: str) -> bool:
        if attr.startswith("mysql_is"):
            return False
        raise AttributeError

    def get_connection_params(self) -> Dict[str, Any]:
        kwargs = {
            "charset": "utf8",
            "use_unicode": True,
            "buffered": False,
            "consume_results": True,
        }

        settings_dict = self.settings_dict

        if settings_dict["USER"]:
            kwargs["user"] = settings_dict["USER"]
        if settings_dict["NAME"]:
            kwargs["database"] = settings_dict["NAME"]
        if settings_dict["PASSWORD"]:
            kwargs["passwd"] = settings_dict["PASSWORD"]
        if settings_dict["HOST"].startswith("/"):
            kwargs["unix_socket"] = settings_dict["HOST"]
        elif settings_dict["HOST"]:
            kwargs["host"] = settings_dict["HOST"]
        if settings_dict["PORT"]:
            kwargs["port"] = int(settings_dict["PORT"])
        if settings_dict.get("OPTIONS", {}).get("init_command"):
            kwargs["init_command"] = settings_dict["OPTIONS"]["init_command"]

        # Raise exceptions for database warnings if DEBUG is on
        kwargs["raise_on_warnings"] = settings.DEBUG

        kwargs["client_flags"] = [
            # Need potentially affected rows on UPDATE
            mysql.connector.constants.ClientFlag.FOUND_ROWS,
        ]

        try:
            options = settings_dict["OPTIONS"].copy()
            isolation_level = options.pop("isolation_level")
            if isolation_level:
                isolation_level = isolation_level.lower()
                if isolation_level not in self.isolation_levels:
                    valid_levels = ", ".join(
                        f"'{level}'" for level in sorted(self.isolation_levels)
                    )
                    raise ImproperlyConfigured(
                        f"Invalid transaction isolation level '{isolation_level}' "
                        f"specified.\nUse one of {valid_levels}, or None."
                    )
            self.isolation_level = isolation_level
            kwargs.update(options)
        except KeyError:
            # OPTIONS missing is OK
            pass
        return kwargs

    def get_new_connection(
        self, conn_params: Dict[str, Any]
    ) -> Union[PooledMySQLConnection, MySQLConnection, CMySQLConnection]:
        if "converter_class" not in conn_params:
            conn_params["converter_class"] = DjangoMySQLConverter
        cnx = mysql.connector.connect(**conn_params)

        return cnx

    def init_connection_state(self) -> None:
        assignments = []
        if self.features.is_sql_auto_is_null_enabled:  # type: ignore[attr-defined]
            # SQL_AUTO_IS_NULL controls whether an AUTO_INCREMENT column on
            # a recently inserted row will return when the field is tested
            # for NULL. Disabling this brings this aspect of MySQL in line
            # with SQL standards.
            assignments.append("SET SQL_AUTO_IS_NULL = 0")

        if self.isolation_level:
            assignments.append(
                "SET SESSION TRANSACTION ISOLATION LEVEL "
                f"{self.isolation_level.upper()}"
            )

        if assignments:
            with self.cursor() as cursor:
                cursor.execute("; ".join(assignments))

        if "AUTOCOMMIT" in self.settings_dict:
            try:
                self.set_autocommit(self.settings_dict["AUTOCOMMIT"])
            except AttributeError:
                self._set_autocommit(self.settings_dict["AUTOCOMMIT"])

    def create_cursor(self, name: Any = None) -> CursorWrapper:
        cursor = self.connection.cursor()
        return CursorWrapper(cursor)

    def _rollback(self) -> None:
        try:
            BaseDatabaseWrapper._rollback(self)  # type: ignore[attr-defined]
        except NotSupportedError:
            pass

    def _set_autocommit(self, autocommit: bool) -> None:
        with self.wrap_database_errors:
            self.connection.autocommit = autocommit

    def disable_constraint_checking(self) -> bool:
        """
        Disable foreign key checks, primarily for use in adding rows with
        forward references. Always return True to indicate constraint checks
        need to be re-enabled.
        """
        with self.cursor() as cursor:
            cursor.execute("SET foreign_key_checks=0")
        return True

    def enable_constraint_checking(self) -> None:
        """
        Re-enable foreign key checks after they have been disabled.
        """
        # Override needs_rollback in case constraint_checks_disabled is
        # nested inside transaction.atomic.
        self.needs_rollback, needs_rollback = False, self.needs_rollback
        try:
            with self.cursor() as cursor:
                cursor.execute("SET foreign_key_checks=1")
        finally:
            self.needs_rollback = needs_rollback

    def check_constraints(self, table_names: Optional[List[str]] = None) -> None:
        """
        Check each table name in `table_names` for rows with invalid foreign
        key references. This method is intended to be used in conjunction with
        `disable_constraint_checking()` and `enable_constraint_checking()`, to
        determine if rows with invalid references were entered while constraint
        checks were off.
        """
        with self.cursor() as cursor:
            if table_names is None:
                table_names = self.introspection.table_names(cursor)
            for table_name in table_names:
                primary_key_column_name = self.introspection.get_primary_key_column(
                    cursor, table_name
                )
                if not primary_key_column_name:
                    continue
                key_columns = self.introspection.get_key_columns(cursor, table_name)
                for (
                    column_name,
                    referenced_table_name,
                    referenced_column_name,
                ) in key_columns:
                    cursor.execute(
                        f"""
                        SELECT REFERRING.`{primary_key_column_name}`,
                        REFERRING.`{column_name}`
                        FROM `{table_name}` as REFERRING
                        LEFT JOIN `{referenced_table_name}` as REFERRED
                        ON (
                            REFERRING.`{column_name}` =
                            REFERRED.`{referenced_column_name}`
                        )
                        WHERE REFERRING.`{column_name}` IS NOT NULL
                        AND REFERRED.`{referenced_column_name}` IS NULL
                        """
                    )
                    for bad_row in cursor.fetchall():
                        raise IntegrityError(
                            f"The row in table '{table_name}' with primary "
                            f"key '{bad_row[0]}' has an invalid foreign key: "
                            f"{table_name}.{column_name} contains a value "
                            f"'{bad_row[1]}' that does not have a "
                            f"corresponding value in "
                            f"{referenced_table_name}."
                            f"{referenced_column_name}."
                        )

    def is_usable(self) -> bool:
        try:
            self.connection.ping()
        except Error:
            return False
        return True

    @cached_property
    @staticmethod
    def display_name() -> str:
        """Display name."""
        return "MySQL"

    @cached_property
    def data_type_check_constraints(self) -> Dict[str, str]:
        """Mapping of Field objects to their SQL for CHECK constraints."""
        if self.features.supports_column_check_constraints:
            check_constraints = {
                "PositiveBigIntegerField": "`%(column)s` >= 0",
                "PositiveIntegerField": "`%(column)s` >= 0",
                "PositiveSmallIntegerField": "`%(column)s` >= 0",
            }
            return check_constraints
        return {}

    @cached_property
    def mysql_server_data(self) -> Dict[str, Any]:
        """Return MySQL server data."""
        with self.temporary_connection() as cursor:
            # Select some server variables and test if the time zone
            # definitions are installed. CONVERT_TZ returns NULL if 'UTC'
            # timezone isn't loaded into the mysql.time_zone table.
            cursor.execute(
                """
                SELECT VERSION(),
                       @@sql_mode,
                       @@default_storage_engine,
                       @@sql_auto_is_null,
                       @@lower_case_table_names,
                       CONVERT_TZ('2001-01-01 01:00:00', 'UTC', 'UTC') IS NOT NULL
            """
            )
            row = cursor.fetchone()
        return {
            "version": row[0],
            "sql_mode": row[1],
            "default_storage_engine": row[2],
            "sql_auto_is_null": bool(row[3]),
            "lower_case_table_names": bool(row[4]),
            "has_zoneinfo_database": bool(row[5]),
        }

    @cached_property
    def mysql_server_info(self) -> Any:
        """Return MySQL version."""
        with self.temporary_connection() as cursor:
            cursor.execute("SELECT VERSION()")
            return cursor.fetchone()[0]

    @cached_property
    def mysql_version(self) -> Tuple[int, ...]:
        """Return MySQL version."""
        config = self.get_connection_params()
        with mysql.connector.connect(**config) as conn:
            server_version: Tuple[int, ...] = conn.get_server_version()
        return server_version

    @cached_property
    def sql_mode(self) -> Set[str]:
        """Return SQL mode."""
        with self.cursor() as cursor:
            cursor.execute("SELECT @@sql_mode")
            sql_mode = cursor.fetchone()
        return set(sql_mode[0].split(",") if sql_mode else ())

    @property
    def use_pure(self) -> bool:
        """Return True if pure Python version is being used."""
        ans: bool = self._use_pure
        return ans


class DjangoMySQLConverter(MySQLConverter):
    """Custom converter for Django."""

    # pylint: disable=unused-argument

    @staticmethod
    def _time_to_python(value: bytes, dsc: Any = None) -> Optional[time]:
        """Return MySQL TIME data type as datetime.time()

        Returns datetime.time()
        """
        return dateparse.parse_time(value.decode("utf-8"))

    @staticmethod
    def _datetime_to_python(value: bytes, dsc: Any = None) -> Optional[datetime]:
        """Connector/Python always returns naive datetime.datetime

        Connector/Python always returns naive timestamps since MySQL has
        no time zone support.

        - A naive datetime is a datetime that doesn't know its own timezone.

        Django needs a non-naive datetime, but in this method we don't need
        to make a datetime value time zone aware since Django itself at some
        point will make it aware (at least in versions 3.2.16 and 4.1.2) when
        USE_TZ=True. This may change in a future release, we need to keep an
        eye on this behaviour.

        Returns datetime.datetime()
        """
        return MySQLConverter._datetime_to_python(value) if value else None

    # pylint: enable=unused-argument

    def _safestring_to_mysql(self, value: str) -> Union[bytes, HexLiteral]:
        return self._str_to_mysql(value)

    def _safetext_to_mysql(self, value: str) -> Union[bytes, HexLiteral]:
        return self._str_to_mysql(value)

    def _safebytes_to_mysql(self, value: bytes) -> bytes:
        return self._bytes_to_mysql(value)

virt/lib/python3.9/site-packages/mysql/connector/django/base.py
PrinceDisant/LostInNL
# Copyright (c) 2020, 2022, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

"""Database Client."""

import os
import subprocess

from typing import Any, Dict, Iterable, List, Optional, Tuple

from django.db.backends.base.client import BaseDatabaseClient


class DatabaseClient(BaseDatabaseClient):
    """Encapsulate backend-specific methods for opening a client shell."""

    executable_name = "mysql"

    @classmethod
    def settings_to_cmd_args_env(
        cls, settings_dict: Dict[str, Any], parameters: Optional[Iterable[str]] = None
    ) -> Tuple[List[str], Optional[Dict[str, Any]]]:
        args = [cls.executable_name]

        db = settings_dict["OPTIONS"].get("database", settings_dict["NAME"])
        user = settings_dict["OPTIONS"].get("user", settings_dict["USER"])
        passwd = settings_dict["OPTIONS"].get("password", settings_dict["PASSWORD"])
        host = settings_dict["OPTIONS"].get("host", settings_dict["HOST"])
        port = settings_dict["OPTIONS"].get("port", settings_dict["PORT"])
        ssl_ca = settings_dict["OPTIONS"].get("ssl_ca")
        ssl_cert = settings_dict["OPTIONS"].get("ssl_cert")
        ssl_key = settings_dict["OPTIONS"].get("ssl_key")
        defaults_file = settings_dict["OPTIONS"].get("read_default_file")
        charset = settings_dict["OPTIONS"].get("charset")

        # --defaults-file should always be the first option
        if defaults_file:
            args.append(f"--defaults-file={defaults_file}")

        # Load any custom init_commands. We always force SQL_MODE to TRADITIONAL
        init_command = settings_dict["OPTIONS"].get("init_command", "")
        args.append(f"--init-command=SET @@session.SQL_MODE=TRADITIONAL;{init_command}")

        if user:
            args.append(f"--user={user}")
        if passwd:
            args.append(f"--password={passwd}")

        if host:
            if "/" in host:
                args.append(f"--socket={host}")
            else:
                args.append(f"--host={host}")

        if port:
            args.append(f"--port={port}")

        if db:
            args.append(f"--database={db}")

        if ssl_ca:
            args.append(f"--ssl-ca={ssl_ca}")
        if ssl_cert:
            args.append(f"--ssl-cert={ssl_cert}")
        if ssl_key:
            args.append(f"--ssl-key={ssl_key}")

        if charset:
            args.append(f"--default-character-set={charset}")

        if parameters:
            args.extend(parameters)

        return args, None

    def runshell(self, parameters: Optional[Iterable[str]] = None) -> None:
        args, env = self.settings_to_cmd_args_env(
            self.connection.settings_dict, parameters
        )
        env = {**os.environ, **env} if env else None
        subprocess.run(args, env=env, check=True)

virt/lib/python3.9/site-packages/mysql/connector/django/client.py
PrinceDisant/LostInNL
# Copyright (c) 2020, 2022, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

"""SQL Compiler classes."""

from django.db.backends.mysql.compiler import (
    SQLAggregateCompiler,
    SQLCompiler,
    SQLDeleteCompiler,
    SQLInsertCompiler,
    SQLUpdateCompiler,
)

__all__ = [
    "SQLAggregateCompiler",
    "SQLCompiler",
    "SQLDeleteCompiler",
    "SQLInsertCompiler",
    "SQLUpdateCompiler",
]

virt/lib/python3.9/site-packages/mysql/connector/django/compiler.py
PrinceDisant/LostInNL
# Copyright (c) 2020, 2022, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

"""Backend specific database creation."""

from django.db.backends.mysql.creation import DatabaseCreation

__all__ = ["DatabaseCreation"]

virt/lib/python3.9/site-packages/mysql/connector/django/creation.py
PrinceDisant/LostInNL
# Copyright (c) 2020, 2022, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

"""Database Features."""

from typing import Any, List

from django.db.backends.mysql.features import DatabaseFeatures as MySQLDatabaseFeatures
from django.utils.functional import cached_property


class DatabaseFeatures(MySQLDatabaseFeatures):
    """Database Features Specification class."""

    empty_fetchmany_value: List[Any] = []

    @cached_property
    def can_introspect_check_constraints(self) -> bool:  # type: ignore[override]
        """Check if backend support introspection CHECK of constraints."""
        return self.connection.mysql_version >= (8, 0, 16)

    @cached_property
    def supports_microsecond_precision(self) -> bool:
        """Check if backend support microsecond precision."""
        return self.connection.mysql_version >= (5, 6, 3)

virt/lib/python3.9/site-packages/mysql/connector/django/features.py
PrinceDisant/LostInNL
# Copyright (c) 2020, 2022, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

# mypy: disable-error-code="override,attr-defined,call-arg"

"""Database Introspection."""

from collections import namedtuple
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Set, Tuple

import sqlparse

from django import VERSION as DJANGO_VERSION
from django.db.backends.base.introspection import (
    BaseDatabaseIntrospection,
    FieldInfo as BaseFieldInfo,
    TableInfo,
)
from django.db.models import Index
from django.utils.datastructures import OrderedSet

from mysql.connector.constants import FieldType

# from .base import CursorWrapper produces a circular import error,
# avoiding importing CursorWrapper explicitly, using a documented
# trick; write the imports inside if TYPE_CHECKING: so that they
# are not executed at runtime.
# Ref: https://buildmedia.readthedocs.org/media/pdf/mypy/stable/mypy.pdf [page 42]
if TYPE_CHECKING:
    # CursorWraper is used exclusively for type hinting
    from mysql.connector.django.base import CursorWrapper

# Based on my investigation, named tuples to
# comply with mypy need to define a static list or tuple
# for field_names (second argument). In this case, the field
# names are created dynamically for FieldInfo which triggers
# a mypy error. The solution is not straightforward since
# FieldInfo attributes are Django version dependent. Code
# refactory is needed to fix this issue.
FieldInfo = namedtuple(  # type: ignore[misc]
    "FieldInfo",
    BaseFieldInfo._fields + ("extra", "is_unsigned", "has_json_constraint"),
)
if DJANGO_VERSION < (3, 2, 0):
    InfoLine = namedtuple(
        "InfoLine",
        "col_name data_type max_len num_prec num_scale extra column_default "
        "is_unsigned",
    )
else:
    InfoLine = namedtuple(  # type: ignore[no-redef]
        "InfoLine",
        "col_name data_type max_len num_prec num_scale extra column_default "
        "collation is_unsigned",
    )


class DatabaseIntrospection(BaseDatabaseIntrospection):
    """Encapsulate backend-specific introspection utilities."""

    data_types_reverse = {
        FieldType.BLOB: "TextField",
        FieldType.DECIMAL: "DecimalField",
        FieldType.NEWDECIMAL: "DecimalField",
        FieldType.DATE: "DateField",
        FieldType.DATETIME: "DateTimeField",
        FieldType.DOUBLE: "FloatField",
        FieldType.FLOAT: "FloatField",
        FieldType.INT24: "IntegerField",
        FieldType.LONG: "IntegerField",
        FieldType.LONGLONG: "BigIntegerField",
        FieldType.SHORT: "SmallIntegerField",
        FieldType.STRING: "CharField",
        FieldType.TIME: "TimeField",
        FieldType.TIMESTAMP: "DateTimeField",
        FieldType.TINY: "IntegerField",
        FieldType.TINY_BLOB: "TextField",
        FieldType.MEDIUM_BLOB: "TextField",
        FieldType.LONG_BLOB: "TextField",
        FieldType.VAR_STRING: "CharField",
    }

    def get_field_type(self, data_type: str, description: FieldInfo) -> str:
        field_type = super().get_field_type(data_type, description)  # type: ignore[arg-type]
        if "auto_increment" in description.extra:
            if field_type == "IntegerField":
                return "AutoField"
            if field_type == "BigIntegerField":
                return "BigAutoField"
            if field_type == "SmallIntegerField":
                return "SmallAutoField"
        if description.is_unsigned:
            if field_type == "BigIntegerField":
                return "PositiveBigIntegerField"
            if field_type == "IntegerField":
                return "PositiveIntegerField"
            if field_type == "SmallIntegerField":
                return "PositiveSmallIntegerField"
        # JSON data type is an alias for LONGTEXT in MariaDB, use check
        # constraints clauses to introspect JSONField.
        if description.has_json_constraint:
            return "JSONField"
        return field_type

    def get_table_list(self, cursor: "CursorWrapper") -> List[TableInfo]:
        """Return a list of table and view names in the current database."""
        cursor.execute("SHOW FULL TABLES")
        return [
            TableInfo(row[0], {"BASE TABLE": "t", "VIEW": "v"}.get(row[1]))
            for row in cursor.fetchall()
        ]

    def get_table_description(
        self, cursor: "CursorWrapper", table_name: str
    ) -> List[FieldInfo]:
        """
        Return a description of the table with the DB-API cursor.description
        interface."
        """
        json_constraints: Dict[Any, Any] = {}
        # A default collation for the given table.
        cursor.execute(
            """
            SELECT  table_collation
            FROM    information_schema.tables
            WHERE   table_schema = DATABASE()
            AND     table_name = %s
        """,
            [table_name],
        )
        row = cursor.fetchone()
        default_column_collation = row[0] if row else ""
        # information_schema database gives more accurate results for some figures:
        # - varchar length returned by cursor.description is an internal length,
        #   not visible length (#5725)
        # - precision and scale (for decimal fields) (#5014)
        # - auto_increment is not available in cursor.description
        if DJANGO_VERSION < (3, 2, 0):
            cursor.execute(
                """
                SELECT
                    column_name, data_type, character_maximum_length,
                    numeric_precision, numeric_scale, extra, column_default,
                    CASE
                        WHEN column_type LIKE '%% unsigned' THEN 1
                        ELSE 0
                    END AS is_unsigned
                FROM information_schema.columns
                WHERE table_name = %s AND table_schema = DATABASE()
            """,
                [table_name],
            )
        else:
            cursor.execute(
                """
                SELECT
                    column_name, data_type, character_maximum_length,
                    numeric_precision, numeric_scale, extra, column_default,
                    CASE
                        WHEN collation_name = %s THEN NULL
                        ELSE collation_name
                    END AS collation_name,
                    CASE
                        WHEN column_type LIKE '%% unsigned' THEN 1
                        ELSE 0
                    END AS is_unsigned
                FROM information_schema.columns
                WHERE table_name = %s AND table_schema = DATABASE()
            """,
                [default_column_collation, table_name],
            )
        field_info = {line[0]: InfoLine(*line) for line in cursor.fetchall()}

        cursor.execute(
            f"SELECT * FROM {self.connection.ops.quote_name(table_name)} LIMIT 1"
        )

        def to_int(i: Any) -> Optional[int]:
            return int(i) if i is not None else i

        fields = []
        for line in cursor.description:
            info = field_info[line[0]]
            if DJANGO_VERSION < (3, 2, 0):
                fields.append(
                    FieldInfo(
                        *line[:3],
                        to_int(info.max_len) or line[3],
                        to_int(info.num_prec) or line[4],
                        to_int(info.num_scale) or line[5],
                        line[6],
                        info.column_default,
                        info.extra,
                        info.is_unsigned,
                        line[0] in json_constraints,
                    )
                )
            else:
                fields.append(
                    FieldInfo(
                        *line[:3],
                        to_int(info.max_len) or line[3],
                        to_int(info.num_prec) or line[4],
                        to_int(info.num_scale) or line[5],
                        line[6],
                        info.column_default,
                        info.collation,
                        info.extra,
                        info.is_unsigned,
                        line[0] in json_constraints,
                    )
                )
        return fields

    def get_indexes(
        self, cursor: "CursorWrapper", table_name: str
    ) -> Dict[int, Dict[str, bool]]:
        """Return indexes from table."""
        cursor.execute(f"SHOW INDEX FROM {self.connection.ops.quote_name(table_name)}")
        # Do a two-pass search for indexes: on first pass check which indexes
        # are multicolumn, on second pass check which single-column indexes
        # are present.
        rows = list(cursor.fetchall())
        multicol_indexes = set()
        for row in rows:
            if row[3] > 1:
                multicol_indexes.add(row[2])
        indexes: Dict[int, Dict[str, bool]] = {}
        for row in rows:
            if row[2] in multicol_indexes:
                continue
            if row[4] not in indexes:
                indexes[row[4]] = {"primary_key": False, "unique": False}
            # It's possible to have the unique and PK constraints in
            # separate indexes.
            if row[2] == "PRIMARY":
                indexes[row[4]]["primary_key"] = True
            if not row[1]:
                indexes[row[4]]["unique"] = True
        return indexes

    def get_primary_key_column(
        self, cursor: "CursorWrapper", table_name: str
    ) -> Optional[int]:
        """
        Returns the name of the primary key column for the given table
        """
        for column in self.get_indexes(cursor, table_name).items():
            if column[1]["primary_key"]:
                return column[0]
        return None

    def get_sequences(
        self, cursor: "CursorWrapper", table_name: str, table_fields: Any = ()
    ) -> List[Dict[str, str]]:
        for field_info in self.get_table_description(cursor, table_name):
            if "auto_increment" in field_info.extra:
                # MySQL allows only one auto-increment column per table.
                return [{"table": table_name, "column": field_info.name}]
        return []

    def get_relations(
        self, cursor: "CursorWrapper", table_name: str
    ) -> Dict[str, Tuple[str, str]]:
        """
        Return a dictionary of {field_name: (field_name_other_table, other_table)}
        representing all relationships to the given table.
        """
        constraints = self.get_key_columns(cursor, table_name)
        relations = {}
        for my_fieldname, other_table, other_field in constraints:
            relations[my_fieldname] = (other_field, other_table)
        return relations

    def get_key_columns(
        self, cursor: "CursorWrapper", table_name: str
    ) -> List[Tuple[str, str, str]]:
        """
        Return a list of (column_name, referenced_table_name, referenced_column_name)
        for all key columns in the given table.
        """
        key_columns: List[Any] = []
        cursor.execute(
            """
            SELECT column_name, referenced_table_name, referenced_column_name
            FROM information_schema.key_column_usage
            WHERE table_name = %s
                AND table_schema = DATABASE()
                AND referenced_table_name IS NOT NULL
                AND referenced_column_name IS NOT NULL""",
            [table_name],
        )
        key_columns.extend(cursor.fetchall())
        return key_columns

    def get_storage_engine(self, cursor: "CursorWrapper", table_name: str) -> str:
        """
        Retrieve the storage engine for a given table. Return the default
        storage engine if the table doesn't exist.
        """
        cursor.execute(
            "SELECT engine FROM information_schema.tables WHERE table_name = %s",
            [table_name],
        )
        result = cursor.fetchone()
        # pylint: disable=protected-access
        if not result:
            return self.connection.features._mysql_storage_engine
        # pylint: enable=protected-access
        return result[0]

    def _parse_constraint_columns(
        self, check_clause: Any, columns: Set[str]
    ) -> OrderedSet:
        check_columns: OrderedSet = OrderedSet()
        statement = sqlparse.parse(check_clause)[0]
        tokens = (token for token in statement.flatten() if not token.is_whitespace)
        for token in tokens:
            if (
                token.ttype == sqlparse.tokens.Name
                and self.connection.ops.quote_name(token.value) == token.value
                and token.value[1:-1] in columns
            ):
                check_columns.add(token.value[1:-1])
        return check_columns

    def get_constraints(
        self, cursor: "CursorWrapper", table_name: str
    ) -> Dict[str, Any]:
        """
        Retrieve any constraints or keys (unique, pk, fk, check, index) across
        one or more columns.
        """
        constraints: Dict[str, Any] = {}
        # Get the actual constraint names and columns
        name_query = """
            SELECT kc.`constraint_name`, kc.`column_name`,
                kc.`referenced_table_name`, kc.`referenced_column_name`
            FROM information_schema.key_column_usage AS kc
            WHERE
                kc.table_schema = DATABASE() AND
                kc.table_name = %s
            ORDER BY kc.`ordinal_position`
        """
        cursor.execute(name_query, [table_name])
        for constraint, column, ref_table, ref_column in cursor.fetchall():
            if constraint not in constraints:
                constraints[constraint] = {
                    "columns": OrderedSet(),
                    "primary_key": False,
                    "unique": False,
                    "index": False,
                    "check": False,
                    "foreign_key": (ref_table, ref_column) if ref_column else None,
                }
                if self.connection.features.supports_index_column_ordering:
                    constraints[constraint]["orders"] = []
            constraints[constraint]["columns"].add(column)
        # Now get the constraint types
        type_query = """
            SELECT c.constraint_name, c.constraint_type
            FROM information_schema.table_constraints AS c
            WHERE
                c.table_schema = DATABASE() AND
                c.table_name = %s
        """
        cursor.execute(type_query, [table_name])
        for constraint, kind in cursor.fetchall():
            if kind.lower() == "primary key":
                constraints[constraint]["primary_key"] = True
                constraints[constraint]["unique"] = True
            elif kind.lower() == "unique":
                constraints[constraint]["unique"] = True
        # Add check constraints.
        if self.connection.features.can_introspect_check_constraints:
            unnamed_constraints_index = 0
            columns = {
                info.name for info in self.get_table_description(cursor, table_name)
            }
            type_query = """
                SELECT cc.constraint_name, cc.check_clause
                FROM
                    information_schema.check_constraints AS cc,
                    information_schema.table_constraints AS tc
                WHERE
                    cc.constraint_schema = DATABASE() AND
                    tc.table_schema = cc.constraint_schema AND
                    cc.constraint_name = tc.constraint_name AND
                    tc.constraint_type = 'CHECK' AND
                    tc.table_name = %s
            """
            cursor.execute(type_query, [table_name])
            for constraint, check_clause in cursor.fetchall():
                constraint_columns = self._parse_constraint_columns(
                    check_clause, columns
                )
                # Ensure uniqueness of unnamed constraints. Unnamed unique
                # and check columns constraints have the same name as
                # a column.
                if set(constraint_columns) == {constraint}:
                    unnamed_constraints_index += 1
                    constraint = f"__unnamed_constraint_{unnamed_constraints_index}__"
                constraints[constraint] = {
                    "columns": constraint_columns,
                    "primary_key": False,
                    "unique": False,
                    "index": False,
                    "check": True,
                    "foreign_key": None,
                }
        # Now add in the indexes
        cursor.execute(f"SHOW INDEX FROM {self.connection.ops.quote_name(table_name)}")
        for _, _, index, _, column, order, type_ in [
            x[:6] + (x[10],) for x in cursor.fetchall()
        ]:
            if index not in constraints:
                constraints[index] = {
                    "columns": OrderedSet(),
                    "primary_key": False,
                    "unique": False,
                    "check": False,
                    "foreign_key": None,
                }
                if self.connection.features.supports_index_column_ordering:
                    constraints[index]["orders"] = []
            constraints[index]["index"] = True
            constraints[index]["type"] = (
                Index.suffix if type_ == "BTREE" else type_.lower()
            )
            constraints[index]["columns"].add(column)
            if self.connection.features.supports_index_column_ordering:
                constraints[index]["orders"].append("DESC" if order == "D" else "ASC")
        # Convert the sorted sets to lists
        for constraint in constraints.values():
            constraint["columns"] = list(constraint["columns"])
        return constraints

virt/lib/python3.9/site-packages/mysql/connector/django/introspection 2.py
PrinceDisant/LostInNL
# Copyright (c) 2020, 2022, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

# mypy: disable-error-code="override,attr-defined,call-arg"

"""Database Introspection."""

from collections import namedtuple
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Set, Tuple

import sqlparse

from django import VERSION as DJANGO_VERSION
from django.db.backends.base.introspection import (
    BaseDatabaseIntrospection,
    FieldInfo as BaseFieldInfo,
    TableInfo,
)
from django.db.models import Index
from django.utils.datastructures import OrderedSet

from mysql.connector.constants import FieldType

# from .base import CursorWrapper produces a circular import error,
# avoiding importing CursorWrapper explicitly, using a documented
# trick; write the imports inside if TYPE_CHECKING: so that they
# are not executed at runtime.
# Ref: https://buildmedia.readthedocs.org/media/pdf/mypy/stable/mypy.pdf [page 42]
if TYPE_CHECKING:
    # CursorWraper is used exclusively for type hinting
    from mysql.connector.django.base import CursorWrapper

# Based on my investigation, named tuples to
# comply with mypy need to define a static list or tuple
# for field_names (second argument). In this case, the field
# names are created dynamically for FieldInfo which triggers
# a mypy error. The solution is not straightforward since
# FieldInfo attributes are Django version dependent. Code
# refactory is needed to fix this issue.
FieldInfo = namedtuple(  # type: ignore[misc]
    "FieldInfo",
    BaseFieldInfo._fields + ("extra", "is_unsigned", "has_json_constraint"),
)
if DJANGO_VERSION < (3, 2, 0):
    InfoLine = namedtuple(
        "InfoLine",
        "col_name data_type max_len num_prec num_scale extra column_default "
        "is_unsigned",
    )
else:
    InfoLine = namedtuple(  # type: ignore[no-redef]
        "InfoLine",
        "col_name data_type max_len num_prec num_scale extra column_default "
        "collation is_unsigned",
    )


class DatabaseIntrospection(BaseDatabaseIntrospection):
    """Encapsulate backend-specific introspection utilities."""

    data_types_reverse = {
        FieldType.BLOB: "TextField",
        FieldType.DECIMAL: "DecimalField",
        FieldType.NEWDECIMAL: "DecimalField",
        FieldType.DATE: "DateField",
        FieldType.DATETIME: "DateTimeField",
        FieldType.DOUBLE: "FloatField",
        FieldType.FLOAT: "FloatField",
        FieldType.INT24: "IntegerField",
        FieldType.LONG: "IntegerField",
        FieldType.LONGLONG: "BigIntegerField",
        FieldType.SHORT: "SmallIntegerField",
        FieldType.STRING: "CharField",
        FieldType.TIME: "TimeField",
        FieldType.TIMESTAMP: "DateTimeField",
        FieldType.TINY: "IntegerField",
        FieldType.TINY_BLOB: "TextField",
        FieldType.MEDIUM_BLOB: "TextField",
        FieldType.LONG_BLOB: "TextField",
        FieldType.VAR_STRING: "CharField",
    }

    def get_field_type(self, data_type: str, description: FieldInfo) -> str:
        field_type = super().get_field_type(data_type, description)  # type: ignore[arg-type]
        if "auto_increment" in description.extra:
            if field_type == "IntegerField":
                return "AutoField"
            if field_type == "BigIntegerField":
                return "BigAutoField"
            if field_type == "SmallIntegerField":
                return "SmallAutoField"
        if description.is_unsigned:
            if field_type == "BigIntegerField":
                return "PositiveBigIntegerField"
            if field_type == "IntegerField":
                return "PositiveIntegerField"
            if field_type == "SmallIntegerField":
                return "PositiveSmallIntegerField"
        # JSON data type is an alias for LONGTEXT in MariaDB, use check
        # constraints clauses to introspect JSONField.
        if description.has_json_constraint:
            return "JSONField"
        return field_type

    def get_table_list(self, cursor: "CursorWrapper") -> List[TableInfo]:
        """Return a list of table and view names in the current database."""
        cursor.execute("SHOW FULL TABLES")
        return [
            TableInfo(row[0], {"BASE TABLE": "t", "VIEW": "v"}.get(row[1]))
            for row in cursor.fetchall()
        ]

    def get_table_description(
        self, cursor: "CursorWrapper", table_name: str
    ) -> List[FieldInfo]:
        """
        Return a description of the table with the DB-API cursor.description
        interface."
        """
        json_constraints: Dict[Any, Any] = {}
        # A default collation for the given table.
        cursor.execute(
            """
            SELECT  table_collation
            FROM    information_schema.tables
            WHERE   table_schema = DATABASE()
            AND     table_name = %s
        """,
            [table_name],
        )
        row = cursor.fetchone()
        default_column_collation = row[0] if row else ""
        # information_schema database gives more accurate results for some figures:
        # - varchar length returned by cursor.description is an internal length,
        #   not visible length (#5725)
        # - precision and scale (for decimal fields) (#5014)
        # - auto_increment is not available in cursor.description
        if DJANGO_VERSION < (3, 2, 0):
            cursor.execute(
                """
                SELECT
                    column_name, data_type, character_maximum_length,
                    numeric_precision, numeric_scale, extra, column_default,
                    CASE
                        WHEN column_type LIKE '%% unsigned' THEN 1
                        ELSE 0
                    END AS is_unsigned
                FROM information_schema.columns
                WHERE table_name = %s AND table_schema = DATABASE()
            """,
                [table_name],
            )
        else:
            cursor.execute(
                """
                SELECT
                    column_name, data_type, character_maximum_length,
                    numeric_precision, numeric_scale, extra, column_default,
                    CASE
                        WHEN collation_name = %s THEN NULL
                        ELSE collation_name
                    END AS collation_name,
                    CASE
                        WHEN column_type LIKE '%% unsigned' THEN 1
                        ELSE 0
                    END AS is_unsigned
                FROM information_schema.columns
                WHERE table_name = %s AND table_schema = DATABASE()
            """,
                [default_column_collation, table_name],
            )
        field_info = {line[0]: InfoLine(*line) for line in cursor.fetchall()}

        cursor.execute(
            f"SELECT * FROM {self.connection.ops.quote_name(table_name)} LIMIT 1"
        )

        def to_int(i: Any) -> Optional[int]:
            return int(i) if i is not None else i

        fields = []
        for line in cursor.description:
            info = field_info[line[0]]
            if DJANGO_VERSION < (3, 2, 0):
                fields.append(
                    FieldInfo(
                        *line[:3],
                        to_int(info.max_len) or line[3],
                        to_int(info.num_prec) or line[4],
                        to_int(info.num_scale) or line[5],
                        line[6],
                        info.column_default,
                        info.extra,
                        info.is_unsigned,
                        line[0] in json_constraints,
                    )
                )
            else:
                fields.append(
                    FieldInfo(
                        *line[:3],
                        to_int(info.max_len) or line[3],
                        to_int(info.num_prec) or line[4],
                        to_int(info.num_scale) or line[5],
                        line[6],
                        info.column_default,
                        info.collation,
                        info.extra,
                        info.is_unsigned,
                        line[0] in json_constraints,
                    )
                )
        return fields

    def get_indexes(
        self, cursor: "CursorWrapper", table_name: str
    ) -> Dict[int, Dict[str, bool]]:
        """Return indexes from table."""
        cursor.execute(f"SHOW INDEX FROM {self.connection.ops.quote_name(table_name)}")
        # Do a two-pass search for indexes: on first pass check which indexes
        # are multicolumn, on second pass check which single-column indexes
        # are present.
        rows = list(cursor.fetchall())
        multicol_indexes = set()
        for row in rows:
            if row[3] > 1:
                multicol_indexes.add(row[2])
        indexes: Dict[int, Dict[str, bool]] = {}
        for row in rows:
            if row[2] in multicol_indexes:
                continue
            if row[4] not in indexes:
                indexes[row[4]] = {"primary_key": False, "unique": False}
            # It's possible to have the unique and PK constraints in
            # separate indexes.
            if row[2] == "PRIMARY":
                indexes[row[4]]["primary_key"] = True
            if not row[1]:
                indexes[row[4]]["unique"] = True
        return indexes

    def get_primary_key_column(
        self, cursor: "CursorWrapper", table_name: str
    ) -> Optional[int]:
        """
        Returns the name of the primary key column for the given table
        """
        for column in self.get_indexes(cursor, table_name).items():
            if column[1]["primary_key"]:
                return column[0]
        return None

    def get_sequences(
        self, cursor: "CursorWrapper", table_name: str, table_fields: Any = ()
    ) -> List[Dict[str, str]]:
        for field_info in self.get_table_description(cursor, table_name):
            if "auto_increment" in field_info.extra:
                # MySQL allows only one auto-increment column per table.
                return [{"table": table_name, "column": field_info.name}]
        return []

    def get_relations(
        self, cursor: "CursorWrapper", table_name: str
    ) -> Dict[str, Tuple[str, str]]:
        """
        Return a dictionary of {field_name: (field_name_other_table, other_table)}
        representing all relationships to the given table.
        """
        constraints = self.get_key_columns(cursor, table_name)
        relations = {}
        for my_fieldname, other_table, other_field in constraints:
            relations[my_fieldname] = (other_field, other_table)
        return relations

    def get_key_columns(
        self, cursor: "CursorWrapper", table_name: str
    ) -> List[Tuple[str, str, str]]:
        """
        Return a list of (column_name, referenced_table_name, referenced_column_name)
        for all key columns in the given table.
        """
        key_columns: List[Any] = []
        cursor.execute(
            """
            SELECT column_name, referenced_table_name, referenced_column_name
            FROM information_schema.key_column_usage
            WHERE table_name = %s
                AND table_schema = DATABASE()
                AND referenced_table_name IS NOT NULL
                AND referenced_column_name IS NOT NULL""",
            [table_name],
        )
        key_columns.extend(cursor.fetchall())
        return key_columns

    def get_storage_engine(self, cursor: "CursorWrapper", table_name: str) -> str:
        """
        Retrieve the storage engine for a given table. Return the default
        storage engine if the table doesn't exist.
        """
        cursor.execute(
            "SELECT engine FROM information_schema.tables WHERE table_name = %s",
            [table_name],
        )
        result = cursor.fetchone()
        # pylint: disable=protected-access
        if not result:
            return self.connection.features._mysql_storage_engine
        # pylint: enable=protected-access
        return result[0]

    def _parse_constraint_columns(
        self, check_clause: Any, columns: Set[str]
    ) -> OrderedSet:
        check_columns: OrderedSet = OrderedSet()
        statement = sqlparse.parse(check_clause)[0]
        tokens = (token for token in statement.flatten() if not token.is_whitespace)
        for token in tokens:
            if (
                token.ttype == sqlparse.tokens.Name
                and self.connection.ops.quote_name(token.value) == token.value
                and token.value[1:-1] in columns
            ):
                check_columns.add(token.value[1:-1])
        return check_columns

    def get_constraints(
        self, cursor: "CursorWrapper", table_name: str
    ) -> Dict[str, Any]:
        """
        Retrieve any constraints or keys (unique, pk, fk, check, index) across
        one or more columns.
        """
        constraints: Dict[str, Any] = {}
        # Get the actual constraint names and columns
        name_query = """
            SELECT kc.`constraint_name`, kc.`column_name`,
                kc.`referenced_table_name`, kc.`referenced_column_name`
            FROM information_schema.key_column_usage AS kc
            WHERE
                kc.table_schema = DATABASE() AND
                kc.table_name = %s
            ORDER BY kc.`ordinal_position`
        """
        cursor.execute(name_query, [table_name])
        for constraint, column, ref_table, ref_column in cursor.fetchall():
            if constraint not in constraints:
                constraints[constraint] = {
                    "columns": OrderedSet(),
                    "primary_key": False,
                    "unique": False,
                    "index": False,
                    "check": False,
                    "foreign_key": (ref_table, ref_column) if ref_column else None,
                }
                if self.connection.features.supports_index_column_ordering:
                    constraints[constraint]["orders"] = []
            constraints[constraint]["columns"].add(column)
        # Now get the constraint types
        type_query = """
            SELECT c.constraint_name, c.constraint_type
            FROM information_schema.table_constraints AS c
            WHERE
                c.table_schema = DATABASE() AND
                c.table_name = %s
        """
        cursor.execute(type_query, [table_name])
        for constraint, kind in cursor.fetchall():
            if kind.lower() == "primary key":
                constraints[constraint]["primary_key"] = True
                constraints[constraint]["unique"] = True
            elif kind.lower() == "unique":
                constraints[constraint]["unique"] = True
        # Add check constraints.
        if self.connection.features.can_introspect_check_constraints:
            unnamed_constraints_index = 0
            columns = {
                info.name for info in self.get_table_description(cursor, table_name)
            }
            type_query = """
                SELECT cc.constraint_name, cc.check_clause
                FROM
                    information_schema.check_constraints AS cc,
                    information_schema.table_constraints AS tc
                WHERE
                    cc.constraint_schema = DATABASE() AND
                    tc.table_schema = cc.constraint_schema AND
                    cc.constraint_name = tc.constraint_name AND
                    tc.constraint_type = 'CHECK' AND
                    tc.table_name = %s
            """
            cursor.execute(type_query, [table_name])
            for constraint, check_clause in cursor.fetchall():
                constraint_columns = self._parse_constraint_columns(
                    check_clause, columns
                )
                # Ensure uniqueness of unnamed constraints. Unnamed unique
                # and check columns constraints have the same name as
                # a column.
                if set(constraint_columns) == {constraint}:
                    unnamed_constraints_index += 1
                    constraint = f"__unnamed_constraint_{unnamed_constraints_index}__"
                constraints[constraint] = {
                    "columns": constraint_columns,
                    "primary_key": False,
                    "unique": False,
                    "index": False,
                    "check": True,
                    "foreign_key": None,
                }
        # Now add in the indexes
        cursor.execute(f"SHOW INDEX FROM {self.connection.ops.quote_name(table_name)}")
        for _, _, index, _, column, order, type_ in [
            x[:6] + (x[10],) for x in cursor.fetchall()
        ]:
            if index not in constraints:
                constraints[index] = {
                    "columns": OrderedSet(),
                    "primary_key": False,
                    "unique": False,
                    "check": False,
                    "foreign_key": None,
                }
                if self.connection.features.supports_index_column_ordering:
                    constraints[index]["orders"] = []
            constraints[index]["index"] = True
            constraints[index]["type"] = (
                Index.suffix if type_ == "BTREE" else type_.lower()
            )
            constraints[index]["columns"].add(column)
            if self.connection.features.supports_index_column_ordering:
                constraints[index]["orders"].append("DESC" if order == "D" else "ASC")
        # Convert the sorted sets to lists
        for constraint in constraints.values():
            constraint["columns"] = list(constraint["columns"])
        return constraints

virt/lib/python3.9/site-packages/mysql/connector/django/introspection 3.py
PrinceDisant/LostInNL
# Copyright (c) 2020, 2022, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

# mypy: disable-error-code="override,attr-defined,call-arg"

"""Database Introspection."""

from collections import namedtuple
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Set, Tuple

import sqlparse

from django import VERSION as DJANGO_VERSION
from django.db.backends.base.introspection import (
    BaseDatabaseIntrospection,
    FieldInfo as BaseFieldInfo,
    TableInfo,
)
from django.db.models import Index
from django.utils.datastructures import OrderedSet

from mysql.connector.constants import FieldType

# from .base import CursorWrapper produces a circular import error,
# avoiding importing CursorWrapper explicitly, using a documented
# trick; write the imports inside if TYPE_CHECKING: so that they
# are not executed at runtime.
# Ref: https://buildmedia.readthedocs.org/media/pdf/mypy/stable/mypy.pdf [page 42]
if TYPE_CHECKING:
    # CursorWraper is used exclusively for type hinting
    from mysql.connector.django.base import CursorWrapper

# Based on my investigation, named tuples to
# comply with mypy need to define a static list or tuple
# for field_names (second argument). In this case, the field
# names are created dynamically for FieldInfo which triggers
# a mypy error. The solution is not straightforward since
# FieldInfo attributes are Django version dependent. Code
# refactory is needed to fix this issue.
FieldInfo = namedtuple(  # type: ignore[misc]
    "FieldInfo",
    BaseFieldInfo._fields + ("extra", "is_unsigned", "has_json_constraint"),
)
if DJANGO_VERSION < (3, 2, 0):
    InfoLine = namedtuple(
        "InfoLine",
        "col_name data_type max_len num_prec num_scale extra column_default "
        "is_unsigned",
    )
else:
    InfoLine = namedtuple(  # type: ignore[no-redef]
        "InfoLine",
        "col_name data_type max_len num_prec num_scale extra column_default "
        "collation is_unsigned",
    )


class DatabaseIntrospection(BaseDatabaseIntrospection):
    """Encapsulate backend-specific introspection utilities."""

    data_types_reverse = {
        FieldType.BLOB: "TextField",
        FieldType.DECIMAL: "DecimalField",
        FieldType.NEWDECIMAL: "DecimalField",
        FieldType.DATE: "DateField",
        FieldType.DATETIME: "DateTimeField",
        FieldType.DOUBLE: "FloatField",
        FieldType.FLOAT: "FloatField",
        FieldType.INT24: "IntegerField",
        FieldType.LONG: "IntegerField",
        FieldType.LONGLONG: "BigIntegerField",
        FieldType.SHORT: "SmallIntegerField",
        FieldType.STRING: "CharField",
        FieldType.TIME: "TimeField",
        FieldType.TIMESTAMP: "DateTimeField",
        FieldType.TINY: "IntegerField",
        FieldType.TINY_BLOB: "TextField",
        FieldType.MEDIUM_BLOB: "TextField",
        FieldType.LONG_BLOB: "TextField",
        FieldType.VAR_STRING: "CharField",
    }

    def get_field_type(self, data_type: str, description: FieldInfo) -> str:
        field_type = super().get_field_type(data_type, description)  # type: ignore[arg-type]
        if "auto_increment" in description.extra:
            if field_type == "IntegerField":
                return "AutoField"
            if field_type == "BigIntegerField":
                return "BigAutoField"
            if field_type == "SmallIntegerField":
                return "SmallAutoField"
        if description.is_unsigned:
            if field_type == "BigIntegerField":
                return "PositiveBigIntegerField"
            if field_type == "IntegerField":
                return "PositiveIntegerField"
            if field_type == "SmallIntegerField":
                return "PositiveSmallIntegerField"
        # JSON data type is an alias for LONGTEXT in MariaDB, use check
        # constraints clauses to introspect JSONField.
        if description.has_json_constraint:
            return "JSONField"
        return field_type

    def get_table_list(self, cursor: "CursorWrapper") -> List[TableInfo]:
        """Return a list of table and view names in the current database."""
        cursor.execute("SHOW FULL TABLES")
        return [
            TableInfo(row[0], {"BASE TABLE": "t", "VIEW": "v"}.get(row[1]))
            for row in cursor.fetchall()
        ]

    def get_table_description(
        self, cursor: "CursorWrapper", table_name: str
    ) -> List[FieldInfo]:
        """
        Return a description of the table with the DB-API cursor.description
        interface."
        """
        json_constraints: Dict[Any, Any] = {}
        # A default collation for the given table.
        cursor.execute(
            """
            SELECT  table_collation
            FROM    information_schema.tables
            WHERE   table_schema = DATABASE()
            AND     table_name = %s
        """,
            [table_name],
        )
        row = cursor.fetchone()
        default_column_collation = row[0] if row else ""
        # information_schema database gives more accurate results for some figures:
        # - varchar length returned by cursor.description is an internal length,
        #   not visible length (#5725)
        # - precision and scale (for decimal fields) (#5014)
        # - auto_increment is not available in cursor.description
        if DJANGO_VERSION < (3, 2, 0):
            cursor.execute(
                """
                SELECT
                    column_name, data_type, character_maximum_length,
                    numeric_precision, numeric_scale, extra, column_default,
                    CASE
                        WHEN column_type LIKE '%% unsigned' THEN 1
                        ELSE 0
                    END AS is_unsigned
                FROM information_schema.columns
                WHERE table_name = %s AND table_schema = DATABASE()
            """,
                [table_name],
            )
        else:
            cursor.execute(
                """
                SELECT
                    column_name, data_type, character_maximum_length,
                    numeric_precision, numeric_scale, extra, column_default,
                    CASE
                        WHEN collation_name = %s THEN NULL
                        ELSE collation_name
                    END AS collation_name,
                    CASE
                        WHEN column_type LIKE '%% unsigned' THEN 1
                        ELSE 0
                    END AS is_unsigned
                FROM information_schema.columns
                WHERE table_name = %s AND table_schema = DATABASE()
            """,
                [default_column_collation, table_name],
            )
        field_info = {line[0]: InfoLine(*line) for line in cursor.fetchall()}

        cursor.execute(
            f"SELECT * FROM {self.connection.ops.quote_name(table_name)} LIMIT 1"
        )

        def to_int(i: Any) -> Optional[int]:
            return int(i) if i is not None else i

        fields = []
        for line in cursor.description:
            info = field_info[line[0]]
            if DJANGO_VERSION < (3, 2, 0):
                fields.append(
                    FieldInfo(
                        *line[:3],
                        to_int(info.max_len) or line[3],
                        to_int(info.num_prec) or line[4],
                        to_int(info.num_scale) or line[5],
                        line[6],
                        info.column_default,
                        info.extra,
                        info.is_unsigned,
                        line[0] in json_constraints,
                    )
                )
            else:
                fields.append(
                    FieldInfo(
                        *line[:3],
                        to_int(info.max_len) or line[3],
                        to_int(info.num_prec) or line[4],
                        to_int(info.num_scale) or line[5],
                        line[6],
                        info.column_default,
                        info.collation,
                        info.extra,
                        info.is_unsigned,
                        line[0] in json_constraints,
                    )
                )
        return fields

    def get_indexes(
        self, cursor: "CursorWrapper", table_name: str
    ) -> Dict[int, Dict[str, bool]]:
        """Return indexes from table."""
        cursor.execute(f"SHOW INDEX FROM {self.connection.ops.quote_name(table_name)}")
        # Do a two-pass search for indexes: on first pass check which indexes
        # are multicolumn, on second pass check which single-column indexes
        # are present.
        rows = list(cursor.fetchall())
        multicol_indexes = set()
        for row in rows:
            if row[3] > 1:
                multicol_indexes.add(row[2])
        indexes: Dict[int, Dict[str, bool]] = {}
        for row in rows:
            if row[2] in multicol_indexes:
                continue
            if row[4] not in indexes:
                indexes[row[4]] = {"primary_key": False, "unique": False}
            # It's possible to have the unique and PK constraints in
            # separate indexes.
            if row[2] == "PRIMARY":
                indexes[row[4]]["primary_key"] = True
            if not row[1]:
                indexes[row[4]]["unique"] = True
        return indexes

    def get_primary_key_column(
        self, cursor: "CursorWrapper", table_name: str
    ) -> Optional[int]:
        """
        Returns the name of the primary key column for the given table
        """
        for column in self.get_indexes(cursor, table_name).items():
            if column[1]["primary_key"]:
                return column[0]
        return None

    def get_sequences(
        self, cursor: "CursorWrapper", table_name: str, table_fields: Any = ()
    ) -> List[Dict[str, str]]:
        for field_info in self.get_table_description(cursor, table_name):
            if "auto_increment" in field_info.extra:
                # MySQL allows only one auto-increment column per table.
                return [{"table": table_name, "column": field_info.name}]
        return []

    def get_relations(
        self, cursor: "CursorWrapper", table_name: str
    ) -> Dict[str, Tuple[str, str]]:
        """
        Return a dictionary of {field_name: (field_name_other_table, other_table)}
        representing all relationships to the given table.
        """
        constraints = self.get_key_columns(cursor, table_name)
        relations = {}
        for my_fieldname, other_table, other_field in constraints:
            relations[my_fieldname] = (other_field, other_table)
        return relations

    def get_key_columns(
        self, cursor: "CursorWrapper", table_name: str
    ) -> List[Tuple[str, str, str]]:
        """
        Return a list of (column_name, referenced_table_name, referenced_column_name)
        for all key columns in the given table.
        """
        key_columns: List[Any] = []
        cursor.execute(
            """
            SELECT column_name, referenced_table_name, referenced_column_name
            FROM information_schema.key_column_usage
            WHERE table_name = %s
                AND table_schema = DATABASE()
                AND referenced_table_name IS NOT NULL
                AND referenced_column_name IS NOT NULL""",
            [table_name],
        )
        key_columns.extend(cursor.fetchall())
        return key_columns

    def get_storage_engine(self, cursor: "CursorWrapper", table_name: str) -> str:
        """
        Retrieve the storage engine for a given table. Return the default
        storage engine if the table doesn't exist.
        """
        cursor.execute(
            "SELECT engine FROM information_schema.tables WHERE table_name = %s",
            [table_name],
        )
        result = cursor.fetchone()
        # pylint: disable=protected-access
        if not result:
            return self.connection.features._mysql_storage_engine
        # pylint: enable=protected-access
        return result[0]

    def _parse_constraint_columns(
        self, check_clause: Any, columns: Set[str]
    ) -> OrderedSet:
        check_columns: OrderedSet = OrderedSet()
        statement = sqlparse.parse(check_clause)[0]
        tokens = (token for token in statement.flatten() if not token.is_whitespace)
        for token in tokens:
            if (
                token.ttype == sqlparse.tokens.Name
                and self.connection.ops.quote_name(token.value) == token.value
                and token.value[1:-1] in columns
            ):
                check_columns.add(token.value[1:-1])
        return check_columns

    def get_constraints(
        self, cursor: "CursorWrapper", table_name: str
    ) -> Dict[str, Any]:
        """
        Retrieve any constraints or keys (unique, pk, fk, check, index) across
        one or more columns.
        """
        constraints: Dict[str, Any] = {}
        # Get the actual constraint names and columns
        name_query = """
            SELECT kc.`constraint_name`, kc.`column_name`,
                kc.`referenced_table_name`, kc.`referenced_column_name`
            FROM information_schema.key_column_usage AS kc
            WHERE
                kc.table_schema = DATABASE() AND
                kc.table_name = %s
            ORDER BY kc.`ordinal_position`
        """
        cursor.execute(name_query, [table_name])
        for constraint, column, ref_table, ref_column in cursor.fetchall():
            if constraint not in constraints:
                constraints[constraint] = {
                    "columns": OrderedSet(),
                    "primary_key": False,
                    "unique": False,
                    "index": False,
                    "check": False,
                    "foreign_key": (ref_table, ref_column) if ref_column else None,
                }
                if self.connection.features.supports_index_column_ordering:
                    constraints[constraint]["orders"] = []
            constraints[constraint]["columns"].add(column)
        # Now get the constraint types
        type_query = """
            SELECT c.constraint_name, c.constraint_type
            FROM information_schema.table_constraints AS c
            WHERE
                c.table_schema = DATABASE() AND
                c.table_name = %s
        """
        cursor.execute(type_query, [table_name])
        for constraint, kind in cursor.fetchall():
            if kind.lower() == "primary key":
                constraints[constraint]["primary_key"] = True
                constraints[constraint]["unique"] = True
            elif kind.lower() == "unique":
                constraints[constraint]["unique"] = True
        # Add check constraints.
        if self.connection.features.can_introspect_check_constraints:
            unnamed_constraints_index = 0
            columns = {
                info.name for info in self.get_table_description(cursor, table_name)
            }
            type_query = """
                SELECT cc.constraint_name, cc.check_clause
                FROM
                    information_schema.check_constraints AS cc,
                    information_schema.table_constraints AS tc
                WHERE
                    cc.constraint_schema = DATABASE() AND
                    tc.table_schema = cc.constraint_schema AND
                    cc.constraint_name = tc.constraint_name AND
                    tc.constraint_type = 'CHECK' AND
                    tc.table_name = %s
            """
            cursor.execute(type_query, [table_name])
            for constraint, check_clause in cursor.fetchall():
                constraint_columns = self._parse_constraint_columns(
                    check_clause, columns
                )
                # Ensure uniqueness of unnamed constraints. Unnamed unique
                # and check columns constraints have the same name as
                # a column.
                if set(constraint_columns) == {constraint}:
                    unnamed_constraints_index += 1
                    constraint = f"__unnamed_constraint_{unnamed_constraints_index}__"
                constraints[constraint] = {
                    "columns": constraint_columns,
                    "primary_key": False,
                    "unique": False,
                    "index": False,
                    "check": True,
                    "foreign_key": None,
                }
        # Now add in the indexes
        cursor.execute(f"SHOW INDEX FROM {self.connection.ops.quote_name(table_name)}")
        for _, _, index, _, column, order, type_ in [
            x[:6] + (x[10],) for x in cursor.fetchall()
        ]:
            if index not in constraints:
                constraints[index] = {
                    "columns": OrderedSet(),
                    "primary_key": False,
                    "unique": False,
                    "check": False,
                    "foreign_key": None,
                }
                if self.connection.features.supports_index_column_ordering:
                    constraints[index]["orders"] = []
            constraints[index]["index"] = True
            constraints[index]["type"] = (
                Index.suffix if type_ == "BTREE" else type_.lower()
            )
            constraints[index]["columns"].add(column)
            if self.connection.features.supports_index_column_ordering:
                constraints[index]["orders"].append("DESC" if order == "D" else "ASC")
        # Convert the sorted sets to lists
        for constraint in constraints.values():
            constraint["columns"] = list(constraint["columns"])
        return constraints

virt/lib/python3.9/site-packages/mysql/connector/django/introspection.py
PrinceDisant/LostInNL
# Copyright (c) 2020, 2022, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

# mypy: disable-error-code="override,attr-defined"

"""Database Operations."""

from datetime import datetime, time
from typing import Optional

from django.conf import settings
from django.db.backends.mysql.operations import (
    DatabaseOperations as MySQLDatabaseOperations,
)
from django.utils import timezone

try:
    from _mysql_connector import datetime_to_mysql, time_to_mysql
except ImportError:
    HAVE_CEXT = False
else:
    HAVE_CEXT = True


class DatabaseOperations(MySQLDatabaseOperations):
    """Database Operations class."""

    compiler_module = "mysql.connector.django.compiler"

    def regex_lookup(self, lookup_type: str) -> str:
        """Return the string to use in a query when performing regular
        expression lookup."""
        if self.connection.mysql_version < (8, 0, 0):
            if lookup_type == "regex":
                return "%s REGEXP BINARY %s"
            return "%s REGEXP %s"

        match_option = "c" if lookup_type == "regex" else "i"
        return f"REGEXP_LIKE(%s, %s, '{match_option}')"

    def adapt_datetimefield_value(self, value: Optional[datetime]) -> Optional[bytes]:
        """Transform a datetime value to an object compatible with what is
        expected by the backend driver for datetime columns."""
        return self.value_to_db_datetime(value)

    def value_to_db_datetime(self, value: Optional[datetime]) -> Optional[bytes]:
        """Convert value to MySQL DATETIME."""
        ans: Optional[bytes] = None
        if value is None:
            return ans
        # MySQL doesn't support tz-aware times
        if timezone.is_aware(value):
            if settings.USE_TZ:
                value = value.astimezone(timezone.utc).replace(tzinfo=None)
            else:
                raise ValueError("MySQL backend does not support timezone-aware times")
        if not self.connection.features.supports_microsecond_precision:
            value = value.replace(microsecond=0)
        if not self.connection.use_pure:
            return datetime_to_mysql(value)
        return self.connection.converter.to_mysql(value)

    def adapt_timefield_value(self, value: Optional[time]) -> Optional[bytes]:
        """Transform a time value to an object compatible with what is expected
        by the backend driver for time columns."""
        return self.value_to_db_time(value)

    def value_to_db_time(self, value: Optional[time]) -> Optional[bytes]:
        """Convert value to MySQL TIME."""
        if value is None:
            return None

        # MySQL doesn't support tz-aware times
        if timezone.is_aware(value):
            raise ValueError("MySQL backend does not support timezone-aware times")

        if not self.connection.use_pure:
            return time_to_mysql(value)
        return self.connection.converter.to_mysql(value)

virt/lib/python3.9/site-packages/mysql/connector/django/operations.py
PrinceDisant/LostInNL
# Copyright (c) 2020, 2022, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

# mypy: disable-error-code="override"

"""Database schema editor."""
from typing import Any

from django.db.backends.mysql.schema import (
    DatabaseSchemaEditor as MySQLDatabaseSchemaEditor,
)


class DatabaseSchemaEditor(MySQLDatabaseSchemaEditor):
    """This class is responsible for emitting schema-changing statements to the
    databases.
    """

    def quote_value(self, value: Any) -> Any:
        """Quote value."""
        self.connection.ensure_connection()
        if isinstance(value, str):
            value = value.replace("%", "%%")
        quoted = self.connection.connection.converter.escape(value)
        if isinstance(value, str) and isinstance(quoted, bytes):
            quoted = quoted.decode()
        return quoted

    def prepare_default(self, value: Any) -> Any:
        """Implement the required abstract method.

        MySQL has requires_literal_defaults=False, therefore return the value.
        """
        return value

virt/lib/python3.9/site-packages/mysql/connector/django/schema.py
PrinceDisant/LostInNL
# Copyright (c) 2020, 2022, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

"""Backend specific database validation."""

from django.db.backends.mysql.validation import DatabaseValidation

__all__ = ["DatabaseValidation"]

virt/lib/python3.9/site-packages/mysql/connector/django/validation.py
PrinceDisant/LostInNL
# -*- coding: utf-8 -*-

# Copyright (c) 2013, 2022, Oracle and/or its affiliates. All rights reserved.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

"""This module contains the MySQL Server and Client error codes."""

# This file was auto-generated.
_GENERATED_ON = "2021-08-11"
_MYSQL_VERSION = (8, 0, 27)

# Start MySQL Errors
OBSOLETE_ER_HASHCHK = 1000
OBSOLETE_ER_NISAMCHK = 1001
ER_NO = 1002
ER_YES = 1003
ER_CANT_CREATE_FILE = 1004
ER_CANT_CREATE_TABLE = 1005
ER_CANT_CREATE_DB = 1006
ER_DB_CREATE_EXISTS = 1007
ER_DB_DROP_EXISTS = 1008
OBSOLETE_ER_DB_DROP_DELETE = 1009
ER_DB_DROP_RMDIR = 1010
OBSOLETE_ER_CANT_DELETE_FILE = 1011
ER_CANT_FIND_SYSTEM_REC = 1012
ER_CANT_GET_STAT = 1013
OBSOLETE_ER_CANT_GET_WD = 1014
ER_CANT_LOCK = 1015
ER_CANT_OPEN_FILE = 1016
ER_FILE_NOT_FOUND = 1017
ER_CANT_READ_DIR = 1018
OBSOLETE_ER_CANT_SET_WD = 1019
ER_CHECKREAD = 1020
OBSOLETE_ER_DISK_FULL = 1021
ER_DUP_KEY = 1022
OBSOLETE_ER_ERROR_ON_CLOSE = 1023
ER_ERROR_ON_READ = 1024
ER_ERROR_ON_RENAME = 1025
ER_ERROR_ON_WRITE = 1026
ER_FILE_USED = 1027
OBSOLETE_ER_FILSORT_ABORT = 1028
OBSOLETE_ER_FORM_NOT_FOUND = 1029
ER_GET_ERRNO = 1030
ER_ILLEGAL_HA = 1031
ER_KEY_NOT_FOUND = 1032
ER_NOT_FORM_FILE = 1033
ER_NOT_KEYFILE = 1034
ER_OLD_KEYFILE = 1035
ER_OPEN_AS_READONLY = 1036
ER_OUTOFMEMORY = 1037
ER_OUT_OF_SORTMEMORY = 1038
OBSOLETE_ER_UNEXPECTED_EOF = 1039
ER_CON_COUNT_ERROR = 1040
ER_OUT_OF_RESOURCES = 1041
ER_BAD_HOST_ERROR = 1042
ER_HANDSHAKE_ERROR = 1043
ER_DBACCESS_DENIED_ERROR = 1044
ER_ACCESS_DENIED_ERROR = 1045
ER_NO_DB_ERROR = 1046
ER_UNKNOWN_COM_ERROR = 1047
ER_BAD_NULL_ERROR = 1048
ER_BAD_DB_ERROR = 1049
ER_TABLE_EXISTS_ERROR = 1050
ER_BAD_TABLE_ERROR = 1051
ER_NON_UNIQ_ERROR = 1052
ER_SERVER_SHUTDOWN = 1053
ER_BAD_FIELD_ERROR = 1054
ER_WRONG_FIELD_WITH_GROUP = 1055
ER_WRONG_GROUP_FIELD = 1056
ER_WRONG_SUM_SELECT = 1057
ER_WRONG_VALUE_COUNT = 1058
ER_TOO_LONG_IDENT = 1059
ER_DUP_FIELDNAME = 1060
ER_DUP_KEYNAME = 1061
ER_DUP_ENTRY = 1062
ER_WRONG_FIELD_SPEC = 1063
ER_PARSE_ERROR = 1064
ER_EMPTY_QUERY = 1065
ER_NONUNIQ_TABLE = 1066
ER_INVALID_DEFAULT = 1067
ER_MULTIPLE_PRI_KEY = 1068
ER_TOO_MANY_KEYS = 1069
ER_TOO_MANY_KEY_PARTS = 1070
ER_TOO_LONG_KEY = 1071
ER_KEY_COLUMN_DOES_NOT_EXITS = 1072
ER_BLOB_USED_AS_KEY = 1073
ER_TOO_BIG_FIELDLENGTH = 1074
ER_WRONG_AUTO_KEY = 1075
ER_READY = 1076
OBSOLETE_ER_NORMAL_SHUTDOWN = 1077
OBSOLETE_ER_GOT_SIGNAL = 1078
ER_SHUTDOWN_COMPLETE = 1079
ER_FORCING_CLOSE = 1080
ER_IPSOCK_ERROR = 1081
ER_NO_SUCH_INDEX = 1082
ER_WRONG_FIELD_TERMINATORS = 1083
ER_BLOBS_AND_NO_TERMINATED = 1084
ER_TEXTFILE_NOT_READABLE = 1085
ER_FILE_EXISTS_ERROR = 1086
ER_LOAD_INFO = 1087
ER_ALTER_INFO = 1088
ER_WRONG_SUB_KEY = 1089
ER_CANT_REMOVE_ALL_FIELDS = 1090
ER_CANT_DROP_FIELD_OR_KEY = 1091
ER_INSERT_INFO = 1092
ER_UPDATE_TABLE_USED = 1093
ER_NO_SUCH_THREAD = 1094
ER_KILL_DENIED_ERROR = 1095
ER_NO_TABLES_USED = 1096
ER_TOO_BIG_SET = 1097
ER_NO_UNIQUE_LOGFILE = 1098
ER_TABLE_NOT_LOCKED_FOR_WRITE = 1099
ER_TABLE_NOT_LOCKED = 1100
ER_BLOB_CANT_HAVE_DEFAULT = 1101
ER_WRONG_DB_NAME = 1102
ER_WRONG_TABLE_NAME = 1103
ER_TOO_BIG_SELECT = 1104
ER_UNKNOWN_ERROR = 1105
ER_UNKNOWN_PROCEDURE = 1106
ER_WRONG_PARAMCOUNT_TO_PROCEDURE = 1107
ER_WRONG_PARAMETERS_TO_PROCEDURE = 1108
ER_UNKNOWN_TABLE = 1109
ER_FIELD_SPECIFIED_TWICE = 1110
ER_INVALID_GROUP_FUNC_USE = 1111
ER_UNSUPPORTED_EXTENSION = 1112
ER_TABLE_MUST_HAVE_COLUMNS = 1113
ER_RECORD_FILE_FULL = 1114
ER_UNKNOWN_CHARACTER_SET = 1115
ER_TOO_MANY_TABLES = 1116
ER_TOO_MANY_FIELDS = 1117
ER_TOO_BIG_ROWSIZE = 1118
ER_STACK_OVERRUN = 1119
ER_WRONG_OUTER_JOIN_UNUSED = 1120
ER_NULL_COLUMN_IN_INDEX = 1121
ER_CANT_FIND_UDF = 1122
ER_CANT_INITIALIZE_UDF = 1123
ER_UDF_NO_PATHS = 1124
ER_UDF_EXISTS = 1125
ER_CANT_OPEN_LIBRARY = 1126
ER_CANT_FIND_DL_ENTRY = 1127
ER_FUNCTION_NOT_DEFINED = 1128
ER_HOST_IS_BLOCKED = 1129
ER_HOST_NOT_PRIVILEGED = 1130
ER_PASSWORD_ANONYMOUS_USER = 1131
ER_PASSWORD_NOT_ALLOWED = 1132
ER_PASSWORD_NO_MATCH = 1133
ER_UPDATE_INFO = 1134
ER_CANT_CREATE_THREAD = 1135
ER_WRONG_VALUE_COUNT_ON_ROW = 1136
ER_CANT_REOPEN_TABLE = 1137
ER_INVALID_USE_OF_NULL = 1138
ER_REGEXP_ERROR = 1139
ER_MIX_OF_GROUP_FUNC_AND_FIELDS = 1140
ER_NONEXISTING_GRANT = 1141
ER_TABLEACCESS_DENIED_ERROR = 1142
ER_COLUMNACCESS_DENIED_ERROR = 1143
ER_ILLEGAL_GRANT_FOR_TABLE = 1144
ER_GRANT_WRONG_HOST_OR_USER = 1145
ER_NO_SUCH_TABLE = 1146
ER_NONEXISTING_TABLE_GRANT = 1147
ER_NOT_ALLOWED_COMMAND = 1148
ER_SYNTAX_ERROR = 1149
OBSOLETE_ER_UNUSED1 = 1150
OBSOLETE_ER_UNUSED2 = 1151
ER_ABORTING_CONNECTION = 1152
ER_NET_PACKET_TOO_LARGE = 1153
ER_NET_READ_ERROR_FROM_PIPE = 1154
ER_NET_FCNTL_ERROR = 1155
ER_NET_PACKETS_OUT_OF_ORDER = 1156
ER_NET_UNCOMPRESS_ERROR = 1157
ER_NET_READ_ERROR = 1158
ER_NET_READ_INTERRUPTED = 1159
ER_NET_ERROR_ON_WRITE = 1160
ER_NET_WRITE_INTERRUPTED = 1161
ER_TOO_LONG_STRING = 1162
ER_TABLE_CANT_HANDLE_BLOB = 1163
ER_TABLE_CANT_HANDLE_AUTO_INCREMENT = 1164
OBSOLETE_ER_UNUSED3 = 1165
ER_WRONG_COLUMN_NAME = 1166
ER_WRONG_KEY_COLUMN = 1167
ER_WRONG_MRG_TABLE = 1168
ER_DUP_UNIQUE = 1169
ER_BLOB_KEY_WITHOUT_LENGTH = 1170
ER_PRIMARY_CANT_HAVE_NULL = 1171
ER_TOO_MANY_ROWS = 1172
ER_REQUIRES_PRIMARY_KEY = 1173
OBSOLETE_ER_NO_RAID_COMPILED = 1174
ER_UPDATE_WITHOUT_KEY_IN_SAFE_MODE = 1175
ER_KEY_DOES_NOT_EXITS = 1176
ER_CHECK_NO_SUCH_TABLE = 1177
ER_CHECK_NOT_IMPLEMENTED = 1178
ER_CANT_DO_THIS_DURING_AN_TRANSACTION = 1179
ER_ERROR_DURING_COMMIT = 1180
ER_ERROR_DURING_ROLLBACK = 1181
ER_ERROR_DURING_FLUSH_LOGS = 1182
OBSOLETE_ER_ERROR_DURING_CHECKPOINT = 1183
ER_NEW_ABORTING_CONNECTION = 1184
OBSOLETE_ER_DUMP_NOT_IMPLEMENTED = 1185
OBSOLETE_ER_FLUSH_MASTER_BINLOG_CLOSED = 1186
OBSOLETE_ER_INDEX_REBUILD = 1187
ER_MASTER = 1188
ER_MASTER_NET_READ = 1189
ER_MASTER_NET_WRITE = 1190
ER_FT_MATCHING_KEY_NOT_FOUND = 1191
ER_LOCK_OR_ACTIVE_TRANSACTION = 1192
ER_UNKNOWN_SYSTEM_VARIABLE = 1193
ER_CRASHED_ON_USAGE = 1194
ER_CRASHED_ON_REPAIR = 1195
ER_WARNING_NOT_COMPLETE_ROLLBACK = 1196
ER_TRANS_CACHE_FULL = 1197
OBSOLETE_ER_SLAVE_MUST_STOP = 1198
ER_SLAVE_NOT_RUNNING = 1199
ER_BAD_SLAVE = 1200
ER_MASTER_INFO = 1201
ER_SLAVE_THREAD = 1202
ER_TOO_MANY_USER_CONNECTIONS = 1203
ER_SET_CONSTANTS_ONLY = 1204
ER_LOCK_WAIT_TIMEOUT = 1205
ER_LOCK_TABLE_FULL = 1206
ER_READ_ONLY_TRANSACTION = 1207
OBSOLETE_ER_DROP_DB_WITH_READ_LOCK = 1208
OBSOLETE_ER_CREATE_DB_WITH_READ_LOCK = 1209
ER_WRONG_ARGUMENTS = 1210
ER_NO_PERMISSION_TO_CREATE_USER = 1211
OBSOLETE_ER_UNION_TABLES_IN_DIFFERENT_DIR = 1212
ER_LOCK_DEADLOCK = 1213
ER_TABLE_CANT_HANDLE_FT = 1214
ER_CANNOT_ADD_FOREIGN = 1215
ER_NO_REFERENCED_ROW = 1216
ER_ROW_IS_REFERENCED = 1217
ER_CONNECT_TO_MASTER = 1218
OBSOLETE_ER_QUERY_ON_MASTER = 1219
ER_ERROR_WHEN_EXECUTING_COMMAND = 1220
ER_WRONG_USAGE = 1221
ER_WRONG_NUMBER_OF_COLUMNS_IN_SELECT = 1222
ER_CANT_UPDATE_WITH_READLOCK = 1223
ER_MIXING_NOT_ALLOWED = 1224
ER_DUP_ARGUMENT = 1225
ER_USER_LIMIT_REACHED = 1226
ER_SPECIFIC_ACCESS_DENIED_ERROR = 1227
ER_LOCAL_VARIABLE = 1228
ER_GLOBAL_VARIABLE = 1229
ER_NO_DEFAULT = 1230
ER_WRONG_VALUE_FOR_VAR = 1231
ER_WRONG_TYPE_FOR_VAR = 1232
ER_VAR_CANT_BE_READ = 1233
ER_CANT_USE_OPTION_HERE = 1234
ER_NOT_SUPPORTED_YET = 1235
ER_MASTER_FATAL_ERROR_READING_BINLOG = 1236
ER_SLAVE_IGNORED_TABLE = 1237
ER_INCORRECT_GLOBAL_LOCAL_VAR = 1238
ER_WRONG_FK_DEF = 1239
ER_KEY_REF_DO_NOT_MATCH_TABLE_REF = 1240
ER_OPERAND_COLUMNS = 1241
ER_SUBQUERY_NO_1_ROW = 1242
ER_UNKNOWN_STMT_HANDLER = 1243
ER_CORRUPT_HELP_DB = 1244
OBSOLETE_ER_CYCLIC_REFERENCE = 1245
ER_AUTO_CONVERT = 1246
ER_ILLEGAL_REFERENCE = 1247
ER_DERIVED_MUST_HAVE_ALIAS = 1248
ER_SELECT_REDUCED = 1249
ER_TABLENAME_NOT_ALLOWED_HERE = 1250
ER_NOT_SUPPORTED_AUTH_MODE = 1251
ER_SPATIAL_CANT_HAVE_NULL = 1252
ER_COLLATION_CHARSET_MISMATCH = 1253
OBSOLETE_ER_SLAVE_WAS_RUNNING = 1254
OBSOLETE_ER_SLAVE_WAS_NOT_RUNNING = 1255
ER_TOO_BIG_FOR_UNCOMPRESS = 1256
ER_ZLIB_Z_MEM_ERROR = 1257
ER_ZLIB_Z_BUF_ERROR = 1258
ER_ZLIB_Z_DATA_ERROR = 1259
ER_CUT_VALUE_GROUP_CONCAT = 1260
ER_WARN_TOO_FEW_RECORDS = 1261
ER_WARN_TOO_MANY_RECORDS = 1262
ER_WARN_NULL_TO_NOTNULL = 1263
ER_WARN_DATA_OUT_OF_RANGE = 1264
WARN_DATA_TRUNCATED = 1265
ER_WARN_USING_OTHER_HANDLER = 1266
ER_CANT_AGGREGATE_2COLLATIONS = 1267
OBSOLETE_ER_DROP_USER = 1268
ER_REVOKE_GRANTS = 1269
ER_CANT_AGGREGATE_3COLLATIONS = 1270
ER_CANT_AGGREGATE_NCOLLATIONS = 1271
ER_VARIABLE_IS_NOT_STRUCT = 1272
ER_UNKNOWN_COLLATION = 1273
ER_SLAVE_IGNORED_SSL_PARAMS = 1274
OBSOLETE_ER_SERVER_IS_IN_SECURE_AUTH_MODE = 1275
ER_WARN_FIELD_RESOLVED = 1276
ER_BAD_SLAVE_UNTIL_COND = 1277
ER_MISSING_SKIP_SLAVE = 1278
ER_UNTIL_COND_IGNORED = 1279
ER_WRONG_NAME_FOR_INDEX = 1280
ER_WRONG_NAME_FOR_CATALOG = 1281
OBSOLETE_ER_WARN_QC_RESIZE = 1282
ER_BAD_FT_COLUMN = 1283
ER_UNKNOWN_KEY_CACHE = 1284
ER_WARN_HOSTNAME_WONT_WORK = 1285
ER_UNKNOWN_STORAGE_ENGINE = 1286
ER_WARN_DEPRECATED_SYNTAX = 1287
ER_NON_UPDATABLE_TABLE = 1288
ER_FEATURE_DISABLED = 1289
ER_OPTION_PREVENTS_STATEMENT = 1290
ER_DUPLICATED_VALUE_IN_TYPE = 1291
ER_TRUNCATED_WRONG_VALUE = 1292
OBSOLETE_ER_TOO_MUCH_AUTO_TIMESTAMP_COLS = 1293
ER_INVALID_ON_UPDATE = 1294
ER_UNSUPPORTED_PS = 1295
ER_GET_ERRMSG = 1296
ER_GET_TEMPORARY_ERRMSG = 1297
ER_UNKNOWN_TIME_ZONE = 1298
ER_WARN_INVALID_TIMESTAMP = 1299
ER_INVALID_CHARACTER_STRING = 1300
ER_WARN_ALLOWED_PACKET_OVERFLOWED = 1301
ER_CONFLICTING_DECLARATIONS = 1302
ER_SP_NO_RECURSIVE_CREATE = 1303
ER_SP_ALREADY_EXISTS = 1304
ER_SP_DOES_NOT_EXIST = 1305
ER_SP_DROP_FAILED = 1306
ER_SP_STORE_FAILED = 1307
ER_SP_LILABEL_MISMATCH = 1308
ER_SP_LABEL_REDEFINE = 1309
ER_SP_LABEL_MISMATCH = 1310
ER_SP_UNINIT_VAR = 1311
ER_SP_BADSELECT = 1312
ER_SP_BADRETURN = 1313
ER_SP_BADSTATEMENT = 1314
ER_UPDATE_LOG_DEPRECATED_IGNORED = 1315
ER_UPDATE_LOG_DEPRECATED_TRANSLATED = 1316
ER_QUERY_INTERRUPTED = 1317
ER_SP_WRONG_NO_OF_ARGS = 1318
ER_SP_COND_MISMATCH = 1319
ER_SP_NORETURN = 1320
ER_SP_NORETURNEND = 1321
ER_SP_BAD_CURSOR_QUERY = 1322
ER_SP_BAD_CURSOR_SELECT = 1323
ER_SP_CURSOR_MISMATCH = 1324
ER_SP_CURSOR_ALREADY_OPEN = 1325
ER_SP_CURSOR_NOT_OPEN = 1326
ER_SP_UNDECLARED_VAR = 1327
ER_SP_WRONG_NO_OF_FETCH_ARGS = 1328
ER_SP_FETCH_NO_DATA = 1329
ER_SP_DUP_PARAM = 1330
ER_SP_DUP_VAR = 1331
ER_SP_DUP_COND = 1332
ER_SP_DUP_CURS = 1333
ER_SP_CANT_ALTER = 1334
ER_SP_SUBSELECT_NYI = 1335
ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG = 1336
ER_SP_VARCOND_AFTER_CURSHNDLR = 1337
ER_SP_CURSOR_AFTER_HANDLER = 1338
ER_SP_CASE_NOT_FOUND = 1339
ER_FPARSER_TOO_BIG_FILE = 1340
ER_FPARSER_BAD_HEADER = 1341
ER_FPARSER_EOF_IN_COMMENT = 1342
ER_FPARSER_ERROR_IN_PARAMETER = 1343
ER_FPARSER_EOF_IN_UNKNOWN_PARAMETER = 1344
ER_VIEW_NO_EXPLAIN = 1345
OBSOLETE_ER_FRM_UNKNOWN_TYPE = 1346
ER_WRONG_OBJECT = 1347
ER_NONUPDATEABLE_COLUMN = 1348
OBSOLETE_ER_VIEW_SELECT_DERIVED_UNUSED = 1349
ER_VIEW_SELECT_CLAUSE = 1350
ER_VIEW_SELECT_VARIABLE = 1351
ER_VIEW_SELECT_TMPTABLE = 1352
ER_VIEW_WRONG_LIST = 1353
ER_WARN_VIEW_MERGE = 1354
ER_WARN_VIEW_WITHOUT_KEY = 1355
ER_VIEW_INVALID = 1356
ER_SP_NO_DROP_SP = 1357
OBSOLETE_ER_SP_GOTO_IN_HNDLR = 1358
ER_TRG_ALREADY_EXISTS = 1359
ER_TRG_DOES_NOT_EXIST = 1360
ER_TRG_ON_VIEW_OR_TEMP_TABLE = 1361
ER_TRG_CANT_CHANGE_ROW = 1362
ER_TRG_NO_SUCH_ROW_IN_TRG = 1363
ER_NO_DEFAULT_FOR_FIELD = 1364
ER_DIVISION_BY_ZERO = 1365
ER_TRUNCATED_WRONG_VALUE_FOR_FIELD = 1366
ER_ILLEGAL_VALUE_FOR_TYPE = 1367
ER_VIEW_NONUPD_CHECK = 1368
ER_VIEW_CHECK_FAILED = 1369
ER_PROCACCESS_DENIED_ERROR = 1370
ER_RELAY_LOG_FAIL = 1371
OBSOLETE_ER_PASSWD_LENGTH = 1372
ER_UNKNOWN_TARGET_BINLOG = 1373
ER_IO_ERR_LOG_INDEX_READ = 1374
ER_BINLOG_PURGE_PROHIBITED = 1375
ER_FSEEK_FAIL = 1376
ER_BINLOG_PURGE_FATAL_ERR = 1377
ER_LOG_IN_USE = 1378
ER_LOG_PURGE_UNKNOWN_ERR = 1379
ER_RELAY_LOG_INIT = 1380
ER_NO_BINARY_LOGGING = 1381
ER_RESERVED_SYNTAX = 1382
OBSOLETE_ER_WSAS_FAILED = 1383
OBSOLETE_ER_DIFF_GROUPS_PROC = 1384
OBSOLETE_ER_NO_GROUP_FOR_PROC = 1385
OBSOLETE_ER_ORDER_WITH_PROC = 1386
OBSOLETE_ER_LOGGING_PROHIBIT_CHANGING_OF = 1387
OBSOLETE_ER_NO_FILE_MAPPING = 1388
OBSOLETE_ER_WRONG_MAGIC = 1389
ER_PS_MANY_PARAM = 1390
ER_KEY_PART_0 = 1391
ER_VIEW_CHECKSUM = 1392
ER_VIEW_MULTIUPDATE = 1393
ER_VIEW_NO_INSERT_FIELD_LIST = 1394
ER_VIEW_DELETE_MERGE_VIEW = 1395
ER_CANNOT_USER = 1396
ER_XAER_NOTA = 1397
ER_XAER_INVAL = 1398
ER_XAER_RMFAIL = 1399
ER_XAER_OUTSIDE = 1400
ER_XAER_RMERR = 1401
ER_XA_RBROLLBACK = 1402
ER_NONEXISTING_PROC_GRANT = 1403
ER_PROC_AUTO_GRANT_FAIL = 1404
ER_PROC_AUTO_REVOKE_FAIL = 1405
ER_DATA_TOO_LONG = 1406
ER_SP_BAD_SQLSTATE = 1407
ER_STARTUP = 1408
ER_LOAD_FROM_FIXED_SIZE_ROWS_TO_VAR = 1409
ER_CANT_CREATE_USER_WITH_GRANT = 1410
ER_WRONG_VALUE_FOR_TYPE = 1411
ER_TABLE_DEF_CHANGED = 1412
ER_SP_DUP_HANDLER = 1413
ER_SP_NOT_VAR_ARG = 1414
ER_SP_NO_RETSET = 1415
ER_CANT_CREATE_GEOMETRY_OBJECT = 1416
OBSOLETE_ER_FAILED_ROUTINE_BREAK_BINLOG = 1417
ER_BINLOG_UNSAFE_ROUTINE = 1418
ER_BINLOG_CREATE_ROUTINE_NEED_SUPER = 1419
OBSOLETE_ER_EXEC_STMT_WITH_OPEN_CURSOR = 1420
ER_STMT_HAS_NO_OPEN_CURSOR = 1421
ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG = 1422
ER_NO_DEFAULT_FOR_VIEW_FIELD = 1423
ER_SP_NO_RECURSION = 1424
ER_TOO_BIG_SCALE = 1425
ER_TOO_BIG_PRECISION = 1426
ER_M_BIGGER_THAN_D = 1427
ER_WRONG_LOCK_OF_SYSTEM_TABLE = 1428
ER_CONNECT_TO_FOREIGN_DATA_SOURCE = 1429
ER_QUERY_ON_FOREIGN_DATA_SOURCE = 1430
ER_FOREIGN_DATA_SOURCE_DOESNT_EXIST = 1431
ER_FOREIGN_DATA_STRING_INVALID_CANT_CREATE = 1432
ER_FOREIGN_DATA_STRING_INVALID = 1433
OBSOLETE_ER_CANT_CREATE_FEDERATED_TABLE = 1434
ER_TRG_IN_WRONG_SCHEMA = 1435
ER_STACK_OVERRUN_NEED_MORE = 1436
ER_TOO_LONG_BODY = 1437
ER_WARN_CANT_DROP_DEFAULT_KEYCACHE = 1438
ER_TOO_BIG_DISPLAYWIDTH = 1439
ER_XAER_DUPID = 1440
ER_DATETIME_FUNCTION_OVERFLOW = 1441
ER_CANT_UPDATE_USED_TABLE_IN_SF_OR_TRG = 1442
ER_VIEW_PREVENT_UPDATE = 1443
ER_PS_NO_RECURSION = 1444
ER_SP_CANT_SET_AUTOCOMMIT = 1445
OBSOLETE_ER_MALFORMED_DEFINER = 1446
ER_VIEW_FRM_NO_USER = 1447
ER_VIEW_OTHER_USER = 1448
ER_NO_SUCH_USER = 1449
ER_FORBID_SCHEMA_CHANGE = 1450
ER_ROW_IS_REFERENCED_2 = 1451
ER_NO_REFERENCED_ROW_2 = 1452
ER_SP_BAD_VAR_SHADOW = 1453
ER_TRG_NO_DEFINER = 1454
ER_OLD_FILE_FORMAT = 1455
ER_SP_RECURSION_LIMIT = 1456
OBSOLETE_ER_SP_PROC_TABLE_CORRUPT = 1457
ER_SP_WRONG_NAME = 1458
ER_TABLE_NEEDS_UPGRADE = 1459
ER_SP_NO_AGGREGATE = 1460
ER_MAX_PREPARED_STMT_COUNT_REACHED = 1461
ER_VIEW_RECURSIVE = 1462
ER_NON_GROUPING_FIELD_USED = 1463
ER_TABLE_CANT_HANDLE_SPKEYS = 1464
ER_NO_TRIGGERS_ON_SYSTEM_SCHEMA = 1465
ER_REMOVED_SPACES = 1466
ER_AUTOINC_READ_FAILED = 1467
ER_USERNAME = 1468
ER_HOSTNAME = 1469
ER_WRONG_STRING_LENGTH = 1470
ER_NON_INSERTABLE_TABLE = 1471
ER_ADMIN_WRONG_MRG_TABLE = 1472
ER_TOO_HIGH_LEVEL_OF_NESTING_FOR_SELECT = 1473
ER_NAME_BECOMES_EMPTY = 1474
ER_AMBIGUOUS_FIELD_TERM = 1475
ER_FOREIGN_SERVER_EXISTS = 1476
ER_FOREIGN_SERVER_DOESNT_EXIST = 1477
ER_ILLEGAL_HA_CREATE_OPTION = 1478
ER_PARTITION_REQUIRES_VALUES_ERROR = 1479
ER_PARTITION_WRONG_VALUES_ERROR = 1480
ER_PARTITION_MAXVALUE_ERROR = 1481
OBSOLETE_ER_PARTITION_SUBPARTITION_ERROR = 1482
OBSOLETE_ER_PARTITION_SUBPART_MIX_ERROR = 1483
ER_PARTITION_WRONG_NO_PART_ERROR = 1484
ER_PARTITION_WRONG_NO_SUBPART_ERROR = 1485
ER_WRONG_EXPR_IN_PARTITION_FUNC_ERROR = 1486
OBSOLETE_ER_NO_CONST_EXPR_IN_RANGE_OR_LIST_ERROR = 1487
ER_FIELD_NOT_FOUND_PART_ERROR = 1488
OBSOLETE_ER_LIST_OF_FIELDS_ONLY_IN_HASH_ERROR = 1489
ER_INCONSISTENT_PARTITION_INFO_ERROR = 1490
ER_PARTITION_FUNC_NOT_ALLOWED_ERROR = 1491
ER_PARTITIONS_MUST_BE_DEFINED_ERROR = 1492
ER_RANGE_NOT_INCREASING_ERROR = 1493
ER_INCONSISTENT_TYPE_OF_FUNCTIONS_ERROR = 1494
ER_MULTIPLE_DEF_CONST_IN_LIST_PART_ERROR = 1495
ER_PARTITION_ENTRY_ERROR = 1496
ER_MIX_HANDLER_ERROR = 1497
ER_PARTITION_NOT_DEFINED_ERROR = 1498
ER_TOO_MANY_PARTITIONS_ERROR = 1499
ER_SUBPARTITION_ERROR = 1500
ER_CANT_CREATE_HANDLER_FILE = 1501
ER_BLOB_FIELD_IN_PART_FUNC_ERROR = 1502
ER_UNIQUE_KEY_NEED_ALL_FIELDS_IN_PF = 1503
ER_NO_PARTS_ERROR = 1504
ER_PARTITION_MGMT_ON_NONPARTITIONED = 1505
ER_FOREIGN_KEY_ON_PARTITIONED = 1506
ER_DROP_PARTITION_NON_EXISTENT = 1507
ER_DROP_LAST_PARTITION = 1508
ER_COALESCE_ONLY_ON_HASH_PARTITION = 1509
ER_REORG_HASH_ONLY_ON_SAME_NO = 1510
ER_REORG_NO_PARAM_ERROR = 1511
ER_ONLY_ON_RANGE_LIST_PARTITION = 1512
ER_ADD_PARTITION_SUBPART_ERROR = 1513
ER_ADD_PARTITION_NO_NEW_PARTITION = 1514
ER_COALESCE_PARTITION_NO_PARTITION = 1515
ER_REORG_PARTITION_NOT_EXIST = 1516
ER_SAME_NAME_PARTITION = 1517
ER_NO_BINLOG_ERROR = 1518
ER_CONSECUTIVE_REORG_PARTITIONS = 1519
ER_REORG_OUTSIDE_RANGE = 1520
ER_PARTITION_FUNCTION_FAILURE = 1521
OBSOLETE_ER_PART_STATE_ERROR = 1522
ER_LIMITED_PART_RANGE = 1523
ER_PLUGIN_IS_NOT_LOADED = 1524
ER_WRONG_VALUE = 1525
ER_NO_PARTITION_FOR_GIVEN_VALUE = 1526
ER_FILEGROUP_OPTION_ONLY_ONCE = 1527
ER_CREATE_FILEGROUP_FAILED = 1528
ER_DROP_FILEGROUP_FAILED = 1529
ER_TABLESPACE_AUTO_EXTEND_ERROR = 1530
ER_WRONG_SIZE_NUMBER = 1531
ER_SIZE_OVERFLOW_ERROR = 1532
ER_ALTER_FILEGROUP_FAILED = 1533
ER_BINLOG_ROW_LOGGING_FAILED = 1534
OBSOLETE_ER_BINLOG_ROW_WRONG_TABLE_DEF = 1535
OBSOLETE_ER_BINLOG_ROW_RBR_TO_SBR = 1536
ER_EVENT_ALREADY_EXISTS = 1537
OBSOLETE_ER_EVENT_STORE_FAILED = 1538
ER_EVENT_DOES_NOT_EXIST = 1539
OBSOLETE_ER_EVENT_CANT_ALTER = 1540
OBSOLETE_ER_EVENT_DROP_FAILED = 1541
ER_EVENT_INTERVAL_NOT_POSITIVE_OR_TOO_BIG = 1542
ER_EVENT_ENDS_BEFORE_STARTS = 1543
ER_EVENT_EXEC_TIME_IN_THE_PAST = 1544
OBSOLETE_ER_EVENT_OPEN_TABLE_FAILED = 1545
OBSOLETE_ER_EVENT_NEITHER_M_EXPR_NOR_M_AT = 1546
OBSOLETE_ER_COL_COUNT_DOESNT_MATCH_CORRUPTED = 1547
OBSOLETE_ER_CANNOT_LOAD_FROM_TABLE = 1548
OBSOLETE_ER_EVENT_CANNOT_DELETE = 1549
OBSOLETE_ER_EVENT_COMPILE_ERROR = 1550
ER_EVENT_SAME_NAME = 1551
OBSOLETE_ER_EVENT_DATA_TOO_LONG = 1552
ER_DROP_INDEX_FK = 1553
ER_WARN_DEPRECATED_SYNTAX_WITH_VER = 1554
OBSOLETE_ER_CANT_WRITE_LOCK_LOG_TABLE = 1555
ER_CANT_LOCK_LOG_TABLE = 1556
ER_FOREIGN_DUPLICATE_KEY_OLD_UNUSED = 1557
ER_COL_COUNT_DOESNT_MATCH_PLEASE_UPDATE = 1558
OBSOLETE_ER_TEMP_TABLE_PREVENTS_SWITCH_OUT_OF_RBR = 1559
ER_STORED_FUNCTION_PREVENTS_SWITCH_BINLOG_FORMAT = 1560
OBSOLETE_ER_NDB_CANT_SWITCH_BINLOG_FORMAT = 1561
ER_PARTITION_NO_TEMPORARY = 1562
ER_PARTITION_CONST_DOMAIN_ERROR = 1563
ER_PARTITION_FUNCTION_IS_NOT_ALLOWED = 1564
OBSOLETE_ER_DDL_LOG_ERROR_UNUSED = 1565
ER_NULL_IN_VALUES_LESS_THAN = 1566
ER_WRONG_PARTITION_NAME = 1567
ER_CANT_CHANGE_TX_CHARACTERISTICS = 1568
ER_DUP_ENTRY_AUTOINCREMENT_CASE = 1569
OBSOLETE_ER_EVENT_MODIFY_QUEUE_ERROR = 1570
ER_EVENT_SET_VAR_ERROR = 1571
ER_PARTITION_MERGE_ERROR = 1572
OBSOLETE_ER_CANT_ACTIVATE_LOG = 1573
OBSOLETE_ER_RBR_NOT_AVAILABLE = 1574
ER_BASE64_DECODE_ERROR = 1575
ER_EVENT_RECURSION_FORBIDDEN = 1576
OBSOLETE_ER_EVENTS_DB_ERROR = 1577
ER_ONLY_INTEGERS_ALLOWED = 1578
ER_UNSUPORTED_LOG_ENGINE = 1579
ER_BAD_LOG_STATEMENT = 1580
ER_CANT_RENAME_LOG_TABLE = 1581
ER_WRONG_PARAMCOUNT_TO_NATIVE_FCT = 1582
ER_WRONG_PARAMETERS_TO_NATIVE_FCT = 1583
ER_WRONG_PARAMETERS_TO_STORED_FCT = 1584
ER_NATIVE_FCT_NAME_COLLISION = 1585
ER_DUP_ENTRY_WITH_KEY_NAME = 1586
ER_BINLOG_PURGE_EMFILE = 1587
ER_EVENT_CANNOT_CREATE_IN_THE_PAST = 1588
ER_EVENT_CANNOT_ALTER_IN_THE_PAST = 1589
OBSOLETE_ER_SLAVE_INCIDENT = 1590
ER_NO_PARTITION_FOR_GIVEN_VALUE_SILENT = 1591
ER_BINLOG_UNSAFE_STATEMENT = 1592
ER_BINLOG_FATAL_ERROR = 1593
OBSOLETE_ER_SLAVE_RELAY_LOG_READ_FAILURE = 1594
OBSOLETE_ER_SLAVE_RELAY_LOG_WRITE_FAILURE = 1595
OBSOLETE_ER_SLAVE_CREATE_EVENT_FAILURE = 1596
OBSOLETE_ER_SLAVE_MASTER_COM_FAILURE = 1597
ER_BINLOG_LOGGING_IMPOSSIBLE = 1598
ER_VIEW_NO_CREATION_CTX = 1599
ER_VIEW_INVALID_CREATION_CTX = 1600
OBSOLETE_ER_SR_INVALID_CREATION_CTX = 1601
ER_TRG_CORRUPTED_FILE = 1602
ER_TRG_NO_CREATION_CTX = 1603
ER_TRG_INVALID_CREATION_CTX = 1604
ER_EVENT_INVALID_CREATION_CTX = 1605
ER_TRG_CANT_OPEN_TABLE = 1606
OBSOLETE_ER_CANT_CREATE_SROUTINE = 1607
OBSOLETE_ER_NEVER_USED = 1608
ER_NO_FORMAT_DESCRIPTION_EVENT_BEFORE_BINLOG_STATEMENT = 1609
ER_SLAVE_CORRUPT_EVENT = 1610
OBSOLETE_ER_LOAD_DATA_INVALID_COLUMN_UNUSED = 1611
ER_LOG_PURGE_NO_FILE = 1612
ER_XA_RBTIMEOUT = 1613
ER_XA_RBDEADLOCK = 1614
ER_NEED_REPREPARE = 1615
OBSOLETE_ER_DELAYED_NOT_SUPPORTED = 1616
WARN_NO_MASTER_INFO = 1617
WARN_OPTION_IGNORED = 1618
ER_PLUGIN_DELETE_BUILTIN = 1619
WARN_PLUGIN_BUSY = 1620
ER_VARIABLE_IS_READONLY = 1621
ER_WARN_ENGINE_TRANSACTION_ROLLBACK = 1622
OBSOLETE_ER_SLAVE_HEARTBEAT_FAILURE = 1623
ER_SLAVE_HEARTBEAT_VALUE_OUT_OF_RANGE = 1624
ER_NDB_REPLICATION_SCHEMA_ERROR = 1625
ER_CONFLICT_FN_PARSE_ERROR = 1626
ER_EXCEPTIONS_WRITE_ERROR = 1627
ER_TOO_LONG_TABLE_COMMENT = 1628
ER_TOO_LONG_FIELD_COMMENT = 1629
ER_FUNC_INEXISTENT_NAME_COLLISION = 1630
ER_DATABASE_NAME = 1631
ER_TABLE_NAME = 1632
ER_PARTITION_NAME = 1633
ER_SUBPARTITION_NAME = 1634
ER_TEMPORARY_NAME = 1635
ER_RENAMED_NAME = 1636
ER_TOO_MANY_CONCURRENT_TRXS = 1637
WARN_NON_ASCII_SEPARATOR_NOT_IMPLEMENTED = 1638
ER_DEBUG_SYNC_TIMEOUT = 1639
ER_DEBUG_SYNC_HIT_LIMIT = 1640
ER_DUP_SIGNAL_SET = 1641
ER_SIGNAL_WARN = 1642
ER_SIGNAL_NOT_FOUND = 1643
ER_SIGNAL_EXCEPTION = 1644
ER_RESIGNAL_WITHOUT_ACTIVE_HANDLER = 1645
ER_SIGNAL_BAD_CONDITION_TYPE = 1646
WARN_COND_ITEM_TRUNCATED = 1647
ER_COND_ITEM_TOO_LONG = 1648
ER_UNKNOWN_LOCALE = 1649
ER_SLAVE_IGNORE_SERVER_IDS = 1650
OBSOLETE_ER_QUERY_CACHE_DISABLED = 1651
ER_SAME_NAME_PARTITION_FIELD = 1652
ER_PARTITION_COLUMN_LIST_ERROR = 1653
ER_WRONG_TYPE_COLUMN_VALUE_ERROR = 1654
ER_TOO_MANY_PARTITION_FUNC_FIELDS_ERROR = 1655
ER_MAXVALUE_IN_VALUES_IN = 1656
ER_TOO_MANY_VALUES_ERROR = 1657
ER_ROW_SINGLE_PARTITION_FIELD_ERROR = 1658
ER_FIELD_TYPE_NOT_ALLOWED_AS_PARTITION_FIELD = 1659
ER_PARTITION_FIELDS_TOO_LONG = 1660
ER_BINLOG_ROW_ENGINE_AND_STMT_ENGINE = 1661
ER_BINLOG_ROW_MODE_AND_STMT_ENGINE = 1662
ER_BINLOG_UNSAFE_AND_STMT_ENGINE = 1663
ER_BINLOG_ROW_INJECTION_AND_STMT_ENGINE = 1664
ER_BINLOG_STMT_MODE_AND_ROW_ENGINE = 1665
ER_BINLOG_ROW_INJECTION_AND_STMT_MODE = 1666
ER_BINLOG_MULTIPLE_ENGINES_AND_SELF_LOGGING_ENGINE = 1667
ER_BINLOG_UNSAFE_LIMIT = 1668
OBSOLETE_ER_UNUSED4 = 1669
ER_BINLOG_UNSAFE_SYSTEM_TABLE = 1670
ER_BINLOG_UNSAFE_AUTOINC_COLUMNS = 1671
ER_BINLOG_UNSAFE_UDF = 1672
ER_BINLOG_UNSAFE_SYSTEM_VARIABLE = 1673
ER_BINLOG_UNSAFE_SYSTEM_FUNCTION = 1674
ER_BINLOG_UNSAFE_NONTRANS_AFTER_TRANS = 1675
ER_MESSAGE_AND_STATEMENT = 1676
OBSOLETE_ER_SLAVE_CONVERSION_FAILED = 1677
ER_SLAVE_CANT_CREATE_CONVERSION = 1678
ER_INSIDE_TRANSACTION_PREVENTS_SWITCH_BINLOG_FORMAT = 1679
ER_PATH_LENGTH = 1680
ER_WARN_DEPRECATED_SYNTAX_NO_REPLACEMENT = 1681
ER_WRONG_NATIVE_TABLE_STRUCTURE = 1682
ER_WRONG_PERFSCHEMA_USAGE = 1683
ER_WARN_I_S_SKIPPED_TABLE = 1684
ER_INSIDE_TRANSACTION_PREVENTS_SWITCH_BINLOG_DIRECT = 1685
ER_STORED_FUNCTION_PREVENTS_SWITCH_BINLOG_DIRECT = 1686
ER_SPATIAL_MUST_HAVE_GEOM_COL = 1687
ER_TOO_LONG_INDEX_COMMENT = 1688
ER_LOCK_ABORTED = 1689
ER_DATA_OUT_OF_RANGE = 1690
OBSOLETE_ER_WRONG_SPVAR_TYPE_IN_LIMIT = 1691
ER_BINLOG_UNSAFE_MULTIPLE_ENGINES_AND_SELF_LOGGING_ENGINE = 1692
ER_BINLOG_UNSAFE_MIXED_STATEMENT = 1693
ER_INSIDE_TRANSACTION_PREVENTS_SWITCH_SQL_LOG_BIN = 1694
ER_STORED_FUNCTION_PREVENTS_SWITCH_SQL_LOG_BIN = 1695
ER_FAILED_READ_FROM_PAR_FILE = 1696
ER_VALUES_IS_NOT_INT_TYPE_ERROR = 1697
ER_ACCESS_DENIED_NO_PASSWORD_ERROR = 1698
ER_SET_PASSWORD_AUTH_PLUGIN = 1699
OBSOLETE_ER_GRANT_PLUGIN_USER_EXISTS = 1700
ER_TRUNCATE_ILLEGAL_FK = 1701
ER_PLUGIN_IS_PERMANENT = 1702
ER_SLAVE_HEARTBEAT_VALUE_OUT_OF_RANGE_MIN = 1703
ER_SLAVE_HEARTBEAT_VALUE_OUT_OF_RANGE_MAX = 1704
ER_STMT_CACHE_FULL = 1705
ER_MULTI_UPDATE_KEY_CONFLICT = 1706
ER_TABLE_NEEDS_REBUILD = 1707
WARN_OPTION_BELOW_LIMIT = 1708
ER_INDEX_COLUMN_TOO_LONG = 1709
ER_ERROR_IN_TRIGGER_BODY = 1710
ER_ERROR_IN_UNKNOWN_TRIGGER_BODY = 1711
ER_INDEX_CORRUPT = 1712
ER_UNDO_RECORD_TOO_BIG = 1713
ER_BINLOG_UNSAFE_INSERT_IGNORE_SELECT = 1714
ER_BINLOG_UNSAFE_INSERT_SELECT_UPDATE = 1715
ER_BINLOG_UNSAFE_REPLACE_SELECT = 1716
ER_BINLOG_UNSAFE_CREATE_IGNORE_SELECT = 1717
ER_BINLOG_UNSAFE_CREATE_REPLACE_SELECT = 1718
ER_BINLOG_UNSAFE_UPDATE_IGNORE = 1719
ER_PLUGIN_NO_UNINSTALL = 1720
ER_PLUGIN_NO_INSTALL = 1721
ER_BINLOG_UNSAFE_WRITE_AUTOINC_SELECT = 1722
ER_BINLOG_UNSAFE_CREATE_SELECT_AUTOINC = 1723
ER_BINLOG_UNSAFE_INSERT_TWO_KEYS = 1724
ER_TABLE_IN_FK_CHECK = 1725
ER_UNSUPPORTED_ENGINE = 1726
ER_BINLOG_UNSAFE_AUTOINC_NOT_FIRST = 1727
ER_CANNOT_LOAD_FROM_TABLE_V2 = 1728
ER_MASTER_DELAY_VALUE_OUT_OF_RANGE = 1729
ER_ONLY_FD_AND_RBR_EVENTS_ALLOWED_IN_BINLOG_STATEMENT = 1730
ER_PARTITION_EXCHANGE_DIFFERENT_OPTION = 1731
ER_PARTITION_EXCHANGE_PART_TABLE = 1732
ER_PARTITION_EXCHANGE_TEMP_TABLE = 1733
ER_PARTITION_INSTEAD_OF_SUBPARTITION = 1734
ER_UNKNOWN_PARTITION = 1735
ER_TABLES_DIFFERENT_METADATA = 1736
ER_ROW_DOES_NOT_MATCH_PARTITION = 1737
ER_BINLOG_CACHE_SIZE_GREATER_THAN_MAX = 1738
ER_WARN_INDEX_NOT_APPLICABLE = 1739
ER_PARTITION_EXCHANGE_FOREIGN_KEY = 1740
OBSOLETE_ER_NO_SUCH_KEY_VALUE = 1741
ER_RPL_INFO_DATA_TOO_LONG = 1742
OBSOLETE_ER_NETWORK_READ_EVENT_CHECKSUM_FAILURE = 1743
OBSOLETE_ER_BINLOG_READ_EVENT_CHECKSUM_FAILURE = 1744
ER_BINLOG_STMT_CACHE_SIZE_GREATER_THAN_MAX = 1745
ER_CANT_UPDATE_TABLE_IN_CREATE_TABLE_SELECT = 1746
ER_PARTITION_CLAUSE_ON_NONPARTITIONED = 1747
ER_ROW_DOES_NOT_MATCH_GIVEN_PARTITION_SET = 1748
OBSOLETE_ER_NO_SUCH_PARTITION__UNUSED = 1749
ER_CHANGE_RPL_INFO_REPOSITORY_FAILURE = 1750
ER_WARNING_NOT_COMPLETE_ROLLBACK_WITH_CREATED_TEMP_TABLE = 1751
ER_WARNING_NOT_COMPLETE_ROLLBACK_WITH_DROPPED_TEMP_TABLE = 1752
ER_MTS_FEATURE_IS_NOT_SUPPORTED = 1753
ER_MTS_UPDATED_DBS_GREATER_MAX = 1754
ER_MTS_CANT_PARALLEL = 1755
ER_MTS_INCONSISTENT_DATA = 1756
ER_FULLTEXT_NOT_SUPPORTED_WITH_PARTITIONING = 1757
ER_DA_INVALID_CONDITION_NUMBER = 1758
ER_INSECURE_PLAIN_TEXT = 1759
ER_INSECURE_CHANGE_MASTER = 1760
ER_FOREIGN_DUPLICATE_KEY_WITH_CHILD_INFO = 1761
ER_FOREIGN_DUPLICATE_KEY_WITHOUT_CHILD_INFO = 1762
ER_SQLTHREAD_WITH_SECURE_SLAVE = 1763
ER_TABLE_HAS_NO_FT = 1764
ER_VARIABLE_NOT_SETTABLE_IN_SF_OR_TRIGGER = 1765
ER_VARIABLE_NOT_SETTABLE_IN_TRANSACTION = 1766
OBSOLETE_ER_GTID_NEXT_IS_NOT_IN_GTID_NEXT_LIST = 1767
OBSOLETE_ER_CANT_CHANGE_GTID_NEXT_IN_TRANSACTION = 1768
ER_SET_STATEMENT_CANNOT_INVOKE_FUNCTION = 1769
ER_GTID_NEXT_CANT_BE_AUTOMATIC_IF_GTID_NEXT_LIST_IS_NON_NULL = 1770
OBSOLETE_ER_SKIPPING_LOGGED_TRANSACTION = 1771
ER_MALFORMED_GTID_SET_SPECIFICATION = 1772
ER_MALFORMED_GTID_SET_ENCODING = 1773
ER_MALFORMED_GTID_SPECIFICATION = 1774
ER_GNO_EXHAUSTED = 1775
ER_BAD_SLAVE_AUTO_POSITION = 1776
ER_AUTO_POSITION_REQUIRES_GTID_MODE_NOT_OFF = 1777
ER_CANT_DO_IMPLICIT_COMMIT_IN_TRX_WHEN_GTID_NEXT_IS_SET = 1778
ER_GTID_MODE_ON_REQUIRES_ENFORCE_GTID_CONSISTENCY_ON = 1779
OBSOLETE_ER_GTID_MODE_REQUIRES_BINLOG = 1780
ER_CANT_SET_GTID_NEXT_TO_GTID_WHEN_GTID_MODE_IS_OFF = 1781
ER_CANT_SET_GTID_NEXT_TO_ANONYMOUS_WHEN_GTID_MODE_IS_ON = 1782
ER_CANT_SET_GTID_NEXT_LIST_TO_NON_NULL_WHEN_GTID_MODE_IS_OFF = 1783
OBSOLETE_ER_FOUND_GTID_EVENT_WHEN_GTID_MODE_IS_OFF__UNUSED = 1784
ER_GTID_UNSAFE_NON_TRANSACTIONAL_TABLE = 1785
ER_GTID_UNSAFE_CREATE_SELECT = 1786
OBSOLETE_ER_GTID_UNSAFE_CREATE_DROP_TEMP_TABLE_IN_TRANSACTION = 1787
ER_GTID_MODE_CAN_ONLY_CHANGE_ONE_STEP_AT_A_TIME = 1788
ER_MASTER_HAS_PURGED_REQUIRED_GTIDS = 1789
ER_CANT_SET_GTID_NEXT_WHEN_OWNING_GTID = 1790
ER_UNKNOWN_EXPLAIN_FORMAT = 1791
ER_CANT_EXECUTE_IN_READ_ONLY_TRANSACTION = 1792
ER_TOO_LONG_TABLE_PARTITION_COMMENT = 1793
ER_SLAVE_CONFIGURATION = 1794
ER_INNODB_FT_LIMIT = 1795
ER_INNODB_NO_FT_TEMP_TABLE = 1796
ER_INNODB_FT_WRONG_DOCID_COLUMN = 1797
ER_INNODB_FT_WRONG_DOCID_INDEX = 1798
ER_INNODB_ONLINE_LOG_TOO_BIG = 1799
ER_UNKNOWN_ALTER_ALGORITHM = 1800
ER_UNKNOWN_ALTER_LOCK = 1801
ER_MTS_CHANGE_MASTER_CANT_RUN_WITH_GAPS = 1802
ER_MTS_RECOVERY_FAILURE = 1803
ER_MTS_RESET_WORKERS = 1804
ER_COL_COUNT_DOESNT_MATCH_CORRUPTED_V2 = 1805
ER_SLAVE_SILENT_RETRY_TRANSACTION = 1806
ER_DISCARD_FK_CHECKS_RUNNING = 1807
ER_TABLE_SCHEMA_MISMATCH = 1808
ER_TABLE_IN_SYSTEM_TABLESPACE = 1809
ER_IO_READ_ERROR = 1810
ER_IO_WRITE_ERROR = 1811
ER_TABLESPACE_MISSING = 1812
ER_TABLESPACE_EXISTS = 1813
ER_TABLESPACE_DISCARDED = 1814
ER_INTERNAL_ERROR = 1815
ER_INNODB_IMPORT_ERROR = 1816
ER_INNODB_INDEX_CORRUPT = 1817
ER_INVALID_YEAR_COLUMN_LENGTH = 1818
ER_NOT_VALID_PASSWORD = 1819
ER_MUST_CHANGE_PASSWORD = 1820
ER_FK_NO_INDEX_CHILD = 1821
ER_FK_NO_INDEX_PARENT = 1822
ER_FK_FAIL_ADD_SYSTEM = 1823
ER_FK_CANNOT_OPEN_PARENT = 1824
ER_FK_INCORRECT_OPTION = 1825
ER_FK_DUP_NAME = 1826
ER_PASSWORD_FORMAT = 1827
ER_FK_COLUMN_CANNOT_DROP = 1828
ER_FK_COLUMN_CANNOT_DROP_CHILD = 1829
ER_FK_COLUMN_NOT_NULL = 1830
ER_DUP_INDEX = 1831
ER_FK_COLUMN_CANNOT_CHANGE = 1832
ER_FK_COLUMN_CANNOT_CHANGE_CHILD = 1833
OBSOLETE_ER_UNUSED5 = 1834
ER_MALFORMED_PACKET = 1835
ER_READ_ONLY_MODE = 1836
ER_GTID_NEXT_TYPE_UNDEFINED_GTID = 1837
ER_VARIABLE_NOT_SETTABLE_IN_SP = 1838
OBSOLETE_ER_CANT_SET_GTID_PURGED_WHEN_GTID_MODE_IS_OFF = 1839
ER_CANT_SET_GTID_PURGED_WHEN_GTID_EXECUTED_IS_NOT_EMPTY = 1840
ER_CANT_SET_GTID_PURGED_WHEN_OWNED_GTIDS_IS_NOT_EMPTY = 1841
ER_GTID_PURGED_WAS_CHANGED = 1842
ER_GTID_EXECUTED_WAS_CHANGED = 1843
ER_BINLOG_STMT_MODE_AND_NO_REPL_TABLES = 1844
ER_ALTER_OPERATION_NOT_SUPPORTED = 1845
ER_ALTER_OPERATION_NOT_SUPPORTED_REASON = 1846
ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_COPY = 1847
ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_PARTITION = 1848
ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_FK_RENAME = 1849
ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_COLUMN_TYPE = 1850
ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_FK_CHECK = 1851
OBSOLETE_ER_UNUSED6 = 1852
ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_NOPK = 1853
ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_AUTOINC = 1854
ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_HIDDEN_FTS = 1855
ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_CHANGE_FTS = 1856
ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_FTS = 1857
OBSOLETE_ER_SQL_REPLICA_SKIP_COUNTER_NOT_SETTABLE_IN_GTID_MODE = 1858
ER_DUP_UNKNOWN_IN_INDEX = 1859
ER_IDENT_CAUSES_TOO_LONG_PATH = 1860
ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_NOT_NULL = 1861
ER_MUST_CHANGE_PASSWORD_LOGIN = 1862
ER_ROW_IN_WRONG_PARTITION = 1863
ER_MTS_EVENT_BIGGER_PENDING_JOBS_SIZE_MAX = 1864
OBSOLETE_ER_INNODB_NO_FT_USES_PARSER = 1865
ER_BINLOG_LOGICAL_CORRUPTION = 1866
ER_WARN_PURGE_LOG_IN_USE = 1867
ER_WARN_PURGE_LOG_IS_ACTIVE = 1868
ER_AUTO_INCREMENT_CONFLICT = 1869
WARN_ON_BLOCKHOLE_IN_RBR = 1870
ER_SLAVE_MI_INIT_REPOSITORY = 1871
ER_SLAVE_RLI_INIT_REPOSITORY = 1872
ER_ACCESS_DENIED_CHANGE_USER_ERROR = 1873
ER_INNODB_READ_ONLY = 1874
ER_STOP_SLAVE_SQL_THREAD_TIMEOUT = 1875
ER_STOP_SLAVE_IO_THREAD_TIMEOUT = 1876
ER_TABLE_CORRUPT = 1877
ER_TEMP_FILE_WRITE_FAILURE = 1878
ER_INNODB_FT_AUX_NOT_HEX_ID = 1879
ER_OLD_TEMPORALS_UPGRADED = 1880
ER_INNODB_FORCED_RECOVERY = 1881
ER_AES_INVALID_IV = 1882
ER_PLUGIN_CANNOT_BE_UNINSTALLED = 1883
ER_GTID_UNSAFE_BINLOG_SPLITTABLE_STATEMENT_AND_ASSIGNED_GTID = 1884
ER_SLAVE_HAS_MORE_GTIDS_THAN_MASTER = 1885
ER_MISSING_KEY = 1886
WARN_NAMED_PIPE_ACCESS_EVERYONE = 1887
ER_FILE_CORRUPT = 3000
ER_ERROR_ON_MASTER = 3001
OBSOLETE_ER_INCONSISTENT_ERROR = 3002
ER_STORAGE_ENGINE_NOT_LOADED = 3003
ER_GET_STACKED_DA_WITHOUT_ACTIVE_HANDLER = 3004
ER_WARN_LEGACY_SYNTAX_CONVERTED = 3005
ER_BINLOG_UNSAFE_FULLTEXT_PLUGIN = 3006
ER_CANNOT_DISCARD_TEMPORARY_TABLE = 3007
ER_FK_DEPTH_EXCEEDED = 3008
ER_COL_COUNT_DOESNT_MATCH_PLEASE_UPDATE_V2 = 3009
ER_WARN_TRIGGER_DOESNT_HAVE_CREATED = 3010
ER_REFERENCED_TRG_DOES_NOT_EXIST = 3011
ER_EXPLAIN_NOT_SUPPORTED = 3012
ER_INVALID_FIELD_SIZE = 3013
ER_MISSING_HA_CREATE_OPTION = 3014
ER_ENGINE_OUT_OF_MEMORY = 3015
ER_PASSWORD_EXPIRE_ANONYMOUS_USER = 3016
ER_SLAVE_SQL_THREAD_MUST_STOP = 3017
ER_NO_FT_MATERIALIZED_SUBQUERY = 3018
ER_INNODB_UNDO_LOG_FULL = 3019
ER_INVALID_ARGUMENT_FOR_LOGARITHM = 3020
ER_SLAVE_CHANNEL_IO_THREAD_MUST_STOP = 3021
ER_WARN_OPEN_TEMP_TABLES_MUST_BE_ZERO = 3022
ER_WARN_ONLY_MASTER_LOG_FILE_NO_POS = 3023
ER_QUERY_TIMEOUT = 3024
ER_NON_RO_SELECT_DISABLE_TIMER = 3025
ER_DUP_LIST_ENTRY = 3026
OBSOLETE_ER_SQL_MODE_NO_EFFECT = 3027
ER_AGGREGATE_ORDER_FOR_UNION = 3028
ER_AGGREGATE_ORDER_NON_AGG_QUERY = 3029
ER_SLAVE_WORKER_STOPPED_PREVIOUS_THD_ERROR = 3030
ER_DONT_SUPPORT_REPLICA_PRESERVE_COMMIT_ORDER = 3031
ER_SERVER_OFFLINE_MODE = 3032
ER_GIS_DIFFERENT_SRIDS = 3033
ER_GIS_UNSUPPORTED_ARGUMENT = 3034
ER_GIS_UNKNOWN_ERROR = 3035
ER_GIS_UNKNOWN_EXCEPTION = 3036
ER_GIS_INVALID_DATA = 3037
ER_BOOST_GEOMETRY_EMPTY_INPUT_EXCEPTION = 3038
ER_BOOST_GEOMETRY_CENTROID_EXCEPTION = 3039
ER_BOOST_GEOMETRY_OVERLAY_INVALID_INPUT_EXCEPTION = 3040
ER_BOOST_GEOMETRY_TURN_INFO_EXCEPTION = 3041
ER_BOOST_GEOMETRY_SELF_INTERSECTION_POINT_EXCEPTION = 3042
ER_BOOST_GEOMETRY_UNKNOWN_EXCEPTION = 3043
ER_STD_BAD_ALLOC_ERROR = 3044
ER_STD_DOMAIN_ERROR = 3045
ER_STD_LENGTH_ERROR = 3046
ER_STD_INVALID_ARGUMENT = 3047
ER_STD_OUT_OF_RANGE_ERROR = 3048
ER_STD_OVERFLOW_ERROR = 3049
ER_STD_RANGE_ERROR = 3050
ER_STD_UNDERFLOW_ERROR = 3051
ER_STD_LOGIC_ERROR = 3052
ER_STD_RUNTIME_ERROR = 3053
ER_STD_UNKNOWN_EXCEPTION = 3054
ER_GIS_DATA_WRONG_ENDIANESS = 3055
ER_CHANGE_MASTER_PASSWORD_LENGTH = 3056
ER_USER_LOCK_WRONG_NAME = 3057
ER_USER_LOCK_DEADLOCK = 3058
ER_REPLACE_INACCESSIBLE_ROWS = 3059
ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_GIS = 3060
ER_ILLEGAL_USER_VAR = 3061
ER_GTID_MODE_OFF = 3062
OBSOLETE_ER_UNSUPPORTED_BY_REPLICATION_THREAD = 3063
ER_INCORRECT_TYPE = 3064
ER_FIELD_IN_ORDER_NOT_SELECT = 3065
ER_AGGREGATE_IN_ORDER_NOT_SELECT = 3066
ER_INVALID_RPL_WILD_TABLE_FILTER_PATTERN = 3067
ER_NET_OK_PACKET_TOO_LARGE = 3068
ER_INVALID_JSON_DATA = 3069
ER_INVALID_GEOJSON_MISSING_MEMBER = 3070
ER_INVALID_GEOJSON_WRONG_TYPE = 3071
ER_INVALID_GEOJSON_UNSPECIFIED = 3072
ER_DIMENSION_UNSUPPORTED = 3073
ER_SLAVE_CHANNEL_DOES_NOT_EXIST = 3074
OBSOLETE_ER_SLAVE_MULTIPLE_CHANNELS_HOST_PORT = 3075
ER_SLAVE_CHANNEL_NAME_INVALID_OR_TOO_LONG = 3076
ER_SLAVE_NEW_CHANNEL_WRONG_REPOSITORY = 3077
OBSOLETE_ER_SLAVE_CHANNEL_DELETE = 3078
ER_SLAVE_MULTIPLE_CHANNELS_CMD = 3079
ER_SLAVE_MAX_CHANNELS_EXCEEDED = 3080
ER_SLAVE_CHANNEL_MUST_STOP = 3081
ER_SLAVE_CHANNEL_NOT_RUNNING = 3082
ER_SLAVE_CHANNEL_WAS_RUNNING = 3083
ER_SLAVE_CHANNEL_WAS_NOT_RUNNING = 3084
ER_SLAVE_CHANNEL_SQL_THREAD_MUST_STOP = 3085
ER_SLAVE_CHANNEL_SQL_SKIP_COUNTER = 3086
ER_WRONG_FIELD_WITH_GROUP_V2 = 3087
ER_MIX_OF_GROUP_FUNC_AND_FIELDS_V2 = 3088
ER_WARN_DEPRECATED_SYSVAR_UPDATE = 3089
ER_WARN_DEPRECATED_SQLMODE = 3090
ER_CANNOT_LOG_PARTIAL_DROP_DATABASE_WITH_GTID = 3091
ER_GROUP_REPLICATION_CONFIGURATION = 3092
ER_GROUP_REPLICATION_RUNNING = 3093
ER_GROUP_REPLICATION_APPLIER_INIT_ERROR = 3094
ER_GROUP_REPLICATION_STOP_APPLIER_THREAD_TIMEOUT = 3095
ER_GROUP_REPLICATION_COMMUNICATION_LAYER_SESSION_ERROR = 3096
ER_GROUP_REPLICATION_COMMUNICATION_LAYER_JOIN_ERROR = 3097
ER_BEFORE_DML_VALIDATION_ERROR = 3098
ER_PREVENTS_VARIABLE_WITHOUT_RBR = 3099
ER_RUN_HOOK_ERROR = 3100
ER_TRANSACTION_ROLLBACK_DURING_COMMIT = 3101
ER_GENERATED_COLUMN_FUNCTION_IS_NOT_ALLOWED = 3102
ER_UNSUPPORTED_ALTER_INPLACE_ON_VIRTUAL_COLUMN = 3103
ER_WRONG_FK_OPTION_FOR_GENERATED_COLUMN = 3104
ER_NON_DEFAULT_VALUE_FOR_GENERATED_COLUMN = 3105
ER_UNSUPPORTED_ACTION_ON_GENERATED_COLUMN = 3106
ER_GENERATED_COLUMN_NON_PRIOR = 3107
ER_DEPENDENT_BY_GENERATED_COLUMN = 3108
ER_GENERATED_COLUMN_REF_AUTO_INC = 3109
ER_FEATURE_NOT_AVAILABLE = 3110
ER_CANT_SET_GTID_MODE = 3111
ER_CANT_USE_AUTO_POSITION_WITH_GTID_MODE_OFF = 3112
OBSOLETE_ER_CANT_REPLICATE_ANONYMOUS_WITH_AUTO_POSITION = 3113
OBSOLETE_ER_CANT_REPLICATE_ANONYMOUS_WITH_GTID_MODE_ON = 3114
OBSOLETE_ER_CANT_REPLICATE_GTID_WITH_GTID_MODE_OFF = 3115
ER_CANT_ENFORCE_GTID_CONSISTENCY_WITH_ONGOING_GTID_VIOLATING_TX = 3116
ER_ENFORCE_GTID_CONSISTENCY_WARN_WITH_ONGOING_GTID_VIOLATING_TX = 3117
ER_ACCOUNT_HAS_BEEN_LOCKED = 3118
ER_WRONG_TABLESPACE_NAME = 3119
ER_TABLESPACE_IS_NOT_EMPTY = 3120
ER_WRONG_FILE_NAME = 3121
ER_BOOST_GEOMETRY_INCONSISTENT_TURNS_EXCEPTION = 3122
ER_WARN_OPTIMIZER_HINT_SYNTAX_ERROR = 3123
ER_WARN_BAD_MAX_EXECUTION_TIME = 3124
ER_WARN_UNSUPPORTED_MAX_EXECUTION_TIME = 3125
ER_WARN_CONFLICTING_HINT = 3126
ER_WARN_UNKNOWN_QB_NAME = 3127
ER_UNRESOLVED_HINT_NAME = 3128
ER_WARN_ON_MODIFYING_GTID_EXECUTED_TABLE = 3129
ER_PLUGGABLE_PROTOCOL_COMMAND_NOT_SUPPORTED = 3130
ER_LOCKING_SERVICE_WRONG_NAME = 3131
ER_LOCKING_SERVICE_DEADLOCK = 3132
ER_LOCKING_SERVICE_TIMEOUT = 3133
ER_GIS_MAX_POINTS_IN_GEOMETRY_OVERFLOWED = 3134
ER_SQL_MODE_MERGED = 3135
ER_VTOKEN_PLUGIN_TOKEN_MISMATCH = 3136
ER_VTOKEN_PLUGIN_TOKEN_NOT_FOUND = 3137
ER_CANT_SET_VARIABLE_WHEN_OWNING_GTID = 3138
ER_SLAVE_CHANNEL_OPERATION_NOT_ALLOWED = 3139
ER_INVALID_JSON_TEXT = 3140
ER_INVALID_JSON_TEXT_IN_PARAM = 3141
ER_INVALID_JSON_BINARY_DATA = 3142
ER_INVALID_JSON_PATH = 3143
ER_INVALID_JSON_CHARSET = 3144
ER_INVALID_JSON_CHARSET_IN_FUNCTION = 3145
ER_INVALID_TYPE_FOR_JSON = 3146
ER_INVALID_CAST_TO_JSON = 3147
ER_INVALID_JSON_PATH_CHARSET = 3148
ER_INVALID_JSON_PATH_WILDCARD = 3149
ER_JSON_VALUE_TOO_BIG = 3150
ER_JSON_KEY_TOO_BIG = 3151
ER_JSON_USED_AS_KEY = 3152
ER_JSON_VACUOUS_PATH = 3153
ER_JSON_BAD_ONE_OR_ALL_ARG = 3154
ER_NUMERIC_JSON_VALUE_OUT_OF_RANGE = 3155
ER_INVALID_JSON_VALUE_FOR_CAST = 3156
ER_JSON_DOCUMENT_TOO_DEEP = 3157
ER_JSON_DOCUMENT_NULL_KEY = 3158
ER_SECURE_TRANSPORT_REQUIRED = 3159
ER_NO_SECURE_TRANSPORTS_CONFIGURED = 3160
ER_DISABLED_STORAGE_ENGINE = 3161
ER_USER_DOES_NOT_EXIST = 3162
ER_USER_ALREADY_EXISTS = 3163
ER_AUDIT_API_ABORT = 3164
ER_INVALID_JSON_PATH_ARRAY_CELL = 3165
ER_BUFPOOL_RESIZE_INPROGRESS = 3166
ER_FEATURE_DISABLED_SEE_DOC = 3167
ER_SERVER_ISNT_AVAILABLE = 3168
ER_SESSION_WAS_KILLED = 3169
ER_CAPACITY_EXCEEDED = 3170
ER_CAPACITY_EXCEEDED_IN_RANGE_OPTIMIZER = 3171
OBSOLETE_ER_TABLE_NEEDS_UPG_PART = 3172
ER_CANT_WAIT_FOR_EXECUTED_GTID_SET_WHILE_OWNING_A_GTID = 3173
ER_CANNOT_ADD_FOREIGN_BASE_COL_VIRTUAL = 3174
ER_CANNOT_CREATE_VIRTUAL_INDEX_CONSTRAINT = 3175
ER_ERROR_ON_MODIFYING_GTID_EXECUTED_TABLE = 3176
ER_LOCK_REFUSED_BY_ENGINE = 3177
ER_UNSUPPORTED_ALTER_ONLINE_ON_VIRTUAL_COLUMN = 3178
ER_MASTER_KEY_ROTATION_NOT_SUPPORTED_BY_SE = 3179
OBSOLETE_ER_MASTER_KEY_ROTATION_ERROR_BY_SE = 3180
ER_MASTER_KEY_ROTATION_BINLOG_FAILED = 3181
ER_MASTER_KEY_ROTATION_SE_UNAVAILABLE = 3182
ER_TABLESPACE_CANNOT_ENCRYPT = 3183
ER_INVALID_ENCRYPTION_OPTION = 3184
ER_CANNOT_FIND_KEY_IN_KEYRING = 3185
ER_CAPACITY_EXCEEDED_IN_PARSER = 3186
ER_UNSUPPORTED_ALTER_ENCRYPTION_INPLACE = 3187
ER_KEYRING_UDF_KEYRING_SERVICE_ERROR = 3188
ER_USER_COLUMN_OLD_LENGTH = 3189
ER_CANT_RESET_MASTER = 3190
ER_GROUP_REPLICATION_MAX_GROUP_SIZE = 3191
ER_CANNOT_ADD_FOREIGN_BASE_COL_STORED = 3192
ER_TABLE_REFERENCED = 3193
OBSOLETE_ER_PARTITION_ENGINE_DEPRECATED_FOR_TABLE = 3194
OBSOLETE_ER_WARN_USING_GEOMFROMWKB_TO_SET_SRID_ZERO = 3195
OBSOLETE_ER_WARN_USING_GEOMFROMWKB_TO_SET_SRID = 3196
ER_XA_RETRY = 3197
ER_KEYRING_AWS_UDF_AWS_KMS_ERROR = 3198
ER_BINLOG_UNSAFE_XA = 3199
ER_UDF_ERROR = 3200
ER_KEYRING_MIGRATION_FAILURE = 3201
ER_KEYRING_ACCESS_DENIED_ERROR = 3202
ER_KEYRING_MIGRATION_STATUS = 3203
OBSOLETE_ER_PLUGIN_FAILED_TO_OPEN_TABLES = 3204
OBSOLETE_ER_PLUGIN_FAILED_TO_OPEN_TABLE = 3205
OBSOLETE_ER_AUDIT_LOG_NO_KEYRING_PLUGIN_INSTALLED = 3206
OBSOLETE_ER_AUDIT_LOG_ENCRYPTION_PASSWORD_HAS_NOT_BEEN_SET = 3207
OBSOLETE_ER_AUDIT_LOG_COULD_NOT_CREATE_AES_KEY = 3208
OBSOLETE_ER_AUDIT_LOG_ENCRYPTION_PASSWORD_CANNOT_BE_FETCHED = 3209
OBSOLETE_ER_AUDIT_LOG_JSON_FILTERING_NOT_ENABLED = 3210
OBSOLETE_ER_AUDIT_LOG_UDF_INSUFFICIENT_PRIVILEGE = 3211
OBSOLETE_ER_AUDIT_LOG_SUPER_PRIVILEGE_REQUIRED = 3212
OBSOLETE_ER_COULD_NOT_REINITIALIZE_AUDIT_LOG_FILTERS = 3213
OBSOLETE_ER_AUDIT_LOG_UDF_INVALID_ARGUMENT_TYPE = 3214
OBSOLETE_ER_AUDIT_LOG_UDF_INVALID_ARGUMENT_COUNT = 3215
OBSOLETE_ER_AUDIT_LOG_HAS_NOT_BEEN_INSTALLED = 3216
OBSOLETE_ER_AUDIT_LOG_UDF_READ_INVALID_MAX_ARRAY_LENGTH_ARG_TYPE = 3217
ER_AUDIT_LOG_UDF_READ_INVALID_MAX_ARRAY_LENGTH_ARG_VALUE = 3218
OBSOLETE_ER_AUDIT_LOG_JSON_FILTER_PARSING_ERROR = 3219
OBSOLETE_ER_AUDIT_LOG_JSON_FILTER_NAME_CANNOT_BE_EMPTY = 3220
OBSOLETE_ER_AUDIT_LOG_JSON_USER_NAME_CANNOT_BE_EMPTY = 3221
OBSOLETE_ER_AUDIT_LOG_JSON_FILTER_DOES_NOT_EXISTS = 3222
OBSOLETE_ER_AUDIT_LOG_USER_FIRST_CHARACTER_MUST_BE_ALPHANUMERIC = 3223
OBSOLETE_ER_AUDIT_LOG_USER_NAME_INVALID_CHARACTER = 3224
OBSOLETE_ER_AUDIT_LOG_HOST_NAME_INVALID_CHARACTER = 3225
OBSOLETE_ER_XA_REPLICATION_FILTERS = 3226
OBSOLETE_ER_CANT_OPEN_ERROR_LOG = 3227
OBSOLETE_ER_GROUPING_ON_TIMESTAMP_IN_DST = 3228
OBSOLETE_ER_CANT_START_SERVER_NAMED_PIPE = 3229
ER_WRITE_SET_EXCEEDS_LIMIT = 3230
ER_UNSUPPORT_COMPRESSED_TEMPORARY_TABLE = 3500
ER_ACL_OPERATION_FAILED = 3501
ER_UNSUPPORTED_INDEX_ALGORITHM = 3502
ER_NO_SUCH_DB = 3503
ER_TOO_BIG_ENUM = 3504
ER_TOO_LONG_SET_ENUM_VALUE = 3505
ER_INVALID_DD_OBJECT = 3506
ER_UPDATING_DD_TABLE = 3507
ER_INVALID_DD_OBJECT_ID = 3508
ER_INVALID_DD_OBJECT_NAME = 3509
ER_TABLESPACE_MISSING_WITH_NAME = 3510
ER_TOO_LONG_ROUTINE_COMMENT = 3511
ER_SP_LOAD_FAILED = 3512
ER_INVALID_BITWISE_OPERANDS_SIZE = 3513
ER_INVALID_BITWISE_AGGREGATE_OPERANDS_SIZE = 3514
ER_WARN_UNSUPPORTED_HINT = 3515
ER_UNEXPECTED_GEOMETRY_TYPE = 3516
ER_SRS_PARSE_ERROR = 3517
ER_SRS_PROJ_PARAMETER_MISSING = 3518
ER_WARN_SRS_NOT_FOUND = 3519
ER_SRS_NOT_CARTESIAN = 3520
ER_SRS_NOT_CARTESIAN_UNDEFINED = 3521
ER_PK_INDEX_CANT_BE_INVISIBLE = 3522
ER_UNKNOWN_AUTHID = 3523
ER_FAILED_ROLE_GRANT = 3524
ER_OPEN_ROLE_TABLES = 3525
ER_FAILED_DEFAULT_ROLES = 3526
ER_COMPONENTS_NO_SCHEME = 3527
ER_COMPONENTS_NO_SCHEME_SERVICE = 3528
ER_COMPONENTS_CANT_LOAD = 3529
ER_ROLE_NOT_GRANTED = 3530
ER_FAILED_REVOKE_ROLE = 3531
ER_RENAME_ROLE = 3532
ER_COMPONENTS_CANT_ACQUIRE_SERVICE_IMPLEMENTATION = 3533
ER_COMPONENTS_CANT_SATISFY_DEPENDENCY = 3534
ER_COMPONENTS_LOAD_CANT_REGISTER_SERVICE_IMPLEMENTATION = 3535
ER_COMPONENTS_LOAD_CANT_INITIALIZE = 3536
ER_COMPONENTS_UNLOAD_NOT_LOADED = 3537
ER_COMPONENTS_UNLOAD_CANT_DEINITIALIZE = 3538
ER_COMPONENTS_CANT_RELEASE_SERVICE = 3539
ER_COMPONENTS_UNLOAD_CANT_UNREGISTER_SERVICE = 3540
ER_COMPONENTS_CANT_UNLOAD = 3541
ER_WARN_UNLOAD_THE_NOT_PERSISTED = 3542
ER_COMPONENT_TABLE_INCORRECT = 3543
ER_COMPONENT_MANIPULATE_ROW_FAILED = 3544
ER_COMPONENTS_UNLOAD_DUPLICATE_IN_GROUP = 3545
ER_CANT_SET_GTID_PURGED_DUE_SETS_CONSTRAINTS = 3546
ER_CANNOT_LOCK_USER_MANAGEMENT_CACHES = 3547
ER_SRS_NOT_FOUND = 3548
ER_VARIABLE_NOT_PERSISTED = 3549
ER_IS_QUERY_INVALID_CLAUSE = 3550
ER_UNABLE_TO_STORE_STATISTICS = 3551
ER_NO_SYSTEM_SCHEMA_ACCESS = 3552
ER_NO_SYSTEM_TABLESPACE_ACCESS = 3553
ER_NO_SYSTEM_TABLE_ACCESS = 3554
ER_NO_SYSTEM_TABLE_ACCESS_FOR_DICTIONARY_TABLE = 3555
ER_NO_SYSTEM_TABLE_ACCESS_FOR_SYSTEM_TABLE = 3556
ER_NO_SYSTEM_TABLE_ACCESS_FOR_TABLE = 3557
ER_INVALID_OPTION_KEY = 3558
ER_INVALID_OPTION_VALUE = 3559
ER_INVALID_OPTION_KEY_VALUE_PAIR = 3560
ER_INVALID_OPTION_START_CHARACTER = 3561
ER_INVALID_OPTION_END_CHARACTER = 3562
ER_INVALID_OPTION_CHARACTERS = 3563
ER_DUPLICATE_OPTION_KEY = 3564
ER_WARN_SRS_NOT_FOUND_AXIS_ORDER = 3565
ER_NO_ACCESS_TO_NATIVE_FCT = 3566
ER_RESET_MASTER_TO_VALUE_OUT_OF_RANGE = 3567
ER_UNRESOLVED_TABLE_LOCK = 3568
ER_DUPLICATE_TABLE_LOCK = 3569
ER_BINLOG_UNSAFE_SKIP_LOCKED = 3570
ER_BINLOG_UNSAFE_NOWAIT = 3571
ER_LOCK_NOWAIT = 3572
ER_CTE_RECURSIVE_REQUIRES_UNION = 3573
ER_CTE_RECURSIVE_REQUIRES_NONRECURSIVE_FIRST = 3574
ER_CTE_RECURSIVE_FORBIDS_AGGREGATION = 3575
ER_CTE_RECURSIVE_FORBIDDEN_JOIN_ORDER = 3576
ER_CTE_RECURSIVE_REQUIRES_SINGLE_REFERENCE = 3577
ER_SWITCH_TMP_ENGINE = 3578
ER_WINDOW_NO_SUCH_WINDOW = 3579
ER_WINDOW_CIRCULARITY_IN_WINDOW_GRAPH = 3580
ER_WINDOW_NO_CHILD_PARTITIONING = 3581
ER_WINDOW_NO_INHERIT_FRAME = 3582
ER_WINDOW_NO_REDEFINE_ORDER_BY = 3583
ER_WINDOW_FRAME_START_ILLEGAL = 3584
ER_WINDOW_FRAME_END_ILLEGAL = 3585
ER_WINDOW_FRAME_ILLEGAL = 3586
ER_WINDOW_RANGE_FRAME_ORDER_TYPE = 3587
ER_WINDOW_RANGE_FRAME_TEMPORAL_TYPE = 3588
ER_WINDOW_RANGE_FRAME_NUMERIC_TYPE = 3589
ER_WINDOW_RANGE_BOUND_NOT_CONSTANT = 3590
ER_WINDOW_DUPLICATE_NAME = 3591
ER_WINDOW_ILLEGAL_ORDER_BY = 3592
ER_WINDOW_INVALID_WINDOW_FUNC_USE = 3593
ER_WINDOW_INVALID_WINDOW_FUNC_ALIAS_USE = 3594
ER_WINDOW_NESTED_WINDOW_FUNC_USE_IN_WINDOW_SPEC = 3595
ER_WINDOW_ROWS_INTERVAL_USE = 3596
ER_WINDOW_NO_GROUP_ORDER_UNUSED = 3597
ER_WINDOW_EXPLAIN_JSON = 3598
ER_WINDOW_FUNCTION_IGNORES_FRAME = 3599
ER_WL9236_NOW_UNUSED = 3600
ER_INVALID_NO_OF_ARGS = 3601
ER_FIELD_IN_GROUPING_NOT_GROUP_BY = 3602
ER_TOO_LONG_TABLESPACE_COMMENT = 3603
ER_ENGINE_CANT_DROP_TABLE = 3604
ER_ENGINE_CANT_DROP_MISSING_TABLE = 3605
ER_TABLESPACE_DUP_FILENAME = 3606
ER_DB_DROP_RMDIR2 = 3607
ER_IMP_NO_FILES_MATCHED = 3608
ER_IMP_SCHEMA_DOES_NOT_EXIST = 3609
ER_IMP_TABLE_ALREADY_EXISTS = 3610
ER_IMP_INCOMPATIBLE_MYSQLD_VERSION = 3611
ER_IMP_INCOMPATIBLE_DD_VERSION = 3612
ER_IMP_INCOMPATIBLE_SDI_VERSION = 3613
ER_WARN_INVALID_HINT = 3614
ER_VAR_DOES_NOT_EXIST = 3615
ER_LONGITUDE_OUT_OF_RANGE = 3616
ER_LATITUDE_OUT_OF_RANGE = 3617
ER_NOT_IMPLEMENTED_FOR_GEOGRAPHIC_SRS = 3618
ER_ILLEGAL_PRIVILEGE_LEVEL = 3619
ER_NO_SYSTEM_VIEW_ACCESS = 3620
ER_COMPONENT_FILTER_FLABBERGASTED = 3621
ER_PART_EXPR_TOO_LONG = 3622
ER_UDF_DROP_DYNAMICALLY_REGISTERED = 3623
ER_UNABLE_TO_STORE_COLUMN_STATISTICS = 3624
ER_UNABLE_TO_UPDATE_COLUMN_STATISTICS = 3625
ER_UNABLE_TO_DROP_COLUMN_STATISTICS = 3626
ER_UNABLE_TO_BUILD_HISTOGRAM = 3627
ER_MANDATORY_ROLE = 3628
ER_MISSING_TABLESPACE_FILE = 3629
ER_PERSIST_ONLY_ACCESS_DENIED_ERROR = 3630
ER_CMD_NEED_SUPER = 3631
ER_PATH_IN_DATADIR = 3632
ER_CLONE_DDL_IN_PROGRESS = 3633
ER_CLONE_TOO_MANY_CONCURRENT_CLONES = 3634
ER_APPLIER_LOG_EVENT_VALIDATION_ERROR = 3635
ER_CTE_MAX_RECURSION_DEPTH = 3636
ER_NOT_HINT_UPDATABLE_VARIABLE = 3637
ER_CREDENTIALS_CONTRADICT_TO_HISTORY = 3638
ER_WARNING_PASSWORD_HISTORY_CLAUSES_VOID = 3639
ER_CLIENT_DOES_NOT_SUPPORT = 3640
ER_I_S_SKIPPED_TABLESPACE = 3641
ER_TABLESPACE_ENGINE_MISMATCH = 3642
ER_WRONG_SRID_FOR_COLUMN = 3643
ER_CANNOT_ALTER_SRID_DUE_TO_INDEX = 3644
ER_WARN_BINLOG_PARTIAL_UPDATES_DISABLED = 3645
ER_WARN_BINLOG_V1_ROW_EVENTS_DISABLED = 3646
ER_WARN_BINLOG_PARTIAL_UPDATES_SUGGESTS_PARTIAL_IMAGES = 3647
ER_COULD_NOT_APPLY_JSON_DIFF = 3648
ER_CORRUPTED_JSON_DIFF = 3649
ER_RESOURCE_GROUP_EXISTS = 3650
ER_RESOURCE_GROUP_NOT_EXISTS = 3651
ER_INVALID_VCPU_ID = 3652
ER_INVALID_VCPU_RANGE = 3653
ER_INVALID_THREAD_PRIORITY = 3654
ER_DISALLOWED_OPERATION = 3655
ER_RESOURCE_GROUP_BUSY = 3656
ER_RESOURCE_GROUP_DISABLED = 3657
ER_FEATURE_UNSUPPORTED = 3658
ER_ATTRIBUTE_IGNORED = 3659
ER_INVALID_THREAD_ID = 3660
ER_RESOURCE_GROUP_BIND_FAILED = 3661
ER_INVALID_USE_OF_FORCE_OPTION = 3662
ER_GROUP_REPLICATION_COMMAND_FAILURE = 3663
ER_SDI_OPERATION_FAILED = 3664
ER_MISSING_JSON_TABLE_VALUE = 3665
ER_WRONG_JSON_TABLE_VALUE = 3666
ER_TF_MUST_HAVE_ALIAS = 3667
ER_TF_FORBIDDEN_JOIN_TYPE = 3668
ER_JT_VALUE_OUT_OF_RANGE = 3669
ER_JT_MAX_NESTED_PATH = 3670
ER_PASSWORD_EXPIRATION_NOT_SUPPORTED_BY_AUTH_METHOD = 3671
ER_INVALID_GEOJSON_CRS_NOT_TOP_LEVEL = 3672
ER_BAD_NULL_ERROR_NOT_IGNORED = 3673
WARN_USELESS_SPATIAL_INDEX = 3674
ER_DISK_FULL_NOWAIT = 3675
ER_PARSE_ERROR_IN_DIGEST_FN = 3676
ER_UNDISCLOSED_PARSE_ERROR_IN_DIGEST_FN = 3677
ER_SCHEMA_DIR_EXISTS = 3678
ER_SCHEMA_DIR_MISSING = 3679
ER_SCHEMA_DIR_CREATE_FAILED = 3680
ER_SCHEMA_DIR_UNKNOWN = 3681
ER_ONLY_IMPLEMENTED_FOR_SRID_0_AND_4326 = 3682
ER_BINLOG_EXPIRE_LOG_DAYS_AND_SECS_USED_TOGETHER = 3683
ER_REGEXP_BUFFER_OVERFLOW = 3684
ER_REGEXP_ILLEGAL_ARGUMENT = 3685
ER_REGEXP_INDEX_OUTOFBOUNDS_ERROR = 3686
ER_REGEXP_INTERNAL_ERROR = 3687
ER_REGEXP_RULE_SYNTAX = 3688
ER_REGEXP_BAD_ESCAPE_SEQUENCE = 3689
ER_REGEXP_UNIMPLEMENTED = 3690
ER_REGEXP_MISMATCHED_PAREN = 3691
ER_REGEXP_BAD_INTERVAL = 3692
ER_REGEXP_MAX_LT_MIN = 3693
ER_REGEXP_INVALID_BACK_REF = 3694
ER_REGEXP_LOOK_BEHIND_LIMIT = 3695
ER_REGEXP_MISSING_CLOSE_BRACKET = 3696
ER_REGEXP_INVALID_RANGE = 3697
ER_REGEXP_STACK_OVERFLOW = 3698
ER_REGEXP_TIME_OUT = 3699
ER_REGEXP_PATTERN_TOO_BIG = 3700
ER_CANT_SET_ERROR_LOG_SERVICE = 3701
ER_EMPTY_PIPELINE_FOR_ERROR_LOG_SERVICE = 3702
ER_COMPONENT_FILTER_DIAGNOSTICS = 3703
ER_NOT_IMPLEMENTED_FOR_CARTESIAN_SRS = 3704
ER_NOT_IMPLEMENTED_FOR_PROJECTED_SRS = 3705
ER_NONPOSITIVE_RADIUS = 3706
ER_RESTART_SERVER_FAILED = 3707
ER_SRS_MISSING_MANDATORY_ATTRIBUTE = 3708
ER_SRS_MULTIPLE_ATTRIBUTE_DEFINITIONS = 3709
ER_SRS_NAME_CANT_BE_EMPTY_OR_WHITESPACE = 3710
ER_SRS_ORGANIZATION_CANT_BE_EMPTY_OR_WHITESPACE = 3711
ER_SRS_ID_ALREADY_EXISTS = 3712
ER_WARN_SRS_ID_ALREADY_EXISTS = 3713
ER_CANT_MODIFY_SRID_0 = 3714
ER_WARN_RESERVED_SRID_RANGE = 3715
ER_CANT_MODIFY_SRS_USED_BY_COLUMN = 3716
ER_SRS_INVALID_CHARACTER_IN_ATTRIBUTE = 3717
ER_SRS_ATTRIBUTE_STRING_TOO_LONG = 3718
ER_DEPRECATED_UTF8_ALIAS = 3719
ER_DEPRECATED_NATIONAL = 3720
ER_INVALID_DEFAULT_UTF8MB4_COLLATION = 3721
ER_UNABLE_TO_COLLECT_LOG_STATUS = 3722
ER_RESERVED_TABLESPACE_NAME = 3723
ER_UNABLE_TO_SET_OPTION = 3724
ER_SLAVE_POSSIBLY_DIVERGED_AFTER_DDL = 3725
ER_SRS_NOT_GEOGRAPHIC = 3726
ER_POLYGON_TOO_LARGE = 3727
ER_SPATIAL_UNIQUE_INDEX = 3728
ER_INDEX_TYPE_NOT_SUPPORTED_FOR_SPATIAL_INDEX = 3729
ER_FK_CANNOT_DROP_PARENT = 3730
ER_GEOMETRY_PARAM_LONGITUDE_OUT_OF_RANGE = 3731
ER_GEOMETRY_PARAM_LATITUDE_OUT_OF_RANGE = 3732
ER_FK_CANNOT_USE_VIRTUAL_COLUMN = 3733
ER_FK_NO_COLUMN_PARENT = 3734
ER_CANT_SET_ERROR_SUPPRESSION_LIST = 3735
ER_SRS_GEOGCS_INVALID_AXES = 3736
ER_SRS_INVALID_SEMI_MAJOR_AXIS = 3737
ER_SRS_INVALID_INVERSE_FLATTENING = 3738
ER_SRS_INVALID_ANGULAR_UNIT = 3739
ER_SRS_INVALID_PRIME_MERIDIAN = 3740
ER_TRANSFORM_SOURCE_SRS_NOT_SUPPORTED = 3741
ER_TRANSFORM_TARGET_SRS_NOT_SUPPORTED = 3742
ER_TRANSFORM_SOURCE_SRS_MISSING_TOWGS84 = 3743
ER_TRANSFORM_TARGET_SRS_MISSING_TOWGS84 = 3744
ER_TEMP_TABLE_PREVENTS_SWITCH_SESSION_BINLOG_FORMAT = 3745
ER_TEMP_TABLE_PREVENTS_SWITCH_GLOBAL_BINLOG_FORMAT = 3746
ER_RUNNING_APPLIER_PREVENTS_SWITCH_GLOBAL_BINLOG_FORMAT = 3747
ER_CLIENT_GTID_UNSAFE_CREATE_DROP_TEMP_TABLE_IN_TRX_IN_SBR = 3748
OBSOLETE_ER_XA_CANT_CREATE_MDL_BACKUP = 3749
ER_TABLE_WITHOUT_PK = 3750
ER_WARN_DATA_TRUNCATED_FUNCTIONAL_INDEX = 3751
ER_WARN_DATA_OUT_OF_RANGE_FUNCTIONAL_INDEX = 3752
ER_FUNCTIONAL_INDEX_ON_JSON_OR_GEOMETRY_FUNCTION = 3753
ER_FUNCTIONAL_INDEX_REF_AUTO_INCREMENT = 3754
ER_CANNOT_DROP_COLUMN_FUNCTIONAL_INDEX = 3755
ER_FUNCTIONAL_INDEX_PRIMARY_KEY = 3756
ER_FUNCTIONAL_INDEX_ON_LOB = 3757
ER_FUNCTIONAL_INDEX_FUNCTION_IS_NOT_ALLOWED = 3758
ER_FULLTEXT_FUNCTIONAL_INDEX = 3759
ER_SPATIAL_FUNCTIONAL_INDEX = 3760
ER_WRONG_KEY_COLUMN_FUNCTIONAL_INDEX = 3761
ER_FUNCTIONAL_INDEX_ON_FIELD = 3762
ER_GENERATED_COLUMN_NAMED_FUNCTION_IS_NOT_ALLOWED = 3763
ER_GENERATED_COLUMN_ROW_VALUE = 3764
ER_GENERATED_COLUMN_VARIABLES = 3765
ER_DEPENDENT_BY_DEFAULT_GENERATED_VALUE = 3766
ER_DEFAULT_VAL_GENERATED_NON_PRIOR = 3767
ER_DEFAULT_VAL_GENERATED_REF_AUTO_INC = 3768
ER_DEFAULT_VAL_GENERATED_FUNCTION_IS_NOT_ALLOWED = 3769
ER_DEFAULT_VAL_GENERATED_NAMED_FUNCTION_IS_NOT_ALLOWED = 3770
ER_DEFAULT_VAL_GENERATED_ROW_VALUE = 3771
ER_DEFAULT_VAL_GENERATED_VARIABLES = 3772
ER_DEFAULT_AS_VAL_GENERATED = 3773
ER_UNSUPPORTED_ACTION_ON_DEFAULT_VAL_GENERATED = 3774
ER_GTID_UNSAFE_ALTER_ADD_COL_WITH_DEFAULT_EXPRESSION = 3775
ER_FK_CANNOT_CHANGE_ENGINE = 3776
ER_WARN_DEPRECATED_USER_SET_EXPR = 3777
ER_WARN_DEPRECATED_UTF8MB3_COLLATION = 3778
ER_WARN_DEPRECATED_NESTED_COMMENT_SYNTAX = 3779
ER_FK_INCOMPATIBLE_COLUMNS = 3780
ER_GR_HOLD_WAIT_TIMEOUT = 3781
ER_GR_HOLD_KILLED = 3782
ER_GR_HOLD_MEMBER_STATUS_ERROR = 3783
ER_RPL_ENCRYPTION_FAILED_TO_FETCH_KEY = 3784
ER_RPL_ENCRYPTION_KEY_NOT_FOUND = 3785
ER_RPL_ENCRYPTION_KEYRING_INVALID_KEY = 3786
ER_RPL_ENCRYPTION_HEADER_ERROR = 3787
ER_RPL_ENCRYPTION_FAILED_TO_ROTATE_LOGS = 3788
ER_RPL_ENCRYPTION_KEY_EXISTS_UNEXPECTED = 3789
ER_RPL_ENCRYPTION_FAILED_TO_GENERATE_KEY = 3790
ER_RPL_ENCRYPTION_FAILED_TO_STORE_KEY = 3791
ER_RPL_ENCRYPTION_FAILED_TO_REMOVE_KEY = 3792
ER_RPL_ENCRYPTION_UNABLE_TO_CHANGE_OPTION = 3793
ER_RPL_ENCRYPTION_MASTER_KEY_RECOVERY_FAILED = 3794
ER_SLOW_LOG_MODE_IGNORED_WHEN_NOT_LOGGING_TO_FILE = 3795
ER_GRP_TRX_CONSISTENCY_NOT_ALLOWED = 3796
ER_GRP_TRX_CONSISTENCY_BEFORE = 3797
ER_GRP_TRX_CONSISTENCY_AFTER_ON_TRX_BEGIN = 3798
ER_GRP_TRX_CONSISTENCY_BEGIN_NOT_ALLOWED = 3799
ER_FUNCTIONAL_INDEX_ROW_VALUE_IS_NOT_ALLOWED = 3800
ER_RPL_ENCRYPTION_FAILED_TO_ENCRYPT = 3801
ER_PAGE_TRACKING_NOT_STARTED = 3802
ER_PAGE_TRACKING_RANGE_NOT_TRACKED = 3803
ER_PAGE_TRACKING_CANNOT_PURGE = 3804
ER_RPL_ENCRYPTION_CANNOT_ROTATE_BINLOG_MASTER_KEY = 3805
ER_BINLOG_MASTER_KEY_RECOVERY_OUT_OF_COMBINATION = 3806
ER_BINLOG_MASTER_KEY_ROTATION_FAIL_TO_OPERATE_KEY = 3807
ER_BINLOG_MASTER_KEY_ROTATION_FAIL_TO_ROTATE_LOGS = 3808
ER_BINLOG_MASTER_KEY_ROTATION_FAIL_TO_REENCRYPT_LOG = 3809
ER_BINLOG_MASTER_KEY_ROTATION_FAIL_TO_CLEANUP_UNUSED_KEYS = 3810
ER_BINLOG_MASTER_KEY_ROTATION_FAIL_TO_CLEANUP_AUX_KEY = 3811
ER_NON_BOOLEAN_EXPR_FOR_CHECK_CONSTRAINT = 3812
ER_COLUMN_CHECK_CONSTRAINT_REFERENCES_OTHER_COLUMN = 3813
ER_CHECK_CONSTRAINT_NAMED_FUNCTION_IS_NOT_ALLOWED = 3814
ER_CHECK_CONSTRAINT_FUNCTION_IS_NOT_ALLOWED = 3815
ER_CHECK_CONSTRAINT_VARIABLES = 3816
ER_CHECK_CONSTRAINT_ROW_VALUE = 3817
ER_CHECK_CONSTRAINT_REFERS_AUTO_INCREMENT_COLUMN = 3818
ER_CHECK_CONSTRAINT_VIOLATED = 3819
ER_CHECK_CONSTRAINT_REFERS_UNKNOWN_COLUMN = 3820
ER_CHECK_CONSTRAINT_NOT_FOUND = 3821
ER_CHECK_CONSTRAINT_DUP_NAME = 3822
ER_CHECK_CONSTRAINT_CLAUSE_USING_FK_REFER_ACTION_COLUMN = 3823
WARN_UNENCRYPTED_TABLE_IN_ENCRYPTED_DB = 3824
ER_INVALID_ENCRYPTION_REQUEST = 3825
ER_CANNOT_SET_TABLE_ENCRYPTION = 3826
ER_CANNOT_SET_DATABASE_ENCRYPTION = 3827
ER_CANNOT_SET_TABLESPACE_ENCRYPTION = 3828
ER_TABLESPACE_CANNOT_BE_ENCRYPTED = 3829
ER_TABLESPACE_CANNOT_BE_DECRYPTED = 3830
ER_TABLESPACE_TYPE_UNKNOWN = 3831
ER_TARGET_TABLESPACE_UNENCRYPTED = 3832
ER_CANNOT_USE_ENCRYPTION_CLAUSE = 3833
ER_INVALID_MULTIPLE_CLAUSES = 3834
ER_UNSUPPORTED_USE_OF_GRANT_AS = 3835
ER_UKNOWN_AUTH_ID_OR_ACCESS_DENIED_FOR_GRANT_AS = 3836
ER_DEPENDENT_BY_FUNCTIONAL_INDEX = 3837
ER_PLUGIN_NOT_EARLY = 3838
ER_INNODB_REDO_LOG_ARCHIVE_START_SUBDIR_PATH = 3839
ER_INNODB_REDO_LOG_ARCHIVE_START_TIMEOUT = 3840
ER_INNODB_REDO_LOG_ARCHIVE_DIRS_INVALID = 3841
ER_INNODB_REDO_LOG_ARCHIVE_LABEL_NOT_FOUND = 3842
ER_INNODB_REDO_LOG_ARCHIVE_DIR_EMPTY = 3843
ER_INNODB_REDO_LOG_ARCHIVE_NO_SUCH_DIR = 3844
ER_INNODB_REDO_LOG_ARCHIVE_DIR_CLASH = 3845
ER_INNODB_REDO_LOG_ARCHIVE_DIR_PERMISSIONS = 3846
ER_INNODB_REDO_LOG_ARCHIVE_FILE_CREATE = 3847
ER_INNODB_REDO_LOG_ARCHIVE_ACTIVE = 3848
ER_INNODB_REDO_LOG_ARCHIVE_INACTIVE = 3849
ER_INNODB_REDO_LOG_ARCHIVE_FAILED = 3850
ER_INNODB_REDO_LOG_ARCHIVE_SESSION = 3851
ER_STD_REGEX_ERROR = 3852
ER_INVALID_JSON_TYPE = 3853
ER_CANNOT_CONVERT_STRING = 3854
ER_DEPENDENT_BY_PARTITION_FUNC = 3855
ER_WARN_DEPRECATED_FLOAT_AUTO_INCREMENT = 3856
ER_RPL_CANT_STOP_SLAVE_WHILE_LOCKED_BACKUP = 3857
ER_WARN_DEPRECATED_FLOAT_DIGITS = 3858
ER_WARN_DEPRECATED_FLOAT_UNSIGNED = 3859
ER_WARN_DEPRECATED_INTEGER_DISPLAY_WIDTH = 3860
ER_WARN_DEPRECATED_ZEROFILL = 3861
ER_CLONE_DONOR = 3862
ER_CLONE_PROTOCOL = 3863
ER_CLONE_DONOR_VERSION = 3864
ER_CLONE_OS = 3865
ER_CLONE_PLATFORM = 3866
ER_CLONE_CHARSET = 3867
ER_CLONE_CONFIG = 3868
ER_CLONE_SYS_CONFIG = 3869
ER_CLONE_PLUGIN_MATCH = 3870
ER_CLONE_LOOPBACK = 3871
ER_CLONE_ENCRYPTION = 3872
ER_CLONE_DISK_SPACE = 3873
ER_CLONE_IN_PROGRESS = 3874
ER_CLONE_DISALLOWED = 3875
ER_CANNOT_GRANT_ROLES_TO_ANONYMOUS_USER = 3876
ER_SECONDARY_ENGINE_PLUGIN = 3877
ER_SECOND_PASSWORD_CANNOT_BE_EMPTY = 3878
ER_DB_ACCESS_DENIED = 3879
ER_DA_AUTH_ID_WITH_SYSTEM_USER_PRIV_IN_MANDATORY_ROLES = 3880
ER_DA_RPL_GTID_TABLE_CANNOT_OPEN = 3881
ER_GEOMETRY_IN_UNKNOWN_LENGTH_UNIT = 3882
ER_DA_PLUGIN_INSTALL_ERROR = 3883
ER_NO_SESSION_TEMP = 3884
ER_DA_UNKNOWN_ERROR_NUMBER = 3885
ER_COLUMN_CHANGE_SIZE = 3886
ER_REGEXP_INVALID_CAPTURE_GROUP_NAME = 3887
ER_DA_SSL_LIBRARY_ERROR = 3888
ER_SECONDARY_ENGINE = 3889
ER_SECONDARY_ENGINE_DDL = 3890
ER_INCORRECT_CURRENT_PASSWORD = 3891
ER_MISSING_CURRENT_PASSWORD = 3892
ER_CURRENT_PASSWORD_NOT_REQUIRED = 3893
ER_PASSWORD_CANNOT_BE_RETAINED_ON_PLUGIN_CHANGE = 3894
ER_CURRENT_PASSWORD_CANNOT_BE_RETAINED = 3895
ER_PARTIAL_REVOKES_EXIST = 3896
ER_CANNOT_GRANT_SYSTEM_PRIV_TO_MANDATORY_ROLE = 3897
ER_XA_REPLICATION_FILTERS = 3898
ER_UNSUPPORTED_SQL_MODE = 3899
ER_REGEXP_INVALID_FLAG = 3900
ER_PARTIAL_REVOKE_AND_DB_GRANT_BOTH_EXISTS = 3901
ER_UNIT_NOT_FOUND = 3902
ER_INVALID_JSON_VALUE_FOR_FUNC_INDEX = 3903
ER_JSON_VALUE_OUT_OF_RANGE_FOR_FUNC_INDEX = 3904
ER_EXCEEDED_MV_KEYS_NUM = 3905
ER_EXCEEDED_MV_KEYS_SPACE = 3906
ER_FUNCTIONAL_INDEX_DATA_IS_TOO_LONG = 3907
ER_WRONG_MVI_VALUE = 3908
ER_WARN_FUNC_INDEX_NOT_APPLICABLE = 3909
ER_GRP_RPL_UDF_ERROR = 3910
ER_UPDATE_GTID_PURGED_WITH_GR = 3911
ER_GROUPING_ON_TIMESTAMP_IN_DST = 3912
ER_TABLE_NAME_CAUSES_TOO_LONG_PATH = 3913
ER_AUDIT_LOG_INSUFFICIENT_PRIVILEGE = 3914
OBSOLETE_ER_AUDIT_LOG_PASSWORD_HAS_BEEN_COPIED = 3915
ER_DA_GRP_RPL_STARTED_AUTO_REJOIN = 3916
ER_SYSVAR_CHANGE_DURING_QUERY = 3917
ER_GLOBSTAT_CHANGE_DURING_QUERY = 3918
ER_GRP_RPL_MESSAGE_SERVICE_INIT_FAILURE = 3919
ER_CHANGE_MASTER_WRONG_COMPRESSION_ALGORITHM_CLIENT = 3920
ER_CHANGE_MASTER_WRONG_COMPRESSION_LEVEL_CLIENT = 3921
ER_WRONG_COMPRESSION_ALGORITHM_CLIENT = 3922
ER_WRONG_COMPRESSION_LEVEL_CLIENT = 3923
ER_CHANGE_MASTER_WRONG_COMPRESSION_ALGORITHM_LIST_CLIENT = 3924
ER_CLIENT_PRIVILEGE_CHECKS_USER_CANNOT_BE_ANONYMOUS = 3925
ER_CLIENT_PRIVILEGE_CHECKS_USER_DOES_NOT_EXIST = 3926
ER_CLIENT_PRIVILEGE_CHECKS_USER_CORRUPT = 3927
ER_CLIENT_PRIVILEGE_CHECKS_USER_NEEDS_RPL_APPLIER_PRIV = 3928
ER_WARN_DA_PRIVILEGE_NOT_REGISTERED = 3929
ER_CLIENT_KEYRING_UDF_KEY_INVALID = 3930
ER_CLIENT_KEYRING_UDF_KEY_TYPE_INVALID = 3931
ER_CLIENT_KEYRING_UDF_KEY_TOO_LONG = 3932
ER_CLIENT_KEYRING_UDF_KEY_TYPE_TOO_LONG = 3933
ER_JSON_SCHEMA_VALIDATION_ERROR_WITH_DETAILED_REPORT = 3934
ER_DA_UDF_INVALID_CHARSET_SPECIFIED = 3935
ER_DA_UDF_INVALID_CHARSET = 3936
ER_DA_UDF_INVALID_COLLATION = 3937
ER_DA_UDF_INVALID_EXTENSION_ARGUMENT_TYPE = 3938
ER_MULTIPLE_CONSTRAINTS_WITH_SAME_NAME = 3939
ER_CONSTRAINT_NOT_FOUND = 3940
ER_ALTER_CONSTRAINT_ENFORCEMENT_NOT_SUPPORTED = 3941
ER_TABLE_VALUE_CONSTRUCTOR_MUST_HAVE_COLUMNS = 3942
ER_TABLE_VALUE_CONSTRUCTOR_CANNOT_HAVE_DEFAULT = 3943
ER_CLIENT_QUERY_FAILURE_INVALID_NON_ROW_FORMAT = 3944
ER_REQUIRE_ROW_FORMAT_INVALID_VALUE = 3945
ER_FAILED_TO_DETERMINE_IF_ROLE_IS_MANDATORY = 3946
ER_FAILED_TO_FETCH_MANDATORY_ROLE_LIST = 3947
ER_CLIENT_LOCAL_FILES_DISABLED = 3948
ER_IMP_INCOMPATIBLE_CFG_VERSION = 3949
ER_DA_OOM = 3950
ER_DA_UDF_INVALID_ARGUMENT_TO_SET_CHARSET = 3951
ER_DA_UDF_INVALID_RETURN_TYPE_TO_SET_CHARSET = 3952
ER_MULTIPLE_INTO_CLAUSES = 3953
ER_MISPLACED_INTO = 3954
ER_USER_ACCESS_DENIED_FOR_USER_ACCOUNT_BLOCKED_BY_PASSWORD_LOCK = 3955
ER_WARN_DEPRECATED_YEAR_UNSIGNED = 3956
ER_CLONE_NETWORK_PACKET = 3957
ER_SDI_OPERATION_FAILED_MISSING_RECORD = 3958
ER_DEPENDENT_BY_CHECK_CONSTRAINT = 3959
ER_GRP_OPERATION_NOT_ALLOWED_GR_MUST_STOP = 3960
ER_WARN_DEPRECATED_JSON_TABLE_ON_ERROR_ON_EMPTY = 3961
ER_WARN_DEPRECATED_INNER_INTO = 3962
ER_WARN_DEPRECATED_VALUES_FUNCTION_ALWAYS_NULL = 3963
ER_WARN_DEPRECATED_SQL_CALC_FOUND_ROWS = 3964
ER_WARN_DEPRECATED_FOUND_ROWS = 3965
ER_MISSING_JSON_VALUE = 3966
ER_MULTIPLE_JSON_VALUES = 3967
ER_HOSTNAME_TOO_LONG = 3968
ER_WARN_CLIENT_DEPRECATED_PARTITION_PREFIX_KEY = 3969
ER_GROUP_REPLICATION_USER_EMPTY_MSG = 3970
ER_GROUP_REPLICATION_USER_MANDATORY_MSG = 3971
ER_GROUP_REPLICATION_PASSWORD_LENGTH = 3972
ER_SUBQUERY_TRANSFORM_REJECTED = 3973
ER_DA_GRP_RPL_RECOVERY_ENDPOINT_FORMAT = 3974
ER_DA_GRP_RPL_RECOVERY_ENDPOINT_INVALID = 3975
ER_WRONG_VALUE_FOR_VAR_PLUS_ACTIONABLE_PART = 3976
ER_STATEMENT_NOT_ALLOWED_AFTER_START_TRANSACTION = 3977
ER_FOREIGN_KEY_WITH_ATOMIC_CREATE_SELECT = 3978
ER_NOT_ALLOWED_WITH_START_TRANSACTION = 3979
ER_INVALID_JSON_ATTRIBUTE = 3980
ER_ENGINE_ATTRIBUTE_NOT_SUPPORTED = 3981
ER_INVALID_USER_ATTRIBUTE_JSON = 3982
ER_INNODB_REDO_DISABLED = 3983
ER_INNODB_REDO_ARCHIVING_ENABLED = 3984
ER_MDL_OUT_OF_RESOURCES = 3985
ER_IMPLICIT_COMPARISON_FOR_JSON = 3986
ER_FUNCTION_DOES_NOT_SUPPORT_CHARACTER_SET = 3987
ER_IMPOSSIBLE_STRING_CONVERSION = 3988
ER_SCHEMA_READ_ONLY = 3989
ER_RPL_ASYNC_RECONNECT_GTID_MODE_OFF = 3990
ER_RPL_ASYNC_RECONNECT_AUTO_POSITION_OFF = 3991
ER_DISABLE_GTID_MODE_REQUIRES_ASYNC_RECONNECT_OFF = 3992
ER_DISABLE_AUTO_POSITION_REQUIRES_ASYNC_RECONNECT_OFF = 3993
ER_INVALID_PARAMETER_USE = 3994
ER_CHARACTER_SET_MISMATCH = 3995
ER_WARN_VAR_VALUE_CHANGE_NOT_SUPPORTED = 3996
ER_INVALID_TIME_ZONE_INTERVAL = 3997
ER_INVALID_CAST = 3998
ER_HYPERGRAPH_NOT_SUPPORTED_YET = 3999
ER_WARN_HYPERGRAPH_EXPERIMENTAL = 4000
ER_DA_NO_ERROR_LOG_PARSER_CONFIGURED = 4001
ER_DA_ERROR_LOG_TABLE_DISABLED = 4002
ER_DA_ERROR_LOG_MULTIPLE_FILTERS = 4003
ER_DA_CANT_OPEN_ERROR_LOG = 4004
ER_USER_REFERENCED_AS_DEFINER = 4005
ER_CANNOT_USER_REFERENCED_AS_DEFINER = 4006
ER_REGEX_NUMBER_TOO_BIG = 4007
ER_SPVAR_NONINTEGER_TYPE = 4008
WARN_UNSUPPORTED_ACL_TABLES_READ = 4009
ER_BINLOG_UNSAFE_ACL_TABLE_READ_IN_DML_DDL = 4010
ER_STOP_REPLICA_MONITOR_IO_THREAD_TIMEOUT = 4011
ER_STARTING_REPLICA_MONITOR_IO_THREAD = 4012
ER_CANT_USE_ANONYMOUS_TO_GTID_WITH_GTID_MODE_NOT_ON = 4013
ER_CANT_COMBINE_ANONYMOUS_TO_GTID_AND_AUTOPOSITION = 4014
ER_ASSIGN_GTIDS_TO_ANONYMOUS_TRANSACTIONS_REQUIRES_GTID_MODE_ON = 4015
ER_SQL_REPLICA_SKIP_COUNTER_USED_WITH_GTID_MODE_ON = 4016
ER_USING_ASSIGN_GTIDS_TO_ANONYMOUS_TRANSACTIONS_AS_LOCAL_OR_UUID = 4017
ER_CANT_SET_ANONYMOUS_TO_GTID_AND_WAIT_UNTIL_SQL_THD_AFTER_GTIDS = 4018
ER_CANT_SET_SQL_AFTER_OR_BEFORE_GTIDS_WITH_ANONYMOUS_TO_GTID = 4019
ER_ANONYMOUS_TO_GTID_UUID_SAME_AS_GROUP_NAME = 4020
ER_CANT_USE_SAME_UUID_AS_GROUP_NAME = 4021
ER_GRP_RPL_RECOVERY_CHANNEL_STILL_RUNNING = 4022
ER_INNODB_INVALID_AUTOEXTEND_SIZE_VALUE = 4023
ER_INNODB_INCOMPATIBLE_WITH_TABLESPACE = 4024
ER_INNODB_AUTOEXTEND_SIZE_OUT_OF_RANGE = 4025
ER_CANNOT_USE_AUTOEXTEND_SIZE_CLAUSE = 4026
ER_ROLE_GRANTED_TO_ITSELF = 4027
ER_TABLE_MUST_HAVE_A_VISIBLE_COLUMN = 4028
ER_INNODB_COMPRESSION_FAILURE = 4029
ER_WARN_ASYNC_CONN_FAILOVER_NETWORK_NAMESPACE = 4030
ER_CLIENT_INTERACTION_TIMEOUT = 4031
ER_INVALID_CAST_TO_GEOMETRY = 4032
ER_INVALID_CAST_POLYGON_RING_DIRECTION = 4033
ER_GIS_DIFFERENT_SRIDS_AGGREGATION = 4034
ER_RELOAD_KEYRING_FAILURE = 4035
ER_SDI_GET_KEYS_INVALID_TABLESPACE = 4036
ER_CHANGE_RPL_SRC_WRONG_COMPRESSION_ALGORITHM_SIZE = 4037
ER_WARN_DEPRECATED_TLS_VERSION_FOR_CHANNEL_CLI = 4038
ER_CANT_USE_SAME_UUID_AS_VIEW_CHANGE_UUID = 4039
ER_ANONYMOUS_TO_GTID_UUID_SAME_AS_VIEW_CHANGE_UUID = 4040
ER_GRP_RPL_VIEW_CHANGE_UUID_FAIL_GET_VARIABLE = 4041
ER_WARN_ADUIT_LOG_MAX_SIZE_AND_PRUNE_SECONDS = 4042
ER_WARN_ADUIT_LOG_MAX_SIZE_CLOSE_TO_ROTATE_ON_SIZE = 4043
ER_KERBEROS_CREATE_USER = 4044
ER_INSTALL_PLUGIN_CONFLICT_CLIENT = 4045
ER_DA_ERROR_LOG_COMPONENT_FLUSH_FAILED = 4046
ER_WARN_SQL_AFTER_MTS_GAPS_GAP_NOT_CALCULATED = 4047
ER_INVALID_ASSIGNMENT_TARGET = 4048
ER_OPERATION_NOT_ALLOWED_ON_GR_SECONDARY = 4049
ER_GRP_RPL_FAILOVER_CHANNEL_STATUS_PROPAGATION = 4050
ER_WARN_AUDIT_LOG_FORMAT_UNIX_TIMESTAMP_ONLY_WHEN_JSON = 4051
ER_INVALID_MFA_PLUGIN_SPECIFIED = 4052
ER_IDENTIFIED_BY_UNSUPPORTED = 4053
ER_INVALID_PLUGIN_FOR_REGISTRATION = 4054
ER_PLUGIN_REQUIRES_REGISTRATION = 4055
ER_MFA_METHOD_EXISTS = 4056
ER_MFA_METHOD_NOT_EXISTS = 4057
ER_AUTHENTICATION_POLICY_MISMATCH = 4058
ER_PLUGIN_REGISTRATION_DONE = 4059
ER_INVALID_USER_FOR_REGISTRATION = 4060
ER_USER_REGISTRATION_FAILED = 4061
ER_MFA_METHODS_INVALID_ORDER = 4062
ER_MFA_METHODS_IDENTICAL = 4063
ER_INVALID_MFA_OPERATIONS_FOR_PASSWORDLESS_USER = 4064
ER_CHANGE_REPLICATION_SOURCE_NO_OPTIONS_FOR_GTID_ONLY = 4065
ER_CHANGE_REP_SOURCE_CANT_DISABLE_REQ_ROW_FORMAT_WITH_GTID_ONLY = 4066
ER_CHANGE_REP_SOURCE_CANT_DISABLE_AUTO_POSITION_WITH_GTID_ONLY = 4067
ER_CHANGE_REP_SOURCE_CANT_DISABLE_GTID_ONLY_WITHOUT_POSITIONS = 4068
ER_CHANGE_REP_SOURCE_CANT_DISABLE_AUTO_POS_WITHOUT_POSITIONS = 4069
ER_CHANGE_REP_SOURCE_GR_CHANNEL_WITH_GTID_MODE_NOT_ON = 4070
ER_CANT_USE_GTID_ONLY_WITH_GTID_MODE_NOT_ON = 4071
ER_WARN_C_DISABLE_GTID_ONLY_WITH_SOURCE_AUTO_POS_INVALID_POS = 4072
ER_DA_SSL_FIPS_MODE_ERROR = 4073
CR_UNKNOWN_ERROR = 2000
CR_SOCKET_CREATE_ERROR = 2001
CR_CONNECTION_ERROR = 2002
CR_CONN_HOST_ERROR = 2003
CR_IPSOCK_ERROR = 2004
CR_UNKNOWN_HOST = 2005
CR_SERVER_GONE_ERROR = 2006
CR_VERSION_ERROR = 2007
CR_OUT_OF_MEMORY = 2008
CR_WRONG_HOST_INFO = 2009
CR_LOCALHOST_CONNECTION = 2010
CR_TCP_CONNECTION = 2011
CR_SERVER_HANDSHAKE_ERR = 2012
CR_SERVER_LOST = 2013
CR_COMMANDS_OUT_OF_SYNC = 2014
CR_NAMEDPIPE_CONNECTION = 2015
CR_NAMEDPIPEWAIT_ERROR = 2016
CR_NAMEDPIPEOPEN_ERROR = 2017
CR_NAMEDPIPESETSTATE_ERROR = 2018
CR_CANT_READ_CHARSET = 2019
CR_NET_PACKET_TOO_LARGE = 2020
CR_EMBEDDED_CONNECTION = 2021
CR_PROBE_SLAVE_STATUS = 2022
CR_PROBE_SLAVE_HOSTS = 2023
CR_PROBE_SLAVE_CONNECT = 2024
CR_PROBE_MASTER_CONNECT = 2025
CR_SSL_CONNECTION_ERROR = 2026
CR_MALFORMED_PACKET = 2027
CR_WRONG_LICENSE = 2028
CR_NULL_POINTER = 2029
CR_NO_PREPARE_STMT = 2030
CR_PARAMS_NOT_BOUND = 2031
CR_DATA_TRUNCATED = 2032
CR_NO_PARAMETERS_EXISTS = 2033
CR_INVALID_PARAMETER_NO = 2034
CR_INVALID_BUFFER_USE = 2035
CR_UNSUPPORTED_PARAM_TYPE = 2036
CR_SHARED_MEMORY_CONNECTION = 2037
CR_SHARED_MEMORY_CONNECT_REQUEST_ERROR = 2038
CR_SHARED_MEMORY_CONNECT_ANSWER_ERROR = 2039
CR_SHARED_MEMORY_CONNECT_FILE_MAP_ERROR = 2040
CR_SHARED_MEMORY_CONNECT_MAP_ERROR = 2041
CR_SHARED_MEMORY_FILE_MAP_ERROR = 2042
CR_SHARED_MEMORY_MAP_ERROR = 2043
CR_SHARED_MEMORY_EVENT_ERROR = 2044
CR_SHARED_MEMORY_CONNECT_ABANDONED_ERROR = 2045
CR_SHARED_MEMORY_CONNECT_SET_ERROR = 2046
CR_CONN_UNKNOW_PROTOCOL = 2047
CR_INVALID_CONN_HANDLE = 2048
CR_UNUSED_1 = 2049
CR_FETCH_CANCELED = 2050
CR_NO_DATA = 2051
CR_NO_STMT_METADATA = 2052
CR_NO_RESULT_SET = 2053
CR_NOT_IMPLEMENTED = 2054
CR_SERVER_LOST_EXTENDED = 2055
CR_STMT_CLOSED = 2056
CR_NEW_STMT_METADATA = 2057
CR_ALREADY_CONNECTED = 2058
CR_AUTH_PLUGIN_CANNOT_LOAD = 2059
CR_DUPLICATE_CONNECTION_ATTR = 2060
CR_AUTH_PLUGIN_ERR = 2061
CR_INSECURE_API_ERR = 2062
CR_FILE_NAME_TOO_LONG = 2063
CR_SSL_FIPS_MODE_ERR = 2064
CR_DEPRECATED_COMPRESSION_NOT_SUPPORTED = 2065
CR_COMPRESSION_WRONGLY_CONFIGURED = 2066
CR_KERBEROS_USER_NOT_FOUND = 2067
CR_LOAD_DATA_LOCAL_INFILE_REJECTED = 2068
CR_LOAD_DATA_LOCAL_INFILE_REALPATH_FAIL = 2069
CR_DNS_SRV_LOOKUP_FAILED = 2070
CR_MANDATORY_TRACKER_NOT_FOUND = 2071
CR_INVALID_FACTOR_NO = 2072
# End MySQL Errors

# Start X Plugin Errors
ER_X_BAD_MESSAGE = 5000
ER_X_CAPABILITIES_PREPARE_FAILED = 5001
ER_X_CAPABILITY_NOT_FOUND = 5002
ER_X_INVALID_PROTOCOL_DATA = 5003
ER_X_BAD_CONNECTION_SESSION_ATTRIBUTE_VALUE_LENGTH = 5004
ER_X_BAD_CONNECTION_SESSION_ATTRIBUTE_KEY_LENGTH = 5005
ER_X_BAD_CONNECTION_SESSION_ATTRIBUTE_EMPTY_KEY = 5006
ER_X_BAD_CONNECTION_SESSION_ATTRIBUTE_LENGTH = 5007
ER_X_BAD_CONNECTION_SESSION_ATTRIBUTE_TYPE = 5008
ER_X_CAPABILITY_SET_NOT_ALLOWED = 5009
ER_X_SERVICE_ERROR = 5010
ER_X_SESSION = 5011
ER_X_INVALID_ARGUMENT = 5012
ER_X_MISSING_ARGUMENT = 5013
ER_X_BAD_INSERT_DATA = 5014
ER_X_CMD_NUM_ARGUMENTS = 5015
ER_X_CMD_ARGUMENT_TYPE = 5016
ER_X_CMD_ARGUMENT_VALUE = 5017
ER_X_BAD_UPSERT_DATA = 5018
ER_X_DUPLICATED_CAPABILITIES = 5019
ER_X_CMD_ARGUMENT_OBJECT_EMPTY = 5020
ER_X_CMD_INVALID_ARGUMENT = 5021
ER_X_BAD_UPDATE_DATA = 5050
ER_X_BAD_TYPE_OF_UPDATE = 5051
ER_X_BAD_COLUMN_TO_UPDATE = 5052
ER_X_BAD_MEMBER_TO_UPDATE = 5053
ER_X_BAD_STATEMENT_ID = 5110
ER_X_BAD_CURSOR_ID = 5111
ER_X_BAD_SCHEMA = 5112
ER_X_BAD_TABLE = 5113
ER_X_BAD_PROJECTION = 5114
ER_X_DOC_ID_MISSING = 5115
ER_X_DUPLICATE_ENTRY = 5116
ER_X_DOC_REQUIRED_FIELD_MISSING = 5117
ER_X_PROJ_BAD_KEY_NAME = 5120
ER_X_BAD_DOC_PATH = 5121
ER_X_CURSOR_EXISTS = 5122
ER_X_CURSOR_REACHED_EOF = 5123
ER_X_PREPARED_STATMENT_CAN_HAVE_ONE_CURSOR = 5131
ER_X_PREPARED_EXECUTE_ARGUMENT_NOT_SUPPORTED = 5133
ER_X_PREPARED_EXECUTE_ARGUMENT_CONSISTENCY = 5134
ER_X_EXPR_BAD_OPERATOR = 5150
ER_X_EXPR_BAD_NUM_ARGS = 5151
ER_X_EXPR_MISSING_ARG = 5152
ER_X_EXPR_BAD_TYPE_VALUE = 5153
ER_X_EXPR_BAD_VALUE = 5154
ER_X_INVALID_COLLECTION = 5156
ER_X_INVALID_ADMIN_COMMAND = 5157
ER_X_EXPECT_NOT_OPEN = 5158
ER_X_EXPECT_NO_ERROR_FAILED = 5159
ER_X_EXPECT_BAD_CONDITION = 5160
ER_X_EXPECT_BAD_CONDITION_VALUE = 5161
ER_X_INVALID_NAMESPACE = 5162
ER_X_BAD_NOTICE = 5163
ER_X_CANNOT_DISABLE_NOTICE = 5164
ER_X_BAD_CONFIGURATION = 5165
ER_X_MYSQLX_ACCOUNT_MISSING_PERMISSIONS = 5167
ER_X_EXPECT_FIELD_EXISTS_FAILED = 5168
ER_X_BAD_LOCKING = 5169
ER_X_FRAME_COMPRESSION_DISABLED = 5170
ER_X_DECOMPRESSION_FAILED = 5171
ER_X_BAD_COMPRESSED_FRAME = 5174
ER_X_CAPABILITY_COMPRESSION_INVALID_ALGORITHM = 5175
ER_X_CAPABILITY_COMPRESSION_INVALID_SERVER_STYLE = 5176
ER_X_CAPABILITY_COMPRESSION_INVALID_CLIENT_STYLE = 5177
ER_X_CAPABILITY_COMPRESSION_INVALID_OPTION = 5178
ER_X_CAPABILITY_COMPRESSION_MISSING_REQUIRED_FIELDS = 5179
ER_X_DOCUMENT_DOESNT_MATCH_EXPECTED_SCHEMA = 5180
ER_X_COLLECTION_OPTION_DOESNT_EXISTS = 5181
ER_X_INVALID_VALIDATION_SCHEMA = 5182
# End X Plugin Errors

virt/lib/python3.9/site-packages/mysql/connector/errorcode.py
PrinceDisant/LostInNL
# Copyright (c) 2009, 2023, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

"""Python exceptions."""
from typing import Dict, Mapping, Optional, Tuple, Type, Union

from .locales import get_client_error
from .types import StrOrBytes
from .utils import read_bytes, read_int


class Error(Exception):
    """Exception that is base class for all other error exceptions"""

    def __init__(
        self,
        msg: Optional[str] = None,
        errno: Optional[int] = None,
        values: Optional[Tuple[Union[int, str], ...]] = None,
        sqlstate: Optional[str] = None,
    ) -> None:
        super().__init__()
        self.msg = msg
        self._full_msg = self.msg
        self.errno = errno or -1
        self.sqlstate = sqlstate

        if not self.msg and (2000 <= self.errno < 3000):
            self.msg = get_client_error(self.errno)
            if values is not None:
                try:
                    self.msg = self.msg % values
                except TypeError as err:
                    self.msg = f"{self.msg} (Warning: {err})"
        elif not self.msg:
            self._full_msg = self.msg = "Unknown error"

        if self.msg and self.errno != -1:
            fields = {"errno": self.errno, "msg": self.msg}
            if self.sqlstate:
                fmt = "{errno} ({state}): {msg}"
                fields["state"] = self.sqlstate
            else:
                fmt = "{errno}: {msg}"
            self._full_msg = fmt.format(**fields)

        self.args = (self.errno, self._full_msg, self.sqlstate)

    def __str__(self) -> str:
        return self._full_msg


class Warning(Exception):  # pylint: disable=redefined-builtin
    """Exception for important warnings"""


class InterfaceError(Error):
    """Exception for errors related to the interface"""


class DatabaseError(Error):
    """Exception for errors related to the database"""


class InternalError(DatabaseError):
    """Exception for errors internal database errors"""


class OperationalError(DatabaseError):
    """Exception for errors related to the database's operation"""


class ProgrammingError(DatabaseError):
    """Exception for errors programming errors"""


class IntegrityError(DatabaseError):
    """Exception for errors regarding relational integrity"""


class DataError(DatabaseError):
    """Exception for errors reporting problems with processed data"""


class NotSupportedError(DatabaseError):
    """Exception for errors when an unsupported database feature was used"""


class PoolError(Error):
    """Exception for errors relating to connection pooling"""


ErrorClassTypes = Union[
    Type[Error],
    Type[InterfaceError],
    Type[DatabaseError],
    Type[InternalError],
    Type[OperationalError],
    Type[ProgrammingError],
    Type[IntegrityError],
    Type[DataError],
    Type[NotSupportedError],
    Type[PoolError],
]
ErrorTypes = Union[
    Error,
    InterfaceError,
    DatabaseError,
    InternalError,
    OperationalError,
    ProgrammingError,
    IntegrityError,
    DataError,
    NotSupportedError,
    PoolError,
    Warning,
]
# _CUSTOM_ERROR_EXCEPTIONS holds custom exceptions and is used by the
# function custom_error_exception. _ERROR_EXCEPTIONS (at bottom of module)
# is similar, but hardcoded exceptions.
_CUSTOM_ERROR_EXCEPTIONS: Dict[int, ErrorClassTypes] = {}


def custom_error_exception(
    error: Optional[Union[int, Dict[int, Optional[ErrorClassTypes]]]] = None,
    exception: Optional[ErrorClassTypes] = None,
) -> Mapping[int, Optional[ErrorClassTypes]]:
    """Define custom exceptions for MySQL server errors

    This function defines custom exceptions for MySQL server errors and
    returns the current set customizations.

    If error is a MySQL Server error number, then you have to pass also the
    exception class.

    The error argument can also be a dictionary in which case the key is
    the server error number, and value the exception to be raised.

    If none of the arguments are given, then custom_error_exception() will
    simply return the current set customizations.

    To reset the customizations, simply supply an empty dictionary.

    Examples:
        import mysql.connector
        from mysql.connector import errorcode

        # Server error 1028 should raise a DatabaseError
        mysql.connector.custom_error_exception(
            1028, mysql.connector.DatabaseError)

        # Or using a dictionary:
        mysql.connector.custom_error_exception({
            1028: mysql.connector.DatabaseError,
            1029: mysql.connector.OperationalError,
            })

        # Reset
        mysql.connector.custom_error_exception({})

    Returns a dictionary.
    """
    global _CUSTOM_ERROR_EXCEPTIONS  # pylint: disable=global-statement

    if isinstance(error, dict) and not error:
        _CUSTOM_ERROR_EXCEPTIONS = {}
        return _CUSTOM_ERROR_EXCEPTIONS

    if not error and not exception:
        return _CUSTOM_ERROR_EXCEPTIONS

    if not isinstance(error, (int, dict)):
        raise ValueError("The error argument should be either an integer or dictionary")

    if isinstance(error, int):
        error = {error: exception}

    for errno, _exception in error.items():
        if not isinstance(errno, int):
            raise ValueError("Error number should be an integer")
        try:
            if _exception is None or not issubclass(_exception, Exception):
                raise TypeError
        except TypeError as err:
            raise ValueError("Exception should be subclass of Exception") from err
        _CUSTOM_ERROR_EXCEPTIONS[errno] = _exception

    return _CUSTOM_ERROR_EXCEPTIONS


def get_mysql_exception(
    errno: int,
    msg: Optional[str] = None,
    sqlstate: Optional[str] = None,
    warning: Optional[bool] = False,
) -> ErrorTypes:
    """Get the exception matching the MySQL error

    This function will return an exception based on the SQLState. The given
    message will be passed on in the returned exception.

    The exception returned can be customized using the
    mysql.connector.custom_error_exception() function.

    Returns an Exception
    """
    try:
        return _CUSTOM_ERROR_EXCEPTIONS[errno](msg=msg, errno=errno, sqlstate=sqlstate)
    except KeyError:
        # Error was not mapped to particular exception
        pass

    try:
        return _ERROR_EXCEPTIONS[errno](msg=msg, errno=errno, sqlstate=sqlstate)
    except KeyError:
        # Error was not mapped to particular exception
        pass

    if not sqlstate:
        if warning:
            return Warning(errno, msg)
        return DatabaseError(msg=msg, errno=errno)

    try:
        return _SQLSTATE_CLASS_EXCEPTION[sqlstate[0:2]](
            msg=msg, errno=errno, sqlstate=sqlstate
        )
    except KeyError:
        # Return default InterfaceError
        return DatabaseError(msg=msg, errno=errno, sqlstate=sqlstate)


def get_exception(packet: bytes) -> ErrorTypes:
    """Returns an exception object based on the MySQL error

    Returns an exception object based on the MySQL error in the given
    packet.

    Returns an Error-Object.
    """
    errno = errmsg = None

    try:
        if packet[4] != 255:
            raise ValueError("Packet is not an error packet")
    except IndexError as err:
        return InterfaceError(f"Failed getting Error information ({err})")

    sqlstate: Optional[StrOrBytes] = None
    try:
        packet = packet[5:]
        packet, errno = read_int(packet, 2)
        if packet[0] != 35:
            # Error without SQLState
            if isinstance(packet, (bytes, bytearray)):
                errmsg = packet.decode("utf8")
            else:
                errmsg = packet
        else:
            packet, sqlstate = read_bytes(packet[1:], 5)
            sqlstate = sqlstate.decode("utf8")
            errmsg = packet.decode("utf8")
    except (IndexError, UnicodeError) as err:
        return InterfaceError(f"Failed getting Error information ({err})")
    return get_mysql_exception(errno, errmsg, sqlstate)  # type: ignore[arg-type]


_SQLSTATE_CLASS_EXCEPTION: Dict[str, ErrorClassTypes] = {
    "02": DataError,  # no data
    "07": DatabaseError,  # dynamic SQL error
    "08": OperationalError,  # connection exception
    "0A": NotSupportedError,  # feature not supported
    "21": DataError,  # cardinality violation
    "22": DataError,  # data exception
    "23": IntegrityError,  # integrity constraint violation
    "24": ProgrammingError,  # invalid cursor state
    "25": ProgrammingError,  # invalid transaction state
    "26": ProgrammingError,  # invalid SQL statement name
    "27": ProgrammingError,  # triggered data change violation
    "28": ProgrammingError,  # invalid authorization specification
    "2A": ProgrammingError,  # direct SQL syntax error or access rule violation
    "2B": DatabaseError,  # dependent privilege descriptors still exist
    "2C": ProgrammingError,  # invalid character set name
    "2D": DatabaseError,  # invalid transaction termination
    "2E": DatabaseError,  # invalid connection name
    "33": DatabaseError,  # invalid SQL descriptor name
    "34": ProgrammingError,  # invalid cursor name
    "35": ProgrammingError,  # invalid condition number
    "37": ProgrammingError,  # dynamic SQL syntax error or access rule violation
    "3C": ProgrammingError,  # ambiguous cursor name
    "3D": ProgrammingError,  # invalid catalog name
    "3F": ProgrammingError,  # invalid schema name
    "40": InternalError,  # transaction rollback
    "42": ProgrammingError,  # syntax error or access rule violation
    "44": InternalError,  # with check option violation
    "HZ": OperationalError,  # remote database access
    "XA": IntegrityError,
    "0K": OperationalError,
    "HY": DatabaseError,  # default when no SQLState provided by MySQL server
}

_ERROR_EXCEPTIONS: Dict[int, ErrorClassTypes] = {
    1243: ProgrammingError,
    1210: ProgrammingError,
    2002: InterfaceError,
    2013: OperationalError,
    2049: NotSupportedError,
    2055: OperationalError,
    2061: InterfaceError,
    2026: InterfaceError,
}

virt/lib/python3.9/site-packages/mysql/connector/errors.py
PrinceDisant/LostInNL
# Copyright (c) 2012, 2022, Oracle and/or its affiliates. All rights reserved.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

"""Translations."""

from typing import List, Optional, Union

__all__: List[str] = ["get_client_error"]

from .. import errorcode


def get_client_error(error: Union[int, str], language: str = "eng") -> Optional[str]:
    """Lookup client error

    This function will lookup the client error message based on the given
    error and return the error message. If the error was not found,
    None will be returned.

    Error can be either an integer or a string. For example:
        error: 2000
        error: CR_UNKNOWN_ERROR

    The language attribute can be used to retrieve a localized message, when
    available.

    Returns a string or None.
    """
    try:
        tmp = __import__(
            f"mysql.connector.locales.{language}",
            globals(),
            locals(),
            ["client_error"],
        )
    except ImportError:
        raise ImportError(
            f"No localization support for language '{language}'"
        ) from None
    client_error = tmp.client_error

    if isinstance(error, int):
        errno = error
        for key, value in errorcode.__dict__.items():
            if value == errno:
                error = key
                break

    if isinstance(error, (str)):
        try:
            return getattr(client_error, error)
        except AttributeError:
            return None

    raise ValueError("error argument needs to be either an integer or string")

virt/lib/python3.9/site-packages/mysql/connector/locales/__init__.py
PrinceDisant/LostInNL
# -*- coding: utf-8 -*-

# Copyright (c) 2013, 2022, Oracle and/or its affiliates. All rights reserved.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

"""MySQL Error Messages."""

# This file was auto-generated.
_GENERATED_ON = "2021-08-11"
_MYSQL_VERSION = (8, 0, 27)

# pylint: disable=line-too-long
# Start MySQL Error messages
CR_UNKNOWN_ERROR = "Unknown MySQL error"
CR_SOCKET_CREATE_ERROR = "Can't create UNIX socket (%s)"
CR_CONNECTION_ERROR = (
    "Can't connect to local MySQL server through socket '%-.100s' (%s)"
)
CR_CONN_HOST_ERROR = "Can't connect to MySQL server on '%-.100s:%u' (%s)"
CR_IPSOCK_ERROR = "Can't create TCP/IP socket (%s)"
CR_UNKNOWN_HOST = "Unknown MySQL server host '%-.100s' (%s)"
CR_SERVER_GONE_ERROR = "MySQL server has gone away"
CR_VERSION_ERROR = "Protocol mismatch; server version = %s, client version = %s"
CR_OUT_OF_MEMORY = "MySQL client ran out of memory"
CR_WRONG_HOST_INFO = "Wrong host info"
CR_LOCALHOST_CONNECTION = "Localhost via UNIX socket"
CR_TCP_CONNECTION = "%-.100s via TCP/IP"
CR_SERVER_HANDSHAKE_ERR = "Error in server handshake"
CR_SERVER_LOST = "Lost connection to MySQL server during query"
CR_COMMANDS_OUT_OF_SYNC = "Commands out of sync; you can't run this command now"
CR_NAMEDPIPE_CONNECTION = "Named pipe: %-.32s"
CR_NAMEDPIPEWAIT_ERROR = "Can't wait for named pipe to host: %-.64s  pipe: %-.32s (%s)"
CR_NAMEDPIPEOPEN_ERROR = "Can't open named pipe to host: %-.64s  pipe: %-.32s (%s)"
CR_NAMEDPIPESETSTATE_ERROR = (
    "Can't set state of named pipe to host: %-.64s  pipe: %-.32s (%s)"
)
CR_CANT_READ_CHARSET = "Can't initialize character set %-.32s (path: %-.100s)"
CR_NET_PACKET_TOO_LARGE = "Got packet bigger than 'max_allowed_packet' bytes"
CR_EMBEDDED_CONNECTION = "Embedded server"
CR_PROBE_SLAVE_STATUS = "Error on SHOW SLAVE STATUS:"
CR_PROBE_SLAVE_HOSTS = "Error on SHOW SLAVE HOSTS:"
CR_PROBE_SLAVE_CONNECT = "Error connecting to slave:"
CR_PROBE_MASTER_CONNECT = "Error connecting to master:"
CR_SSL_CONNECTION_ERROR = "SSL connection error: %-.100s"
CR_MALFORMED_PACKET = "Malformed packet"
CR_WRONG_LICENSE = "This client library is licensed only for use with MySQL servers having '%s' license"
CR_NULL_POINTER = "Invalid use of null pointer"
CR_NO_PREPARE_STMT = "Statement not prepared"
CR_PARAMS_NOT_BOUND = "No data supplied for parameters in prepared statement"
CR_DATA_TRUNCATED = "Data truncated"
CR_NO_PARAMETERS_EXISTS = "No parameters exist in the statement"
CR_INVALID_PARAMETER_NO = "Invalid parameter number"
CR_INVALID_BUFFER_USE = (
    "Can't send long data for non-string/non-binary data types (parameter: %s)"
)
CR_UNSUPPORTED_PARAM_TYPE = "Using unsupported buffer type: %s  (parameter: %s)"
CR_SHARED_MEMORY_CONNECTION = "Shared memory: %-.100s"
CR_SHARED_MEMORY_CONNECT_REQUEST_ERROR = (
    "Can't open shared memory; client could not create request event (%s)"
)
CR_SHARED_MEMORY_CONNECT_ANSWER_ERROR = (
    "Can't open shared memory; no answer event received from server (%s)"
)
CR_SHARED_MEMORY_CONNECT_FILE_MAP_ERROR = (
    "Can't open shared memory; server could not allocate file mapping (%s)"
)
CR_SHARED_MEMORY_CONNECT_MAP_ERROR = (
    "Can't open shared memory; server could not get pointer to file mapping (%s)"
)
CR_SHARED_MEMORY_FILE_MAP_ERROR = (
    "Can't open shared memory; client could not allocate file mapping (%s)"
)
CR_SHARED_MEMORY_MAP_ERROR = (
    "Can't open shared memory; client could not get pointer to file mapping (%s)"
)
CR_SHARED_MEMORY_EVENT_ERROR = (
    "Can't open shared memory; client could not create %s event (%s)"
)
CR_SHARED_MEMORY_CONNECT_ABANDONED_ERROR = (
    "Can't open shared memory; no answer from server (%s)"
)
CR_SHARED_MEMORY_CONNECT_SET_ERROR = (
    "Can't open shared memory; cannot send request event to server (%s)"
)
CR_CONN_UNKNOW_PROTOCOL = "Wrong or unknown protocol"
CR_INVALID_CONN_HANDLE = "Invalid connection handle"
CR_UNUSED_1 = "Connection using old (pre-4.1.1) authentication protocol refused (client option 'secure_auth' enabled)"
CR_FETCH_CANCELED = "Row retrieval was canceled by mysql_stmt_close() call"
CR_NO_DATA = "Attempt to read column without prior row fetch"
CR_NO_STMT_METADATA = "Prepared statement contains no metadata"
CR_NO_RESULT_SET = (
    "Attempt to read a row while there is no result set associated with the statement"
)
CR_NOT_IMPLEMENTED = "This feature is not implemented yet"
CR_SERVER_LOST_EXTENDED = "Lost connection to MySQL server at '%s', system error: %s"
CR_STMT_CLOSED = "Statement closed indirectly because of a preceding %s() call"
CR_NEW_STMT_METADATA = "The number of columns in the result set differs from the number of bound buffers. You must reset the statement, rebind the result set columns, and execute the statement again"
CR_ALREADY_CONNECTED = (
    "This handle is already connected. Use a separate handle for each connection."
)
CR_AUTH_PLUGIN_CANNOT_LOAD = "Authentication plugin '%s' cannot be loaded: %s"
CR_DUPLICATE_CONNECTION_ATTR = "There is an attribute with the same name already"
CR_AUTH_PLUGIN_ERR = "Authentication plugin '%s' reported error: %s"
CR_INSECURE_API_ERR = "Insecure API function call: '%s' Use instead: '%s'"
CR_FILE_NAME_TOO_LONG = "File name is too long"
CR_SSL_FIPS_MODE_ERR = "Set FIPS mode ON/STRICT failed"
CR_DEPRECATED_COMPRESSION_NOT_SUPPORTED = (
    "Compression protocol not supported with asynchronous protocol"
)
CR_COMPRESSION_WRONGLY_CONFIGURED = (
    "Connection failed due to wrongly configured compression algorithm"
)
CR_KERBEROS_USER_NOT_FOUND = (
    "SSO user not found, Please perform SSO authentication using kerberos."
)
CR_LOAD_DATA_LOCAL_INFILE_REJECTED = (
    "LOAD DATA LOCAL INFILE file request rejected due to restrictions on access."
)
CR_LOAD_DATA_LOCAL_INFILE_REALPATH_FAIL = (
    "Determining the real path for '%s' failed with error (%s): %s"
)
CR_DNS_SRV_LOOKUP_FAILED = "DNS SRV lookup failed with error : %s"
CR_MANDATORY_TRACKER_NOT_FOUND = (
    "Client does not recognise tracker type %s marked as mandatory by server."
)
CR_INVALID_FACTOR_NO = "Invalid first argument for MYSQL_OPT_USER_PASSWORD option. Valid value should be between 1 and 3 inclusive."
# End MySQL Error messages

virt/lib/python3.9/site-packages/mysql/connector/locales/eng/client_error.py
PrinceDisant/LostInNL
# Copyright (c) 2022, 2023, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

"""Setup of the `mysql.connector` logger."""

import logging

logger = logging.getLogger("mysql.connector")

virt/lib/python3.9/site-packages/mysql/connector/logger.py
PrinceDisant/LostInNL
# Copyright (c) 2012, 2023, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

# mypy: disable-error-code="attr-defined"

"""Module implementing low-level socket communication with MySQL servers.
"""

import os
import socket
import struct
import warnings
import zlib

from abc import ABC, abstractmethod
from collections import deque

try:
    import ssl

    TLS_VERSIONS = {
        "TLSv1": ssl.PROTOCOL_TLSv1,
        "TLSv1.1": ssl.PROTOCOL_TLSv1_1,
        "TLSv1.2": ssl.PROTOCOL_TLSv1_2,
    }
    # TLSv1.3 included in PROTOCOL_TLS, but PROTOCOL_TLS is not included on 3.4
    TLS_VERSIONS["TLSv1.3"] = (
        ssl.PROTOCOL_TLS
        if hasattr(ssl, "PROTOCOL_TLS")
        else ssl.PROTOCOL_SSLv23  # Alias of PROTOCOL_TLS
    )
    TLS_V1_3_SUPPORTED = hasattr(ssl, "HAS_TLSv1_3") and ssl.HAS_TLSv1_3
except ImportError:
    # If import fails, we don't have SSL support.
    TLS_V1_3_SUPPORTED = False

from typing import Any, Deque, List, Optional, Tuple, Union

from .errors import InterfaceError, NotSupportedError, OperationalError
from .types import StrOrBytesPath

MIN_COMPRESS_LENGTH: int = 50
MAX_PAYLOAD_LENGTH: int = 2**24 - 1
PACKET_HEADER_LENGTH: int = 4
COMPRESSED_PACKET_HEADER_LENGTH: int = 7


def _strioerror(err: IOError) -> str:
    """Reformat the IOError error message.

    This function reformats the IOError error message.
    """
    return str(err) if not err.errno else f"{err.errno} {err.strerror}"


class NetworkBroker(ABC):
    """Broker class interface.

    The network object is a broker used as a delegate by a socket object. Whenever the
    socket wants to deliver or get packets to or from the MySQL server it needs to rely
    on its network broker (netbroker).

    The netbroker sends `payloads` and receives `packets`.

    A packet is a bytes sequence, it has a header and body (referred to as payload).
    The first `PACKET_HEADER_LENGTH` or `COMPRESSED_PACKET_HEADER_LENGTH`
    (as appropriate) bytes correspond to the `header`, the remaining ones represent the
    `payload`.

    The maximum payload length allowed to be sent per packet to the server is
    `MAX_PAYLOAD_LENGTH`. When  `send` is called with a payload whose length is greater
    than `MAX_PAYLOAD_LENGTH` the netbroker breaks it down into packets, so the caller
    of `send` can provide payloads of arbitrary length.

    Finally, data received by the netbroker comes directly from the server, expect to
    get a packet for each call to `recv`. The received packet contains a header and
    payload, the latter respecting `MAX_PAYLOAD_LENGTH`.
    """

    @abstractmethod
    def send(
        self,
        sock: socket.socket,
        address: str,
        payload: bytes,
        packet_number: Optional[int] = None,
        compressed_packet_number: Optional[int] = None,
    ) -> None:
        """Send `payload` to the MySQL server.

        If provided a payload whose length is greater than `MAX_PAYLOAD_LENGTH`, it is
        broken down into packets.

        Args:
            sock: Object holding the socket connection.
            address: Socket's location.
            payload: Packet's body to send.
            packet_number: Sequence id (packet ID) to attach to the header when sending
                           plain packets.
            compressed_packet_number: Same as `packet_number` but used when sending
                                      compressed packets.

        Raises:
            :class:`OperationalError`: If something goes wrong while sending packets to
                                       the MySQL server.
        """

    @abstractmethod
    def recv(self, sock: socket.socket, address: str) -> bytearray:
        """Get the next available packet from the MySQL server.

        Args:
            sock: Object holding the socket connection.
            address: Socket's location.

        Returns:
            packet: A packet from the MySQL server.

        Raises:
            :class:`OperationalError`: If something goes wrong while receiving packets
                                       from the MySQL server.
            :class:`InterfaceError`: If something goes wrong while receiving packets
                                     from the MySQL server.
        """


class NetworkBrokerPlain(NetworkBroker):
    """Broker class for MySQL socket communication."""

    def __init__(self) -> None:
        self._pktnr: int = -1  # packet number

    def _set_next_pktnr(self) -> None:
        """Increment packet id."""
        self._pktnr = (self._pktnr + 1) % 256

    def _send_pkt(self, sock: socket.socket, address: str, pkt: bytes) -> None:
        """Write packet to the comm channel."""
        try:
            sock.sendall(pkt)
        except IOError as err:
            raise OperationalError(
                errno=2055, values=(address, _strioerror(err))
            ) from err
        except AttributeError as err:
            raise OperationalError(errno=2006) from err

    def _recv_chunk(self, sock: socket.socket, size: int = 0) -> bytearray:
        """Read `size` bytes from the comm channel."""
        pkt = bytearray(size)
        pkt_view = memoryview(pkt)
        while size:
            read = sock.recv_into(pkt_view, size)
            if read == 0 and size > 0:
                raise InterfaceError(errno=2013)
            pkt_view = pkt_view[read:]
            size -= read
        return pkt

    def send(
        self,
        sock: socket.socket,
        address: str,
        payload: bytes,
        packet_number: Optional[int] = None,
        compressed_packet_number: Optional[int] = None,
    ) -> None:
        """Send payload to the MySQL server.

        If provided a payload whose length is greater than `MAX_PAYLOAD_LENGTH`, it is
        broken down into packets.
        """
        if packet_number is None:
            self._set_next_pktnr()
        else:
            self._pktnr = packet_number

        # If the payload is larger than or equal to MAX_PAYLOAD_LENGTH
        # the length is set to 2^24 - 1 (ff ff ff) and additional
        # packets are sent with the rest of the payload until the
        # payload of a packet is less than MAX_PAYLOAD_LENGTH.
        if len(payload) >= MAX_PAYLOAD_LENGTH:
            offset = 0
            for _ in range(len(payload) // MAX_PAYLOAD_LENGTH):
                # payload_len, sequence_id, payload
                self._send_pkt(
                    sock,
                    address,
                    b"\xff\xff\xff"
                    + struct.pack("<B", self._pktnr)
                    + payload[offset : offset + MAX_PAYLOAD_LENGTH],
                )
                self._set_next_pktnr()
                offset += MAX_PAYLOAD_LENGTH
            payload = payload[offset:]
        self._send_pkt(
            sock,
            address,
            struct.pack("<I", len(payload))[0:3]
            + struct.pack("<B", self._pktnr)
            + payload,
        )

    def recv(self, sock: socket.socket, address: str) -> bytearray:
        """Receive `one` packet from the MySQL server."""
        try:
            # Read the header of the MySQL packet
            header = self._recv_chunk(sock, size=PACKET_HEADER_LENGTH)

            # Pull the payload length and sequence id
            payload_len, self._pktnr = (
                struct.unpack("<I", header[0:3] + b"\x00")[0],
                header[3],
            )

            # Read the payload, and return packet
            return header + self._recv_chunk(sock, size=payload_len)
        except IOError as err:
            raise OperationalError(
                errno=2055, values=(address, _strioerror(err))
            ) from err


class NetworkBrokerCompressed(NetworkBrokerPlain):
    """Broker class for MySQL socket communication."""

    def __init__(self) -> None:
        super().__init__()
        self._compressed_pktnr = -1
        self._queue_read: Deque[bytearray] = deque()

    @staticmethod
    def _prepare_packets(payload: bytes, pktnr: int) -> List[bytes]:
        """Prepare a payload for sending to the MySQL server."""
        pkts = []

        # If the payload is larger than or equal to MAX_PAYLOAD_LENGTH
        # the length is set to 2^24 - 1 (ff ff ff) and additional
        # packets are sent with the rest of the payload until the
        # payload of a packet is less than MAX_PAYLOAD_LENGTH.
        if len(payload) >= MAX_PAYLOAD_LENGTH:
            offset = 0
            for _ in range(len(payload) // MAX_PAYLOAD_LENGTH):
                # payload length + sequence id + payload
                pkts.append(
                    b"\xff\xff\xff"
                    + struct.pack("<B", pktnr)
                    + payload[offset : offset + MAX_PAYLOAD_LENGTH]
                )
                pktnr = (pktnr + 1) % 256
                offset += MAX_PAYLOAD_LENGTH
            payload = payload[offset:]
        pkts.append(
            struct.pack("<I", len(payload))[0:3] + struct.pack("<B", pktnr) + payload
        )
        return pkts

    def _set_next_compressed_pktnr(self) -> None:
        """Increment packet id."""
        self._compressed_pktnr = (self._compressed_pktnr + 1) % 256

    def _send_pkt(self, sock: socket.socket, address: str, pkt: bytes) -> None:
        """Compress packet and write it to the comm channel."""
        compressed_pkt = zlib.compress(pkt)
        pkt = (
            struct.pack("<I", len(compressed_pkt))[0:3]
            + struct.pack("<B", self._compressed_pktnr)
            + struct.pack("<I", len(pkt))[0:3]
            + compressed_pkt
        )
        return super()._send_pkt(sock, address, pkt)

    def send(
        self,
        sock: socket.socket,
        address: str,
        payload: bytes,
        packet_number: Optional[int] = None,
        compressed_packet_number: Optional[int] = None,
    ) -> None:
        """Send `payload` as compressed packets to the MySQL server.

        If provided a payload whose length is greater than `MAX_PAYLOAD_LENGTH`, it is
        broken down into packets.
        """
        # get next packet numbers
        if packet_number is None:
            self._set_next_pktnr()
        else:
            self._pktnr = packet_number
        if compressed_packet_number is None:
            self._set_next_compressed_pktnr()
        else:
            self._compressed_pktnr = compressed_packet_number

        payload_prep = bytearray(b"").join(self._prepare_packets(payload, self._pktnr))
        if len(payload) >= MAX_PAYLOAD_LENGTH - PACKET_HEADER_LENGTH:
            # sending a MySQL payload of the size greater or equal to 2^24 - 5
            # via compression leads to at least one extra compressed packet
            # WHY? let's say len(payload) is MAX_PAYLOAD_LENGTH - 3; when preparing
            # the payload, a header of size PACKET_HEADER_LENGTH is pre-appended
            # to the payload. This means that len(payload_prep) is
            # MAX_PAYLOAD_LENGTH - 3 + PACKET_HEADER_LENGTH = MAX_PAYLOAD_LENGTH + 1
            # surpassing the maximum allowed payload size per packet.
            offset = 0

            # send several MySQL packets
            for _ in range(len(payload_prep) // MAX_PAYLOAD_LENGTH):
                self._send_pkt(
                    sock, address, payload_prep[offset : offset + MAX_PAYLOAD_LENGTH]
                )
                self._set_next_compressed_pktnr()
                offset += MAX_PAYLOAD_LENGTH
            self._send_pkt(sock, address, payload_prep[offset:])
        else:
            # send one MySQL packet
            # For small packets it may be too costly to compress the packet.
            # Usually payloads less than 50 bytes (MIN_COMPRESS_LENGTH)
            # aren't compressed (see MySQL source code Documentation).
            if len(payload) > MIN_COMPRESS_LENGTH:
                # perform compression
                self._send_pkt(sock, address, payload_prep)
            else:
                # skip compression
                super()._send_pkt(
                    sock,
                    address,
                    struct.pack("<I", len(payload_prep))[0:3]
                    + struct.pack("<B", self._compressed_pktnr)
                    + struct.pack("<I", 0)[0:3]
                    + payload_prep,
                )

    def _recv_compressed_pkt(
        self, sock: socket.socket, compressed_pll: int, uncompressed_pll: int
    ) -> None:
        """Handle reading of a compressed packet."""
        # compressed_pll stands for compressed payload length.
        # Recalling that if uncompressed payload length == 0, the packet
        # comes in uncompressed, so no decompression is needed.
        compressed_pkt = super()._recv_chunk(sock, size=compressed_pll)
        pkt = (
            compressed_pkt
            if uncompressed_pll == 0
            else bytearray(zlib.decompress(compressed_pkt))
        )

        offset = 0
        while offset < len(pkt):
            # pll stands for payload length
            pll = struct.unpack(
                "<I", pkt[offset : offset + PACKET_HEADER_LENGTH - 1] + b"\x00"
            )[0]
            if PACKET_HEADER_LENGTH + pll > len(pkt) - offset:
                # More bytes need to be consumed
                # Read the header of the next MySQL packet
                header = super()._recv_chunk(sock, size=COMPRESSED_PACKET_HEADER_LENGTH)

                # compressed payload length, sequence id, uncompressed payload length
                (
                    compressed_pll,
                    self._compressed_pktnr,
                    uncompressed_pll,
                ) = (
                    struct.unpack("<I", header[0:3] + b"\x00")[0],
                    header[3],
                    struct.unpack("<I", header[4:7] + b"\x00")[0],
                )
                compressed_pkt = super()._recv_chunk(sock, size=compressed_pll)

                # recalling that if uncompressed payload length == 0, the packet
                # comes in uncompressed, so no decompression is needed.
                pkt += (
                    compressed_pkt
                    if uncompressed_pll == 0
                    else zlib.decompress(compressed_pkt)
                )

            self._queue_read.append(pkt[offset : offset + PACKET_HEADER_LENGTH + pll])
            offset += PACKET_HEADER_LENGTH + pll

    def recv(self, sock: socket.socket, address: str) -> bytearray:
        """Receive `one` or `several` packets from the MySQL server, enqueue them, and
        return the packet at the head.
        """
        if not self._queue_read:
            try:
                # Read the header of the next MySQL packet
                header = super()._recv_chunk(sock, size=COMPRESSED_PACKET_HEADER_LENGTH)

                # compressed payload length, sequence id, uncompressed payload length
                (
                    compressed_pll,
                    self._compressed_pktnr,
                    uncompressed_pll,
                ) = (
                    struct.unpack("<I", header[0:3] + b"\x00")[0],
                    header[3],
                    struct.unpack("<I", header[4:7] + b"\x00")[0],
                )
                self._recv_compressed_pkt(sock, compressed_pll, uncompressed_pll)
            except IOError as err:
                raise OperationalError(
                    errno=2055, values=(address, _strioerror(err))
                ) from err

        if not self._queue_read:
            return None

        pkt = self._queue_read.popleft()
        self._pktnr = pkt[3]

        return pkt


class MySQLSocket(ABC):
    """MySQL socket communication interface.

    Examples:
        Subclasses: network.MySQLTCPSocket and network.MySQLUnixSocket.
    """

    def __init__(self) -> None:
        """Network layer where transactions are made with plain (uncompressed) packets
        is enabled by default.
        """
        # holds the socket connection
        self.sock: Optional[socket.socket] = None
        self._connection_timeout: Optional[int] = None
        self.server_host: Optional[str] = None
        self._netbroker: NetworkBroker = NetworkBrokerPlain()

    def switch_to_compressed_mode(self) -> None:
        """Enable network layer where transactions are made with compressed packets."""
        self._netbroker = NetworkBrokerCompressed()

    def shutdown(self) -> None:
        """Shut down the socket before closing it."""
        try:
            self.sock.shutdown(socket.SHUT_RDWR)
            self.sock.close()
        except (AttributeError, OSError):
            pass

    def close_connection(self) -> None:
        """Close the socket."""
        try:
            self.sock.close()
        except (AttributeError, OSError):
            pass

    def __del__(self) -> None:
        self.shutdown()

    def set_connection_timeout(self, timeout: Optional[int]) -> None:
        """Set the connection timeout."""
        self._connection_timeout = timeout
        if self.sock:
            self.sock.settimeout(timeout)

    def switch_to_ssl(
        self,
        ca: StrOrBytesPath,
        cert: StrOrBytesPath,
        key: StrOrBytesPath,
        verify_cert: bool = False,
        verify_identity: bool = False,
        cipher_suites: Optional[str] = None,
        tls_versions: Optional[List[str]] = None,
    ) -> None:
        """Switch the socket to use SSL"""
        if not self.sock:
            raise InterfaceError(errno=2048)

        try:
            if verify_cert:
                cert_reqs = ssl.CERT_REQUIRED
            elif verify_identity:
                cert_reqs = ssl.CERT_OPTIONAL
            else:
                cert_reqs = ssl.CERT_NONE

            if tls_versions is None or not tls_versions:
                context = ssl.create_default_context()
                if not verify_identity:
                    context.check_hostname = False
            else:
                tls_versions.sort(reverse=True)

                tls_version = tls_versions[0]
                if (
                    not TLS_V1_3_SUPPORTED
                    and tls_version == "TLSv1.3"
                    and len(tls_versions) > 1
                ):
                    tls_version = tls_versions[1]
                ssl_protocol = TLS_VERSIONS[tls_version]
                context = ssl.SSLContext(ssl_protocol)

                if tls_version == "TLSv1.3":
                    if "TLSv1.2" not in tls_versions:
                        context.options |= ssl.OP_NO_TLSv1_2
                    if "TLSv1.1" not in tls_versions:
                        context.options |= ssl.OP_NO_TLSv1_1
                    if "TLSv1" not in tls_versions:
                        context.options |= ssl.OP_NO_TLSv1

            context.check_hostname = False
            context.verify_mode = cert_reqs
            context.load_default_certs()

            if ca:
                try:
                    context.load_verify_locations(ca)
                except (IOError, ssl.SSLError) as err:
                    self.sock.close()
                    raise InterfaceError(f"Invalid CA Certificate: {err}") from err
            if cert:
                try:
                    context.load_cert_chain(cert, key)
                except (IOError, ssl.SSLError) as err:
                    self.sock.close()
                    raise InterfaceError(f"Invalid Certificate/Key: {err}") from err
            if cipher_suites:
                context.set_ciphers(cipher_suites)

            if hasattr(self, "server_host"):
                self.sock = context.wrap_socket(
                    self.sock, server_hostname=self.server_host
                )
            else:
                self.sock = context.wrap_socket(self.sock)

            if verify_identity:
                context.check_hostname = True
                hostnames: List[str] = [self.server_host] if self.server_host else []
                if os.name == "nt" and self.server_host == "localhost":
                    hostnames = ["localhost", "127.0.0.1"]
                    aliases = socket.gethostbyaddr(self.server_host)
                    hostnames.extend([aliases[0]] + aliases[1])
                match_found = False
                errs = []
                for hostname in hostnames:
                    try:
                        # Deprecated in Python 3.7 without a replacement and
                        # should be removed in the future, since OpenSSL now
                        # performs hostname matching
                        # pylint: disable=deprecated-method
                        ssl.match_hostname(self.sock.getpeercert(), hostname)
                        # pylint: enable=deprecated-method
                    except ssl.CertificateError as err:
                        errs.append(str(err))
                    else:
                        match_found = True
                        break
                if not match_found:
                    self.sock.close()
                    raise InterfaceError(
                        f"Unable to verify server identity: {', '.join(errs)}"
                    )
        except NameError as err:
            raise NotSupportedError("Python installation has no SSL support") from err
        except (ssl.SSLError, IOError) as err:
            raise InterfaceError(
                errno=2055, values=(self.address, _strioerror(err))
            ) from err
        except ssl.CertificateError as err:
            raise InterfaceError(str(err)) from err
        except NotImplementedError as err:
            raise InterfaceError(str(err)) from err

    def send(
        self,
        payload: bytes,
        packet_number: Optional[int] = None,
        compressed_packet_number: Optional[int] = None,
    ) -> None:
        """Send `payload` to the MySQL server."""
        return self._netbroker.send(
            self.sock,
            self.address,
            payload,
            packet_number=packet_number,
            compressed_packet_number=compressed_packet_number,
        )

    def recv(self) -> bytearray:
        """Get packet from the MySQL server comm channel."""
        return self._netbroker.recv(self.sock, self.address)

    @abstractmethod
    def open_connection(self) -> None:
        """Open the socket."""

    @property
    @abstractmethod
    def address(self) -> str:
        """Get the location of the socket."""


class MySQLUnixSocket(MySQLSocket):
    """MySQL socket class using UNIX sockets.

    Opens a connection through the UNIX socket of the MySQL Server.
    """

    def __init__(self, unix_socket: str = "/tmp/mysql.sock") -> None:
        super().__init__()
        self.unix_socket: str = unix_socket
        self._address: str = unix_socket

    @property
    def address(self) -> str:
        return self._address

    def open_connection(self) -> None:
        try:
            self.sock = socket.socket(
                socket.AF_UNIX, socket.SOCK_STREAM  # pylint: disable=no-member
            )
            self.sock.settimeout(self._connection_timeout)
            self.sock.connect(self.unix_socket)
        except IOError as err:
            raise InterfaceError(
                errno=2002, values=(self.address, _strioerror(err))
            ) from err
        except Exception as err:
            raise InterfaceError(str(err)) from err

    def switch_to_ssl(
        self, *args: Any, **kwargs: Any  # pylint: disable=unused-argument
    ) -> None:
        """Switch the socket to use SSL."""
        warnings.warn(
            "SSL is disabled when using unix socket connections",
            Warning,
        )


class MySQLTCPSocket(MySQLSocket):
    """MySQL socket class using TCP/IP.

    Opens a TCP/IP connection to the MySQL Server.
    """

    def __init__(
        self,
        host: str = "127.0.0.1",
        port: int = 3306,
        force_ipv6: bool = False,
    ) -> None:
        super().__init__()
        self.server_host: str = host
        self.server_port: int = port
        self.force_ipv6: bool = force_ipv6
        self._family: int = 0
        self._address: str = f"{host}:{port}"

    @property
    def address(self) -> str:
        return self._address

    def open_connection(self) -> None:
        """Open the TCP/IP connection to the MySQL server."""
        # pylint: disable=no-member
        # Get address information
        addrinfo: Union[
            Tuple[None, None, None, None, None],
            Tuple[
                socket.AddressFamily,
                socket.SocketKind,
                int,
                str,
                Union[Tuple[str, int], Tuple[str, int, int, int]],
            ],
        ] = (None, None, None, None, None)
        try:
            addrinfos = socket.getaddrinfo(
                self.server_host,
                self.server_port,
                0,
                socket.SOCK_STREAM,
                socket.SOL_TCP,
            )
            # If multiple results we favor IPv4, unless IPv6 was forced.
            for info in addrinfos:
                if self.force_ipv6 and info[0] == socket.AF_INET6:
                    addrinfo = info
                    break
                if info[0] == socket.AF_INET:
                    addrinfo = info
                    break
            if self.force_ipv6 and addrinfo[0] is None:
                raise InterfaceError(f"No IPv6 address found for {self.server_host}")
            if addrinfo[0] is None:
                addrinfo = addrinfos[0]
        except IOError as err:
            raise InterfaceError(
                errno=2003, values=(self.address, _strioerror(err))
            ) from err

        (self._family, socktype, proto, _, sockaddr) = addrinfo

        # Instanciate the socket and connect
        try:
            self.sock = socket.socket(self._family, socktype, proto)
            self.sock.settimeout(self._connection_timeout)
            self.sock.connect(sockaddr)
        except IOError as err:
            raise InterfaceError(
                errno=2003,
                values=(
                    self.server_host,
                    self.server_port,
                    _strioerror(err),
                ),
            ) from err
        except Exception as err:
            raise OperationalError(str(err)) from err

virt/lib/python3.9/site-packages/mysql/connector/network 2.py
PrinceDisant/LostInNL
# Copyright (c) 2012, 2023, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

# mypy: disable-error-code="attr-defined"

"""Module implementing low-level socket communication with MySQL servers.
"""

import os
import socket
import struct
import warnings
import zlib

from abc import ABC, abstractmethod
from collections import deque

try:
    import ssl

    TLS_VERSIONS = {
        "TLSv1": ssl.PROTOCOL_TLSv1,
        "TLSv1.1": ssl.PROTOCOL_TLSv1_1,
        "TLSv1.2": ssl.PROTOCOL_TLSv1_2,
    }
    # TLSv1.3 included in PROTOCOL_TLS, but PROTOCOL_TLS is not included on 3.4
    TLS_VERSIONS["TLSv1.3"] = (
        ssl.PROTOCOL_TLS
        if hasattr(ssl, "PROTOCOL_TLS")
        else ssl.PROTOCOL_SSLv23  # Alias of PROTOCOL_TLS
    )
    TLS_V1_3_SUPPORTED = hasattr(ssl, "HAS_TLSv1_3") and ssl.HAS_TLSv1_3
except ImportError:
    # If import fails, we don't have SSL support.
    TLS_V1_3_SUPPORTED = False

from typing import Any, Deque, List, Optional, Tuple, Union

from .errors import InterfaceError, NotSupportedError, OperationalError
from .types import StrOrBytesPath

MIN_COMPRESS_LENGTH: int = 50
MAX_PAYLOAD_LENGTH: int = 2**24 - 1
PACKET_HEADER_LENGTH: int = 4
COMPRESSED_PACKET_HEADER_LENGTH: int = 7


def _strioerror(err: IOError) -> str:
    """Reformat the IOError error message.

    This function reformats the IOError error message.
    """
    return str(err) if not err.errno else f"{err.errno} {err.strerror}"


class NetworkBroker(ABC):
    """Broker class interface.

    The network object is a broker used as a delegate by a socket object. Whenever the
    socket wants to deliver or get packets to or from the MySQL server it needs to rely
    on its network broker (netbroker).

    The netbroker sends `payloads` and receives `packets`.

    A packet is a bytes sequence, it has a header and body (referred to as payload).
    The first `PACKET_HEADER_LENGTH` or `COMPRESSED_PACKET_HEADER_LENGTH`
    (as appropriate) bytes correspond to the `header`, the remaining ones represent the
    `payload`.

    The maximum payload length allowed to be sent per packet to the server is
    `MAX_PAYLOAD_LENGTH`. When  `send` is called with a payload whose length is greater
    than `MAX_PAYLOAD_LENGTH` the netbroker breaks it down into packets, so the caller
    of `send` can provide payloads of arbitrary length.

    Finally, data received by the netbroker comes directly from the server, expect to
    get a packet for each call to `recv`. The received packet contains a header and
    payload, the latter respecting `MAX_PAYLOAD_LENGTH`.
    """

    @abstractmethod
    def send(
        self,
        sock: socket.socket,
        address: str,
        payload: bytes,
        packet_number: Optional[int] = None,
        compressed_packet_number: Optional[int] = None,
    ) -> None:
        """Send `payload` to the MySQL server.

        If provided a payload whose length is greater than `MAX_PAYLOAD_LENGTH`, it is
        broken down into packets.

        Args:
            sock: Object holding the socket connection.
            address: Socket's location.
            payload: Packet's body to send.
            packet_number: Sequence id (packet ID) to attach to the header when sending
                           plain packets.
            compressed_packet_number: Same as `packet_number` but used when sending
                                      compressed packets.

        Raises:
            :class:`OperationalError`: If something goes wrong while sending packets to
                                       the MySQL server.
        """

    @abstractmethod
    def recv(self, sock: socket.socket, address: str) -> bytearray:
        """Get the next available packet from the MySQL server.

        Args:
            sock: Object holding the socket connection.
            address: Socket's location.

        Returns:
            packet: A packet from the MySQL server.

        Raises:
            :class:`OperationalError`: If something goes wrong while receiving packets
                                       from the MySQL server.
            :class:`InterfaceError`: If something goes wrong while receiving packets
                                     from the MySQL server.
        """


class NetworkBrokerPlain(NetworkBroker):
    """Broker class for MySQL socket communication."""

    def __init__(self) -> None:
        self._pktnr: int = -1  # packet number

    def _set_next_pktnr(self) -> None:
        """Increment packet id."""
        self._pktnr = (self._pktnr + 1) % 256

    def _send_pkt(self, sock: socket.socket, address: str, pkt: bytes) -> None:
        """Write packet to the comm channel."""
        try:
            sock.sendall(pkt)
        except IOError as err:
            raise OperationalError(
                errno=2055, values=(address, _strioerror(err))
            ) from err
        except AttributeError as err:
            raise OperationalError(errno=2006) from err

    def _recv_chunk(self, sock: socket.socket, size: int = 0) -> bytearray:
        """Read `size` bytes from the comm channel."""
        pkt = bytearray(size)
        pkt_view = memoryview(pkt)
        while size:
            read = sock.recv_into(pkt_view, size)
            if read == 0 and size > 0:
                raise InterfaceError(errno=2013)
            pkt_view = pkt_view[read:]
            size -= read
        return pkt

    def send(
        self,
        sock: socket.socket,
        address: str,
        payload: bytes,
        packet_number: Optional[int] = None,
        compressed_packet_number: Optional[int] = None,
    ) -> None:
        """Send payload to the MySQL server.

        If provided a payload whose length is greater than `MAX_PAYLOAD_LENGTH`, it is
        broken down into packets.
        """
        if packet_number is None:
            self._set_next_pktnr()
        else:
            self._pktnr = packet_number

        # If the payload is larger than or equal to MAX_PAYLOAD_LENGTH
        # the length is set to 2^24 - 1 (ff ff ff) and additional
        # packets are sent with the rest of the payload until the
        # payload of a packet is less than MAX_PAYLOAD_LENGTH.
        if len(payload) >= MAX_PAYLOAD_LENGTH:
            offset = 0
            for _ in range(len(payload) // MAX_PAYLOAD_LENGTH):
                # payload_len, sequence_id, payload
                self._send_pkt(
                    sock,
                    address,
                    b"\xff\xff\xff"
                    + struct.pack("<B", self._pktnr)
                    + payload[offset : offset + MAX_PAYLOAD_LENGTH],
                )
                self._set_next_pktnr()
                offset += MAX_PAYLOAD_LENGTH
            payload = payload[offset:]
        self._send_pkt(
            sock,
            address,
            struct.pack("<I", len(payload))[0:3]
            + struct.pack("<B", self._pktnr)
            + payload,
        )

    def recv(self, sock: socket.socket, address: str) -> bytearray:
        """Receive `one` packet from the MySQL server."""
        try:
            # Read the header of the MySQL packet
            header = self._recv_chunk(sock, size=PACKET_HEADER_LENGTH)

            # Pull the payload length and sequence id
            payload_len, self._pktnr = (
                struct.unpack("<I", header[0:3] + b"\x00")[0],
                header[3],
            )

            # Read the payload, and return packet
            return header + self._recv_chunk(sock, size=payload_len)
        except IOError as err:
            raise OperationalError(
                errno=2055, values=(address, _strioerror(err))
            ) from err


class NetworkBrokerCompressed(NetworkBrokerPlain):
    """Broker class for MySQL socket communication."""

    def __init__(self) -> None:
        super().__init__()
        self._compressed_pktnr = -1
        self._queue_read: Deque[bytearray] = deque()

    @staticmethod
    def _prepare_packets(payload: bytes, pktnr: int) -> List[bytes]:
        """Prepare a payload for sending to the MySQL server."""
        pkts = []

        # If the payload is larger than or equal to MAX_PAYLOAD_LENGTH
        # the length is set to 2^24 - 1 (ff ff ff) and additional
        # packets are sent with the rest of the payload until the
        # payload of a packet is less than MAX_PAYLOAD_LENGTH.
        if len(payload) >= MAX_PAYLOAD_LENGTH:
            offset = 0
            for _ in range(len(payload) // MAX_PAYLOAD_LENGTH):
                # payload length + sequence id + payload
                pkts.append(
                    b"\xff\xff\xff"
                    + struct.pack("<B", pktnr)
                    + payload[offset : offset + MAX_PAYLOAD_LENGTH]
                )
                pktnr = (pktnr + 1) % 256
                offset += MAX_PAYLOAD_LENGTH
            payload = payload[offset:]
        pkts.append(
            struct.pack("<I", len(payload))[0:3] + struct.pack("<B", pktnr) + payload
        )
        return pkts

    def _set_next_compressed_pktnr(self) -> None:
        """Increment packet id."""
        self._compressed_pktnr = (self._compressed_pktnr + 1) % 256

    def _send_pkt(self, sock: socket.socket, address: str, pkt: bytes) -> None:
        """Compress packet and write it to the comm channel."""
        compressed_pkt = zlib.compress(pkt)
        pkt = (
            struct.pack("<I", len(compressed_pkt))[0:3]
            + struct.pack("<B", self._compressed_pktnr)
            + struct.pack("<I", len(pkt))[0:3]
            + compressed_pkt
        )
        return super()._send_pkt(sock, address, pkt)

    def send(
        self,
        sock: socket.socket,
        address: str,
        payload: bytes,
        packet_number: Optional[int] = None,
        compressed_packet_number: Optional[int] = None,
    ) -> None:
        """Send `payload` as compressed packets to the MySQL server.

        If provided a payload whose length is greater than `MAX_PAYLOAD_LENGTH`, it is
        broken down into packets.
        """
        # get next packet numbers
        if packet_number is None:
            self._set_next_pktnr()
        else:
            self._pktnr = packet_number
        if compressed_packet_number is None:
            self._set_next_compressed_pktnr()
        else:
            self._compressed_pktnr = compressed_packet_number

        payload_prep = bytearray(b"").join(self._prepare_packets(payload, self._pktnr))
        if len(payload) >= MAX_PAYLOAD_LENGTH - PACKET_HEADER_LENGTH:
            # sending a MySQL payload of the size greater or equal to 2^24 - 5
            # via compression leads to at least one extra compressed packet
            # WHY? let's say len(payload) is MAX_PAYLOAD_LENGTH - 3; when preparing
            # the payload, a header of size PACKET_HEADER_LENGTH is pre-appended
            # to the payload. This means that len(payload_prep) is
            # MAX_PAYLOAD_LENGTH - 3 + PACKET_HEADER_LENGTH = MAX_PAYLOAD_LENGTH + 1
            # surpassing the maximum allowed payload size per packet.
            offset = 0

            # send several MySQL packets
            for _ in range(len(payload_prep) // MAX_PAYLOAD_LENGTH):
                self._send_pkt(
                    sock, address, payload_prep[offset : offset + MAX_PAYLOAD_LENGTH]
                )
                self._set_next_compressed_pktnr()
                offset += MAX_PAYLOAD_LENGTH
            self._send_pkt(sock, address, payload_prep[offset:])
        else:
            # send one MySQL packet
            # For small packets it may be too costly to compress the packet.
            # Usually payloads less than 50 bytes (MIN_COMPRESS_LENGTH)
            # aren't compressed (see MySQL source code Documentation).
            if len(payload) > MIN_COMPRESS_LENGTH:
                # perform compression
                self._send_pkt(sock, address, payload_prep)
            else:
                # skip compression
                super()._send_pkt(
                    sock,
                    address,
                    struct.pack("<I", len(payload_prep))[0:3]
                    + struct.pack("<B", self._compressed_pktnr)
                    + struct.pack("<I", 0)[0:3]
                    + payload_prep,
                )

    def _recv_compressed_pkt(
        self, sock: socket.socket, compressed_pll: int, uncompressed_pll: int
    ) -> None:
        """Handle reading of a compressed packet."""
        # compressed_pll stands for compressed payload length.
        # Recalling that if uncompressed payload length == 0, the packet
        # comes in uncompressed, so no decompression is needed.
        compressed_pkt = super()._recv_chunk(sock, size=compressed_pll)
        pkt = (
            compressed_pkt
            if uncompressed_pll == 0
            else bytearray(zlib.decompress(compressed_pkt))
        )

        offset = 0
        while offset < len(pkt):
            # pll stands for payload length
            pll = struct.unpack(
                "<I", pkt[offset : offset + PACKET_HEADER_LENGTH - 1] + b"\x00"
            )[0]
            if PACKET_HEADER_LENGTH + pll > len(pkt) - offset:
                # More bytes need to be consumed
                # Read the header of the next MySQL packet
                header = super()._recv_chunk(sock, size=COMPRESSED_PACKET_HEADER_LENGTH)

                # compressed payload length, sequence id, uncompressed payload length
                (
                    compressed_pll,
                    self._compressed_pktnr,
                    uncompressed_pll,
                ) = (
                    struct.unpack("<I", header[0:3] + b"\x00")[0],
                    header[3],
                    struct.unpack("<I", header[4:7] + b"\x00")[0],
                )
                compressed_pkt = super()._recv_chunk(sock, size=compressed_pll)

                # recalling that if uncompressed payload length == 0, the packet
                # comes in uncompressed, so no decompression is needed.
                pkt += (
                    compressed_pkt
                    if uncompressed_pll == 0
                    else zlib.decompress(compressed_pkt)
                )

            self._queue_read.append(pkt[offset : offset + PACKET_HEADER_LENGTH + pll])
            offset += PACKET_HEADER_LENGTH + pll

    def recv(self, sock: socket.socket, address: str) -> bytearray:
        """Receive `one` or `several` packets from the MySQL server, enqueue them, and
        return the packet at the head.
        """
        if not self._queue_read:
            try:
                # Read the header of the next MySQL packet
                header = super()._recv_chunk(sock, size=COMPRESSED_PACKET_HEADER_LENGTH)

                # compressed payload length, sequence id, uncompressed payload length
                (
                    compressed_pll,
                    self._compressed_pktnr,
                    uncompressed_pll,
                ) = (
                    struct.unpack("<I", header[0:3] + b"\x00")[0],
                    header[3],
                    struct.unpack("<I", header[4:7] + b"\x00")[0],
                )
                self._recv_compressed_pkt(sock, compressed_pll, uncompressed_pll)
            except IOError as err:
                raise OperationalError(
                    errno=2055, values=(address, _strioerror(err))
                ) from err

        if not self._queue_read:
            return None

        pkt = self._queue_read.popleft()
        self._pktnr = pkt[3]

        return pkt


class MySQLSocket(ABC):
    """MySQL socket communication interface.

    Examples:
        Subclasses: network.MySQLTCPSocket and network.MySQLUnixSocket.
    """

    def __init__(self) -> None:
        """Network layer where transactions are made with plain (uncompressed) packets
        is enabled by default.
        """
        # holds the socket connection
        self.sock: Optional[socket.socket] = None
        self._connection_timeout: Optional[int] = None
        self.server_host: Optional[str] = None
        self._netbroker: NetworkBroker = NetworkBrokerPlain()

    def switch_to_compressed_mode(self) -> None:
        """Enable network layer where transactions are made with compressed packets."""
        self._netbroker = NetworkBrokerCompressed()

    def shutdown(self) -> None:
        """Shut down the socket before closing it."""
        try:
            self.sock.shutdown(socket.SHUT_RDWR)
            self.sock.close()
        except (AttributeError, OSError):
            pass

    def close_connection(self) -> None:
        """Close the socket."""
        try:
            self.sock.close()
        except (AttributeError, OSError):
            pass

    def __del__(self) -> None:
        self.shutdown()

    def set_connection_timeout(self, timeout: Optional[int]) -> None:
        """Set the connection timeout."""
        self._connection_timeout = timeout
        if self.sock:
            self.sock.settimeout(timeout)

    def switch_to_ssl(
        self,
        ca: StrOrBytesPath,
        cert: StrOrBytesPath,
        key: StrOrBytesPath,
        verify_cert: bool = False,
        verify_identity: bool = False,
        cipher_suites: Optional[str] = None,
        tls_versions: Optional[List[str]] = None,
    ) -> None:
        """Switch the socket to use SSL"""
        if not self.sock:
            raise InterfaceError(errno=2048)

        try:
            if verify_cert:
                cert_reqs = ssl.CERT_REQUIRED
            elif verify_identity:
                cert_reqs = ssl.CERT_OPTIONAL
            else:
                cert_reqs = ssl.CERT_NONE

            if tls_versions is None or not tls_versions:
                context = ssl.create_default_context()
                if not verify_identity:
                    context.check_hostname = False
            else:
                tls_versions.sort(reverse=True)

                tls_version = tls_versions[0]
                if (
                    not TLS_V1_3_SUPPORTED
                    and tls_version == "TLSv1.3"
                    and len(tls_versions) > 1
                ):
                    tls_version = tls_versions[1]
                ssl_protocol = TLS_VERSIONS[tls_version]
                context = ssl.SSLContext(ssl_protocol)

                if tls_version == "TLSv1.3":
                    if "TLSv1.2" not in tls_versions:
                        context.options |= ssl.OP_NO_TLSv1_2
                    if "TLSv1.1" not in tls_versions:
                        context.options |= ssl.OP_NO_TLSv1_1
                    if "TLSv1" not in tls_versions:
                        context.options |= ssl.OP_NO_TLSv1

            context.check_hostname = False
            context.verify_mode = cert_reqs
            context.load_default_certs()

            if ca:
                try:
                    context.load_verify_locations(ca)
                except (IOError, ssl.SSLError) as err:
                    self.sock.close()
                    raise InterfaceError(f"Invalid CA Certificate: {err}") from err
            if cert:
                try:
                    context.load_cert_chain(cert, key)
                except (IOError, ssl.SSLError) as err:
                    self.sock.close()
                    raise InterfaceError(f"Invalid Certificate/Key: {err}") from err
            if cipher_suites:
                context.set_ciphers(cipher_suites)

            if hasattr(self, "server_host"):
                self.sock = context.wrap_socket(
                    self.sock, server_hostname=self.server_host
                )
            else:
                self.sock = context.wrap_socket(self.sock)

            if verify_identity:
                context.check_hostname = True
                hostnames: List[str] = [self.server_host] if self.server_host else []
                if os.name == "nt" and self.server_host == "localhost":
                    hostnames = ["localhost", "127.0.0.1"]
                    aliases = socket.gethostbyaddr(self.server_host)
                    hostnames.extend([aliases[0]] + aliases[1])
                match_found = False
                errs = []
                for hostname in hostnames:
                    try:
                        # Deprecated in Python 3.7 without a replacement and
                        # should be removed in the future, since OpenSSL now
                        # performs hostname matching
                        # pylint: disable=deprecated-method
                        ssl.match_hostname(self.sock.getpeercert(), hostname)
                        # pylint: enable=deprecated-method
                    except ssl.CertificateError as err:
                        errs.append(str(err))
                    else:
                        match_found = True
                        break
                if not match_found:
                    self.sock.close()
                    raise InterfaceError(
                        f"Unable to verify server identity: {', '.join(errs)}"
                    )
        except NameError as err:
            raise NotSupportedError("Python installation has no SSL support") from err
        except (ssl.SSLError, IOError) as err:
            raise InterfaceError(
                errno=2055, values=(self.address, _strioerror(err))
            ) from err
        except ssl.CertificateError as err:
            raise InterfaceError(str(err)) from err
        except NotImplementedError as err:
            raise InterfaceError(str(err)) from err

    def send(
        self,
        payload: bytes,
        packet_number: Optional[int] = None,
        compressed_packet_number: Optional[int] = None,
    ) -> None:
        """Send `payload` to the MySQL server."""
        return self._netbroker.send(
            self.sock,
            self.address,
            payload,
            packet_number=packet_number,
            compressed_packet_number=compressed_packet_number,
        )

    def recv(self) -> bytearray:
        """Get packet from the MySQL server comm channel."""
        return self._netbroker.recv(self.sock, self.address)

    @abstractmethod
    def open_connection(self) -> None:
        """Open the socket."""

    @property
    @abstractmethod
    def address(self) -> str:
        """Get the location of the socket."""


class MySQLUnixSocket(MySQLSocket):
    """MySQL socket class using UNIX sockets.

    Opens a connection through the UNIX socket of the MySQL Server.
    """

    def __init__(self, unix_socket: str = "/tmp/mysql.sock") -> None:
        super().__init__()
        self.unix_socket: str = unix_socket
        self._address: str = unix_socket

    @property
    def address(self) -> str:
        return self._address

    def open_connection(self) -> None:
        try:
            self.sock = socket.socket(
                socket.AF_UNIX, socket.SOCK_STREAM  # pylint: disable=no-member
            )
            self.sock.settimeout(self._connection_timeout)
            self.sock.connect(self.unix_socket)
        except IOError as err:
            raise InterfaceError(
                errno=2002, values=(self.address, _strioerror(err))
            ) from err
        except Exception as err:
            raise InterfaceError(str(err)) from err

    def switch_to_ssl(
        self, *args: Any, **kwargs: Any  # pylint: disable=unused-argument
    ) -> None:
        """Switch the socket to use SSL."""
        warnings.warn(
            "SSL is disabled when using unix socket connections",
            Warning,
        )


class MySQLTCPSocket(MySQLSocket):
    """MySQL socket class using TCP/IP.

    Opens a TCP/IP connection to the MySQL Server.
    """

    def __init__(
        self,
        host: str = "127.0.0.1",
        port: int = 3306,
        force_ipv6: bool = False,
    ) -> None:
        super().__init__()
        self.server_host: str = host
        self.server_port: int = port
        self.force_ipv6: bool = force_ipv6
        self._family: int = 0
        self._address: str = f"{host}:{port}"

    @property
    def address(self) -> str:
        return self._address

    def open_connection(self) -> None:
        """Open the TCP/IP connection to the MySQL server."""
        # pylint: disable=no-member
        # Get address information
        addrinfo: Union[
            Tuple[None, None, None, None, None],
            Tuple[
                socket.AddressFamily,
                socket.SocketKind,
                int,
                str,
                Union[Tuple[str, int], Tuple[str, int, int, int]],
            ],
        ] = (None, None, None, None, None)
        try:
            addrinfos = socket.getaddrinfo(
                self.server_host,
                self.server_port,
                0,
                socket.SOCK_STREAM,
                socket.SOL_TCP,
            )
            # If multiple results we favor IPv4, unless IPv6 was forced.
            for info in addrinfos:
                if self.force_ipv6 and info[0] == socket.AF_INET6:
                    addrinfo = info
                    break
                if info[0] == socket.AF_INET:
                    addrinfo = info
                    break
            if self.force_ipv6 and addrinfo[0] is None:
                raise InterfaceError(f"No IPv6 address found for {self.server_host}")
            if addrinfo[0] is None:
                addrinfo = addrinfos[0]
        except IOError as err:
            raise InterfaceError(
                errno=2003, values=(self.address, _strioerror(err))
            ) from err

        (self._family, socktype, proto, _, sockaddr) = addrinfo

        # Instanciate the socket and connect
        try:
            self.sock = socket.socket(self._family, socktype, proto)
            self.sock.settimeout(self._connection_timeout)
            self.sock.connect(sockaddr)
        except IOError as err:
            raise InterfaceError(
                errno=2003,
                values=(
                    self.server_host,
                    self.server_port,
                    _strioerror(err),
                ),
            ) from err
        except Exception as err:
            raise OperationalError(str(err)) from err

virt/lib/python3.9/site-packages/mysql/connector/network 3.py
PrinceDisant/LostInNL
# Copyright (c) 2012, 2023, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

# mypy: disable-error-code="attr-defined"

"""Module implementing low-level socket communication with MySQL servers.
"""

import os
import socket
import struct
import warnings
import zlib

from abc import ABC, abstractmethod
from collections import deque

try:
    import ssl

    TLS_VERSIONS = {
        "TLSv1": ssl.PROTOCOL_TLSv1,
        "TLSv1.1": ssl.PROTOCOL_TLSv1_1,
        "TLSv1.2": ssl.PROTOCOL_TLSv1_2,
    }
    # TLSv1.3 included in PROTOCOL_TLS, but PROTOCOL_TLS is not included on 3.4
    TLS_VERSIONS["TLSv1.3"] = (
        ssl.PROTOCOL_TLS
        if hasattr(ssl, "PROTOCOL_TLS")
        else ssl.PROTOCOL_SSLv23  # Alias of PROTOCOL_TLS
    )
    TLS_V1_3_SUPPORTED = hasattr(ssl, "HAS_TLSv1_3") and ssl.HAS_TLSv1_3
except ImportError:
    # If import fails, we don't have SSL support.
    TLS_V1_3_SUPPORTED = False

from typing import Any, Deque, List, Optional, Tuple, Union

from .errors import InterfaceError, NotSupportedError, OperationalError
from .types import StrOrBytesPath

MIN_COMPRESS_LENGTH: int = 50
MAX_PAYLOAD_LENGTH: int = 2**24 - 1
PACKET_HEADER_LENGTH: int = 4
COMPRESSED_PACKET_HEADER_LENGTH: int = 7


def _strioerror(err: IOError) -> str:
    """Reformat the IOError error message.

    This function reformats the IOError error message.
    """
    return str(err) if not err.errno else f"{err.errno} {err.strerror}"


class NetworkBroker(ABC):
    """Broker class interface.

    The network object is a broker used as a delegate by a socket object. Whenever the
    socket wants to deliver or get packets to or from the MySQL server it needs to rely
    on its network broker (netbroker).

    The netbroker sends `payloads` and receives `packets`.

    A packet is a bytes sequence, it has a header and body (referred to as payload).
    The first `PACKET_HEADER_LENGTH` or `COMPRESSED_PACKET_HEADER_LENGTH`
    (as appropriate) bytes correspond to the `header`, the remaining ones represent the
    `payload`.

    The maximum payload length allowed to be sent per packet to the server is
    `MAX_PAYLOAD_LENGTH`. When  `send` is called with a payload whose length is greater
    than `MAX_PAYLOAD_LENGTH` the netbroker breaks it down into packets, so the caller
    of `send` can provide payloads of arbitrary length.

    Finally, data received by the netbroker comes directly from the server, expect to
    get a packet for each call to `recv`. The received packet contains a header and
    payload, the latter respecting `MAX_PAYLOAD_LENGTH`.
    """

    @abstractmethod
    def send(
        self,
        sock: socket.socket,
        address: str,
        payload: bytes,
        packet_number: Optional[int] = None,
        compressed_packet_number: Optional[int] = None,
    ) -> None:
        """Send `payload` to the MySQL server.

        If provided a payload whose length is greater than `MAX_PAYLOAD_LENGTH`, it is
        broken down into packets.

        Args:
            sock: Object holding the socket connection.
            address: Socket's location.
            payload: Packet's body to send.
            packet_number: Sequence id (packet ID) to attach to the header when sending
                           plain packets.
            compressed_packet_number: Same as `packet_number` but used when sending
                                      compressed packets.

        Raises:
            :class:`OperationalError`: If something goes wrong while sending packets to
                                       the MySQL server.
        """

    @abstractmethod
    def recv(self, sock: socket.socket, address: str) -> bytearray:
        """Get the next available packet from the MySQL server.

        Args:
            sock: Object holding the socket connection.
            address: Socket's location.

        Returns:
            packet: A packet from the MySQL server.

        Raises:
            :class:`OperationalError`: If something goes wrong while receiving packets
                                       from the MySQL server.
            :class:`InterfaceError`: If something goes wrong while receiving packets
                                     from the MySQL server.
        """


class NetworkBrokerPlain(NetworkBroker):
    """Broker class for MySQL socket communication."""

    def __init__(self) -> None:
        self._pktnr: int = -1  # packet number

    def _set_next_pktnr(self) -> None:
        """Increment packet id."""
        self._pktnr = (self._pktnr + 1) % 256

    def _send_pkt(self, sock: socket.socket, address: str, pkt: bytes) -> None:
        """Write packet to the comm channel."""
        try:
            sock.sendall(pkt)
        except IOError as err:
            raise OperationalError(
                errno=2055, values=(address, _strioerror(err))
            ) from err
        except AttributeError as err:
            raise OperationalError(errno=2006) from err

    def _recv_chunk(self, sock: socket.socket, size: int = 0) -> bytearray:
        """Read `size` bytes from the comm channel."""
        pkt = bytearray(size)
        pkt_view = memoryview(pkt)
        while size:
            read = sock.recv_into(pkt_view, size)
            if read == 0 and size > 0:
                raise InterfaceError(errno=2013)
            pkt_view = pkt_view[read:]
            size -= read
        return pkt

    def send(
        self,
        sock: socket.socket,
        address: str,
        payload: bytes,
        packet_number: Optional[int] = None,
        compressed_packet_number: Optional[int] = None,
    ) -> None:
        """Send payload to the MySQL server.

        If provided a payload whose length is greater than `MAX_PAYLOAD_LENGTH`, it is
        broken down into packets.
        """
        if packet_number is None:
            self._set_next_pktnr()
        else:
            self._pktnr = packet_number

        # If the payload is larger than or equal to MAX_PAYLOAD_LENGTH
        # the length is set to 2^24 - 1 (ff ff ff) and additional
        # packets are sent with the rest of the payload until the
        # payload of a packet is less than MAX_PAYLOAD_LENGTH.
        if len(payload) >= MAX_PAYLOAD_LENGTH:
            offset = 0
            for _ in range(len(payload) // MAX_PAYLOAD_LENGTH):
                # payload_len, sequence_id, payload
                self._send_pkt(
                    sock,
                    address,
                    b"\xff\xff\xff"
                    + struct.pack("<B", self._pktnr)
                    + payload[offset : offset + MAX_PAYLOAD_LENGTH],
                )
                self._set_next_pktnr()
                offset += MAX_PAYLOAD_LENGTH
            payload = payload[offset:]
        self._send_pkt(
            sock,
            address,
            struct.pack("<I", len(payload))[0:3]
            + struct.pack("<B", self._pktnr)
            + payload,
        )

    def recv(self, sock: socket.socket, address: str) -> bytearray:
        """Receive `one` packet from the MySQL server."""
        try:
            # Read the header of the MySQL packet
            header = self._recv_chunk(sock, size=PACKET_HEADER_LENGTH)

            # Pull the payload length and sequence id
            payload_len, self._pktnr = (
                struct.unpack("<I", header[0:3] + b"\x00")[0],
                header[3],
            )

            # Read the payload, and return packet
            return header + self._recv_chunk(sock, size=payload_len)
        except IOError as err:
            raise OperationalError(
                errno=2055, values=(address, _strioerror(err))
            ) from err


class NetworkBrokerCompressed(NetworkBrokerPlain):
    """Broker class for MySQL socket communication."""

    def __init__(self) -> None:
        super().__init__()
        self._compressed_pktnr = -1
        self._queue_read: Deque[bytearray] = deque()

    @staticmethod
    def _prepare_packets(payload: bytes, pktnr: int) -> List[bytes]:
        """Prepare a payload for sending to the MySQL server."""
        pkts = []

        # If the payload is larger than or equal to MAX_PAYLOAD_LENGTH
        # the length is set to 2^24 - 1 (ff ff ff) and additional
        # packets are sent with the rest of the payload until the
        # payload of a packet is less than MAX_PAYLOAD_LENGTH.
        if len(payload) >= MAX_PAYLOAD_LENGTH:
            offset = 0
            for _ in range(len(payload) // MAX_PAYLOAD_LENGTH):
                # payload length + sequence id + payload
                pkts.append(
                    b"\xff\xff\xff"
                    + struct.pack("<B", pktnr)
                    + payload[offset : offset + MAX_PAYLOAD_LENGTH]
                )
                pktnr = (pktnr + 1) % 256
                offset += MAX_PAYLOAD_LENGTH
            payload = payload[offset:]
        pkts.append(
            struct.pack("<I", len(payload))[0:3] + struct.pack("<B", pktnr) + payload
        )
        return pkts

    def _set_next_compressed_pktnr(self) -> None:
        """Increment packet id."""
        self._compressed_pktnr = (self._compressed_pktnr + 1) % 256

    def _send_pkt(self, sock: socket.socket, address: str, pkt: bytes) -> None:
        """Compress packet and write it to the comm channel."""
        compressed_pkt = zlib.compress(pkt)
        pkt = (
            struct.pack("<I", len(compressed_pkt))[0:3]
            + struct.pack("<B", self._compressed_pktnr)
            + struct.pack("<I", len(pkt))[0:3]
            + compressed_pkt
        )
        return super()._send_pkt(sock, address, pkt)

    def send(
        self,
        sock: socket.socket,
        address: str,
        payload: bytes,
        packet_number: Optional[int] = None,
        compressed_packet_number: Optional[int] = None,
    ) -> None:
        """Send `payload` as compressed packets to the MySQL server.

        If provided a payload whose length is greater than `MAX_PAYLOAD_LENGTH`, it is
        broken down into packets.
        """
        # get next packet numbers
        if packet_number is None:
            self._set_next_pktnr()
        else:
            self._pktnr = packet_number
        if compressed_packet_number is None:
            self._set_next_compressed_pktnr()
        else:
            self._compressed_pktnr = compressed_packet_number

        payload_prep = bytearray(b"").join(self._prepare_packets(payload, self._pktnr))
        if len(payload) >= MAX_PAYLOAD_LENGTH - PACKET_HEADER_LENGTH:
            # sending a MySQL payload of the size greater or equal to 2^24 - 5
            # via compression leads to at least one extra compressed packet
            # WHY? let's say len(payload) is MAX_PAYLOAD_LENGTH - 3; when preparing
            # the payload, a header of size PACKET_HEADER_LENGTH is pre-appended
            # to the payload. This means that len(payload_prep) is
            # MAX_PAYLOAD_LENGTH - 3 + PACKET_HEADER_LENGTH = MAX_PAYLOAD_LENGTH + 1
            # surpassing the maximum allowed payload size per packet.
            offset = 0

            # send several MySQL packets
            for _ in range(len(payload_prep) // MAX_PAYLOAD_LENGTH):
                self._send_pkt(
                    sock, address, payload_prep[offset : offset + MAX_PAYLOAD_LENGTH]
                )
                self._set_next_compressed_pktnr()
                offset += MAX_PAYLOAD_LENGTH
            self._send_pkt(sock, address, payload_prep[offset:])
        else:
            # send one MySQL packet
            # For small packets it may be too costly to compress the packet.
            # Usually payloads less than 50 bytes (MIN_COMPRESS_LENGTH)
            # aren't compressed (see MySQL source code Documentation).
            if len(payload) > MIN_COMPRESS_LENGTH:
                # perform compression
                self._send_pkt(sock, address, payload_prep)
            else:
                # skip compression
                super()._send_pkt(
                    sock,
                    address,
                    struct.pack("<I", len(payload_prep))[0:3]
                    + struct.pack("<B", self._compressed_pktnr)
                    + struct.pack("<I", 0)[0:3]
                    + payload_prep,
                )

    def _recv_compressed_pkt(
        self, sock: socket.socket, compressed_pll: int, uncompressed_pll: int
    ) -> None:
        """Handle reading of a compressed packet."""
        # compressed_pll stands for compressed payload length.
        # Recalling that if uncompressed payload length == 0, the packet
        # comes in uncompressed, so no decompression is needed.
        compressed_pkt = super()._recv_chunk(sock, size=compressed_pll)
        pkt = (
            compressed_pkt
            if uncompressed_pll == 0
            else bytearray(zlib.decompress(compressed_pkt))
        )

        offset = 0
        while offset < len(pkt):
            # pll stands for payload length
            pll = struct.unpack(
                "<I", pkt[offset : offset + PACKET_HEADER_LENGTH - 1] + b"\x00"
            )[0]
            if PACKET_HEADER_LENGTH + pll > len(pkt) - offset:
                # More bytes need to be consumed
                # Read the header of the next MySQL packet
                header = super()._recv_chunk(sock, size=COMPRESSED_PACKET_HEADER_LENGTH)

                # compressed payload length, sequence id, uncompressed payload length
                (
                    compressed_pll,
                    self._compressed_pktnr,
                    uncompressed_pll,
                ) = (
                    struct.unpack("<I", header[0:3] + b"\x00")[0],
                    header[3],
                    struct.unpack("<I", header[4:7] + b"\x00")[0],
                )
                compressed_pkt = super()._recv_chunk(sock, size=compressed_pll)

                # recalling that if uncompressed payload length == 0, the packet
                # comes in uncompressed, so no decompression is needed.
                pkt += (
                    compressed_pkt
                    if uncompressed_pll == 0
                    else zlib.decompress(compressed_pkt)
                )

            self._queue_read.append(pkt[offset : offset + PACKET_HEADER_LENGTH + pll])
            offset += PACKET_HEADER_LENGTH + pll

    def recv(self, sock: socket.socket, address: str) -> bytearray:
        """Receive `one` or `several` packets from the MySQL server, enqueue them, and
        return the packet at the head.
        """
        if not self._queue_read:
            try:
                # Read the header of the next MySQL packet
                header = super()._recv_chunk(sock, size=COMPRESSED_PACKET_HEADER_LENGTH)

                # compressed payload length, sequence id, uncompressed payload length
                (
                    compressed_pll,
                    self._compressed_pktnr,
                    uncompressed_pll,
                ) = (
                    struct.unpack("<I", header[0:3] + b"\x00")[0],
                    header[3],
                    struct.unpack("<I", header[4:7] + b"\x00")[0],
                )
                self._recv_compressed_pkt(sock, compressed_pll, uncompressed_pll)
            except IOError as err:
                raise OperationalError(
                    errno=2055, values=(address, _strioerror(err))
                ) from err

        if not self._queue_read:
            return None

        pkt = self._queue_read.popleft()
        self._pktnr = pkt[3]

        return pkt


class MySQLSocket(ABC):
    """MySQL socket communication interface.

    Examples:
        Subclasses: network.MySQLTCPSocket and network.MySQLUnixSocket.
    """

    def __init__(self) -> None:
        """Network layer where transactions are made with plain (uncompressed) packets
        is enabled by default.
        """
        # holds the socket connection
        self.sock: Optional[socket.socket] = None
        self._connection_timeout: Optional[int] = None
        self.server_host: Optional[str] = None
        self._netbroker: NetworkBroker = NetworkBrokerPlain()

    def switch_to_compressed_mode(self) -> None:
        """Enable network layer where transactions are made with compressed packets."""
        self._netbroker = NetworkBrokerCompressed()

    def shutdown(self) -> None:
        """Shut down the socket before closing it."""
        try:
            self.sock.shutdown(socket.SHUT_RDWR)
            self.sock.close()
        except (AttributeError, OSError):
            pass

    def close_connection(self) -> None:
        """Close the socket."""
        try:
            self.sock.close()
        except (AttributeError, OSError):
            pass

    def __del__(self) -> None:
        self.shutdown()

    def set_connection_timeout(self, timeout: Optional[int]) -> None:
        """Set the connection timeout."""
        self._connection_timeout = timeout
        if self.sock:
            self.sock.settimeout(timeout)

    def switch_to_ssl(
        self,
        ca: StrOrBytesPath,
        cert: StrOrBytesPath,
        key: StrOrBytesPath,
        verify_cert: bool = False,
        verify_identity: bool = False,
        cipher_suites: Optional[str] = None,
        tls_versions: Optional[List[str]] = None,
    ) -> None:
        """Switch the socket to use SSL"""
        if not self.sock:
            raise InterfaceError(errno=2048)

        try:
            if verify_cert:
                cert_reqs = ssl.CERT_REQUIRED
            elif verify_identity:
                cert_reqs = ssl.CERT_OPTIONAL
            else:
                cert_reqs = ssl.CERT_NONE

            if tls_versions is None or not tls_versions:
                context = ssl.create_default_context()
                if not verify_identity:
                    context.check_hostname = False
            else:
                tls_versions.sort(reverse=True)

                tls_version = tls_versions[0]
                if (
                    not TLS_V1_3_SUPPORTED
                    and tls_version == "TLSv1.3"
                    and len(tls_versions) > 1
                ):
                    tls_version = tls_versions[1]
                ssl_protocol = TLS_VERSIONS[tls_version]
                context = ssl.SSLContext(ssl_protocol)

                if tls_version == "TLSv1.3":
                    if "TLSv1.2" not in tls_versions:
                        context.options |= ssl.OP_NO_TLSv1_2
                    if "TLSv1.1" not in tls_versions:
                        context.options |= ssl.OP_NO_TLSv1_1
                    if "TLSv1" not in tls_versions:
                        context.options |= ssl.OP_NO_TLSv1

            context.check_hostname = False
            context.verify_mode = cert_reqs
            context.load_default_certs()

            if ca:
                try:
                    context.load_verify_locations(ca)
                except (IOError, ssl.SSLError) as err:
                    self.sock.close()
                    raise InterfaceError(f"Invalid CA Certificate: {err}") from err
            if cert:
                try:
                    context.load_cert_chain(cert, key)
                except (IOError, ssl.SSLError) as err:
                    self.sock.close()
                    raise InterfaceError(f"Invalid Certificate/Key: {err}") from err
            if cipher_suites:
                context.set_ciphers(cipher_suites)

            if hasattr(self, "server_host"):
                self.sock = context.wrap_socket(
                    self.sock, server_hostname=self.server_host
                )
            else:
                self.sock = context.wrap_socket(self.sock)

            if verify_identity:
                context.check_hostname = True
                hostnames: List[str] = [self.server_host] if self.server_host else []
                if os.name == "nt" and self.server_host == "localhost":
                    hostnames = ["localhost", "127.0.0.1"]
                    aliases = socket.gethostbyaddr(self.server_host)
                    hostnames.extend([aliases[0]] + aliases[1])
                match_found = False
                errs = []
                for hostname in hostnames:
                    try:
                        # Deprecated in Python 3.7 without a replacement and
                        # should be removed in the future, since OpenSSL now
                        # performs hostname matching
                        # pylint: disable=deprecated-method
                        ssl.match_hostname(self.sock.getpeercert(), hostname)
                        # pylint: enable=deprecated-method
                    except ssl.CertificateError as err:
                        errs.append(str(err))
                    else:
                        match_found = True
                        break
                if not match_found:
                    self.sock.close()
                    raise InterfaceError(
                        f"Unable to verify server identity: {', '.join(errs)}"
                    )
        except NameError as err:
            raise NotSupportedError("Python installation has no SSL support") from err
        except (ssl.SSLError, IOError) as err:
            raise InterfaceError(
                errno=2055, values=(self.address, _strioerror(err))
            ) from err
        except ssl.CertificateError as err:
            raise InterfaceError(str(err)) from err
        except NotImplementedError as err:
            raise InterfaceError(str(err)) from err

    def send(
        self,
        payload: bytes,
        packet_number: Optional[int] = None,
        compressed_packet_number: Optional[int] = None,
    ) -> None:
        """Send `payload` to the MySQL server."""
        return self._netbroker.send(
            self.sock,
            self.address,
            payload,
            packet_number=packet_number,
            compressed_packet_number=compressed_packet_number,
        )

    def recv(self) -> bytearray:
        """Get packet from the MySQL server comm channel."""
        return self._netbroker.recv(self.sock, self.address)

    @abstractmethod
    def open_connection(self) -> None:
        """Open the socket."""

    @property
    @abstractmethod
    def address(self) -> str:
        """Get the location of the socket."""


class MySQLUnixSocket(MySQLSocket):
    """MySQL socket class using UNIX sockets.

    Opens a connection through the UNIX socket of the MySQL Server.
    """

    def __init__(self, unix_socket: str = "/tmp/mysql.sock") -> None:
        super().__init__()
        self.unix_socket: str = unix_socket
        self._address: str = unix_socket

    @property
    def address(self) -> str:
        return self._address

    def open_connection(self) -> None:
        try:
            self.sock = socket.socket(
                socket.AF_UNIX, socket.SOCK_STREAM  # pylint: disable=no-member
            )
            self.sock.settimeout(self._connection_timeout)
            self.sock.connect(self.unix_socket)
        except IOError as err:
            raise InterfaceError(
                errno=2002, values=(self.address, _strioerror(err))
            ) from err
        except Exception as err:
            raise InterfaceError(str(err)) from err

    def switch_to_ssl(
        self, *args: Any, **kwargs: Any  # pylint: disable=unused-argument
    ) -> None:
        """Switch the socket to use SSL."""
        warnings.warn(
            "SSL is disabled when using unix socket connections",
            Warning,
        )


class MySQLTCPSocket(MySQLSocket):
    """MySQL socket class using TCP/IP.

    Opens a TCP/IP connection to the MySQL Server.
    """

    def __init__(
        self,
        host: str = "127.0.0.1",
        port: int = 3306,
        force_ipv6: bool = False,
    ) -> None:
        super().__init__()
        self.server_host: str = host
        self.server_port: int = port
        self.force_ipv6: bool = force_ipv6
        self._family: int = 0
        self._address: str = f"{host}:{port}"

    @property
    def address(self) -> str:
        return self._address

    def open_connection(self) -> None:
        """Open the TCP/IP connection to the MySQL server."""
        # pylint: disable=no-member
        # Get address information
        addrinfo: Union[
            Tuple[None, None, None, None, None],
            Tuple[
                socket.AddressFamily,
                socket.SocketKind,
                int,
                str,
                Union[Tuple[str, int], Tuple[str, int, int, int]],
            ],
        ] = (None, None, None, None, None)
        try:
            addrinfos = socket.getaddrinfo(
                self.server_host,
                self.server_port,
                0,
                socket.SOCK_STREAM,
                socket.SOL_TCP,
            )
            # If multiple results we favor IPv4, unless IPv6 was forced.
            for info in addrinfos:
                if self.force_ipv6 and info[0] == socket.AF_INET6:
                    addrinfo = info
                    break
                if info[0] == socket.AF_INET:
                    addrinfo = info
                    break
            if self.force_ipv6 and addrinfo[0] is None:
                raise InterfaceError(f"No IPv6 address found for {self.server_host}")
            if addrinfo[0] is None:
                addrinfo = addrinfos[0]
        except IOError as err:
            raise InterfaceError(
                errno=2003, values=(self.address, _strioerror(err))
            ) from err

        (self._family, socktype, proto, _, sockaddr) = addrinfo

        # Instanciate the socket and connect
        try:
            self.sock = socket.socket(self._family, socktype, proto)
            self.sock.settimeout(self._connection_timeout)
            self.sock.connect(sockaddr)
        except IOError as err:
            raise InterfaceError(
                errno=2003,
                values=(
                    self.server_host,
                    self.server_port,
                    _strioerror(err),
                ),
            ) from err
        except Exception as err:
            raise OperationalError(str(err)) from err

virt/lib/python3.9/site-packages/mysql/connector/network.py
PrinceDisant/LostInNL
"""Constants used by the opentelemetry instrumentation implementation."""
# mypy: disable-error-code="no-redef,assignment"

# pylint: disable=unused-import
OTEL_ENABLED = True
try:
    # try to load otel from the system
    from opentelemetry import trace  # check api
    from opentelemetry.sdk.trace import TracerProvider  # check sdk
    from opentelemetry.semconv.trace import SpanAttributes  # check semconv
except ImportError:
    # falling back to the bundled installation
    try:
        from mysql.opentelemetry import trace
        from mysql.opentelemetry.sdk.trace import TracerProvider
        from mysql.opentelemetry.semconv.trace import SpanAttributes
    except ImportError:
        # bundled installation has missing dependencies
        OTEL_ENABLED = False


OPTION_CNX_SPAN = "_span"
"""
Connection option name used to inject the connection span.
This connection option name must not be used, is reserved.
"""

OPTION_CNX_TRACER = "_tracer"
"""
Connection option name used to inject the opentelemetry tracer.
This connection option name must not be used, is reserved.
"""

CONNECTION_SPAN_NAME = "connection"
"""
Connection span name to be used by the instrumentor.
"""

FIRST_SUPPORTED_VERSION = "8.1.0"
"""
First mysql-connector-python version to support opentelemetry instrumentation.
"""

TRACEPARENT_HEADER_NAME = "traceparent"

DB_SYSTEM = "mysql"
DEFAULT_THREAD_NAME = "main"
DEFAULT_THREAD_ID = 0

# Reference: https://github.com/open-telemetry/opentelemetry-specification/blob/main/
# specification/trace/semantic_conventions/span-general.md
NET_SOCK_FAMILY = "net.sock.family"
NET_SOCK_PEER_ADDR = "net.sock.peer.addr"
NET_SOCK_PEER_PORT = "net.sock.peer.port"
NET_SOCK_HOST_ADDR = "net.sock.host.addr"
NET_SOCK_HOST_PORT = "net.sock.host.port"

virt/lib/python3.9/site-packages/mysql/connector/opentelemetry/constants.py
PrinceDisant/LostInNL
"""Trace context propagation utilities."""
# mypy: disable-error-code="no-redef"
# pylint: disable=invalid-name

from typing import TYPE_CHECKING, Any, Callable, Union

from .constants import OTEL_ENABLED, TRACEPARENT_HEADER_NAME

if OTEL_ENABLED:
    from .instrumentation import OTEL_SYSTEM_AVAILABLE

    if OTEL_SYSTEM_AVAILABLE:
        # pylint: disable=import-error
        # load otel from the system
        from opentelemetry import trace
        from opentelemetry.trace.span import format_span_id, format_trace_id
    else:
        # load otel from the bundled installation
        from mysql.opentelemetry import trace
        from mysql.opentelemetry.trace.span import format_span_id, format_trace_id


if TYPE_CHECKING:
    from ..connection import MySQLConnection
    from ..connection_cext import CMySQLConnection


def build_traceparent_header(span: Any) -> str:
    """Build a traceparent header according to the provided span.

    The context information from the provided span is used to build the traceparent
    header that will be propagated to the MySQL server. For particulars regarding
    the header creation, refer to [1].

    This method assumes version 0 of the W3C specification.

    Args:
        span (opentelemetry.trace.span.Span): current span in trace.

    Returns:
        traceparent_header (str): HTTP header field that identifies requests in a
        tracing system.

    References:
        [1]: https://www.w3.org/TR/trace-context/#traceparent-header
    """
    ctx = span.get_span_context()

    version = "00"  # version 0 of the W3C specification
    trace_id = format_trace_id(ctx.trace_id)
    span_id = format_span_id(ctx.span_id)
    trace_flags = "00"  # sampled flag is off

    return "-".join([version, trace_id, span_id, trace_flags])


def with_context_propagation(method: Callable) -> Callable:
    """Perform trace context propagation.

    The trace context is propagated via query attributes. The `traceparent` header
    from W3C specification [1] is used, in this sense, the attribute name is
    `traceparent` (is RESERVED, avoid using it), and its value is built as per
    instructed in [1].

    If opentelemetry API/SDK is unavailable or there is no recording span,
    trace context propagation is skipped.

    References:
        [1]: https://www.w3.org/TR/trace-context/#traceparent-header
    """

    def wrapper(
        cnx: Union["MySQLConnection", "CMySQLConnection"], *args: Any, **kwargs: Any
    ) -> Any:
        """Context propagation decorator."""
        if not OTEL_ENABLED or not cnx.otel_context_propagation:
            return method(cnx, *args, **kwargs)

        current_span = trace.get_current_span()
        tp_header = None
        if current_span.is_recording():
            tp_header = build_traceparent_header(current_span)
            cnx.query_attrs_append(value=(TRACEPARENT_HEADER_NAME, tp_header))

        try:
            result = method(cnx, *args, **kwargs)
        finally:
            if tp_header is not None:
                cnx.query_attrs_remove(name=TRACEPARENT_HEADER_NAME)
        return result

    return wrapper

virt/lib/python3.9/site-packages/mysql/connector/opentelemetry/context_propagation.py
PrinceDisant/LostInNL
"""MySQL instrumentation supporting mysql-connector."""
# mypy: disable-error-code="no-redef"
# pylint: disable=protected-access,global-statement,invalid-name

from __future__ import annotations

import functools
import re

from abc import ABC, abstractmethod
from typing import TYPE_CHECKING, Any, Callable, Collection, Dict, Optional, Union

# pylint: disable=cyclic-import
if TYPE_CHECKING:
    # `TYPE_CHECKING` is always False at run time, hence circular import
    # will not happen at run time (no error happens whatsoever).
    # Since pylint is a static checker it happens that `TYPE_CHECKING`
    # is True when analyzing the code which makes pylint believe there
    # is a circular import issue when there isn't.

    from ..abstracts import MySQLConnectionAbstract
    from ..connection import MySQLConnection
    from ..cursor import MySQLCursor
    from ..pooling import PooledMySQLConnection

    try:
        from ..connection_cext import CMySQLConnection
        from ..cursor_cext import CMySQLCursor
    except ImportError:
        # The cext is not available.
        pass

from ... import connector
from ..constants import CNX_POOL_ARGS, DEFAULT_CONFIGURATION
from ..logger import logger
from ..version import VERSION_TEXT

try:
    # pylint: disable=unused-import
    # try to load otel from the system
    from opentelemetry import trace  # check api
    from opentelemetry.sdk.trace import TracerProvider  # check sdk
    from opentelemetry.semconv.trace import SpanAttributes  # check semconv

    OTEL_SYSTEM_AVAILABLE = True
except ImportError:
    try:
        # falling back to the bundled installation
        from mysql.opentelemetry import trace
        from mysql.opentelemetry.semconv.trace import SpanAttributes

        OTEL_SYSTEM_AVAILABLE = False
    except ImportError as missing_dependencies_err:
        raise connector.errors.ProgrammingError(
            "Bundled installation has missing dependencies. "
            "Please use `pip install mysql-connector-python[opentelemetry]`, "
            "or for an editable install use `pip install -e '.[opentelemetry]'`, "
            "to install the dependencies required by the bundled opentelemetry package."
        ) from missing_dependencies_err


from .constants import (
    CONNECTION_SPAN_NAME,
    DB_SYSTEM,
    DEFAULT_THREAD_ID,
    DEFAULT_THREAD_NAME,
    FIRST_SUPPORTED_VERSION,
    NET_SOCK_FAMILY,
    NET_SOCK_HOST_ADDR,
    NET_SOCK_HOST_PORT,
    NET_SOCK_PEER_ADDR,
    NET_SOCK_PEER_PORT,
    OPTION_CNX_SPAN,
    OPTION_CNX_TRACER,
)

leading_comment_remover: re.Pattern = re.compile(r"^/\*.*?\*/")


def record_exception_event(span: trace.Span, exc: Optional[Exception]) -> None:
    """Records an exeception event."""
    if not span or not span.is_recording() or not exc:
        return

    span.set_status(trace.Status(trace.StatusCode.ERROR))
    span.record_exception(exc)


def end_span(span: trace.Span) -> None:
    """Ends span."""
    if not span or not span.is_recording():
        return

    span.end()


def get_operation_name(operation: str) -> str:
    """Parse query to extract operation name."""
    if operation and isinstance(operation, str):
        # Strip leading comments so we get the operation name.
        return leading_comment_remover.sub("", operation).split()[0]
    return ""


def set_connection_span_attrs(
    cnx: Optional["MySQLConnectionAbstract"],
    cnx_span: trace.Span,
    cnx_kwargs: Optional[Dict[str, Any]] = None,
) -> None:
    """Defines connection span attributes. If `cnx` is None then we use `cnx_kwargs`
    to get basic net information. Basic net attributes are defined such as:

    * DB_SYSTEM
    * NET_TRANSPORT
    * NET_SOCK_FAMILY

    Socket-level attributes [*] are also defined [**].

    [*]: Socket-level attributes identify peer and host that are directly connected to
    each other. Since instrumentations may have limited knowledge on network
    information, instrumentations SHOULD populate such attributes to the best of
    their knowledge when populate them at all.

    [**]: `CMySQLConnection` connections have no access to socket-level
    details so socket-level attributes aren't included. `MySQLConnection`
    connections, on the other hand, do include socket-level attributes.

    References:
    [1]: https://github.com/open-telemetry/opentelemetry-specification/blob/main/
    specification/trace/semantic_conventions/span-general.md
    """
    # pylint: disable=broad-exception-caught
    if not cnx_span or not cnx_span.is_recording():
        return

    if cnx_kwargs is None:
        cnx_kwargs = {}

    is_tcp = not cnx._unix_socket if cnx else "unix_socket" not in cnx_kwargs

    attrs: Dict[str, Any] = {
        SpanAttributes.DB_SYSTEM: DB_SYSTEM,
        SpanAttributes.NET_TRANSPORT: "ip_tcp" if is_tcp else "inproc",
        NET_SOCK_FAMILY: "inet" if is_tcp else "unix",
    }

    # Only socket and tcp connections are supported.
    if is_tcp:
        attrs[SpanAttributes.NET_PEER_NAME] = (
            cnx._host if cnx else cnx_kwargs.get("host", DEFAULT_CONFIGURATION["host"])
        )
        attrs[SpanAttributes.NET_PEER_PORT] = (
            cnx._port if cnx else cnx_kwargs.get("port", DEFAULT_CONFIGURATION["port"])
        )

        if hasattr(cnx, "_socket") and cnx._socket:
            try:
                (
                    attrs[NET_SOCK_PEER_ADDR],
                    sock_peer_port,
                ) = cnx._socket.sock.getpeername()

                (
                    attrs[NET_SOCK_HOST_ADDR],
                    attrs[NET_SOCK_HOST_PORT],
                ) = cnx._socket.sock.getsockname()
            except Exception as sock_err:
                logger.warning("Connection socket is down %s", sock_err)
            else:
                if attrs[SpanAttributes.NET_PEER_PORT] != sock_peer_port:
                    # NET_SOCK_PEER_PORT is recommended if different than net.peer.port
                    # and if net.sock.peer.addr is set.
                    attrs[NET_SOCK_PEER_PORT] = sock_peer_port
    else:
        # For Unix domain socket, net.sock.peer.addr attribute represents
        # destination name and net.peer.name SHOULD NOT be set.
        attrs[NET_SOCK_PEER_ADDR] = (
            cnx._unix_socket if cnx else cnx_kwargs.get("unix_socket")
        )

        if hasattr(cnx, "_socket") and cnx._socket:
            try:
                attrs[NET_SOCK_HOST_ADDR] = cnx._socket.sock.getsockname()
            except Exception as sock_err:
                logger.warning("Connection socket is down %s", sock_err)

    cnx_span.set_attributes(attrs)


def instrument_execution(
    query_method: Callable,
    tracer: trace.Tracer,
    connection_span_link: trace.Link,
    wrapped: Union["MySQLCursor", "CMySQLCursor"],
    *args: Any,
    **kwargs: Any,
) -> Callable:
    """Instruments the execution of `query_method`.

    A query span with a link to the corresponding connection span is generated.
    """
    connection: Union["MySQLConnection", "CMySQLConnection"] = (
        getattr(wrapped, "_connection")
        if hasattr(wrapped, "_connection")
        else getattr(wrapped, "_cnx")
    )

    # SpanAttributes.DB_NAME: connection.database or ""; introduces performance
    # degradation, at this time the database attribute is something nice to have but
    # not a requirement.
    query_span_attributes: Dict = {
        SpanAttributes.DB_SYSTEM: DB_SYSTEM,
        SpanAttributes.DB_USER: connection._user,
        SpanAttributes.THREAD_ID: DEFAULT_THREAD_ID,
        SpanAttributes.THREAD_NAME: DEFAULT_THREAD_NAME,
        "cursor_type": wrapped.__class__.__name__,
    }
    with tracer.start_as_current_span(
        name=get_operation_name(args[0]) or "SQL statement",
        kind=trace.SpanKind.CLIENT,
        links=[connection_span_link],
        attributes=query_span_attributes,
    ):
        return query_method(*args, **kwargs)


class BaseMySQLTracer(ABC):
    """Base class that provides basic object wrapper functionality."""

    @abstractmethod
    def __init__(self) -> None:
        """Must be implemented by subclasses."""

    def __getattr__(self, attr: str) -> Any:
        """Gets an attribute.

        Attributes defined in the wrapper object have higher precedence
        than those wrapped object equivalent. Attributes not found in
        the wrapper are then searched in the wrapped object.
        """
        if attr in self.__dict__:
            # this object has it
            return getattr(self, attr)
        # proxy to the wrapped object
        return getattr(self._wrapped, attr)

    def __setattr__(self, name: str, value: Any) -> None:
        if "_wrapped" not in self.__dict__:
            self.__dict__["_wrapped"] = value
            return

        if name in self.__dict__:
            # this object has it
            super().__setattr__(name, value)
            return
        # proxy to the wrapped object
        self._wrapped.__setattr__(name, value)

    def __enter__(self) -> Any:
        """Magic method."""
        self._wrapped.__enter__()
        return self

    def __exit__(self, *args: Any, **kwargs: Any) -> None:
        """Magic method."""
        self._wrapped.__exit__(*args, **kwargs)

    def get_wrapped_class(self) -> str:
        """Gets the wrapped class name."""
        return self._wrapped.__class__.__name__


class TracedMySQLCursor(BaseMySQLTracer):
    """Wrapper class for a `MySQLCursor` or `CMySQLCursor` object."""

    def __init__(
        self,
        wrapped: Union["MySQLCursor", "CMySQLCursor"],
        tracer: trace.Tracer,
        connection_span: trace.Span,
    ):
        """Constructor."""
        self._wrapped: Union["MySQLCursor", "CMySQLCursor"] = wrapped
        self._tracer: trace.Tracer = tracer
        self._connection_span_link: trace.Link = trace.Link(
            connection_span.get_span_context()
        )

    def execute(self, *args: Any, **kwargs: Any) -> Any:
        """Instruments execute method."""
        return instrument_execution(
            self._wrapped.execute,
            self._tracer,
            self._connection_span_link,
            self._wrapped,
            *args,
            **kwargs,
        )

    def executemany(self, *args: Any, **kwargs: Any) -> Any:
        """Instruments executemany method."""
        return instrument_execution(
            self._wrapped.executemany,
            self._tracer,
            self._connection_span_link,
            self._wrapped,
            *args,
            **kwargs,
        )

    def callproc(self, *args: Any, **kwargs: Any) -> Any:
        """Instruments callproc method."""
        return instrument_execution(
            self._wrapped.callproc,
            self._tracer,
            self._connection_span_link,
            self._wrapped,
            *args,
            **kwargs,
        )


class TracedMySQLConnection(BaseMySQLTracer):
    """Wrapper class for a `MySQLConnection` or `CMySQLConnection` object."""

    def __init__(self, wrapped: Union["MySQLConnection", "CMySQLConnection"]) -> None:
        """Constructor."""
        self._wrapped: Union["MySQLConnection", "CMySQLConnection"] = wrapped

        # call `sql_mode` so its value is cached internally and querying it does not
        # interfere when recording query span events later.
        _ = self._wrapped.sql_mode

    def cursor(self, *args: Any, **kwargs: Any) -> TracedMySQLCursor:
        """Wraps the cursor object."""
        return TracedMySQLCursor(
            wrapped=self._wrapped.cursor(*args, **kwargs),
            tracer=self._tracer,
            connection_span=self._span,
        )


def instrument_connect(
    connect: Callable[
        ..., Union["MySQLConnection", "CMySQLConnection", "PooledMySQLConnection"]
    ],
    tracer_provider: Optional[trace.TracerProvider] = None,
) -> Callable[
    ..., Union["MySQLConnection", "CMySQLConnection", "PooledMySQLConnection"]
]:
    """Retrurn the instrumented version of `connect`."""

    # let's preserve `connect` identity.
    @functools.wraps(connect)
    def wrapper(
        *args: Any, **kwargs: Any
    ) -> Union["MySQLConnection", "CMySQLConnection", "PooledMySQLConnection"]:
        """Wraps the connection object returned by the method `connect`.

        Instrumentation for PooledConnections is not supported.
        """
        if any(key in kwargs for key in CNX_POOL_ARGS):
            logger.warning("Instrumentation for pooled connections not supported")
            return connect(*args, **kwargs)

        tracer = trace.get_tracer(
            instrumenting_module_name="MySQL Connector/Python",
            instrumenting_library_version=VERSION_TEXT,
            tracer_provider=tracer_provider,
        )

        # The connection span is passed in as an argument so the connection object can
        # keep a pointer to it.
        kwargs[OPTION_CNX_SPAN] = tracer.start_span(
            name=CONNECTION_SPAN_NAME, kind=trace.SpanKind.CLIENT
        )
        kwargs[OPTION_CNX_TRACER] = tracer

        # Add basic net information.
        set_connection_span_attrs(None, kwargs[OPTION_CNX_SPAN], kwargs)

        # Connection may fail at this point, in case it does, basic net info is already
        # included so the user can check the net configuration she/he provided.
        cnx = connect(*args, **kwargs)

        # connection went ok, let's refine the net information.
        set_connection_span_attrs(cnx, cnx._span, kwargs)  # type: ignore[arg-type]

        return TracedMySQLConnection(
            wrapped=cnx,  # type: ignore[return-value, arg-type]
        )

    return wrapper


class MySQLInstrumentor:
    """MySQL instrumentation supporting mysql-connector-python."""

    _instance: Optional[MySQLInstrumentor] = None

    def __new__(cls, *args: Any, **kwargs: Any) -> MySQLInstrumentor:
        """Singlenton.

        Restricts the instantiation to a singular instance.
        """
        if cls._instance is None:
            # create instance
            cls._instance = object.__new__(cls, *args, **kwargs)
            # keep a pointer to the uninstrumented connect method
            setattr(cls._instance, "_original_connect", connector.connect)
        return cls._instance

    def instrumentation_dependencies(self) -> Collection[str]:
        """Return a list of python packages with versions
        that the will be instrumented (e.g., versions >= 8.1.0)."""
        return [f"mysql-connector-python >= {FIRST_SUPPORTED_VERSION}"]

    def instrument(self, **kwargs: Any) -> None:
        """Instrument the library.

        Args:
            trace_module: reference to the 'trace' module from opentelemetry.
            tracer_provider (optional): TracerProvider instance.

        NOTE: Instrumentation for pooled connections not supported.
        """
        if connector.connect != getattr(self, "_original_connect"):
            logger.warning("MySQL Connector/Python module already instrumented.")
            return
        connector.connect = instrument_connect(
            connect=getattr(self, "_original_connect"),
            tracer_provider=kwargs.get("tracer_provider"),
        )

    def instrument_connection(
        self,
        connection: Union["MySQLConnection", "CMySQLConnection"],
        tracer_provider: Optional[trace.TracerProvider] = None,
    ) -> Union["MySQLConnection", "CMySQLConnection"]:
        """Enable instrumentation in a MySQL connection.

        Args:
            connection: uninstrumented connection instance.
            trace_module: reference to the 'trace' module from opentelemetry.
            tracer_provider (optional): TracerProvider instance.

        Returns:
            connection: instrumented connection instace.

        NOTE: Instrumentation for pooled connections not supported.
        """
        if isinstance(connection, TracedMySQLConnection):
            logger.warning("Connection already instrumented.")
            return connection

        if not hasattr(connection, "_span") or not hasattr(connection, "_tracer"):
            logger.warning(
                "Instrumentation for class %s not supported.",
                connection.__class__.__name__,
            )
            return connection

        tracer = trace.get_tracer(
            instrumenting_module_name="MySQL Connector/Python",
            instrumenting_library_version=VERSION_TEXT,
            tracer_provider=tracer_provider,
        )
        connection._span = tracer.start_span(
            name=CONNECTION_SPAN_NAME, kind=trace.SpanKind.CLIENT
        )
        connection._tracer = tracer

        set_connection_span_attrs(connection, connection._span)

        return TracedMySQLConnection(wrapped=connection)  # type: ignore[return-value]

    def uninstrument(self, **kwargs: Any) -> None:
        """Uninstrument the library."""
        # pylint: disable=unused-argument
        if connector.connect == getattr(self, "_original_connect"):
            logger.warning("MySQL Connector/Python module already uninstrumented.")
            return
        connector.connect = getattr(self, "_original_connect")

    def uninstrument_connection(
        self, connection: Union["MySQLConnection", "CMySQLConnection"]
    ) -> Union["MySQLConnection", "CMySQLConnection"]:
        """Disable instrumentation in a MySQL connection.

        Args:
            connection: instrumented connection instance.

        Returns:
            connection: uninstrumented connection instace.

        NOTE: Instrumentation for pooled connections not supported.
        """
        if not hasattr(connection, "_span"):
            logger.warning(
                "Uninstrumentation for class %s not supported.",
                connection.__class__.__name__,
            )
            return connection

        if not isinstance(connection, TracedMySQLConnection):
            logger.warning("Connection already uninstrumented.")
            return connection

        # stop connection span recording
        if connection._span and connection._span.is_recording():
            connection._span.end()
            connection._span = None

        return connection._wrapped

virt/lib/python3.9/site-packages/mysql/connector/opentelemetry/instrumentation 2.py
PrinceDisant/LostInNL
"""MySQL instrumentation supporting mysql-connector."""
# mypy: disable-error-code="no-redef"
# pylint: disable=protected-access,global-statement,invalid-name

from __future__ import annotations

import functools
import re

from abc import ABC, abstractmethod
from typing import TYPE_CHECKING, Any, Callable, Collection, Dict, Optional, Union

# pylint: disable=cyclic-import
if TYPE_CHECKING:
    # `TYPE_CHECKING` is always False at run time, hence circular import
    # will not happen at run time (no error happens whatsoever).
    # Since pylint is a static checker it happens that `TYPE_CHECKING`
    # is True when analyzing the code which makes pylint believe there
    # is a circular import issue when there isn't.

    from ..abstracts import MySQLConnectionAbstract
    from ..connection import MySQLConnection
    from ..cursor import MySQLCursor
    from ..pooling import PooledMySQLConnection

    try:
        from ..connection_cext import CMySQLConnection
        from ..cursor_cext import CMySQLCursor
    except ImportError:
        # The cext is not available.
        pass

from ... import connector
from ..constants import CNX_POOL_ARGS, DEFAULT_CONFIGURATION
from ..logger import logger
from ..version import VERSION_TEXT

try:
    # pylint: disable=unused-import
    # try to load otel from the system
    from opentelemetry import trace  # check api
    from opentelemetry.sdk.trace import TracerProvider  # check sdk
    from opentelemetry.semconv.trace import SpanAttributes  # check semconv

    OTEL_SYSTEM_AVAILABLE = True
except ImportError:
    try:
        # falling back to the bundled installation
        from mysql.opentelemetry import trace
        from mysql.opentelemetry.semconv.trace import SpanAttributes

        OTEL_SYSTEM_AVAILABLE = False
    except ImportError as missing_dependencies_err:
        raise connector.errors.ProgrammingError(
            "Bundled installation has missing dependencies. "
            "Please use `pip install mysql-connector-python[opentelemetry]`, "
            "or for an editable install use `pip install -e '.[opentelemetry]'`, "
            "to install the dependencies required by the bundled opentelemetry package."
        ) from missing_dependencies_err


from .constants import (
    CONNECTION_SPAN_NAME,
    DB_SYSTEM,
    DEFAULT_THREAD_ID,
    DEFAULT_THREAD_NAME,
    FIRST_SUPPORTED_VERSION,
    NET_SOCK_FAMILY,
    NET_SOCK_HOST_ADDR,
    NET_SOCK_HOST_PORT,
    NET_SOCK_PEER_ADDR,
    NET_SOCK_PEER_PORT,
    OPTION_CNX_SPAN,
    OPTION_CNX_TRACER,
)

leading_comment_remover: re.Pattern = re.compile(r"^/\*.*?\*/")


def record_exception_event(span: trace.Span, exc: Optional[Exception]) -> None:
    """Records an exeception event."""
    if not span or not span.is_recording() or not exc:
        return

    span.set_status(trace.Status(trace.StatusCode.ERROR))
    span.record_exception(exc)


def end_span(span: trace.Span) -> None:
    """Ends span."""
    if not span or not span.is_recording():
        return

    span.end()


def get_operation_name(operation: str) -> str:
    """Parse query to extract operation name."""
    if operation and isinstance(operation, str):
        # Strip leading comments so we get the operation name.
        return leading_comment_remover.sub("", operation).split()[0]
    return ""


def set_connection_span_attrs(
    cnx: Optional["MySQLConnectionAbstract"],
    cnx_span: trace.Span,
    cnx_kwargs: Optional[Dict[str, Any]] = None,
) -> None:
    """Defines connection span attributes. If `cnx` is None then we use `cnx_kwargs`
    to get basic net information. Basic net attributes are defined such as:

    * DB_SYSTEM
    * NET_TRANSPORT
    * NET_SOCK_FAMILY

    Socket-level attributes [*] are also defined [**].

    [*]: Socket-level attributes identify peer and host that are directly connected to
    each other. Since instrumentations may have limited knowledge on network
    information, instrumentations SHOULD populate such attributes to the best of
    their knowledge when populate them at all.

    [**]: `CMySQLConnection` connections have no access to socket-level
    details so socket-level attributes aren't included. `MySQLConnection`
    connections, on the other hand, do include socket-level attributes.

    References:
    [1]: https://github.com/open-telemetry/opentelemetry-specification/blob/main/
    specification/trace/semantic_conventions/span-general.md
    """
    # pylint: disable=broad-exception-caught
    if not cnx_span or not cnx_span.is_recording():
        return

    if cnx_kwargs is None:
        cnx_kwargs = {}

    is_tcp = not cnx._unix_socket if cnx else "unix_socket" not in cnx_kwargs

    attrs: Dict[str, Any] = {
        SpanAttributes.DB_SYSTEM: DB_SYSTEM,
        SpanAttributes.NET_TRANSPORT: "ip_tcp" if is_tcp else "inproc",
        NET_SOCK_FAMILY: "inet" if is_tcp else "unix",
    }

    # Only socket and tcp connections are supported.
    if is_tcp:
        attrs[SpanAttributes.NET_PEER_NAME] = (
            cnx._host if cnx else cnx_kwargs.get("host", DEFAULT_CONFIGURATION["host"])
        )
        attrs[SpanAttributes.NET_PEER_PORT] = (
            cnx._port if cnx else cnx_kwargs.get("port", DEFAULT_CONFIGURATION["port"])
        )

        if hasattr(cnx, "_socket") and cnx._socket:
            try:
                (
                    attrs[NET_SOCK_PEER_ADDR],
                    sock_peer_port,
                ) = cnx._socket.sock.getpeername()

                (
                    attrs[NET_SOCK_HOST_ADDR],
                    attrs[NET_SOCK_HOST_PORT],
                ) = cnx._socket.sock.getsockname()
            except Exception as sock_err:
                logger.warning("Connection socket is down %s", sock_err)
            else:
                if attrs[SpanAttributes.NET_PEER_PORT] != sock_peer_port:
                    # NET_SOCK_PEER_PORT is recommended if different than net.peer.port
                    # and if net.sock.peer.addr is set.
                    attrs[NET_SOCK_PEER_PORT] = sock_peer_port
    else:
        # For Unix domain socket, net.sock.peer.addr attribute represents
        # destination name and net.peer.name SHOULD NOT be set.
        attrs[NET_SOCK_PEER_ADDR] = (
            cnx._unix_socket if cnx else cnx_kwargs.get("unix_socket")
        )

        if hasattr(cnx, "_socket") and cnx._socket:
            try:
                attrs[NET_SOCK_HOST_ADDR] = cnx._socket.sock.getsockname()
            except Exception as sock_err:
                logger.warning("Connection socket is down %s", sock_err)

    cnx_span.set_attributes(attrs)


def instrument_execution(
    query_method: Callable,
    tracer: trace.Tracer,
    connection_span_link: trace.Link,
    wrapped: Union["MySQLCursor", "CMySQLCursor"],
    *args: Any,
    **kwargs: Any,
) -> Callable:
    """Instruments the execution of `query_method`.

    A query span with a link to the corresponding connection span is generated.
    """
    connection: Union["MySQLConnection", "CMySQLConnection"] = (
        getattr(wrapped, "_connection")
        if hasattr(wrapped, "_connection")
        else getattr(wrapped, "_cnx")
    )

    # SpanAttributes.DB_NAME: connection.database or ""; introduces performance
    # degradation, at this time the database attribute is something nice to have but
    # not a requirement.
    query_span_attributes: Dict = {
        SpanAttributes.DB_SYSTEM: DB_SYSTEM,
        SpanAttributes.DB_USER: connection._user,
        SpanAttributes.THREAD_ID: DEFAULT_THREAD_ID,
        SpanAttributes.THREAD_NAME: DEFAULT_THREAD_NAME,
        "cursor_type": wrapped.__class__.__name__,
    }
    with tracer.start_as_current_span(
        name=get_operation_name(args[0]) or "SQL statement",
        kind=trace.SpanKind.CLIENT,
        links=[connection_span_link],
        attributes=query_span_attributes,
    ):
        return query_method(*args, **kwargs)


class BaseMySQLTracer(ABC):
    """Base class that provides basic object wrapper functionality."""

    @abstractmethod
    def __init__(self) -> None:
        """Must be implemented by subclasses."""

    def __getattr__(self, attr: str) -> Any:
        """Gets an attribute.

        Attributes defined in the wrapper object have higher precedence
        than those wrapped object equivalent. Attributes not found in
        the wrapper are then searched in the wrapped object.
        """
        if attr in self.__dict__:
            # this object has it
            return getattr(self, attr)
        # proxy to the wrapped object
        return getattr(self._wrapped, attr)

    def __setattr__(self, name: str, value: Any) -> None:
        if "_wrapped" not in self.__dict__:
            self.__dict__["_wrapped"] = value
            return

        if name in self.__dict__:
            # this object has it
            super().__setattr__(name, value)
            return
        # proxy to the wrapped object
        self._wrapped.__setattr__(name, value)

    def __enter__(self) -> Any:
        """Magic method."""
        self._wrapped.__enter__()
        return self

    def __exit__(self, *args: Any, **kwargs: Any) -> None:
        """Magic method."""
        self._wrapped.__exit__(*args, **kwargs)

    def get_wrapped_class(self) -> str:
        """Gets the wrapped class name."""
        return self._wrapped.__class__.__name__


class TracedMySQLCursor(BaseMySQLTracer):
    """Wrapper class for a `MySQLCursor` or `CMySQLCursor` object."""

    def __init__(
        self,
        wrapped: Union["MySQLCursor", "CMySQLCursor"],
        tracer: trace.Tracer,
        connection_span: trace.Span,
    ):
        """Constructor."""
        self._wrapped: Union["MySQLCursor", "CMySQLCursor"] = wrapped
        self._tracer: trace.Tracer = tracer
        self._connection_span_link: trace.Link = trace.Link(
            connection_span.get_span_context()
        )

    def execute(self, *args: Any, **kwargs: Any) -> Any:
        """Instruments execute method."""
        return instrument_execution(
            self._wrapped.execute,
            self._tracer,
            self._connection_span_link,
            self._wrapped,
            *args,
            **kwargs,
        )

    def executemany(self, *args: Any, **kwargs: Any) -> Any:
        """Instruments executemany method."""
        return instrument_execution(
            self._wrapped.executemany,
            self._tracer,
            self._connection_span_link,
            self._wrapped,
            *args,
            **kwargs,
        )

    def callproc(self, *args: Any, **kwargs: Any) -> Any:
        """Instruments callproc method."""
        return instrument_execution(
            self._wrapped.callproc,
            self._tracer,
            self._connection_span_link,
            self._wrapped,
            *args,
            **kwargs,
        )


class TracedMySQLConnection(BaseMySQLTracer):
    """Wrapper class for a `MySQLConnection` or `CMySQLConnection` object."""

    def __init__(self, wrapped: Union["MySQLConnection", "CMySQLConnection"]) -> None:
        """Constructor."""
        self._wrapped: Union["MySQLConnection", "CMySQLConnection"] = wrapped

        # call `sql_mode` so its value is cached internally and querying it does not
        # interfere when recording query span events later.
        _ = self._wrapped.sql_mode

    def cursor(self, *args: Any, **kwargs: Any) -> TracedMySQLCursor:
        """Wraps the cursor object."""
        return TracedMySQLCursor(
            wrapped=self._wrapped.cursor(*args, **kwargs),
            tracer=self._tracer,
            connection_span=self._span,
        )


def instrument_connect(
    connect: Callable[
        ..., Union["MySQLConnection", "CMySQLConnection", "PooledMySQLConnection"]
    ],
    tracer_provider: Optional[trace.TracerProvider] = None,
) -> Callable[
    ..., Union["MySQLConnection", "CMySQLConnection", "PooledMySQLConnection"]
]:
    """Retrurn the instrumented version of `connect`."""

    # let's preserve `connect` identity.
    @functools.wraps(connect)
    def wrapper(
        *args: Any, **kwargs: Any
    ) -> Union["MySQLConnection", "CMySQLConnection", "PooledMySQLConnection"]:
        """Wraps the connection object returned by the method `connect`.

        Instrumentation for PooledConnections is not supported.
        """
        if any(key in kwargs for key in CNX_POOL_ARGS):
            logger.warning("Instrumentation for pooled connections not supported")
            return connect(*args, **kwargs)

        tracer = trace.get_tracer(
            instrumenting_module_name="MySQL Connector/Python",
            instrumenting_library_version=VERSION_TEXT,
            tracer_provider=tracer_provider,
        )

        # The connection span is passed in as an argument so the connection object can
        # keep a pointer to it.
        kwargs[OPTION_CNX_SPAN] = tracer.start_span(
            name=CONNECTION_SPAN_NAME, kind=trace.SpanKind.CLIENT
        )
        kwargs[OPTION_CNX_TRACER] = tracer

        # Add basic net information.
        set_connection_span_attrs(None, kwargs[OPTION_CNX_SPAN], kwargs)

        # Connection may fail at this point, in case it does, basic net info is already
        # included so the user can check the net configuration she/he provided.
        cnx = connect(*args, **kwargs)

        # connection went ok, let's refine the net information.
        set_connection_span_attrs(cnx, cnx._span, kwargs)  # type: ignore[arg-type]

        return TracedMySQLConnection(
            wrapped=cnx,  # type: ignore[return-value, arg-type]
        )

    return wrapper


class MySQLInstrumentor:
    """MySQL instrumentation supporting mysql-connector-python."""

    _instance: Optional[MySQLInstrumentor] = None

    def __new__(cls, *args: Any, **kwargs: Any) -> MySQLInstrumentor:
        """Singlenton.

        Restricts the instantiation to a singular instance.
        """
        if cls._instance is None:
            # create instance
            cls._instance = object.__new__(cls, *args, **kwargs)
            # keep a pointer to the uninstrumented connect method
            setattr(cls._instance, "_original_connect", connector.connect)
        return cls._instance

    def instrumentation_dependencies(self) -> Collection[str]:
        """Return a list of python packages with versions
        that the will be instrumented (e.g., versions >= 8.1.0)."""
        return [f"mysql-connector-python >= {FIRST_SUPPORTED_VERSION}"]

    def instrument(self, **kwargs: Any) -> None:
        """Instrument the library.

        Args:
            trace_module: reference to the 'trace' module from opentelemetry.
            tracer_provider (optional): TracerProvider instance.

        NOTE: Instrumentation for pooled connections not supported.
        """
        if connector.connect != getattr(self, "_original_connect"):
            logger.warning("MySQL Connector/Python module already instrumented.")
            return
        connector.connect = instrument_connect(
            connect=getattr(self, "_original_connect"),
            tracer_provider=kwargs.get("tracer_provider"),
        )

    def instrument_connection(
        self,
        connection: Union["MySQLConnection", "CMySQLConnection"],
        tracer_provider: Optional[trace.TracerProvider] = None,
    ) -> Union["MySQLConnection", "CMySQLConnection"]:
        """Enable instrumentation in a MySQL connection.

        Args:
            connection: uninstrumented connection instance.
            trace_module: reference to the 'trace' module from opentelemetry.
            tracer_provider (optional): TracerProvider instance.

        Returns:
            connection: instrumented connection instace.

        NOTE: Instrumentation for pooled connections not supported.
        """
        if isinstance(connection, TracedMySQLConnection):
            logger.warning("Connection already instrumented.")
            return connection

        if not hasattr(connection, "_span") or not hasattr(connection, "_tracer"):
            logger.warning(
                "Instrumentation for class %s not supported.",
                connection.__class__.__name__,
            )
            return connection

        tracer = trace.get_tracer(
            instrumenting_module_name="MySQL Connector/Python",
            instrumenting_library_version=VERSION_TEXT,
            tracer_provider=tracer_provider,
        )
        connection._span = tracer.start_span(
            name=CONNECTION_SPAN_NAME, kind=trace.SpanKind.CLIENT
        )
        connection._tracer = tracer

        set_connection_span_attrs(connection, connection._span)

        return TracedMySQLConnection(wrapped=connection)  # type: ignore[return-value]

    def uninstrument(self, **kwargs: Any) -> None:
        """Uninstrument the library."""
        # pylint: disable=unused-argument
        if connector.connect == getattr(self, "_original_connect"):
            logger.warning("MySQL Connector/Python module already uninstrumented.")
            return
        connector.connect = getattr(self, "_original_connect")

    def uninstrument_connection(
        self, connection: Union["MySQLConnection", "CMySQLConnection"]
    ) -> Union["MySQLConnection", "CMySQLConnection"]:
        """Disable instrumentation in a MySQL connection.

        Args:
            connection: instrumented connection instance.

        Returns:
            connection: uninstrumented connection instace.

        NOTE: Instrumentation for pooled connections not supported.
        """
        if not hasattr(connection, "_span"):
            logger.warning(
                "Uninstrumentation for class %s not supported.",
                connection.__class__.__name__,
            )
            return connection

        if not isinstance(connection, TracedMySQLConnection):
            logger.warning("Connection already uninstrumented.")
            return connection

        # stop connection span recording
        if connection._span and connection._span.is_recording():
            connection._span.end()
            connection._span = None

        return connection._wrapped

virt/lib/python3.9/site-packages/mysql/connector/opentelemetry/instrumentation 3.py
PrinceDisant/LostInNL
"""MySQL instrumentation supporting mysql-connector."""
# mypy: disable-error-code="no-redef"
# pylint: disable=protected-access,global-statement,invalid-name

from __future__ import annotations

import functools
import re

from abc import ABC, abstractmethod
from typing import TYPE_CHECKING, Any, Callable, Collection, Dict, Optional, Union

# pylint: disable=cyclic-import
if TYPE_CHECKING:
    # `TYPE_CHECKING` is always False at run time, hence circular import
    # will not happen at run time (no error happens whatsoever).
    # Since pylint is a static checker it happens that `TYPE_CHECKING`
    # is True when analyzing the code which makes pylint believe there
    # is a circular import issue when there isn't.

    from ..abstracts import MySQLConnectionAbstract
    from ..connection import MySQLConnection
    from ..cursor import MySQLCursor
    from ..pooling import PooledMySQLConnection

    try:
        from ..connection_cext import CMySQLConnection
        from ..cursor_cext import CMySQLCursor
    except ImportError:
        # The cext is not available.
        pass

from ... import connector
from ..constants import CNX_POOL_ARGS, DEFAULT_CONFIGURATION
from ..logger import logger
from ..version import VERSION_TEXT

try:
    # pylint: disable=unused-import
    # try to load otel from the system
    from opentelemetry import trace  # check api
    from opentelemetry.sdk.trace import TracerProvider  # check sdk
    from opentelemetry.semconv.trace import SpanAttributes  # check semconv

    OTEL_SYSTEM_AVAILABLE = True
except ImportError:
    try:
        # falling back to the bundled installation
        from mysql.opentelemetry import trace
        from mysql.opentelemetry.semconv.trace import SpanAttributes

        OTEL_SYSTEM_AVAILABLE = False
    except ImportError as missing_dependencies_err:
        raise connector.errors.ProgrammingError(
            "Bundled installation has missing dependencies. "
            "Please use `pip install mysql-connector-python[opentelemetry]`, "
            "or for an editable install use `pip install -e '.[opentelemetry]'`, "
            "to install the dependencies required by the bundled opentelemetry package."
        ) from missing_dependencies_err


from .constants import (
    CONNECTION_SPAN_NAME,
    DB_SYSTEM,
    DEFAULT_THREAD_ID,
    DEFAULT_THREAD_NAME,
    FIRST_SUPPORTED_VERSION,
    NET_SOCK_FAMILY,
    NET_SOCK_HOST_ADDR,
    NET_SOCK_HOST_PORT,
    NET_SOCK_PEER_ADDR,
    NET_SOCK_PEER_PORT,
    OPTION_CNX_SPAN,
    OPTION_CNX_TRACER,
)

leading_comment_remover: re.Pattern = re.compile(r"^/\*.*?\*/")


def record_exception_event(span: trace.Span, exc: Optional[Exception]) -> None:
    """Records an exeception event."""
    if not span or not span.is_recording() or not exc:
        return

    span.set_status(trace.Status(trace.StatusCode.ERROR))
    span.record_exception(exc)


def end_span(span: trace.Span) -> None:
    """Ends span."""
    if not span or not span.is_recording():
        return

    span.end()


def get_operation_name(operation: str) -> str:
    """Parse query to extract operation name."""
    if operation and isinstance(operation, str):
        # Strip leading comments so we get the operation name.
        return leading_comment_remover.sub("", operation).split()[0]
    return ""


def set_connection_span_attrs(
    cnx: Optional["MySQLConnectionAbstract"],
    cnx_span: trace.Span,
    cnx_kwargs: Optional[Dict[str, Any]] = None,
) -> None:
    """Defines connection span attributes. If `cnx` is None then we use `cnx_kwargs`
    to get basic net information. Basic net attributes are defined such as:

    * DB_SYSTEM
    * NET_TRANSPORT
    * NET_SOCK_FAMILY

    Socket-level attributes [*] are also defined [**].

    [*]: Socket-level attributes identify peer and host that are directly connected to
    each other. Since instrumentations may have limited knowledge on network
    information, instrumentations SHOULD populate such attributes to the best of
    their knowledge when populate them at all.

    [**]: `CMySQLConnection` connections have no access to socket-level
    details so socket-level attributes aren't included. `MySQLConnection`
    connections, on the other hand, do include socket-level attributes.

    References:
    [1]: https://github.com/open-telemetry/opentelemetry-specification/blob/main/
    specification/trace/semantic_conventions/span-general.md
    """
    # pylint: disable=broad-exception-caught
    if not cnx_span or not cnx_span.is_recording():
        return

    if cnx_kwargs is None:
        cnx_kwargs = {}

    is_tcp = not cnx._unix_socket if cnx else "unix_socket" not in cnx_kwargs

    attrs: Dict[str, Any] = {
        SpanAttributes.DB_SYSTEM: DB_SYSTEM,
        SpanAttributes.NET_TRANSPORT: "ip_tcp" if is_tcp else "inproc",
        NET_SOCK_FAMILY: "inet" if is_tcp else "unix",
    }

    # Only socket and tcp connections are supported.
    if is_tcp:
        attrs[SpanAttributes.NET_PEER_NAME] = (
            cnx._host if cnx else cnx_kwargs.get("host", DEFAULT_CONFIGURATION["host"])
        )
        attrs[SpanAttributes.NET_PEER_PORT] = (
            cnx._port if cnx else cnx_kwargs.get("port", DEFAULT_CONFIGURATION["port"])
        )

        if hasattr(cnx, "_socket") and cnx._socket:
            try:
                (
                    attrs[NET_SOCK_PEER_ADDR],
                    sock_peer_port,
                ) = cnx._socket.sock.getpeername()

                (
                    attrs[NET_SOCK_HOST_ADDR],
                    attrs[NET_SOCK_HOST_PORT],
                ) = cnx._socket.sock.getsockname()
            except Exception as sock_err:
                logger.warning("Connection socket is down %s", sock_err)
            else:
                if attrs[SpanAttributes.NET_PEER_PORT] != sock_peer_port:
                    # NET_SOCK_PEER_PORT is recommended if different than net.peer.port
                    # and if net.sock.peer.addr is set.
                    attrs[NET_SOCK_PEER_PORT] = sock_peer_port
    else:
        # For Unix domain socket, net.sock.peer.addr attribute represents
        # destination name and net.peer.name SHOULD NOT be set.
        attrs[NET_SOCK_PEER_ADDR] = (
            cnx._unix_socket if cnx else cnx_kwargs.get("unix_socket")
        )

        if hasattr(cnx, "_socket") and cnx._socket:
            try:
                attrs[NET_SOCK_HOST_ADDR] = cnx._socket.sock.getsockname()
            except Exception as sock_err:
                logger.warning("Connection socket is down %s", sock_err)

    cnx_span.set_attributes(attrs)


def instrument_execution(
    query_method: Callable,
    tracer: trace.Tracer,
    connection_span_link: trace.Link,
    wrapped: Union["MySQLCursor", "CMySQLCursor"],
    *args: Any,
    **kwargs: Any,
) -> Callable:
    """Instruments the execution of `query_method`.

    A query span with a link to the corresponding connection span is generated.
    """
    connection: Union["MySQLConnection", "CMySQLConnection"] = (
        getattr(wrapped, "_connection")
        if hasattr(wrapped, "_connection")
        else getattr(wrapped, "_cnx")
    )

    # SpanAttributes.DB_NAME: connection.database or ""; introduces performance
    # degradation, at this time the database attribute is something nice to have but
    # not a requirement.
    query_span_attributes: Dict = {
        SpanAttributes.DB_SYSTEM: DB_SYSTEM,
        SpanAttributes.DB_USER: connection._user,
        SpanAttributes.THREAD_ID: DEFAULT_THREAD_ID,
        SpanAttributes.THREAD_NAME: DEFAULT_THREAD_NAME,
        "cursor_type": wrapped.__class__.__name__,
    }
    with tracer.start_as_current_span(
        name=get_operation_name(args[0]) or "SQL statement",
        kind=trace.SpanKind.CLIENT,
        links=[connection_span_link],
        attributes=query_span_attributes,
    ):
        return query_method(*args, **kwargs)


class BaseMySQLTracer(ABC):
    """Base class that provides basic object wrapper functionality."""

    @abstractmethod
    def __init__(self) -> None:
        """Must be implemented by subclasses."""

    def __getattr__(self, attr: str) -> Any:
        """Gets an attribute.

        Attributes defined in the wrapper object have higher precedence
        than those wrapped object equivalent. Attributes not found in
        the wrapper are then searched in the wrapped object.
        """
        if attr in self.__dict__:
            # this object has it
            return getattr(self, attr)
        # proxy to the wrapped object
        return getattr(self._wrapped, attr)

    def __setattr__(self, name: str, value: Any) -> None:
        if "_wrapped" not in self.__dict__:
            self.__dict__["_wrapped"] = value
            return

        if name in self.__dict__:
            # this object has it
            super().__setattr__(name, value)
            return
        # proxy to the wrapped object
        self._wrapped.__setattr__(name, value)

    def __enter__(self) -> Any:
        """Magic method."""
        self._wrapped.__enter__()
        return self

    def __exit__(self, *args: Any, **kwargs: Any) -> None:
        """Magic method."""
        self._wrapped.__exit__(*args, **kwargs)

    def get_wrapped_class(self) -> str:
        """Gets the wrapped class name."""
        return self._wrapped.__class__.__name__


class TracedMySQLCursor(BaseMySQLTracer):
    """Wrapper class for a `MySQLCursor` or `CMySQLCursor` object."""

    def __init__(
        self,
        wrapped: Union["MySQLCursor", "CMySQLCursor"],
        tracer: trace.Tracer,
        connection_span: trace.Span,
    ):
        """Constructor."""
        self._wrapped: Union["MySQLCursor", "CMySQLCursor"] = wrapped
        self._tracer: trace.Tracer = tracer
        self._connection_span_link: trace.Link = trace.Link(
            connection_span.get_span_context()
        )

    def execute(self, *args: Any, **kwargs: Any) -> Any:
        """Instruments execute method."""
        return instrument_execution(
            self._wrapped.execute,
            self._tracer,
            self._connection_span_link,
            self._wrapped,
            *args,
            **kwargs,
        )

    def executemany(self, *args: Any, **kwargs: Any) -> Any:
        """Instruments executemany method."""
        return instrument_execution(
            self._wrapped.executemany,
            self._tracer,
            self._connection_span_link,
            self._wrapped,
            *args,
            **kwargs,
        )

    def callproc(self, *args: Any, **kwargs: Any) -> Any:
        """Instruments callproc method."""
        return instrument_execution(
            self._wrapped.callproc,
            self._tracer,
            self._connection_span_link,
            self._wrapped,
            *args,
            **kwargs,
        )


class TracedMySQLConnection(BaseMySQLTracer):
    """Wrapper class for a `MySQLConnection` or `CMySQLConnection` object."""

    def __init__(self, wrapped: Union["MySQLConnection", "CMySQLConnection"]) -> None:
        """Constructor."""
        self._wrapped: Union["MySQLConnection", "CMySQLConnection"] = wrapped

        # call `sql_mode` so its value is cached internally and querying it does not
        # interfere when recording query span events later.
        _ = self._wrapped.sql_mode

    def cursor(self, *args: Any, **kwargs: Any) -> TracedMySQLCursor:
        """Wraps the cursor object."""
        return TracedMySQLCursor(
            wrapped=self._wrapped.cursor(*args, **kwargs),
            tracer=self._tracer,
            connection_span=self._span,
        )


def instrument_connect(
    connect: Callable[
        ..., Union["MySQLConnection", "CMySQLConnection", "PooledMySQLConnection"]
    ],
    tracer_provider: Optional[trace.TracerProvider] = None,
) -> Callable[
    ..., Union["MySQLConnection", "CMySQLConnection", "PooledMySQLConnection"]
]:
    """Retrurn the instrumented version of `connect`."""

    # let's preserve `connect` identity.
    @functools.wraps(connect)
    def wrapper(
        *args: Any, **kwargs: Any
    ) -> Union["MySQLConnection", "CMySQLConnection", "PooledMySQLConnection"]:
        """Wraps the connection object returned by the method `connect`.

        Instrumentation for PooledConnections is not supported.
        """
        if any(key in kwargs for key in CNX_POOL_ARGS):
            logger.warning("Instrumentation for pooled connections not supported")
            return connect(*args, **kwargs)

        tracer = trace.get_tracer(
            instrumenting_module_name="MySQL Connector/Python",
            instrumenting_library_version=VERSION_TEXT,
            tracer_provider=tracer_provider,
        )

        # The connection span is passed in as an argument so the connection object can
        # keep a pointer to it.
        kwargs[OPTION_CNX_SPAN] = tracer.start_span(
            name=CONNECTION_SPAN_NAME, kind=trace.SpanKind.CLIENT
        )
        kwargs[OPTION_CNX_TRACER] = tracer

        # Add basic net information.
        set_connection_span_attrs(None, kwargs[OPTION_CNX_SPAN], kwargs)

        # Connection may fail at this point, in case it does, basic net info is already
        # included so the user can check the net configuration she/he provided.
        cnx = connect(*args, **kwargs)

        # connection went ok, let's refine the net information.
        set_connection_span_attrs(cnx, cnx._span, kwargs)  # type: ignore[arg-type]

        return TracedMySQLConnection(
            wrapped=cnx,  # type: ignore[return-value, arg-type]
        )

    return wrapper


class MySQLInstrumentor:
    """MySQL instrumentation supporting mysql-connector-python."""

    _instance: Optional[MySQLInstrumentor] = None

    def __new__(cls, *args: Any, **kwargs: Any) -> MySQLInstrumentor:
        """Singlenton.

        Restricts the instantiation to a singular instance.
        """
        if cls._instance is None:
            # create instance
            cls._instance = object.__new__(cls, *args, **kwargs)
            # keep a pointer to the uninstrumented connect method
            setattr(cls._instance, "_original_connect", connector.connect)
        return cls._instance

    def instrumentation_dependencies(self) -> Collection[str]:
        """Return a list of python packages with versions
        that the will be instrumented (e.g., versions >= 8.1.0)."""
        return [f"mysql-connector-python >= {FIRST_SUPPORTED_VERSION}"]

    def instrument(self, **kwargs: Any) -> None:
        """Instrument the library.

        Args:
            trace_module: reference to the 'trace' module from opentelemetry.
            tracer_provider (optional): TracerProvider instance.

        NOTE: Instrumentation for pooled connections not supported.
        """
        if connector.connect != getattr(self, "_original_connect"):
            logger.warning("MySQL Connector/Python module already instrumented.")
            return
        connector.connect = instrument_connect(
            connect=getattr(self, "_original_connect"),
            tracer_provider=kwargs.get("tracer_provider"),
        )

    def instrument_connection(
        self,
        connection: Union["MySQLConnection", "CMySQLConnection"],
        tracer_provider: Optional[trace.TracerProvider] = None,
    ) -> Union["MySQLConnection", "CMySQLConnection"]:
        """Enable instrumentation in a MySQL connection.

        Args:
            connection: uninstrumented connection instance.
            trace_module: reference to the 'trace' module from opentelemetry.
            tracer_provider (optional): TracerProvider instance.

        Returns:
            connection: instrumented connection instace.

        NOTE: Instrumentation for pooled connections not supported.
        """
        if isinstance(connection, TracedMySQLConnection):
            logger.warning("Connection already instrumented.")
            return connection

        if not hasattr(connection, "_span") or not hasattr(connection, "_tracer"):
            logger.warning(
                "Instrumentation for class %s not supported.",
                connection.__class__.__name__,
            )
            return connection

        tracer = trace.get_tracer(
            instrumenting_module_name="MySQL Connector/Python",
            instrumenting_library_version=VERSION_TEXT,
            tracer_provider=tracer_provider,
        )
        connection._span = tracer.start_span(
            name=CONNECTION_SPAN_NAME, kind=trace.SpanKind.CLIENT
        )
        connection._tracer = tracer

        set_connection_span_attrs(connection, connection._span)

        return TracedMySQLConnection(wrapped=connection)  # type: ignore[return-value]

    def uninstrument(self, **kwargs: Any) -> None:
        """Uninstrument the library."""
        # pylint: disable=unused-argument
        if connector.connect == getattr(self, "_original_connect"):
            logger.warning("MySQL Connector/Python module already uninstrumented.")
            return
        connector.connect = getattr(self, "_original_connect")

    def uninstrument_connection(
        self, connection: Union["MySQLConnection", "CMySQLConnection"]
    ) -> Union["MySQLConnection", "CMySQLConnection"]:
        """Disable instrumentation in a MySQL connection.

        Args:
            connection: instrumented connection instance.

        Returns:
            connection: uninstrumented connection instace.

        NOTE: Instrumentation for pooled connections not supported.
        """
        if not hasattr(connection, "_span"):
            logger.warning(
                "Uninstrumentation for class %s not supported.",
                connection.__class__.__name__,
            )
            return connection

        if not isinstance(connection, TracedMySQLConnection):
            logger.warning("Connection already uninstrumented.")
            return connection

        # stop connection span recording
        if connection._span and connection._span.is_recording():
            connection._span.end()
            connection._span = None

        return connection._wrapped

virt/lib/python3.9/site-packages/mysql/connector/opentelemetry/instrumentation.py
PrinceDisant/LostInNL
# Copyright (c) 2014, 2022, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

# mypy: disable-error-code="attr-defined"

"""Implements parser to parse MySQL option files."""

import codecs
import io
import os
import re

from configparser import ConfigParser as SafeConfigParser, MissingSectionHeaderError
from typing import Any, Dict, List, Optional, Tuple, Union

from .constants import CNX_POOL_ARGS, DEFAULT_CONFIGURATION

DEFAULT_EXTENSIONS: Dict[str, Tuple[str, ...]] = {
    "nt": ("ini", "cnf"),
    "posix": ("cnf",),
}


def read_option_files(**config: Union[str, List[str]]) -> Dict[str, Any]:
    """
    Read option files for connection parameters.

    Checks if connection arguments contain option file arguments, and then
    reads option files accordingly.
    """
    if "option_files" in config:
        try:
            if isinstance(config["option_groups"], str):
                config["option_groups"] = [config["option_groups"]]
            groups = config["option_groups"]
            del config["option_groups"]
        except KeyError:
            groups = ["client", "connector_python"]

        if isinstance(config["option_files"], str):
            config["option_files"] = [config["option_files"]]
        option_parser = MySQLOptionsParser(
            list(config["option_files"]), keep_dashes=False
        )
        del config["option_files"]

        config_from_file = option_parser.get_groups_as_dict_with_priority(*groups)
        config_options: Dict[str, Tuple[str, int]] = {}
        for group in groups:
            try:
                for option, value in config_from_file[group].items():
                    try:
                        if option == "socket":
                            option = "unix_socket"

                        if option not in CNX_POOL_ARGS and option != "failover":
                            _ = DEFAULT_CONFIGURATION[option]

                        if (
                            option not in config_options
                            or config_options[option][1] <= value[1]
                        ):
                            config_options[option] = value
                    except KeyError:
                        if group == "connector_python":
                            raise AttributeError(
                                f"Unsupported argument '{option}'"
                            ) from None
            except KeyError:
                continue

        not_evaluate = ("password", "passwd")
        for option, value in config_options.items():
            if option not in config:
                try:
                    if option in not_evaluate:
                        config[option] = value[0]
                    else:
                        config[option] = eval(value[0])  # pylint: disable=eval-used
                except (NameError, SyntaxError):
                    config[option] = value[0]

    return config


class MySQLOptionsParser(SafeConfigParser):
    """This class implements methods to parse MySQL option files"""

    def __init__(
        self, files: Optional[Union[List[str], str]] = None, keep_dashes: bool = True
    ) -> None:
        """Initialize

        If defaults is True, default option files are read first

        Raises ValueError if defaults is set to True but defaults files
        cannot be found.
        """

        # Regular expression to allow options with no value(For Python v2.6)
        self.optcre: re.Pattern = re.compile(
            r"(?P<option>[^:=\s][^:=]*)"
            r"\s*(?:"
            r"(?P<vi>[:=])\s*"
            r"(?P<value>.*))?$"
        )

        self._options_dict: Dict[str, Dict[str, Tuple[str, int]]] = {}

        SafeConfigParser.__init__(self, strict=False)

        self.default_extension: Tuple[str, ...] = DEFAULT_EXTENSIONS[os.name]
        self.keep_dashes: bool = keep_dashes

        if not files:
            raise ValueError("files argument should be given")
        self.files: List[str] = [files] if isinstance(files, str) else files

        self._parse_options(list(self.files))
        self._sections: Dict[str, Dict[str, str]] = self.get_groups_as_dict()

    def optionxform(self, optionstr: str) -> str:
        """Converts option strings

        Converts option strings to lower case and replaces dashes(-) with
        underscores(_) if keep_dashes variable is set.
        """
        if not self.keep_dashes:
            optionstr = optionstr.replace("-", "_")
        return optionstr.lower()

    def _parse_options(self, files: List[str]) -> None:
        """Parse options from files given as arguments.
         This method checks for !include or !inculdedir directives and if there
         is any, those files included by these directives are also parsed
         for options.

        Raises ValueError if any of the included or file given in arguments
        is not readable.
        """
        initial_files = files[:]
        files = []
        index = 0
        err_msg = "Option file '{0}' being included again in file '{1}'"

        for file_ in initial_files:
            try:
                if file_ in initial_files[index + 1 :]:
                    raise ValueError(
                        f"Same option file '{file_}' occurring more "
                        "than once in the list"
                    )
                with open(file_, "r", encoding="utf-8") as op_file:
                    for line in op_file.readlines():
                        if line.startswith("!includedir"):
                            _, dir_path = line.split(None, 1)
                            dir_path = dir_path.strip()
                            for entry in os.listdir(dir_path):
                                entry = os.path.join(dir_path, entry)
                                if entry in files:
                                    raise ValueError(err_msg.format(entry, file_))
                                if os.path.isfile(entry) and entry.endswith(
                                    self.default_extension
                                ):
                                    files.append(entry)

                        elif line.startswith("!include"):
                            _, filename = line.split(None, 1)
                            filename = filename.strip()
                            if filename in files:
                                raise ValueError(err_msg.format(filename, file_))
                            files.append(filename)

                    index += 1
                    files.append(file_)
            except IOError as err:
                raise ValueError(f"Failed reading file '{file_}': {err}") from err

        read_files = self.read(files)
        not_read_files = set(files) - set(read_files)
        if not_read_files:
            raise ValueError(f"File(s) {', '.join(not_read_files)} could not be read.")

    def read(  # type: ignore[override]
        self, filenames: Union[str, List[str]], encoding: Optional[str] = None
    ) -> List[str]:
        """Read and parse a filename or a list of filenames.

        Overridden from ConfigParser and modified so as to allow options
        which are not inside any section header

        Return list of successfully read files.
        """
        if isinstance(filenames, str):
            filenames = [filenames]
        read_ok = []
        for priority, filename in enumerate(filenames):
            try:
                out_file = io.StringIO()
                with codecs.open(filename, encoding="utf-8") as in_file:
                    for line in in_file:
                        line = line.strip()
                        # Skip lines that begin with "!includedir" or "!include"
                        if line.startswith("!include"):
                            continue

                        match_obj = self.optcre.match(line)
                        if not self.SECTCRE.match(line) and match_obj:
                            optname, delimiter, optval = match_obj.group(
                                "option", "vi", "value"
                            )
                            if optname and not optval and not delimiter:
                                out_file.write(f"{line}=\n")
                            else:
                                out_file.write(f"{line}\n")
                        else:
                            out_file.write(f"{line}\n")
                out_file.seek(0)
            except IOError:
                continue
            try:
                self._read(out_file, filename)
                for group in self._sections.keys():
                    try:
                        self._options_dict[group]
                    except KeyError:
                        self._options_dict[group] = {}
                    for option, value in self._sections[group].items():
                        self._options_dict[group][option] = (value, priority)

                self._sections = self._dict()

            except MissingSectionHeaderError:
                self._read(out_file, filename)
            out_file.close()
            read_ok.append(filename)
        return read_ok

    def get_groups(self, *args: str) -> Dict[str, str]:
        """Returns options as a dictionary.

        Returns options from all the groups specified as arguments, returns
        the options from all groups if no argument provided. Options are
        overridden when they are found in the next group.

        Returns a dictionary
        """
        if not args:
            args = tuple(self._options_dict.keys())

        options = {}
        priority: Dict[str, int] = {}
        for group in args:
            try:
                for option, value in [
                    (
                        key,
                        value,
                    )
                    for key, value in self._options_dict[group].items()
                    if key != "__name__" and not key.startswith("!")
                ]:
                    if option not in options or priority[option] <= value[1]:
                        priority[option] = value[1]
                        options[option] = value[0]
            except KeyError:
                pass

        return options

    def get_groups_as_dict_with_priority(
        self, *args: str
    ) -> Dict[str, Dict[str, Tuple[str, int]]]:
        """Returns options as dictionary of dictionaries.

        Returns options from all the groups specified as arguments. For each
        group the option are contained in a dictionary. The order in which
        the groups are specified is unimportant. Also options are not
        overridden in between the groups.

        The value is a tuple with two elements, first being the actual value
        and second is the priority of the value which is higher for a value
        read from a higher priority file.

        Returns an dictionary of dictionaries
        """
        if not args:
            args = tuple(self._options_dict.keys())

        options = {}
        for group in args:
            try:
                options[group] = dict(
                    (
                        key,
                        value,
                    )
                    for key, value in self._options_dict[group].items()
                    if key != "__name__" and not key.startswith("!")
                )
            except KeyError:
                pass
        return options

    def get_groups_as_dict(self, *args: str) -> Dict[str, Dict[str, str]]:
        """Returns options as dictionary of dictionaries.

        Returns options from all the groups specified as arguments. For each
        group the option are contained in a dictionary. The order in which
        the groups are specified is unimportant. Also options are not
        overridden in between the groups.

        Returns an dictionary of dictionaries
        """
        if not args:
            args = tuple(self._options_dict.keys())

        options = {}
        for group in args:
            try:
                options[group] = dict(
                    (
                        key,
                        value[0],
                    )
                    for key, value in self._options_dict[group].items()
                    if key != "__name__" and not key.startswith("!")
                )
            except KeyError:
                pass

        return options

virt/lib/python3.9/site-packages/mysql/connector/optionfiles.py
PrinceDisant/LostInNL
# Copyright (c) 2022, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

"""Base Authentication Plugin class."""

from abc import ABC
from typing import Optional, Union

from .. import errors
from ..types import StrOrBytes


class BaseAuthPlugin(ABC):
    """Base class for authentication pluginsF


    Classes inheriting from BaseAuthPlugin should implement the method
    prepare_password(). When instantiating, auth_data argument is
    required. The username, password and database are optional. The
    ssl_enabled argument can be used to tell the plugin whether SSL is
    active or not.

    The method auth_response() method is used to retrieve the password
    which was prepared by prepare_password().
    """

    requires_ssl: int = False
    plugin_name: str = ""

    def __init__(
        self,
        auth_data: Optional[bytes],
        username: Optional[StrOrBytes] = None,
        password: Optional[str] = None,
        database: Optional[str] = None,
        ssl_enabled: bool = False,
    ) -> None:
        """Initialization"""
        self._auth_data: bytes = auth_data
        self._username: Optional[str] = (
            username.decode("utf8")
            if isinstance(username, (bytes, bytearray))
            else username
        )
        self._password: Optional[str] = password
        self._database: Optional[str] = database
        self._ssl_enabled: bool = ssl_enabled

    def prepare_password(self) -> bytes:
        """Prepare and return password as as clear text.

        Returns:
            bytes: Prepared password.
        """
        if not self._password:
            return b"\x00"
        password: StrOrBytes = self._password

        if isinstance(password, str):
            password = password.encode("utf8")

        return password + b"\x00"

    def auth_response(
        self, auth_data: Optional[bytes] = None  # pylint: disable=unused-argument
    ) -> bytes:
        """Return the prepared password to send to MySQL.

        Raises:
            InterfaceError: When SSL is required by not enabled.

        Returns:
            str: The prepared password.
        """
        if self.requires_ssl and not self._ssl_enabled:
            raise errors.InterfaceError(f"{self.plugin_name} requires SSL")
        return self.prepare_password()

virt/lib/python3.9/site-packages/mysql/connector/plugins/__init__.py
PrinceDisant/LostInNL
# Copyright (c) 2022, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

# mypy: disable-error-code="str-bytes-safe,misc"

"""Kerberos Authentication Plugin."""

import getpass
import os
import struct

from pathlib import Path
from typing import Any, Optional, Tuple

from ..errors import InterfaceError, ProgrammingError
from ..logger import logger

try:
    import gssapi
except ImportError:
    gssapi = None
    if os.name != "nt":
        raise ProgrammingError(
            "Module gssapi is required for GSSAPI authentication "
            "mechanism but was not found. Unable to authenticate "
            "with the server"
        ) from None

try:
    import sspi
    import sspicon
except ImportError:
    sspi = None
    sspicon = None

from . import BaseAuthPlugin

AUTHENTICATION_PLUGIN_CLASS = (
    "MySQLSSPIKerberosAuthPlugin" if os.name == "nt" else "MySQLKerberosAuthPlugin"
)


# pylint: disable=c-extension-no-member,no-member
class MySQLKerberosAuthPlugin(BaseAuthPlugin):
    """Implement the MySQL Kerberos authentication plugin."""

    plugin_name: str = "authentication_kerberos_client"
    requires_ssl: bool = False
    context: Optional[gssapi.SecurityContext] = None

    @staticmethod
    def get_user_from_credentials() -> str:
        """Get user from credentials without realm."""
        try:
            creds = gssapi.Credentials(usage="initiate")
            user = str(creds.name)
            if user.find("@") != -1:
                user, _ = user.split("@", 1)
            return user
        except gssapi.raw.misc.GSSError:
            return getpass.getuser()

    @staticmethod
    def get_store() -> dict:
        """Get a credentials store dictionary.

        Returns:
            dict: Credentials store dictionary with the krb5 ccache name.

        Raises:
            InterfaceError: If 'KRB5CCNAME' environment variable is empty.
        """
        krb5ccname = os.environ.get(
            "KRB5CCNAME",
            f"/tmp/krb5cc_{os.getuid()}"
            if os.name == "posix"
            else Path("%TEMP%").joinpath("krb5cc"),
        )
        if not krb5ccname:
            raise InterfaceError(
                "The 'KRB5CCNAME' environment variable is set to empty"
            )
        logger.debug("Using krb5 ccache name: FILE:%s", krb5ccname)
        store = {b"ccache": f"FILE:{krb5ccname}".encode("utf-8")}
        return store

    def _acquire_cred_with_password(self, upn: str) -> gssapi.raw.creds.Creds:
        """Acquire and store credentials through provided password.

        Args:
            upn (str): User Principal Name.

        Returns:
            gssapi.raw.creds.Creds: GSSAPI credentials.
        """
        logger.debug("Attempt to acquire credentials through provided password")
        user = gssapi.Name(upn, gssapi.NameType.user)
        password = self._password.encode("utf-8")

        try:
            acquire_cred_result = gssapi.raw.acquire_cred_with_password(
                user, password, usage="initiate"
            )
            creds = acquire_cred_result.creds
            gssapi.raw.store_cred_into(
                self.get_store(),
                creds=creds,
                mech=gssapi.MechType.kerberos,
                overwrite=True,
                set_default=True,
            )
        except gssapi.raw.misc.GSSError as err:
            raise ProgrammingError(
                f"Unable to acquire credentials with the given password: {err}"
            ) from err
        return creds

    @staticmethod
    def _parse_auth_data(packet: bytes) -> Tuple[str, str]:
        """Parse authentication data.

        Get the SPN and REALM from the authentication data packet.

        Format:
            SPN string length two bytes <B1> <B2> +
            SPN string +
            UPN realm string length two bytes <B1> <B2> +
            UPN realm string

        Returns:
            tuple: With 'spn' and 'realm'.
        """
        spn_len = struct.unpack("<H", packet[:2])[0]
        packet = packet[2:]

        spn = struct.unpack(f"<{spn_len}s", packet[:spn_len])[0]
        packet = packet[spn_len:]

        realm_len = struct.unpack("<H", packet[:2])[0]
        realm = struct.unpack(f"<{realm_len}s", packet[2:])[0]

        return spn.decode(), realm.decode()

    def auth_response(self, auth_data: Optional[bytes] = None) -> Optional[bytes]:
        """Prepare the first message to the server."""
        spn = None
        realm = None

        if auth_data:
            try:
                spn, realm = self._parse_auth_data(auth_data)
            except struct.error as err:
                raise InterruptedError(f"Invalid authentication data: {err}") from err

        if spn is None:
            return self.prepare_password()

        upn = f"{self._username}@{realm}" if self._username else None

        logger.debug("Service Principal: %s", spn)
        logger.debug("Realm: %s", realm)

        try:
            # Attempt to retrieve credentials from cache file
            creds: Any = gssapi.Credentials(usage="initiate")
            creds_upn = str(creds.name)

            logger.debug("Cached credentials found")
            logger.debug("Cached credentials UPN: %s", creds_upn)

            # Remove the realm from user
            if creds_upn.find("@") != -1:
                creds_user, creds_realm = creds_upn.split("@", 1)
            else:
                creds_user = creds_upn
                creds_realm = None

            upn = f"{self._username}@{realm}" if self._username else creds_upn

            # The user from cached credentials matches with the given user?
            if self._username and self._username != creds_user:
                logger.debug(
                    "The user from cached credentials doesn't match with the "
                    "given user"
                )
                if self._password is not None:
                    creds = self._acquire_cred_with_password(upn)
            if creds_realm and creds_realm != realm and self._password is not None:
                creds = self._acquire_cred_with_password(upn)
        except gssapi.raw.exceptions.ExpiredCredentialsError as err:
            if upn and self._password is not None:
                creds = self._acquire_cred_with_password(upn)
            else:
                raise InterfaceError(f"Credentials has expired: {err}") from err
        except gssapi.raw.misc.GSSError as err:
            if upn and self._password is not None:
                creds = self._acquire_cred_with_password(upn)
            else:
                raise InterfaceError(
                    f"Unable to retrieve cached credentials error: {err}"
                ) from err

        flags = (
            gssapi.RequirementFlag.mutual_authentication,
            gssapi.RequirementFlag.extended_error,
            gssapi.RequirementFlag.delegate_to_peer,
        )
        name = gssapi.Name(spn, name_type=gssapi.NameType.kerberos_principal)
        cname = name.canonicalize(gssapi.MechType.kerberos)
        self.context = gssapi.SecurityContext(
            name=cname, creds=creds, flags=sum(flags), usage="initiate"
        )

        try:
            initial_client_token: Optional[bytes] = self.context.step()
        except gssapi.raw.misc.GSSError as err:
            raise InterfaceError(f"Unable to initiate security context: {err}") from err

        logger.debug("Initial client token: %s", initial_client_token)
        return initial_client_token

    def auth_continue(
        self, tgt_auth_challenge: Optional[bytes]
    ) -> Tuple[Optional[bytes], bool]:
        """Continue with the Kerberos TGT service request.

        With the TGT authentication service given response generate a TGT
        service request. This method must be invoked sequentially (in a loop)
        until the security context is completed and an empty response needs to
        be send to acknowledge the server.

        Args:
            tgt_auth_challenge: the challenge for the negotiation.

        Returns:
            tuple (bytearray TGS service request,
            bool True if context is completed otherwise False).
        """
        logger.debug("tgt_auth challenge: %s", tgt_auth_challenge)

        resp: Optional[bytes] = self.context.step(tgt_auth_challenge)

        logger.debug("Context step response: %s", resp)
        logger.debug("Context completed?: %s", self.context.complete)

        return resp, self.context.complete

    def auth_accept_close_handshake(self, message: bytes) -> bytes:
        """Accept handshake and generate closing handshake message for server.

        This method verifies the server authenticity from the given message
        and included signature and generates the closing handshake for the
        server.

        When this method is invoked the security context is already established
        and the client and server can send GSSAPI formated secure messages.

        To finish the authentication handshake the server sends a message
        with the security layer availability and the maximum buffer size.

        Since the connector only uses the GSSAPI authentication mechanism to
        authenticate the user with the server, the server will verify clients
        message signature and terminate the GSSAPI authentication and send two
        messages; an authentication acceptance b'\x01\x00\x00\x08\x01' and a
        OK packet (that must be received after sent the returned message from
        this method).

        Args:
            message: a wrapped gssapi message from the server.

        Returns:
            bytearray (closing handshake message to be send to the server).
        """
        if not self.context.complete:
            raise ProgrammingError("Security context is not completed")
        logger.debug("Server message: %s", message)
        logger.debug("GSSAPI flags in use: %s", self.context.actual_flags)
        try:
            unwraped = self.context.unwrap(message)
            logger.debug("Unwraped: %s", unwraped)
        except gssapi.raw.exceptions.BadMICError as err:
            logger.debug("Unable to unwrap server message: %s", err)
            raise InterfaceError(f"Unable to unwrap server message: {err}") from err

        logger.debug("Unwrapped server message: %s", unwraped)
        # The message contents for the clients closing message:
        #   - security level 1 byte, must be always 1.
        #   - conciliated buffer size 3 bytes, without importance as no
        #     further GSSAPI messages will be sends.
        response = bytearray(b"\x01\x00\x00\00")
        # Closing handshake must not be encrypted.
        logger.debug("Message response: %s", response)
        wraped = self.context.wrap(response, encrypt=False)
        logger.debug(
            "Wrapped message response: %s, length: %d",
            wraped[0],
            len(wraped[0]),
        )

        return wraped.message


class MySQLSSPIKerberosAuthPlugin(BaseAuthPlugin):
    """Implement the MySQL Kerberos authentication plugin with Windows SSPI"""

    plugin_name: str = "authentication_kerberos_client"
    requires_ssl: bool = False
    context: Any = None
    clientauth: Any = None

    @staticmethod
    def _parse_auth_data(packet: bytes) -> Tuple[str, str]:
        """Parse authentication data.

        Get the SPN and REALM from the authentication data packet.

        Format:
            SPN string length two bytes <B1> <B2> +
            SPN string +
            UPN realm string length two bytes <B1> <B2> +
            UPN realm string

        Returns:
            tuple: With 'spn' and 'realm'.
        """
        spn_len = struct.unpack("<H", packet[:2])[0]
        packet = packet[2:]

        spn = struct.unpack(f"<{spn_len}s", packet[:spn_len])[0]
        packet = packet[spn_len:]

        realm_len = struct.unpack("<H", packet[:2])[0]
        realm = struct.unpack(f"<{realm_len}s", packet[2:])[0]

        return spn.decode(), realm.decode()

    def auth_response(self, auth_data: Optional[bytes] = None) -> Optional[bytes]:
        """Prepare the first message to the server."""
        logger.debug("auth_response for sspi")
        spn = None
        realm = None

        if auth_data:
            try:
                spn, realm = self._parse_auth_data(auth_data)
            except struct.error as err:
                raise InterruptedError(f"Invalid authentication data: {err}") from err

        logger.debug("Service Principal: %s", spn)
        logger.debug("Realm: %s", realm)

        if sspicon is None or sspi is None:
            raise ProgrammingError(
                'Package "pywin32" (Python for Win32 (pywin32) extensions)'
                " is not installed."
            )

        flags = (sspicon.ISC_REQ_MUTUAL_AUTH, sspicon.ISC_REQ_DELEGATE)

        if self._username and self._password:
            _auth_info = (self._username, realm, self._password)
        else:
            _auth_info = None

        targetspn = spn
        logger.debug("targetspn: %s", targetspn)
        logger.debug("_auth_info is None: %s", _auth_info is None)

        # The Security Support Provider Interface (SSPI) is an interface
        # that allows us to choose from a set of SSPs available in the
        # system; the idea of SSPI is to keep interface consistent no
        # matter what back end (a.k.a., SSP) we choose.

        # When using SSPI we should not use Kerberos directly as SSP,
        # as remarked in [2], but we can use it indirectly via another
        # SSP named Negotiate that acts as an application layer between
        # SSPI and the other SSPs [1].

        # Negotiate can select between Kerberos and NTLM on the fly;
        # it chooses Kerberos unless it cannot be used by one of the
        # systems involved in the authentication or the calling
        # application did not provide sufficient information to use
        # Kerberos.

        # prefix: https://docs.microsoft.com/en-us/windows/win32/secauthn
        # [1] prefix/microsoft-negotiate?source=recommendations
        # [2] prefix/microsoft-kerberos?source=recommendations
        self.clientauth = sspi.ClientAuth(
            "Negotiate",
            targetspn=targetspn,
            auth_info=_auth_info,
            scflags=sum(flags),
            datarep=sspicon.SECURITY_NETWORK_DREP,
        )

        try:
            data = None
            err, out_buf = self.clientauth.authorize(data)
            logger.debug("Context step err: %s", err)
            logger.debug("Context step out_buf: %s", out_buf)
            logger.debug("Context completed?: %s", self.clientauth.authenticated)
            initial_client_token = out_buf[0].Buffer
            logger.debug("pkg_info: %s", self.clientauth.pkg_info)
        except Exception as err:
            raise InterfaceError(f"Unable to initiate security context: {err}") from err

        logger.debug("Initial client token: %s", initial_client_token)
        return initial_client_token

    def auth_continue(
        self, tgt_auth_challenge: Optional[bytes]
    ) -> Tuple[Optional[bytes], bool]:
        """Continue with the Kerberos TGT service request.

        With the TGT authentication service given response generate a TGT
        service request. This method must be invoked sequentially (in a loop)
        until the security context is completed and an empty response needs to
        be send to acknowledge the server.

        Args:
            tgt_auth_challenge: the challenge for the negotiation.

        Returns:
            tuple (bytearray TGS service request,
            bool True if context is completed otherwise False).
        """
        logger.debug("tgt_auth challenge: %s", tgt_auth_challenge)

        err, out_buf = self.clientauth.authorize(tgt_auth_challenge)

        logger.debug("Context step err: %s", err)
        logger.debug("Context step out_buf: %s", out_buf)
        resp = out_buf[0].Buffer
        logger.debug("Context step resp: %s", resp)
        logger.debug("Context completed?: %s", self.clientauth.authenticated)

        return resp, self.clientauth.authenticated

virt/lib/python3.9/site-packages/mysql/connector/plugins/authentication_kerberos_client 2.py
PrinceDisant/LostInNL
# Copyright (c) 2022, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

# mypy: disable-error-code="str-bytes-safe,misc"

"""Kerberos Authentication Plugin."""

import getpass
import os
import struct

from pathlib import Path
from typing import Any, Optional, Tuple

from ..errors import InterfaceError, ProgrammingError
from ..logger import logger

try:
    import gssapi
except ImportError:
    gssapi = None
    if os.name != "nt":
        raise ProgrammingError(
            "Module gssapi is required for GSSAPI authentication "
            "mechanism but was not found. Unable to authenticate "
            "with the server"
        ) from None

try:
    import sspi
    import sspicon
except ImportError:
    sspi = None
    sspicon = None

from . import BaseAuthPlugin

AUTHENTICATION_PLUGIN_CLASS = (
    "MySQLSSPIKerberosAuthPlugin" if os.name == "nt" else "MySQLKerberosAuthPlugin"
)


# pylint: disable=c-extension-no-member,no-member
class MySQLKerberosAuthPlugin(BaseAuthPlugin):
    """Implement the MySQL Kerberos authentication plugin."""

    plugin_name: str = "authentication_kerberos_client"
    requires_ssl: bool = False
    context: Optional[gssapi.SecurityContext] = None

    @staticmethod
    def get_user_from_credentials() -> str:
        """Get user from credentials without realm."""
        try:
            creds = gssapi.Credentials(usage="initiate")
            user = str(creds.name)
            if user.find("@") != -1:
                user, _ = user.split("@", 1)
            return user
        except gssapi.raw.misc.GSSError:
            return getpass.getuser()

    @staticmethod
    def get_store() -> dict:
        """Get a credentials store dictionary.

        Returns:
            dict: Credentials store dictionary with the krb5 ccache name.

        Raises:
            InterfaceError: If 'KRB5CCNAME' environment variable is empty.
        """
        krb5ccname = os.environ.get(
            "KRB5CCNAME",
            f"/tmp/krb5cc_{os.getuid()}"
            if os.name == "posix"
            else Path("%TEMP%").joinpath("krb5cc"),
        )
        if not krb5ccname:
            raise InterfaceError(
                "The 'KRB5CCNAME' environment variable is set to empty"
            )
        logger.debug("Using krb5 ccache name: FILE:%s", krb5ccname)
        store = {b"ccache": f"FILE:{krb5ccname}".encode("utf-8")}
        return store

    def _acquire_cred_with_password(self, upn: str) -> gssapi.raw.creds.Creds:
        """Acquire and store credentials through provided password.

        Args:
            upn (str): User Principal Name.

        Returns:
            gssapi.raw.creds.Creds: GSSAPI credentials.
        """
        logger.debug("Attempt to acquire credentials through provided password")
        user = gssapi.Name(upn, gssapi.NameType.user)
        password = self._password.encode("utf-8")

        try:
            acquire_cred_result = gssapi.raw.acquire_cred_with_password(
                user, password, usage="initiate"
            )
            creds = acquire_cred_result.creds
            gssapi.raw.store_cred_into(
                self.get_store(),
                creds=creds,
                mech=gssapi.MechType.kerberos,
                overwrite=True,
                set_default=True,
            )
        except gssapi.raw.misc.GSSError as err:
            raise ProgrammingError(
                f"Unable to acquire credentials with the given password: {err}"
            ) from err
        return creds

    @staticmethod
    def _parse_auth_data(packet: bytes) -> Tuple[str, str]:
        """Parse authentication data.

        Get the SPN and REALM from the authentication data packet.

        Format:
            SPN string length two bytes <B1> <B2> +
            SPN string +
            UPN realm string length two bytes <B1> <B2> +
            UPN realm string

        Returns:
            tuple: With 'spn' and 'realm'.
        """
        spn_len = struct.unpack("<H", packet[:2])[0]
        packet = packet[2:]

        spn = struct.unpack(f"<{spn_len}s", packet[:spn_len])[0]
        packet = packet[spn_len:]

        realm_len = struct.unpack("<H", packet[:2])[0]
        realm = struct.unpack(f"<{realm_len}s", packet[2:])[0]

        return spn.decode(), realm.decode()

    def auth_response(self, auth_data: Optional[bytes] = None) -> Optional[bytes]:
        """Prepare the first message to the server."""
        spn = None
        realm = None

        if auth_data:
            try:
                spn, realm = self._parse_auth_data(auth_data)
            except struct.error as err:
                raise InterruptedError(f"Invalid authentication data: {err}") from err

        if spn is None:
            return self.prepare_password()

        upn = f"{self._username}@{realm}" if self._username else None

        logger.debug("Service Principal: %s", spn)
        logger.debug("Realm: %s", realm)

        try:
            # Attempt to retrieve credentials from cache file
            creds: Any = gssapi.Credentials(usage="initiate")
            creds_upn = str(creds.name)

            logger.debug("Cached credentials found")
            logger.debug("Cached credentials UPN: %s", creds_upn)

            # Remove the realm from user
            if creds_upn.find("@") != -1:
                creds_user, creds_realm = creds_upn.split("@", 1)
            else:
                creds_user = creds_upn
                creds_realm = None

            upn = f"{self._username}@{realm}" if self._username else creds_upn

            # The user from cached credentials matches with the given user?
            if self._username and self._username != creds_user:
                logger.debug(
                    "The user from cached credentials doesn't match with the "
                    "given user"
                )
                if self._password is not None:
                    creds = self._acquire_cred_with_password(upn)
            if creds_realm and creds_realm != realm and self._password is not None:
                creds = self._acquire_cred_with_password(upn)
        except gssapi.raw.exceptions.ExpiredCredentialsError as err:
            if upn and self._password is not None:
                creds = self._acquire_cred_with_password(upn)
            else:
                raise InterfaceError(f"Credentials has expired: {err}") from err
        except gssapi.raw.misc.GSSError as err:
            if upn and self._password is not None:
                creds = self._acquire_cred_with_password(upn)
            else:
                raise InterfaceError(
                    f"Unable to retrieve cached credentials error: {err}"
                ) from err

        flags = (
            gssapi.RequirementFlag.mutual_authentication,
            gssapi.RequirementFlag.extended_error,
            gssapi.RequirementFlag.delegate_to_peer,
        )
        name = gssapi.Name(spn, name_type=gssapi.NameType.kerberos_principal)
        cname = name.canonicalize(gssapi.MechType.kerberos)
        self.context = gssapi.SecurityContext(
            name=cname, creds=creds, flags=sum(flags), usage="initiate"
        )

        try:
            initial_client_token: Optional[bytes] = self.context.step()
        except gssapi.raw.misc.GSSError as err:
            raise InterfaceError(f"Unable to initiate security context: {err}") from err

        logger.debug("Initial client token: %s", initial_client_token)
        return initial_client_token

    def auth_continue(
        self, tgt_auth_challenge: Optional[bytes]
    ) -> Tuple[Optional[bytes], bool]:
        """Continue with the Kerberos TGT service request.

        With the TGT authentication service given response generate a TGT
        service request. This method must be invoked sequentially (in a loop)
        until the security context is completed and an empty response needs to
        be send to acknowledge the server.

        Args:
            tgt_auth_challenge: the challenge for the negotiation.

        Returns:
            tuple (bytearray TGS service request,
            bool True if context is completed otherwise False).
        """
        logger.debug("tgt_auth challenge: %s", tgt_auth_challenge)

        resp: Optional[bytes] = self.context.step(tgt_auth_challenge)

        logger.debug("Context step response: %s", resp)
        logger.debug("Context completed?: %s", self.context.complete)

        return resp, self.context.complete

    def auth_accept_close_handshake(self, message: bytes) -> bytes:
        """Accept handshake and generate closing handshake message for server.

        This method verifies the server authenticity from the given message
        and included signature and generates the closing handshake for the
        server.

        When this method is invoked the security context is already established
        and the client and server can send GSSAPI formated secure messages.

        To finish the authentication handshake the server sends a message
        with the security layer availability and the maximum buffer size.

        Since the connector only uses the GSSAPI authentication mechanism to
        authenticate the user with the server, the server will verify clients
        message signature and terminate the GSSAPI authentication and send two
        messages; an authentication acceptance b'\x01\x00\x00\x08\x01' and a
        OK packet (that must be received after sent the returned message from
        this method).

        Args:
            message: a wrapped gssapi message from the server.

        Returns:
            bytearray (closing handshake message to be send to the server).
        """
        if not self.context.complete:
            raise ProgrammingError("Security context is not completed")
        logger.debug("Server message: %s", message)
        logger.debug("GSSAPI flags in use: %s", self.context.actual_flags)
        try:
            unwraped = self.context.unwrap(message)
            logger.debug("Unwraped: %s", unwraped)
        except gssapi.raw.exceptions.BadMICError as err:
            logger.debug("Unable to unwrap server message: %s", err)
            raise InterfaceError(f"Unable to unwrap server message: {err}") from err

        logger.debug("Unwrapped server message: %s", unwraped)
        # The message contents for the clients closing message:
        #   - security level 1 byte, must be always 1.
        #   - conciliated buffer size 3 bytes, without importance as no
        #     further GSSAPI messages will be sends.
        response = bytearray(b"\x01\x00\x00\00")
        # Closing handshake must not be encrypted.
        logger.debug("Message response: %s", response)
        wraped = self.context.wrap(response, encrypt=False)
        logger.debug(
            "Wrapped message response: %s, length: %d",
            wraped[0],
            len(wraped[0]),
        )

        return wraped.message


class MySQLSSPIKerberosAuthPlugin(BaseAuthPlugin):
    """Implement the MySQL Kerberos authentication plugin with Windows SSPI"""

    plugin_name: str = "authentication_kerberos_client"
    requires_ssl: bool = False
    context: Any = None
    clientauth: Any = None

    @staticmethod
    def _parse_auth_data(packet: bytes) -> Tuple[str, str]:
        """Parse authentication data.

        Get the SPN and REALM from the authentication data packet.

        Format:
            SPN string length two bytes <B1> <B2> +
            SPN string +
            UPN realm string length two bytes <B1> <B2> +
            UPN realm string

        Returns:
            tuple: With 'spn' and 'realm'.
        """
        spn_len = struct.unpack("<H", packet[:2])[0]
        packet = packet[2:]

        spn = struct.unpack(f"<{spn_len}s", packet[:spn_len])[0]
        packet = packet[spn_len:]

        realm_len = struct.unpack("<H", packet[:2])[0]
        realm = struct.unpack(f"<{realm_len}s", packet[2:])[0]

        return spn.decode(), realm.decode()

    def auth_response(self, auth_data: Optional[bytes] = None) -> Optional[bytes]:
        """Prepare the first message to the server."""
        logger.debug("auth_response for sspi")
        spn = None
        realm = None

        if auth_data:
            try:
                spn, realm = self._parse_auth_data(auth_data)
            except struct.error as err:
                raise InterruptedError(f"Invalid authentication data: {err}") from err

        logger.debug("Service Principal: %s", spn)
        logger.debug("Realm: %s", realm)

        if sspicon is None or sspi is None:
            raise ProgrammingError(
                'Package "pywin32" (Python for Win32 (pywin32) extensions)'
                " is not installed."
            )

        flags = (sspicon.ISC_REQ_MUTUAL_AUTH, sspicon.ISC_REQ_DELEGATE)

        if self._username and self._password:
            _auth_info = (self._username, realm, self._password)
        else:
            _auth_info = None

        targetspn = spn
        logger.debug("targetspn: %s", targetspn)
        logger.debug("_auth_info is None: %s", _auth_info is None)

        # The Security Support Provider Interface (SSPI) is an interface
        # that allows us to choose from a set of SSPs available in the
        # system; the idea of SSPI is to keep interface consistent no
        # matter what back end (a.k.a., SSP) we choose.

        # When using SSPI we should not use Kerberos directly as SSP,
        # as remarked in [2], but we can use it indirectly via another
        # SSP named Negotiate that acts as an application layer between
        # SSPI and the other SSPs [1].

        # Negotiate can select between Kerberos and NTLM on the fly;
        # it chooses Kerberos unless it cannot be used by one of the
        # systems involved in the authentication or the calling
        # application did not provide sufficient information to use
        # Kerberos.

        # prefix: https://docs.microsoft.com/en-us/windows/win32/secauthn
        # [1] prefix/microsoft-negotiate?source=recommendations
        # [2] prefix/microsoft-kerberos?source=recommendations
        self.clientauth = sspi.ClientAuth(
            "Negotiate",
            targetspn=targetspn,
            auth_info=_auth_info,
            scflags=sum(flags),
            datarep=sspicon.SECURITY_NETWORK_DREP,
        )

        try:
            data = None
            err, out_buf = self.clientauth.authorize(data)
            logger.debug("Context step err: %s", err)
            logger.debug("Context step out_buf: %s", out_buf)
            logger.debug("Context completed?: %s", self.clientauth.authenticated)
            initial_client_token = out_buf[0].Buffer
            logger.debug("pkg_info: %s", self.clientauth.pkg_info)
        except Exception as err:
            raise InterfaceError(f"Unable to initiate security context: {err}") from err

        logger.debug("Initial client token: %s", initial_client_token)
        return initial_client_token

    def auth_continue(
        self, tgt_auth_challenge: Optional[bytes]
    ) -> Tuple[Optional[bytes], bool]:
        """Continue with the Kerberos TGT service request.

        With the TGT authentication service given response generate a TGT
        service request. This method must be invoked sequentially (in a loop)
        until the security context is completed and an empty response needs to
        be send to acknowledge the server.

        Args:
            tgt_auth_challenge: the challenge for the negotiation.

        Returns:
            tuple (bytearray TGS service request,
            bool True if context is completed otherwise False).
        """
        logger.debug("tgt_auth challenge: %s", tgt_auth_challenge)

        err, out_buf = self.clientauth.authorize(tgt_auth_challenge)

        logger.debug("Context step err: %s", err)
        logger.debug("Context step out_buf: %s", out_buf)
        resp = out_buf[0].Buffer
        logger.debug("Context step resp: %s", resp)
        logger.debug("Context completed?: %s", self.clientauth.authenticated)

        return resp, self.clientauth.authenticated

virt/lib/python3.9/site-packages/mysql/connector/plugins/authentication_kerberos_client 3.py
PrinceDisant/LostInNL
# Copyright (c) 2022, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

# mypy: disable-error-code="str-bytes-safe,misc"

"""Kerberos Authentication Plugin."""

import getpass
import os
import struct

from pathlib import Path
from typing import Any, Optional, Tuple

from ..errors import InterfaceError, ProgrammingError
from ..logger import logger

try:
    import gssapi
except ImportError:
    gssapi = None
    if os.name != "nt":
        raise ProgrammingError(
            "Module gssapi is required for GSSAPI authentication "
            "mechanism but was not found. Unable to authenticate "
            "with the server"
        ) from None

try:
    import sspi
    import sspicon
except ImportError:
    sspi = None
    sspicon = None

from . import BaseAuthPlugin

AUTHENTICATION_PLUGIN_CLASS = (
    "MySQLSSPIKerberosAuthPlugin" if os.name == "nt" else "MySQLKerberosAuthPlugin"
)


# pylint: disable=c-extension-no-member,no-member
class MySQLKerberosAuthPlugin(BaseAuthPlugin):
    """Implement the MySQL Kerberos authentication plugin."""

    plugin_name: str = "authentication_kerberos_client"
    requires_ssl: bool = False
    context: Optional[gssapi.SecurityContext] = None

    @staticmethod
    def get_user_from_credentials() -> str:
        """Get user from credentials without realm."""
        try:
            creds = gssapi.Credentials(usage="initiate")
            user = str(creds.name)
            if user.find("@") != -1:
                user, _ = user.split("@", 1)
            return user
        except gssapi.raw.misc.GSSError:
            return getpass.getuser()

    @staticmethod
    def get_store() -> dict:
        """Get a credentials store dictionary.

        Returns:
            dict: Credentials store dictionary with the krb5 ccache name.

        Raises:
            InterfaceError: If 'KRB5CCNAME' environment variable is empty.
        """
        krb5ccname = os.environ.get(
            "KRB5CCNAME",
            f"/tmp/krb5cc_{os.getuid()}"
            if os.name == "posix"
            else Path("%TEMP%").joinpath("krb5cc"),
        )
        if not krb5ccname:
            raise InterfaceError(
                "The 'KRB5CCNAME' environment variable is set to empty"
            )
        logger.debug("Using krb5 ccache name: FILE:%s", krb5ccname)
        store = {b"ccache": f"FILE:{krb5ccname}".encode("utf-8")}
        return store

    def _acquire_cred_with_password(self, upn: str) -> gssapi.raw.creds.Creds:
        """Acquire and store credentials through provided password.

        Args:
            upn (str): User Principal Name.

        Returns:
            gssapi.raw.creds.Creds: GSSAPI credentials.
        """
        logger.debug("Attempt to acquire credentials through provided password")
        user = gssapi.Name(upn, gssapi.NameType.user)
        password = self._password.encode("utf-8")

        try:
            acquire_cred_result = gssapi.raw.acquire_cred_with_password(
                user, password, usage="initiate"
            )
            creds = acquire_cred_result.creds
            gssapi.raw.store_cred_into(
                self.get_store(),
                creds=creds,
                mech=gssapi.MechType.kerberos,
                overwrite=True,
                set_default=True,
            )
        except gssapi.raw.misc.GSSError as err:
            raise ProgrammingError(
                f"Unable to acquire credentials with the given password: {err}"
            ) from err
        return creds

    @staticmethod
    def _parse_auth_data(packet: bytes) -> Tuple[str, str]:
        """Parse authentication data.

        Get the SPN and REALM from the authentication data packet.

        Format:
            SPN string length two bytes <B1> <B2> +
            SPN string +
            UPN realm string length two bytes <B1> <B2> +
            UPN realm string

        Returns:
            tuple: With 'spn' and 'realm'.
        """
        spn_len = struct.unpack("<H", packet[:2])[0]
        packet = packet[2:]

        spn = struct.unpack(f"<{spn_len}s", packet[:spn_len])[0]
        packet = packet[spn_len:]

        realm_len = struct.unpack("<H", packet[:2])[0]
        realm = struct.unpack(f"<{realm_len}s", packet[2:])[0]

        return spn.decode(), realm.decode()

    def auth_response(self, auth_data: Optional[bytes] = None) -> Optional[bytes]:
        """Prepare the first message to the server."""
        spn = None
        realm = None

        if auth_data:
            try:
                spn, realm = self._parse_auth_data(auth_data)
            except struct.error as err:
                raise InterruptedError(f"Invalid authentication data: {err}") from err

        if spn is None:
            return self.prepare_password()

        upn = f"{self._username}@{realm}" if self._username else None

        logger.debug("Service Principal: %s", spn)
        logger.debug("Realm: %s", realm)

        try:
            # Attempt to retrieve credentials from cache file
            creds: Any = gssapi.Credentials(usage="initiate")
            creds_upn = str(creds.name)

            logger.debug("Cached credentials found")
            logger.debug("Cached credentials UPN: %s", creds_upn)

            # Remove the realm from user
            if creds_upn.find("@") != -1:
                creds_user, creds_realm = creds_upn.split("@", 1)
            else:
                creds_user = creds_upn
                creds_realm = None

            upn = f"{self._username}@{realm}" if self._username else creds_upn

            # The user from cached credentials matches with the given user?
            if self._username and self._username != creds_user:
                logger.debug(
                    "The user from cached credentials doesn't match with the "
                    "given user"
                )
                if self._password is not None:
                    creds = self._acquire_cred_with_password(upn)
            if creds_realm and creds_realm != realm and self._password is not None:
                creds = self._acquire_cred_with_password(upn)
        except gssapi.raw.exceptions.ExpiredCredentialsError as err:
            if upn and self._password is not None:
                creds = self._acquire_cred_with_password(upn)
            else:
                raise InterfaceError(f"Credentials has expired: {err}") from err
        except gssapi.raw.misc.GSSError as err:
            if upn and self._password is not None:
                creds = self._acquire_cred_with_password(upn)
            else:
                raise InterfaceError(
                    f"Unable to retrieve cached credentials error: {err}"
                ) from err

        flags = (
            gssapi.RequirementFlag.mutual_authentication,
            gssapi.RequirementFlag.extended_error,
            gssapi.RequirementFlag.delegate_to_peer,
        )
        name = gssapi.Name(spn, name_type=gssapi.NameType.kerberos_principal)
        cname = name.canonicalize(gssapi.MechType.kerberos)
        self.context = gssapi.SecurityContext(
            name=cname, creds=creds, flags=sum(flags), usage="initiate"
        )

        try:
            initial_client_token: Optional[bytes] = self.context.step()
        except gssapi.raw.misc.GSSError as err:
            raise InterfaceError(f"Unable to initiate security context: {err}") from err

        logger.debug("Initial client token: %s", initial_client_token)
        return initial_client_token

    def auth_continue(
        self, tgt_auth_challenge: Optional[bytes]
    ) -> Tuple[Optional[bytes], bool]:
        """Continue with the Kerberos TGT service request.

        With the TGT authentication service given response generate a TGT
        service request. This method must be invoked sequentially (in a loop)
        until the security context is completed and an empty response needs to
        be send to acknowledge the server.

        Args:
            tgt_auth_challenge: the challenge for the negotiation.

        Returns:
            tuple (bytearray TGS service request,
            bool True if context is completed otherwise False).
        """
        logger.debug("tgt_auth challenge: %s", tgt_auth_challenge)

        resp: Optional[bytes] = self.context.step(tgt_auth_challenge)

        logger.debug("Context step response: %s", resp)
        logger.debug("Context completed?: %s", self.context.complete)

        return resp, self.context.complete

    def auth_accept_close_handshake(self, message: bytes) -> bytes:
        """Accept handshake and generate closing handshake message for server.

        This method verifies the server authenticity from the given message
        and included signature and generates the closing handshake for the
        server.

        When this method is invoked the security context is already established
        and the client and server can send GSSAPI formated secure messages.

        To finish the authentication handshake the server sends a message
        with the security layer availability and the maximum buffer size.

        Since the connector only uses the GSSAPI authentication mechanism to
        authenticate the user with the server, the server will verify clients
        message signature and terminate the GSSAPI authentication and send two
        messages; an authentication acceptance b'\x01\x00\x00\x08\x01' and a
        OK packet (that must be received after sent the returned message from
        this method).

        Args:
            message: a wrapped gssapi message from the server.

        Returns:
            bytearray (closing handshake message to be send to the server).
        """
        if not self.context.complete:
            raise ProgrammingError("Security context is not completed")
        logger.debug("Server message: %s", message)
        logger.debug("GSSAPI flags in use: %s", self.context.actual_flags)
        try:
            unwraped = self.context.unwrap(message)
            logger.debug("Unwraped: %s", unwraped)
        except gssapi.raw.exceptions.BadMICError as err:
            logger.debug("Unable to unwrap server message: %s", err)
            raise InterfaceError(f"Unable to unwrap server message: {err}") from err

        logger.debug("Unwrapped server message: %s", unwraped)
        # The message contents for the clients closing message:
        #   - security level 1 byte, must be always 1.
        #   - conciliated buffer size 3 bytes, without importance as no
        #     further GSSAPI messages will be sends.
        response = bytearray(b"\x01\x00\x00\00")
        # Closing handshake must not be encrypted.
        logger.debug("Message response: %s", response)
        wraped = self.context.wrap(response, encrypt=False)
        logger.debug(
            "Wrapped message response: %s, length: %d",
            wraped[0],
            len(wraped[0]),
        )

        return wraped.message


class MySQLSSPIKerberosAuthPlugin(BaseAuthPlugin):
    """Implement the MySQL Kerberos authentication plugin with Windows SSPI"""

    plugin_name: str = "authentication_kerberos_client"
    requires_ssl: bool = False
    context: Any = None
    clientauth: Any = None

    @staticmethod
    def _parse_auth_data(packet: bytes) -> Tuple[str, str]:
        """Parse authentication data.

        Get the SPN and REALM from the authentication data packet.

        Format:
            SPN string length two bytes <B1> <B2> +
            SPN string +
            UPN realm string length two bytes <B1> <B2> +
            UPN realm string

        Returns:
            tuple: With 'spn' and 'realm'.
        """
        spn_len = struct.unpack("<H", packet[:2])[0]
        packet = packet[2:]

        spn = struct.unpack(f"<{spn_len}s", packet[:spn_len])[0]
        packet = packet[spn_len:]

        realm_len = struct.unpack("<H", packet[:2])[0]
        realm = struct.unpack(f"<{realm_len}s", packet[2:])[0]

        return spn.decode(), realm.decode()

    def auth_response(self, auth_data: Optional[bytes] = None) -> Optional[bytes]:
        """Prepare the first message to the server."""
        logger.debug("auth_response for sspi")
        spn = None
        realm = None

        if auth_data:
            try:
                spn, realm = self._parse_auth_data(auth_data)
            except struct.error as err:
                raise InterruptedError(f"Invalid authentication data: {err}") from err

        logger.debug("Service Principal: %s", spn)
        logger.debug("Realm: %s", realm)

        if sspicon is None or sspi is None:
            raise ProgrammingError(
                'Package "pywin32" (Python for Win32 (pywin32) extensions)'
                " is not installed."
            )

        flags = (sspicon.ISC_REQ_MUTUAL_AUTH, sspicon.ISC_REQ_DELEGATE)

        if self._username and self._password:
            _auth_info = (self._username, realm, self._password)
        else:
            _auth_info = None

        targetspn = spn
        logger.debug("targetspn: %s", targetspn)
        logger.debug("_auth_info is None: %s", _auth_info is None)

        # The Security Support Provider Interface (SSPI) is an interface
        # that allows us to choose from a set of SSPs available in the
        # system; the idea of SSPI is to keep interface consistent no
        # matter what back end (a.k.a., SSP) we choose.

        # When using SSPI we should not use Kerberos directly as SSP,
        # as remarked in [2], but we can use it indirectly via another
        # SSP named Negotiate that acts as an application layer between
        # SSPI and the other SSPs [1].

        # Negotiate can select between Kerberos and NTLM on the fly;
        # it chooses Kerberos unless it cannot be used by one of the
        # systems involved in the authentication or the calling
        # application did not provide sufficient information to use
        # Kerberos.

        # prefix: https://docs.microsoft.com/en-us/windows/win32/secauthn
        # [1] prefix/microsoft-negotiate?source=recommendations
        # [2] prefix/microsoft-kerberos?source=recommendations
        self.clientauth = sspi.ClientAuth(
            "Negotiate",
            targetspn=targetspn,
            auth_info=_auth_info,
            scflags=sum(flags),
            datarep=sspicon.SECURITY_NETWORK_DREP,
        )

        try:
            data = None
            err, out_buf = self.clientauth.authorize(data)
            logger.debug("Context step err: %s", err)
            logger.debug("Context step out_buf: %s", out_buf)
            logger.debug("Context completed?: %s", self.clientauth.authenticated)
            initial_client_token = out_buf[0].Buffer
            logger.debug("pkg_info: %s", self.clientauth.pkg_info)
        except Exception as err:
            raise InterfaceError(f"Unable to initiate security context: {err}") from err

        logger.debug("Initial client token: %s", initial_client_token)
        return initial_client_token

    def auth_continue(
        self, tgt_auth_challenge: Optional[bytes]
    ) -> Tuple[Optional[bytes], bool]:
        """Continue with the Kerberos TGT service request.

        With the TGT authentication service given response generate a TGT
        service request. This method must be invoked sequentially (in a loop)
        until the security context is completed and an empty response needs to
        be send to acknowledge the server.

        Args:
            tgt_auth_challenge: the challenge for the negotiation.

        Returns:
            tuple (bytearray TGS service request,
            bool True if context is completed otherwise False).
        """
        logger.debug("tgt_auth challenge: %s", tgt_auth_challenge)

        err, out_buf = self.clientauth.authorize(tgt_auth_challenge)

        logger.debug("Context step err: %s", err)
        logger.debug("Context step out_buf: %s", out_buf)
        resp = out_buf[0].Buffer
        logger.debug("Context step resp: %s", resp)
        logger.debug("Context completed?: %s", self.clientauth.authenticated)

        return resp, self.clientauth.authenticated

virt/lib/python3.9/site-packages/mysql/connector/plugins/authentication_kerberos_client.py
PrinceDisant/LostInNL
# Copyright (c) 2022, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

"""LDAP SASL Authentication Plugin."""

import hmac

from base64 import b64decode, b64encode
from hashlib import sha1, sha256
from typing import Any, Callable, List, Optional, Tuple
from uuid import uuid4

from ..errors import InterfaceError, ProgrammingError
from ..logger import logger
from ..types import StrOrBytes

try:
    import gssapi
except ImportError:
    raise ProgrammingError(
        "Module gssapi is required for GSSAPI authentication "
        "mechanism but was not found. Unable to authenticate "
        "with the server"
    ) from None

from ..utils import (
    normalize_unicode_string as norm_ustr,
    validate_normalized_unicode_string as valid_norm,
)
from . import BaseAuthPlugin

AUTHENTICATION_PLUGIN_CLASS = "MySQLLdapSaslPasswordAuthPlugin"


# pylint: disable=c-extension-no-member,no-member
class MySQLLdapSaslPasswordAuthPlugin(BaseAuthPlugin):
    """Class implementing the MySQL ldap sasl authentication plugin.

    The MySQL's ldap sasl authentication plugin support two authentication
    methods SCRAM-SHA-1 and GSSAPI (using Kerberos). This implementation only
    support SCRAM-SHA-1 and SCRAM-SHA-256.

    SCRAM-SHA-1 amd SCRAM-SHA-256
        This method requires 2 messages from client and 2 responses from
        server.

        The first message from client will be generated by prepare_password(),
        after receive the response from the server, it is required that this
        response is passed back to auth_continue() which will return the
        second message from the client. After send this second message to the
        server, the second server respond needs to be passed to auth_finalize()
        to finish the authentication process.
    """

    sasl_mechanisms: List[str] = ["SCRAM-SHA-1", "SCRAM-SHA-256", "GSSAPI"]
    requires_ssl: bool = False
    plugin_name: str = "authentication_ldap_sasl_client"
    def_digest_mode: Callable = sha1
    client_nonce: Optional[str] = None
    client_salt: Any = None
    server_salt: Optional[str] = None
    krb_service_principal: Optional[StrOrBytes] = None
    iterations: int = 0
    server_auth_var: Optional[str] = None
    target_name: Optional[gssapi.Name] = None
    ctx: gssapi.SecurityContext = None
    servers_first: Optional[str] = None
    server_nonce: Optional[str] = None

    @staticmethod
    def _xor(bytes1: bytes, bytes2: bytes) -> bytes:
        return bytes([b1 ^ b2 for b1, b2 in zip(bytes1, bytes2)])

    def _hmac(self, password: bytes, salt: bytes) -> bytes:
        digest_maker = hmac.new(password, salt, self.def_digest_mode)
        return digest_maker.digest()

    def _hi(self, password: str, salt: bytes, count: int) -> bytes:
        """Prepares Hi
        Hi(password, salt, iterations) where Hi(p,s,i) is defined as
        PBKDF2 (HMAC, p, s, i, output length of H).
        """
        pw = password.encode()
        hi = self._hmac(pw, salt + b"\x00\x00\x00\x01")
        aux = hi
        for _ in range(count - 1):
            aux = self._hmac(pw, aux)
            hi = self._xor(hi, aux)
        return hi

    @staticmethod
    def _normalize(string: str) -> str:
        norm_str = norm_ustr(string)
        broken_rule = valid_norm(norm_str)
        if broken_rule is not None:
            raise InterfaceError(f"broken_rule: {broken_rule}")
        return norm_str

    def _first_message(self) -> bytes:
        """This method generates the first message to the server to start the

        The client-first message consists of a gs2-header,
        the desired username, and a randomly generated client nonce cnonce.

        The first message from the server has the form:
            b'n,a=<user_name>,n=<user_name>,r=<client_nonce>

        Returns client's first message
        """
        cfm_fprnat = "n,a={user_name},n={user_name},r={client_nonce}"
        self.client_nonce = str(uuid4()).replace("-", "")
        cfm: StrOrBytes = cfm_fprnat.format(
            user_name=self._normalize(self._username),
            client_nonce=self.client_nonce,
        )

        if isinstance(cfm, str):
            cfm = cfm.encode("utf8")
        return cfm

    def _first_message_krb(self) -> Optional[bytes]:
        """Get a TGT Authentication request and initiates security context.

        This method will contact the Kerberos KDC in order of obtain a TGT.
        """
        user_name = gssapi.raw.names.import_name(
            self._username.encode("utf8"), name_type=gssapi.NameType.user
        )

        # Use defaults store = {'ccache': 'FILE:/tmp/krb5cc_1000'}#,
        #                       'keytab':'/etc/some.keytab' }
        # Attempt to retrieve credential from default cache file.
        try:
            cred: Any = gssapi.Credentials()
            logger.debug(
                "# Stored credentials found, if password was given it will be ignored."
            )
            try:
                # validate credentials has not expired.
                cred.lifetime
            except gssapi.raw.exceptions.ExpiredCredentialsError as err:
                logger.warning(" Credentials has expired: %s", err)
                cred.acquire(user_name)
                raise InterfaceError(f"Credentials has expired: {err}") from err
        except gssapi.raw.misc.GSSError as err:
            if not self._password:
                raise InterfaceError(
                    f"Unable to retrieve stored credentials error: {err}"
                ) from err
            try:
                logger.debug("# Attempt to retrieve credentials with given password")
                acquire_cred_result = gssapi.raw.acquire_cred_with_password(
                    user_name,
                    self._password.encode("utf8"),
                    usage="initiate",
                )
                cred = acquire_cred_result[0]
            except gssapi.raw.misc.GSSError as err2:
                raise ProgrammingError(
                    f"Unable to retrieve credentials with the given password: {err2}"
                ) from err

        flags_l = (
            gssapi.RequirementFlag.mutual_authentication,
            gssapi.RequirementFlag.extended_error,
            gssapi.RequirementFlag.delegate_to_peer,
        )

        if self.krb_service_principal:
            service_principal = self.krb_service_principal
        else:
            service_principal = "ldap/ldapauth"
        logger.debug("# service principal: %s", service_principal)
        servk = gssapi.Name(
            service_principal, name_type=gssapi.NameType.kerberos_principal
        )
        self.target_name = servk
        self.ctx = gssapi.SecurityContext(
            name=servk, creds=cred, flags=sum(flags_l), usage="initiate"
        )

        try:
            # step() returns bytes | None, see documentation,
            # so this method could return a NULL payload.
            # ref: https://pythongssapi.github.io/<suffix>
            # suffix: python-gssapi/latest/gssapi.html#gssapi.sec_contexts.SecurityContext
            initial_client_token = self.ctx.step()
        except gssapi.raw.misc.GSSError as err:
            raise InterfaceError(f"Unable to initiate security context: {err}") from err

        logger.debug("# initial client token: %s", initial_client_token)
        return initial_client_token

    def auth_continue_krb(
        self, tgt_auth_challenge: Optional[bytes]
    ) -> Tuple[Optional[bytes], bool]:
        """Continue with the Kerberos TGT service request.

        With the TGT authentication service given response generate a TGT
        service request. This method must be invoked sequentially (in a loop)
        until the security context is completed and an empty response needs to
        be send to acknowledge the server.

        Args:
            tgt_auth_challenge the challenge for the negotiation.

        Returns: tuple (bytearray TGS service request,
                        bool True if context is completed otherwise False).
        """
        logger.debug("tgt_auth challenge: %s", tgt_auth_challenge)

        resp = self.ctx.step(tgt_auth_challenge)
        logger.debug("# context step response: %s", resp)
        logger.debug("# context completed?: %s", self.ctx.complete)

        return resp, self.ctx.complete

    def auth_accept_close_handshake(self, message: bytes) -> bytes:
        """Accept handshake and generate closing handshake message for server.

        This method verifies the server authenticity from the given message
        and included signature and generates the closing handshake for the
        server.

        When this method is invoked the security context is already established
        and the client and server can send GSSAPI formated secure messages.

        To finish the authentication handshake the server sends a message
        with the security layer availability and the maximum buffer size.

        Since the connector only uses the GSSAPI authentication mechanism to
        authenticate the user with the server, the server will verify clients
        message signature and terminate the GSSAPI authentication and send two
        messages; an authentication acceptance b'\x01\x00\x00\x08\x01' and a
        OK packet (that must be received after sent the returned message from
        this method).

        Args:
            message a wrapped hssapi message from the server.

        Returns: bytearray closing handshake message to be send to the server.
        """
        if not self.ctx.complete:
            raise ProgrammingError("Security context is not completed.")
        logger.debug("# servers message: %s", message)
        logger.debug("# GSSAPI flags in use: %s", self.ctx.actual_flags)
        try:
            unwraped = self.ctx.unwrap(message)
            logger.debug("# unwraped: %s", unwraped)
        except gssapi.raw.exceptions.BadMICError as err:
            raise InterfaceError(f"Unable to unwrap server message: {err}") from err

        logger.debug("# unwrapped server message: %s", unwraped)
        # The message contents for the clients closing message:
        #   - security level 1 byte, must be always 1.
        #   - conciliated buffer size 3 bytes, without importance as no
        #     further GSSAPI messages will be sends.
        response = bytearray(b"\x01\x00\x00\00")
        # Closing handshake must not be encrypted.
        logger.debug("# message response: %s", response)
        wraped = self.ctx.wrap(response, encrypt=False)
        logger.debug(
            "# wrapped message response: %s, length: %d",
            wraped[0],
            len(wraped[0]),
        )

        return wraped.message

    def auth_response(  # type: ignore[override]
        self,
        auth_data: Optional[str] = None,
    ) -> Optional[bytes]:
        """This method will prepare the fist message to the server.

        Returns bytes to send to the server as the first message.
        """
        krb_service_principal = auth_data
        auth_mechanism = self._auth_data.decode()
        self.krb_service_principal = krb_service_principal
        logger.debug("read_method_name_from_server: %s", auth_mechanism)
        if auth_mechanism not in self.sasl_mechanisms:
            auth_mechanisms = '", "'.join(self.sasl_mechanisms[:-1])
            raise InterfaceError(
                f'The sasl authentication method "{auth_mechanism}" requested '
                f'from the server is not supported. Only "{auth_mechanisms}" '
                f'and "{self.sasl_mechanisms[-1]}" are supported'
            )

        if b"GSSAPI" in self._auth_data:
            return self._first_message_krb()

        if self._auth_data == b"SCRAM-SHA-256":
            self.def_digest_mode = sha256

        return self._first_message()

    def _second_message(self) -> bytes:
        """This method generates the second message to the server

        Second message consist on the concatenation of the client and the
        server nonce, and cproof.

        c=<n,a=<user_name>>,r=<server_nonce>,p=<client_proof>
        where:
            <client_proof>: xor(<client_key>, <client_signature>)

            <client_key>: hmac(salted_password, b"Client Key")
            <client_signature>: hmac(<stored_key>, <auth_msg>)
            <stored_key>: h(<client_key>)
            <auth_msg>: <client_first_no_header>,<servers_first>,
                        c=<client_header>,r=<server_nonce>
            <client_first_no_header>: n=<username>r=<client_nonce>
        """
        if not self._auth_data:
            raise InterfaceError("Missing authentication data (seed)")

        passw = self._normalize(self._password)
        salted_password = self._hi(passw, b64decode(self.server_salt), self.iterations)
        logger.debug("salted_password: %s", b64encode(salted_password).decode())

        client_key = self._hmac(salted_password, b"Client Key")
        logger.debug("client_key: %s", b64encode(client_key).decode())

        stored_key = self.def_digest_mode(client_key).digest()
        logger.debug("stored_key: %s", b64encode(stored_key).decode())

        server_key = self._hmac(salted_password, b"Server Key")
        logger.debug("server_key: %s", b64encode(server_key).decode())

        client_first_no_header = ",".join(
            [
                f"n={self._normalize(self._username)}",
                f"r={self.client_nonce}",
            ]
        )
        logger.debug("client_first_no_header: %s", client_first_no_header)

        client_header = b64encode(
            f"n,a={self._normalize(self._username)},".encode()
        ).decode()

        auth_msg = ",".join(
            [
                client_first_no_header,
                self.servers_first,
                f"c={client_header}",
                f"r={self.server_nonce}",
            ]
        )
        logger.debug("auth_msg: %s", auth_msg)

        client_signature = self._hmac(stored_key, auth_msg.encode())
        logger.debug("client_signature: %s", b64encode(client_signature).decode())

        client_proof = self._xor(client_key, client_signature)
        logger.debug("client_proof: %s", b64encode(client_proof).decode())

        self.server_auth_var = b64encode(
            self._hmac(server_key, auth_msg.encode())
        ).decode()
        logger.debug("server_auth_var: %s", self.server_auth_var)

        msg = ",".join(
            [
                f"c={client_header}",
                f"r={self.server_nonce}",
                f"p={b64encode(client_proof).decode()}",
            ]
        )
        logger.debug("second_message: %s", msg)
        return msg.encode()

    def _validate_first_reponse(self, servers_first: bytes) -> None:
        """Validates first message from the server.

        Extracts the server's salt and iterations from the servers 1st response.
        First message from the server is in the form:
            <server_salt>,i=<iterations>
        """
        if not servers_first or not isinstance(servers_first, (bytearray, bytes)):
            raise InterfaceError(f"Unexpected server message: {repr(servers_first)}")
        try:
            servers_first_str = servers_first.decode()
            self.servers_first = servers_first_str
            r_server_nonce, s_salt, i_counter = servers_first_str.split(",")
        except ValueError:
            raise InterfaceError(
                f"Unexpected server message: {servers_first_str}"
            ) from None
        if (
            not r_server_nonce.startswith("r=")
            or not s_salt.startswith("s=")
            or not i_counter.startswith("i=")
        ):
            raise InterfaceError(
                f"Incomplete reponse from the server: {servers_first_str}"
            )
        if self.client_nonce in r_server_nonce:
            self.server_nonce = r_server_nonce[2:]
            logger.debug("server_nonce: %s", self.server_nonce)
        else:
            raise InterfaceError(
                "Unable to authenticate response: response not well formed "
                f"{servers_first_str}"
            )
        self.server_salt = s_salt[2:]
        logger.debug(
            "server_salt: %s length: %s",
            self.server_salt,
            len(self.server_salt),
        )
        try:
            i_counter = i_counter[2:]
            logger.debug("iterations: %s", i_counter)
            self.iterations = int(i_counter)
        except Exception as err:
            raise InterfaceError(
                f"Unable to authenticate: iterations not found {servers_first_str}"
            ) from err

    def auth_continue(self, servers_first_response: bytes) -> bytes:
        """return the second message from the client.

        Returns bytes to send to the server as the second message.
        """
        self._validate_first_reponse(servers_first_response)
        return self._second_message()

    def _validate_second_reponse(self, servers_second: bytearray) -> bool:
        """Validates second message from the server.

        The client and the server prove to each other they have the same Auth
        variable.

        The second message from the server consist of the server's proof:
            server_proof = HMAC(<server_key>, <auth_msg>)
            where:
                <server_key>: hmac(<salted_password>, b"Server Key")
                <auth_msg>: <client_first_no_header>,<servers_first>,
                            c=<client_header>,r=<server_nonce>

        Our server_proof must be equal to the Auth variable send on this second
        response.
        """
        if (
            not servers_second
            or not isinstance(servers_second, bytearray)
            or len(servers_second) <= 2
            or not servers_second.startswith(b"v=")
        ):
            raise InterfaceError("The server's proof is not well formated")
        server_var = servers_second[2:].decode()
        logger.debug("server auth variable: %s", server_var)
        return self.server_auth_var == server_var

    def auth_finalize(self, servers_second_response: bytearray) -> bool:
        """finalize the authentication process.

        Raises InterfaceError if the ervers_second_response is invalid.

        Returns True in successful authentication False otherwise.
        """
        if not self._validate_second_reponse(servers_second_response):
            raise InterfaceError(
                "Authentication failed: Unable to proof server identity"
            )
        return True


# pylint: enable=c-extension-no-member,no-member

virt/lib/python3.9/site-packages/mysql/connector/plugins/authentication_ldap_sasl_client 2.py
PrinceDisant/LostInNL
# Copyright (c) 2022, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

"""LDAP SASL Authentication Plugin."""

import hmac

from base64 import b64decode, b64encode
from hashlib import sha1, sha256
from typing import Any, Callable, List, Optional, Tuple
from uuid import uuid4

from ..errors import InterfaceError, ProgrammingError
from ..logger import logger
from ..types import StrOrBytes

try:
    import gssapi
except ImportError:
    raise ProgrammingError(
        "Module gssapi is required for GSSAPI authentication "
        "mechanism but was not found. Unable to authenticate "
        "with the server"
    ) from None

from ..utils import (
    normalize_unicode_string as norm_ustr,
    validate_normalized_unicode_string as valid_norm,
)
from . import BaseAuthPlugin

AUTHENTICATION_PLUGIN_CLASS = "MySQLLdapSaslPasswordAuthPlugin"


# pylint: disable=c-extension-no-member,no-member
class MySQLLdapSaslPasswordAuthPlugin(BaseAuthPlugin):
    """Class implementing the MySQL ldap sasl authentication plugin.

    The MySQL's ldap sasl authentication plugin support two authentication
    methods SCRAM-SHA-1 and GSSAPI (using Kerberos). This implementation only
    support SCRAM-SHA-1 and SCRAM-SHA-256.

    SCRAM-SHA-1 amd SCRAM-SHA-256
        This method requires 2 messages from client and 2 responses from
        server.

        The first message from client will be generated by prepare_password(),
        after receive the response from the server, it is required that this
        response is passed back to auth_continue() which will return the
        second message from the client. After send this second message to the
        server, the second server respond needs to be passed to auth_finalize()
        to finish the authentication process.
    """

    sasl_mechanisms: List[str] = ["SCRAM-SHA-1", "SCRAM-SHA-256", "GSSAPI"]
    requires_ssl: bool = False
    plugin_name: str = "authentication_ldap_sasl_client"
    def_digest_mode: Callable = sha1
    client_nonce: Optional[str] = None
    client_salt: Any = None
    server_salt: Optional[str] = None
    krb_service_principal: Optional[StrOrBytes] = None
    iterations: int = 0
    server_auth_var: Optional[str] = None
    target_name: Optional[gssapi.Name] = None
    ctx: gssapi.SecurityContext = None
    servers_first: Optional[str] = None
    server_nonce: Optional[str] = None

    @staticmethod
    def _xor(bytes1: bytes, bytes2: bytes) -> bytes:
        return bytes([b1 ^ b2 for b1, b2 in zip(bytes1, bytes2)])

    def _hmac(self, password: bytes, salt: bytes) -> bytes:
        digest_maker = hmac.new(password, salt, self.def_digest_mode)
        return digest_maker.digest()

    def _hi(self, password: str, salt: bytes, count: int) -> bytes:
        """Prepares Hi
        Hi(password, salt, iterations) where Hi(p,s,i) is defined as
        PBKDF2 (HMAC, p, s, i, output length of H).
        """
        pw = password.encode()
        hi = self._hmac(pw, salt + b"\x00\x00\x00\x01")
        aux = hi
        for _ in range(count - 1):
            aux = self._hmac(pw, aux)
            hi = self._xor(hi, aux)
        return hi

    @staticmethod
    def _normalize(string: str) -> str:
        norm_str = norm_ustr(string)
        broken_rule = valid_norm(norm_str)
        if broken_rule is not None:
            raise InterfaceError(f"broken_rule: {broken_rule}")
        return norm_str

    def _first_message(self) -> bytes:
        """This method generates the first message to the server to start the

        The client-first message consists of a gs2-header,
        the desired username, and a randomly generated client nonce cnonce.

        The first message from the server has the form:
            b'n,a=<user_name>,n=<user_name>,r=<client_nonce>

        Returns client's first message
        """
        cfm_fprnat = "n,a={user_name},n={user_name},r={client_nonce}"
        self.client_nonce = str(uuid4()).replace("-", "")
        cfm: StrOrBytes = cfm_fprnat.format(
            user_name=self._normalize(self._username),
            client_nonce=self.client_nonce,
        )

        if isinstance(cfm, str):
            cfm = cfm.encode("utf8")
        return cfm

    def _first_message_krb(self) -> Optional[bytes]:
        """Get a TGT Authentication request and initiates security context.

        This method will contact the Kerberos KDC in order of obtain a TGT.
        """
        user_name = gssapi.raw.names.import_name(
            self._username.encode("utf8"), name_type=gssapi.NameType.user
        )

        # Use defaults store = {'ccache': 'FILE:/tmp/krb5cc_1000'}#,
        #                       'keytab':'/etc/some.keytab' }
        # Attempt to retrieve credential from default cache file.
        try:
            cred: Any = gssapi.Credentials()
            logger.debug(
                "# Stored credentials found, if password was given it will be ignored."
            )
            try:
                # validate credentials has not expired.
                cred.lifetime
            except gssapi.raw.exceptions.ExpiredCredentialsError as err:
                logger.warning(" Credentials has expired: %s", err)
                cred.acquire(user_name)
                raise InterfaceError(f"Credentials has expired: {err}") from err
        except gssapi.raw.misc.GSSError as err:
            if not self._password:
                raise InterfaceError(
                    f"Unable to retrieve stored credentials error: {err}"
                ) from err
            try:
                logger.debug("# Attempt to retrieve credentials with given password")
                acquire_cred_result = gssapi.raw.acquire_cred_with_password(
                    user_name,
                    self._password.encode("utf8"),
                    usage="initiate",
                )
                cred = acquire_cred_result[0]
            except gssapi.raw.misc.GSSError as err2:
                raise ProgrammingError(
                    f"Unable to retrieve credentials with the given password: {err2}"
                ) from err

        flags_l = (
            gssapi.RequirementFlag.mutual_authentication,
            gssapi.RequirementFlag.extended_error,
            gssapi.RequirementFlag.delegate_to_peer,
        )

        if self.krb_service_principal:
            service_principal = self.krb_service_principal
        else:
            service_principal = "ldap/ldapauth"
        logger.debug("# service principal: %s", service_principal)
        servk = gssapi.Name(
            service_principal, name_type=gssapi.NameType.kerberos_principal
        )
        self.target_name = servk
        self.ctx = gssapi.SecurityContext(
            name=servk, creds=cred, flags=sum(flags_l), usage="initiate"
        )

        try:
            # step() returns bytes | None, see documentation,
            # so this method could return a NULL payload.
            # ref: https://pythongssapi.github.io/<suffix>
            # suffix: python-gssapi/latest/gssapi.html#gssapi.sec_contexts.SecurityContext
            initial_client_token = self.ctx.step()
        except gssapi.raw.misc.GSSError as err:
            raise InterfaceError(f"Unable to initiate security context: {err}") from err

        logger.debug("# initial client token: %s", initial_client_token)
        return initial_client_token

    def auth_continue_krb(
        self, tgt_auth_challenge: Optional[bytes]
    ) -> Tuple[Optional[bytes], bool]:
        """Continue with the Kerberos TGT service request.

        With the TGT authentication service given response generate a TGT
        service request. This method must be invoked sequentially (in a loop)
        until the security context is completed and an empty response needs to
        be send to acknowledge the server.

        Args:
            tgt_auth_challenge the challenge for the negotiation.

        Returns: tuple (bytearray TGS service request,
                        bool True if context is completed otherwise False).
        """
        logger.debug("tgt_auth challenge: %s", tgt_auth_challenge)

        resp = self.ctx.step(tgt_auth_challenge)
        logger.debug("# context step response: %s", resp)
        logger.debug("# context completed?: %s", self.ctx.complete)

        return resp, self.ctx.complete

    def auth_accept_close_handshake(self, message: bytes) -> bytes:
        """Accept handshake and generate closing handshake message for server.

        This method verifies the server authenticity from the given message
        and included signature and generates the closing handshake for the
        server.

        When this method is invoked the security context is already established
        and the client and server can send GSSAPI formated secure messages.

        To finish the authentication handshake the server sends a message
        with the security layer availability and the maximum buffer size.

        Since the connector only uses the GSSAPI authentication mechanism to
        authenticate the user with the server, the server will verify clients
        message signature and terminate the GSSAPI authentication and send two
        messages; an authentication acceptance b'\x01\x00\x00\x08\x01' and a
        OK packet (that must be received after sent the returned message from
        this method).

        Args:
            message a wrapped hssapi message from the server.

        Returns: bytearray closing handshake message to be send to the server.
        """
        if not self.ctx.complete:
            raise ProgrammingError("Security context is not completed.")
        logger.debug("# servers message: %s", message)
        logger.debug("# GSSAPI flags in use: %s", self.ctx.actual_flags)
        try:
            unwraped = self.ctx.unwrap(message)
            logger.debug("# unwraped: %s", unwraped)
        except gssapi.raw.exceptions.BadMICError as err:
            raise InterfaceError(f"Unable to unwrap server message: {err}") from err

        logger.debug("# unwrapped server message: %s", unwraped)
        # The message contents for the clients closing message:
        #   - security level 1 byte, must be always 1.
        #   - conciliated buffer size 3 bytes, without importance as no
        #     further GSSAPI messages will be sends.
        response = bytearray(b"\x01\x00\x00\00")
        # Closing handshake must not be encrypted.
        logger.debug("# message response: %s", response)
        wraped = self.ctx.wrap(response, encrypt=False)
        logger.debug(
            "# wrapped message response: %s, length: %d",
            wraped[0],
            len(wraped[0]),
        )

        return wraped.message

    def auth_response(  # type: ignore[override]
        self,
        auth_data: Optional[str] = None,
    ) -> Optional[bytes]:
        """This method will prepare the fist message to the server.

        Returns bytes to send to the server as the first message.
        """
        krb_service_principal = auth_data
        auth_mechanism = self._auth_data.decode()
        self.krb_service_principal = krb_service_principal
        logger.debug("read_method_name_from_server: %s", auth_mechanism)
        if auth_mechanism not in self.sasl_mechanisms:
            auth_mechanisms = '", "'.join(self.sasl_mechanisms[:-1])
            raise InterfaceError(
                f'The sasl authentication method "{auth_mechanism}" requested '
                f'from the server is not supported. Only "{auth_mechanisms}" '
                f'and "{self.sasl_mechanisms[-1]}" are supported'
            )

        if b"GSSAPI" in self._auth_data:
            return self._first_message_krb()

        if self._auth_data == b"SCRAM-SHA-256":
            self.def_digest_mode = sha256

        return self._first_message()

    def _second_message(self) -> bytes:
        """This method generates the second message to the server

        Second message consist on the concatenation of the client and the
        server nonce, and cproof.

        c=<n,a=<user_name>>,r=<server_nonce>,p=<client_proof>
        where:
            <client_proof>: xor(<client_key>, <client_signature>)

            <client_key>: hmac(salted_password, b"Client Key")
            <client_signature>: hmac(<stored_key>, <auth_msg>)
            <stored_key>: h(<client_key>)
            <auth_msg>: <client_first_no_header>,<servers_first>,
                        c=<client_header>,r=<server_nonce>
            <client_first_no_header>: n=<username>r=<client_nonce>
        """
        if not self._auth_data:
            raise InterfaceError("Missing authentication data (seed)")

        passw = self._normalize(self._password)
        salted_password = self._hi(passw, b64decode(self.server_salt), self.iterations)
        logger.debug("salted_password: %s", b64encode(salted_password).decode())

        client_key = self._hmac(salted_password, b"Client Key")
        logger.debug("client_key: %s", b64encode(client_key).decode())

        stored_key = self.def_digest_mode(client_key).digest()
        logger.debug("stored_key: %s", b64encode(stored_key).decode())

        server_key = self._hmac(salted_password, b"Server Key")
        logger.debug("server_key: %s", b64encode(server_key).decode())

        client_first_no_header = ",".join(
            [
                f"n={self._normalize(self._username)}",
                f"r={self.client_nonce}",
            ]
        )
        logger.debug("client_first_no_header: %s", client_first_no_header)

        client_header = b64encode(
            f"n,a={self._normalize(self._username)},".encode()
        ).decode()

        auth_msg = ",".join(
            [
                client_first_no_header,
                self.servers_first,
                f"c={client_header}",
                f"r={self.server_nonce}",
            ]
        )
        logger.debug("auth_msg: %s", auth_msg)

        client_signature = self._hmac(stored_key, auth_msg.encode())
        logger.debug("client_signature: %s", b64encode(client_signature).decode())

        client_proof = self._xor(client_key, client_signature)
        logger.debug("client_proof: %s", b64encode(client_proof).decode())

        self.server_auth_var = b64encode(
            self._hmac(server_key, auth_msg.encode())
        ).decode()
        logger.debug("server_auth_var: %s", self.server_auth_var)

        msg = ",".join(
            [
                f"c={client_header}",
                f"r={self.server_nonce}",
                f"p={b64encode(client_proof).decode()}",
            ]
        )
        logger.debug("second_message: %s", msg)
        return msg.encode()

    def _validate_first_reponse(self, servers_first: bytes) -> None:
        """Validates first message from the server.

        Extracts the server's salt and iterations from the servers 1st response.
        First message from the server is in the form:
            <server_salt>,i=<iterations>
        """
        if not servers_first or not isinstance(servers_first, (bytearray, bytes)):
            raise InterfaceError(f"Unexpected server message: {repr(servers_first)}")
        try:
            servers_first_str = servers_first.decode()
            self.servers_first = servers_first_str
            r_server_nonce, s_salt, i_counter = servers_first_str.split(",")
        except ValueError:
            raise InterfaceError(
                f"Unexpected server message: {servers_first_str}"
            ) from None
        if (
            not r_server_nonce.startswith("r=")
            or not s_salt.startswith("s=")
            or not i_counter.startswith("i=")
        ):
            raise InterfaceError(
                f"Incomplete reponse from the server: {servers_first_str}"
            )
        if self.client_nonce in r_server_nonce:
            self.server_nonce = r_server_nonce[2:]
            logger.debug("server_nonce: %s", self.server_nonce)
        else:
            raise InterfaceError(
                "Unable to authenticate response: response not well formed "
                f"{servers_first_str}"
            )
        self.server_salt = s_salt[2:]
        logger.debug(
            "server_salt: %s length: %s",
            self.server_salt,
            len(self.server_salt),
        )
        try:
            i_counter = i_counter[2:]
            logger.debug("iterations: %s", i_counter)
            self.iterations = int(i_counter)
        except Exception as err:
            raise InterfaceError(
                f"Unable to authenticate: iterations not found {servers_first_str}"
            ) from err

    def auth_continue(self, servers_first_response: bytes) -> bytes:
        """return the second message from the client.

        Returns bytes to send to the server as the second message.
        """
        self._validate_first_reponse(servers_first_response)
        return self._second_message()

    def _validate_second_reponse(self, servers_second: bytearray) -> bool:
        """Validates second message from the server.

        The client and the server prove to each other they have the same Auth
        variable.

        The second message from the server consist of the server's proof:
            server_proof = HMAC(<server_key>, <auth_msg>)
            where:
                <server_key>: hmac(<salted_password>, b"Server Key")
                <auth_msg>: <client_first_no_header>,<servers_first>,
                            c=<client_header>,r=<server_nonce>

        Our server_proof must be equal to the Auth variable send on this second
        response.
        """
        if (
            not servers_second
            or not isinstance(servers_second, bytearray)
            or len(servers_second) <= 2
            or not servers_second.startswith(b"v=")
        ):
            raise InterfaceError("The server's proof is not well formated")
        server_var = servers_second[2:].decode()
        logger.debug("server auth variable: %s", server_var)
        return self.server_auth_var == server_var

    def auth_finalize(self, servers_second_response: bytearray) -> bool:
        """finalize the authentication process.

        Raises InterfaceError if the ervers_second_response is invalid.

        Returns True in successful authentication False otherwise.
        """
        if not self._validate_second_reponse(servers_second_response):
            raise InterfaceError(
                "Authentication failed: Unable to proof server identity"
            )
        return True


# pylint: enable=c-extension-no-member,no-member

virt/lib/python3.9/site-packages/mysql/connector/plugins/authentication_ldap_sasl_client 3.py
PrinceDisant/LostInNL
# Copyright (c) 2022, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

"""LDAP SASL Authentication Plugin."""

import hmac

from base64 import b64decode, b64encode
from hashlib import sha1, sha256
from typing import Any, Callable, List, Optional, Tuple
from uuid import uuid4

from ..errors import InterfaceError, ProgrammingError
from ..logger import logger
from ..types import StrOrBytes

try:
    import gssapi
except ImportError:
    raise ProgrammingError(
        "Module gssapi is required for GSSAPI authentication "
        "mechanism but was not found. Unable to authenticate "
        "with the server"
    ) from None

from ..utils import (
    normalize_unicode_string as norm_ustr,
    validate_normalized_unicode_string as valid_norm,
)
from . import BaseAuthPlugin

AUTHENTICATION_PLUGIN_CLASS = "MySQLLdapSaslPasswordAuthPlugin"


# pylint: disable=c-extension-no-member,no-member
class MySQLLdapSaslPasswordAuthPlugin(BaseAuthPlugin):
    """Class implementing the MySQL ldap sasl authentication plugin.

    The MySQL's ldap sasl authentication plugin support two authentication
    methods SCRAM-SHA-1 and GSSAPI (using Kerberos). This implementation only
    support SCRAM-SHA-1 and SCRAM-SHA-256.

    SCRAM-SHA-1 amd SCRAM-SHA-256
        This method requires 2 messages from client and 2 responses from
        server.

        The first message from client will be generated by prepare_password(),
        after receive the response from the server, it is required that this
        response is passed back to auth_continue() which will return the
        second message from the client. After send this second message to the
        server, the second server respond needs to be passed to auth_finalize()
        to finish the authentication process.
    """

    sasl_mechanisms: List[str] = ["SCRAM-SHA-1", "SCRAM-SHA-256", "GSSAPI"]
    requires_ssl: bool = False
    plugin_name: str = "authentication_ldap_sasl_client"
    def_digest_mode: Callable = sha1
    client_nonce: Optional[str] = None
    client_salt: Any = None
    server_salt: Optional[str] = None
    krb_service_principal: Optional[StrOrBytes] = None
    iterations: int = 0
    server_auth_var: Optional[str] = None
    target_name: Optional[gssapi.Name] = None
    ctx: gssapi.SecurityContext = None
    servers_first: Optional[str] = None
    server_nonce: Optional[str] = None

    @staticmethod
    def _xor(bytes1: bytes, bytes2: bytes) -> bytes:
        return bytes([b1 ^ b2 for b1, b2 in zip(bytes1, bytes2)])

    def _hmac(self, password: bytes, salt: bytes) -> bytes:
        digest_maker = hmac.new(password, salt, self.def_digest_mode)
        return digest_maker.digest()

    def _hi(self, password: str, salt: bytes, count: int) -> bytes:
        """Prepares Hi
        Hi(password, salt, iterations) where Hi(p,s,i) is defined as
        PBKDF2 (HMAC, p, s, i, output length of H).
        """
        pw = password.encode()
        hi = self._hmac(pw, salt + b"\x00\x00\x00\x01")
        aux = hi
        for _ in range(count - 1):
            aux = self._hmac(pw, aux)
            hi = self._xor(hi, aux)
        return hi

    @staticmethod
    def _normalize(string: str) -> str:
        norm_str = norm_ustr(string)
        broken_rule = valid_norm(norm_str)
        if broken_rule is not None:
            raise InterfaceError(f"broken_rule: {broken_rule}")
        return norm_str

    def _first_message(self) -> bytes:
        """This method generates the first message to the server to start the

        The client-first message consists of a gs2-header,
        the desired username, and a randomly generated client nonce cnonce.

        The first message from the server has the form:
            b'n,a=<user_name>,n=<user_name>,r=<client_nonce>

        Returns client's first message
        """
        cfm_fprnat = "n,a={user_name},n={user_name},r={client_nonce}"
        self.client_nonce = str(uuid4()).replace("-", "")
        cfm: StrOrBytes = cfm_fprnat.format(
            user_name=self._normalize(self._username),
            client_nonce=self.client_nonce,
        )

        if isinstance(cfm, str):
            cfm = cfm.encode("utf8")
        return cfm

    def _first_message_krb(self) -> Optional[bytes]:
        """Get a TGT Authentication request and initiates security context.

        This method will contact the Kerberos KDC in order of obtain a TGT.
        """
        user_name = gssapi.raw.names.import_name(
            self._username.encode("utf8"), name_type=gssapi.NameType.user
        )

        # Use defaults store = {'ccache': 'FILE:/tmp/krb5cc_1000'}#,
        #                       'keytab':'/etc/some.keytab' }
        # Attempt to retrieve credential from default cache file.
        try:
            cred: Any = gssapi.Credentials()
            logger.debug(
                "# Stored credentials found, if password was given it will be ignored."
            )
            try:
                # validate credentials has not expired.
                cred.lifetime
            except gssapi.raw.exceptions.ExpiredCredentialsError as err:
                logger.warning(" Credentials has expired: %s", err)
                cred.acquire(user_name)
                raise InterfaceError(f"Credentials has expired: {err}") from err
        except gssapi.raw.misc.GSSError as err:
            if not self._password:
                raise InterfaceError(
                    f"Unable to retrieve stored credentials error: {err}"
                ) from err
            try:
                logger.debug("# Attempt to retrieve credentials with given password")
                acquire_cred_result = gssapi.raw.acquire_cred_with_password(
                    user_name,
                    self._password.encode("utf8"),
                    usage="initiate",
                )
                cred = acquire_cred_result[0]
            except gssapi.raw.misc.GSSError as err2:
                raise ProgrammingError(
                    f"Unable to retrieve credentials with the given password: {err2}"
                ) from err

        flags_l = (
            gssapi.RequirementFlag.mutual_authentication,
            gssapi.RequirementFlag.extended_error,
            gssapi.RequirementFlag.delegate_to_peer,
        )

        if self.krb_service_principal:
            service_principal = self.krb_service_principal
        else:
            service_principal = "ldap/ldapauth"
        logger.debug("# service principal: %s", service_principal)
        servk = gssapi.Name(
            service_principal, name_type=gssapi.NameType.kerberos_principal
        )
        self.target_name = servk
        self.ctx = gssapi.SecurityContext(
            name=servk, creds=cred, flags=sum(flags_l), usage="initiate"
        )

        try:
            # step() returns bytes | None, see documentation,
            # so this method could return a NULL payload.
            # ref: https://pythongssapi.github.io/<suffix>
            # suffix: python-gssapi/latest/gssapi.html#gssapi.sec_contexts.SecurityContext
            initial_client_token = self.ctx.step()
        except gssapi.raw.misc.GSSError as err:
            raise InterfaceError(f"Unable to initiate security context: {err}") from err

        logger.debug("# initial client token: %s", initial_client_token)
        return initial_client_token

    def auth_continue_krb(
        self, tgt_auth_challenge: Optional[bytes]
    ) -> Tuple[Optional[bytes], bool]:
        """Continue with the Kerberos TGT service request.

        With the TGT authentication service given response generate a TGT
        service request. This method must be invoked sequentially (in a loop)
        until the security context is completed and an empty response needs to
        be send to acknowledge the server.

        Args:
            tgt_auth_challenge the challenge for the negotiation.

        Returns: tuple (bytearray TGS service request,
                        bool True if context is completed otherwise False).
        """
        logger.debug("tgt_auth challenge: %s", tgt_auth_challenge)

        resp = self.ctx.step(tgt_auth_challenge)
        logger.debug("# context step response: %s", resp)
        logger.debug("# context completed?: %s", self.ctx.complete)

        return resp, self.ctx.complete

    def auth_accept_close_handshake(self, message: bytes) -> bytes:
        """Accept handshake and generate closing handshake message for server.

        This method verifies the server authenticity from the given message
        and included signature and generates the closing handshake for the
        server.

        When this method is invoked the security context is already established
        and the client and server can send GSSAPI formated secure messages.

        To finish the authentication handshake the server sends a message
        with the security layer availability and the maximum buffer size.

        Since the connector only uses the GSSAPI authentication mechanism to
        authenticate the user with the server, the server will verify clients
        message signature and terminate the GSSAPI authentication and send two
        messages; an authentication acceptance b'\x01\x00\x00\x08\x01' and a
        OK packet (that must be received after sent the returned message from
        this method).

        Args:
            message a wrapped hssapi message from the server.

        Returns: bytearray closing handshake message to be send to the server.
        """
        if not self.ctx.complete:
            raise ProgrammingError("Security context is not completed.")
        logger.debug("# servers message: %s", message)
        logger.debug("# GSSAPI flags in use: %s", self.ctx.actual_flags)
        try:
            unwraped = self.ctx.unwrap(message)
            logger.debug("# unwraped: %s", unwraped)
        except gssapi.raw.exceptions.BadMICError as err:
            raise InterfaceError(f"Unable to unwrap server message: {err}") from err

        logger.debug("# unwrapped server message: %s", unwraped)
        # The message contents for the clients closing message:
        #   - security level 1 byte, must be always 1.
        #   - conciliated buffer size 3 bytes, without importance as no
        #     further GSSAPI messages will be sends.
        response = bytearray(b"\x01\x00\x00\00")
        # Closing handshake must not be encrypted.
        logger.debug("# message response: %s", response)
        wraped = self.ctx.wrap(response, encrypt=False)
        logger.debug(
            "# wrapped message response: %s, length: %d",
            wraped[0],
            len(wraped[0]),
        )

        return wraped.message

    def auth_response(  # type: ignore[override]
        self,
        auth_data: Optional[str] = None,
    ) -> Optional[bytes]:
        """This method will prepare the fist message to the server.

        Returns bytes to send to the server as the first message.
        """
        krb_service_principal = auth_data
        auth_mechanism = self._auth_data.decode()
        self.krb_service_principal = krb_service_principal
        logger.debug("read_method_name_from_server: %s", auth_mechanism)
        if auth_mechanism not in self.sasl_mechanisms:
            auth_mechanisms = '", "'.join(self.sasl_mechanisms[:-1])
            raise InterfaceError(
                f'The sasl authentication method "{auth_mechanism}" requested '
                f'from the server is not supported. Only "{auth_mechanisms}" '
                f'and "{self.sasl_mechanisms[-1]}" are supported'
            )

        if b"GSSAPI" in self._auth_data:
            return self._first_message_krb()

        if self._auth_data == b"SCRAM-SHA-256":
            self.def_digest_mode = sha256

        return self._first_message()

    def _second_message(self) -> bytes:
        """This method generates the second message to the server

        Second message consist on the concatenation of the client and the
        server nonce, and cproof.

        c=<n,a=<user_name>>,r=<server_nonce>,p=<client_proof>
        where:
            <client_proof>: xor(<client_key>, <client_signature>)

            <client_key>: hmac(salted_password, b"Client Key")
            <client_signature>: hmac(<stored_key>, <auth_msg>)
            <stored_key>: h(<client_key>)
            <auth_msg>: <client_first_no_header>,<servers_first>,
                        c=<client_header>,r=<server_nonce>
            <client_first_no_header>: n=<username>r=<client_nonce>
        """
        if not self._auth_data:
            raise InterfaceError("Missing authentication data (seed)")

        passw = self._normalize(self._password)
        salted_password = self._hi(passw, b64decode(self.server_salt), self.iterations)
        logger.debug("salted_password: %s", b64encode(salted_password).decode())

        client_key = self._hmac(salted_password, b"Client Key")
        logger.debug("client_key: %s", b64encode(client_key).decode())

        stored_key = self.def_digest_mode(client_key).digest()
        logger.debug("stored_key: %s", b64encode(stored_key).decode())

        server_key = self._hmac(salted_password, b"Server Key")
        logger.debug("server_key: %s", b64encode(server_key).decode())

        client_first_no_header = ",".join(
            [
                f"n={self._normalize(self._username)}",
                f"r={self.client_nonce}",
            ]
        )
        logger.debug("client_first_no_header: %s", client_first_no_header)

        client_header = b64encode(
            f"n,a={self._normalize(self._username)},".encode()
        ).decode()

        auth_msg = ",".join(
            [
                client_first_no_header,
                self.servers_first,
                f"c={client_header}",
                f"r={self.server_nonce}",
            ]
        )
        logger.debug("auth_msg: %s", auth_msg)

        client_signature = self._hmac(stored_key, auth_msg.encode())
        logger.debug("client_signature: %s", b64encode(client_signature).decode())

        client_proof = self._xor(client_key, client_signature)
        logger.debug("client_proof: %s", b64encode(client_proof).decode())

        self.server_auth_var = b64encode(
            self._hmac(server_key, auth_msg.encode())
        ).decode()
        logger.debug("server_auth_var: %s", self.server_auth_var)

        msg = ",".join(
            [
                f"c={client_header}",
                f"r={self.server_nonce}",
                f"p={b64encode(client_proof).decode()}",
            ]
        )
        logger.debug("second_message: %s", msg)
        return msg.encode()

    def _validate_first_reponse(self, servers_first: bytes) -> None:
        """Validates first message from the server.

        Extracts the server's salt and iterations from the servers 1st response.
        First message from the server is in the form:
            <server_salt>,i=<iterations>
        """
        if not servers_first or not isinstance(servers_first, (bytearray, bytes)):
            raise InterfaceError(f"Unexpected server message: {repr(servers_first)}")
        try:
            servers_first_str = servers_first.decode()
            self.servers_first = servers_first_str
            r_server_nonce, s_salt, i_counter = servers_first_str.split(",")
        except ValueError:
            raise InterfaceError(
                f"Unexpected server message: {servers_first_str}"
            ) from None
        if (
            not r_server_nonce.startswith("r=")
            or not s_salt.startswith("s=")
            or not i_counter.startswith("i=")
        ):
            raise InterfaceError(
                f"Incomplete reponse from the server: {servers_first_str}"
            )
        if self.client_nonce in r_server_nonce:
            self.server_nonce = r_server_nonce[2:]
            logger.debug("server_nonce: %s", self.server_nonce)
        else:
            raise InterfaceError(
                "Unable to authenticate response: response not well formed "
                f"{servers_first_str}"
            )
        self.server_salt = s_salt[2:]
        logger.debug(
            "server_salt: %s length: %s",
            self.server_salt,
            len(self.server_salt),
        )
        try:
            i_counter = i_counter[2:]
            logger.debug("iterations: %s", i_counter)
            self.iterations = int(i_counter)
        except Exception as err:
            raise InterfaceError(
                f"Unable to authenticate: iterations not found {servers_first_str}"
            ) from err

    def auth_continue(self, servers_first_response: bytes) -> bytes:
        """return the second message from the client.

        Returns bytes to send to the server as the second message.
        """
        self._validate_first_reponse(servers_first_response)
        return self._second_message()

    def _validate_second_reponse(self, servers_second: bytearray) -> bool:
        """Validates second message from the server.

        The client and the server prove to each other they have the same Auth
        variable.

        The second message from the server consist of the server's proof:
            server_proof = HMAC(<server_key>, <auth_msg>)
            where:
                <server_key>: hmac(<salted_password>, b"Server Key")
                <auth_msg>: <client_first_no_header>,<servers_first>,
                            c=<client_header>,r=<server_nonce>

        Our server_proof must be equal to the Auth variable send on this second
        response.
        """
        if (
            not servers_second
            or not isinstance(servers_second, bytearray)
            or len(servers_second) <= 2
            or not servers_second.startswith(b"v=")
        ):
            raise InterfaceError("The server's proof is not well formated")
        server_var = servers_second[2:].decode()
        logger.debug("server auth variable: %s", server_var)
        return self.server_auth_var == server_var

    def auth_finalize(self, servers_second_response: bytearray) -> bool:
        """finalize the authentication process.

        Raises InterfaceError if the ervers_second_response is invalid.

        Returns True in successful authentication False otherwise.
        """
        if not self._validate_second_reponse(servers_second_response):
            raise InterfaceError(
                "Authentication failed: Unable to proof server identity"
            )
        return True


# pylint: enable=c-extension-no-member,no-member

virt/lib/python3.9/site-packages/mysql/connector/plugins/authentication_ldap_sasl_client.py
PrinceDisant/LostInNL
# Copyright (c) 2022, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

# mypy: disable-error-code="arg-type,union-attr,call-arg"

"""OCI Authentication Plugin."""

import json
import os

from base64 import b64encode
from pathlib import Path
from typing import Any, Dict, Optional

from .. import errors
from ..logger import logger

try:
    from cryptography.exceptions import UnsupportedAlgorithm
    from cryptography.hazmat.primitives import hashes, serialization
    from cryptography.hazmat.primitives.asymmetric import padding
    from cryptography.hazmat.primitives.asymmetric.types import PRIVATE_KEY_TYPES
except ImportError:
    raise errors.ProgrammingError("Package 'cryptography' is not installed") from None

try:
    from oci import config, exceptions
except ImportError:
    raise errors.ProgrammingError(
        "Package 'oci' (Oracle Cloud Infrastructure Python SDK) is not installed"
    ) from None

from . import BaseAuthPlugin

AUTHENTICATION_PLUGIN_CLASS = "MySQLOCIAuthPlugin"
OCI_SECURITY_TOKEN_MAX_SIZE = 10 * 1024  # In bytes
OCI_SECURITY_TOKEN_TOO_LARGE = "Ephemeral security token is too large (10KB max)"
OCI_SECURITY_TOKEN_FILE_NOT_AVAILABLE = (
    "Ephemeral security token file ('security_token_file') could not be read"
)
OCI_PROFILE_MISSING_PROPERTIES = (
    "OCI configuration file does not contain a 'fingerprint' or 'key_file' entry"
)


class MySQLOCIAuthPlugin(BaseAuthPlugin):
    """Implement the MySQL OCI IAM authentication plugin."""

    plugin_name: str = "authentication_oci_client"
    requires_ssl: bool = False
    context: Any = None
    oci_config_profile: str = "DEFAULT"
    oci_config_file: str = config.DEFAULT_LOCATION

    @staticmethod
    def _prepare_auth_response(signature: bytes, oci_config: Dict[str, Any]) -> str:
        """Prepare client's authentication response

        Prepares client's authentication response in JSON format
        Args:
            signature (bytes):  server's nonce to be signed by client.
            oci_config (dict): OCI configuration object.

        Returns:
            str: JSON string with the following format:
                 {"fingerprint": str, "signature": str, "token": base64.base64.base64}

        Raises:
            ProgrammingError: If the ephemeral security token file can't be open or the
                              token is too large.
        """
        signature_64 = b64encode(signature)
        auth_response = {
            "fingerprint": oci_config["fingerprint"],
            "signature": signature_64.decode(),
        }

        # The security token, if it exists, should be a JWT (JSON Web Token), consisted
        # of a base64-encoded header, body, and signature, separated by '.',
        # e.g. "Base64.Base64.Base64", stored in a file at the path specified by the
        # security_token_file configuration property
        if oci_config.get("security_token_file"):
            try:
                security_token_file = Path(oci_config["security_token_file"])
                # Check if token exceeds the maximum size
                if security_token_file.stat().st_size > OCI_SECURITY_TOKEN_MAX_SIZE:
                    raise errors.ProgrammingError(OCI_SECURITY_TOKEN_TOO_LARGE)
                auth_response["token"] = security_token_file.read_text(encoding="utf-8")
            except (OSError, UnicodeError) as err:
                raise errors.ProgrammingError(
                    OCI_SECURITY_TOKEN_FILE_NOT_AVAILABLE
                ) from err
        return json.dumps(auth_response, separators=(",", ":"))

    @staticmethod
    def _get_private_key(key_path: str) -> PRIVATE_KEY_TYPES:
        """Get the private_key form the given location"""
        try:
            with open(os.path.expanduser(key_path), "rb") as key_file:
                private_key = serialization.load_pem_private_key(
                    key_file.read(),
                    password=None,
                )
        except (TypeError, OSError, ValueError, UnsupportedAlgorithm) as err:
            raise errors.ProgrammingError(
                "An error occurred while reading the API_KEY from "
                f'"{key_path}": {err}'
            )

        return private_key

    def _get_valid_oci_config(self) -> Dict[str, Any]:
        """Get a valid OCI config from the given configuration file path"""
        error_list = []
        req_keys = {
            "fingerprint": (lambda x: len(x) > 32),
            "key_file": (lambda x: os.path.exists(os.path.expanduser(x))),
        }

        oci_config: Dict[str, Any] = {}
        try:
            # key_file is validated by oci.config if present
            oci_config = config.from_file(
                self.oci_config_file or config.DEFAULT_LOCATION,
                self.oci_config_profile or "DEFAULT",
            )
            for req_key, req_value in req_keys.items():
                try:
                    # Verify parameter in req_key is present and valid
                    if oci_config[req_key] and not req_value(oci_config[req_key]):
                        error_list.append(f'Parameter "{req_key}" is invalid')
                except KeyError:
                    error_list.append(f"Does not contain parameter {req_key}")
        except (
            exceptions.ConfigFileNotFound,
            exceptions.InvalidConfig,
            exceptions.InvalidKeyFilePath,
            exceptions.InvalidPrivateKey,
            exceptions.ProfileNotFound,
        ) as err:
            error_list.append(str(err))

        # Raise errors if any
        if error_list:
            raise errors.ProgrammingError(
                f"Invalid oci-config-file: {self.oci_config_file}. "
                f"Errors found: {error_list}"
            )

        return oci_config

    def auth_response(self, auth_data: Optional[bytes] = None) -> bytes:
        """Prepare authentication string for the server."""
        logger.debug("server nonce: %s, len %d", self._auth_data, len(self._auth_data))

        oci_config = self._get_valid_oci_config()

        private_key = self._get_private_key(oci_config["key_file"])
        signature = private_key.sign(
            self._auth_data, padding.PKCS1v15(), hashes.SHA256()
        )

        auth_response = self._prepare_auth_response(signature, oci_config)
        logger.debug("authentication response: %s", auth_response)
        return auth_response.encode()

virt/lib/python3.9/site-packages/mysql/connector/plugins/authentication_oci_client.py
PrinceDisant/LostInNL
# Copyright (c) 2022, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

"""Caching SHA2 Password Authentication Plugin."""

import struct

from hashlib import sha256
from typing import Optional

from ..errors import InterfaceError
from . import BaseAuthPlugin

AUTHENTICATION_PLUGIN_CLASS = "MySQLCachingSHA2PasswordAuthPlugin"


class MySQLCachingSHA2PasswordAuthPlugin(BaseAuthPlugin):
    """Class implementing the MySQL caching_sha2_password authentication plugin

    Note that encrypting using RSA is not supported since the Python
    Standard Library does not provide this OpenSSL functionality.
    """

    requires_ssl: bool = False
    plugin_name: str = "caching_sha2_password"
    perform_full_authentication: int = 4
    fast_auth_success: int = 3

    def _scramble(self) -> bytes:
        """Return a scramble of the password using a Nonce sent by the
        server.

        The scramble is of the form:
        XOR(SHA2(password), SHA2(SHA2(SHA2(password)), Nonce))
        """
        if not self._auth_data:
            raise InterfaceError("Missing authentication data (seed)")

        if not self._password:
            return b""

        password = (
            self._password.encode("utf-8")
            if isinstance(self._password, str)
            else self._password
        )
        auth_data = self._auth_data

        hash1 = sha256(password).digest()
        hash2 = sha256()
        hash2.update(sha256(hash1).digest())
        hash2.update(auth_data)
        hash2_digest: bytes = hash2.digest()
        xored = [h1 ^ h2 for (h1, h2) in zip(hash1, hash2_digest)]
        hash3 = struct.pack("32B", *xored)
        return hash3

    def _full_authentication(self) -> bytes:
        """Returns password as as clear text"""
        if not self._ssl_enabled:
            raise InterfaceError(f"{self.plugin_name} requires SSL")
        return super().prepare_password()

    def prepare_password(self) -> Optional[bytes]:
        """Prepare and return password.

        Returns:
            bytes: Prepared password.
        """
        if not self._auth_data:
            return None
        if len(self._auth_data) > 1:
            return self._scramble()
        if self._auth_data[0] == self.perform_full_authentication:
            return self._full_authentication()
        return None

virt/lib/python3.9/site-packages/mysql/connector/plugins/caching_sha2_password.py
PrinceDisant/LostInNL
# Copyright (c) 2022, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

"""Clear Password Authentication Plugin."""

from . import BaseAuthPlugin

AUTHENTICATION_PLUGIN_CLASS = "MySQLClearPasswordAuthPlugin"


class MySQLClearPasswordAuthPlugin(BaseAuthPlugin):
    """Class implementing the MySQL Clear Password authentication plugin"""

    requires_ssl: bool = True
    plugin_name: str = "mysql_clear_password"

virt/lib/python3.9/site-packages/mysql/connector/plugins/mysql_clear_password.py
PrinceDisant/LostInNL
# Copyright (c) 2022, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

"""Native Password Authentication Plugin."""

import struct

from hashlib import sha1

from ..errors import InterfaceError
from ..types import StrOrBytes
from . import BaseAuthPlugin

AUTHENTICATION_PLUGIN_CLASS = "MySQLNativePasswordAuthPlugin"


class MySQLNativePasswordAuthPlugin(BaseAuthPlugin):
    """Class implementing the MySQL Native Password authentication plugin"""

    requires_ssl: bool = False
    plugin_name: str = "mysql_native_password"

    def prepare_password(self) -> bytes:
        """Prepares and returns password as native MySQL 4.1+ password"""
        if not self._auth_data:
            raise InterfaceError("Missing authentication data (seed)")

        if not self._password:
            return b""
        password: StrOrBytes = self._password

        if isinstance(self._password, str):
            password = self._password.encode("utf-8")
        else:
            password = self._password

        auth_data = self._auth_data

        hash4 = None
        try:
            hash1 = sha1(password).digest()
            hash2 = sha1(hash1).digest()
            hash3 = sha1(auth_data + hash2).digest()
            xored = [h1 ^ h3 for (h1, h3) in zip(hash1, hash3)]
            hash4 = struct.pack("20B", *xored)
        except (struct.error, TypeError) as err:
            raise InterfaceError(f"Failed scrambling password; {err}") from err

        return hash4

virt/lib/python3.9/site-packages/mysql/connector/plugins/mysql_native_password.py
PrinceDisant/LostInNL
# Copyright (c) 2022, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

"""SHA256 Password Authentication Plugin."""

from . import BaseAuthPlugin

AUTHENTICATION_PLUGIN_CLASS = "MySQLSHA256PasswordAuthPlugin"


class MySQLSHA256PasswordAuthPlugin(BaseAuthPlugin):
    """Class implementing the MySQL SHA256 authentication plugin

    Note that encrypting using RSA is not supported since the Python
    Standard Library does not provide this OpenSSL functionality.
    """

    requires_ssl: bool = True
    plugin_name: str = "sha256_password"

virt/lib/python3.9/site-packages/mysql/connector/plugins/sha256_password.py
PrinceDisant/LostInNL
# Copyright (c) 2013, 2022, Oracle and/or its affiliates. All rights reserved.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

"""Implementing pooling of connections to MySQL servers."""
from __future__ import annotations

import queue
import random
import re
import threading

from types import TracebackType
from typing import Any, Dict, NoReturn, Optional, Tuple, Type, Union
from uuid import uuid4

try:
    import dns.exception
    import dns.resolver
except ImportError:
    HAVE_DNSPYTHON = False
else:
    HAVE_DNSPYTHON = True

try:
    from .connection_cext import CMySQLConnection
except ImportError:
    CMySQLConnection = None  # type: ignore[misc]

from .connection import MySQLConnection
from .constants import CNX_POOL_ARGS, DEFAULT_CONFIGURATION
from .errors import (
    Error,
    InterfaceError,
    NotSupportedError,
    PoolError,
    ProgrammingError,
)
from .optionfiles import read_option_files

CONNECTION_POOL_LOCK = threading.RLock()
CNX_POOL_MAXSIZE = 32
CNX_POOL_MAXNAMESIZE = 64
CNX_POOL_NAMEREGEX = re.compile(r"[^a-zA-Z0-9._:\-*$#]")
ERROR_NO_CEXT = "MySQL Connector/Python C Extension not available"
MYSQL_CNX_CLASS: Union[type, Tuple[type, ...]] = (
    MySQLConnection if CMySQLConnection is None else (MySQLConnection, CMySQLConnection)
)

_CONNECTION_POOLS: Dict[str, MySQLConnectionPool] = {}


def _get_pooled_connection(**kwargs: Any) -> PooledMySQLConnection:
    """Return a pooled MySQL connection."""
    # If no pool name specified, generate one
    pool_name = (
        kwargs["pool_name"] if "pool_name" in kwargs else generate_pool_name(**kwargs)
    )

    if kwargs.get("use_pure") is False and CMySQLConnection is None:
        raise ImportError(ERROR_NO_CEXT)

    # Setup the pool, ensuring only 1 thread can update at a time
    with CONNECTION_POOL_LOCK:
        if pool_name not in _CONNECTION_POOLS:
            _CONNECTION_POOLS[pool_name] = MySQLConnectionPool(**kwargs)
        elif isinstance(_CONNECTION_POOLS[pool_name], MySQLConnectionPool):
            # pool_size must be the same
            check_size = _CONNECTION_POOLS[pool_name].pool_size
            if "pool_size" in kwargs and kwargs["pool_size"] != check_size:
                raise PoolError("Size can not be changed for active pools.")

    # Return pooled connection
    try:
        return _CONNECTION_POOLS[pool_name].get_connection()
    except AttributeError:
        raise InterfaceError(
            f"Failed getting connection from pool '{pool_name}'"
        ) from None


def _get_failover_connection(
    **kwargs: Any,
) -> Union[PooledMySQLConnection, MySQLConnection, CMySQLConnection]:
    """Return a MySQL connection and try to failover if needed.

    An InterfaceError is raise when no MySQL is available. ValueError is
    raised when the failover server configuration contains an illegal
    connection argument. Supported arguments are user, password, host, port,
    unix_socket and database. ValueError is also raised when the failover
    argument was not provided.

    Returns MySQLConnection instance.
    """
    config = kwargs.copy()
    try:
        failover = config["failover"]
    except KeyError:
        raise ValueError("failover argument not provided") from None
    del config["failover"]

    support_cnx_args = set(
        [
            "user",
            "password",
            "host",
            "port",
            "unix_socket",
            "database",
            "pool_name",
            "pool_size",
            "priority",
        ]
    )

    # First check if we can add all use the configuration
    priority_count = 0
    for server in failover:
        diff = set(server.keys()) - support_cnx_args
        if diff:
            arg = "s" if len(diff) > 1 else ""
            lst = ", ".join(diff)
            raise ValueError(
                f"Unsupported connection argument {arg} in failover: {lst}"
            )
        if hasattr(server, "priority"):
            priority_count += 1

        server["priority"] = server.get("priority", 100)
        if server["priority"] < 0 or server["priority"] > 100:
            raise InterfaceError(
                "Priority value should be in the range of 0 to 100, "
                f"got : {server['priority']}"
            )
        if not isinstance(server["priority"], int):
            raise InterfaceError(
                "Priority value should be an integer in the range of 0 to "
                f"100, got : {server['priority']}"
            )

    if 0 < priority_count < len(failover):
        raise ProgrammingError(
            "You must either assign no priority to any "
            "of the routers or give a priority for "
            "every router"
        )

    server_directory = {}
    server_priority_list = []
    for server in sorted(failover, key=lambda x: x["priority"], reverse=True):
        if server["priority"] not in server_directory:
            server_directory[server["priority"]] = [server]
            server_priority_list.append(server["priority"])
        else:
            server_directory[server["priority"]].append(server)

    for priority in server_priority_list:
        failover_list = server_directory[priority]
        for _ in range(len(failover_list)):
            last = len(failover_list) - 1
            index = random.randint(0, last)
            server = failover_list.pop(index)
            new_config = config.copy()
            new_config.update(server)
            new_config.pop("priority", None)
            try:
                return connect(**new_config)
            except Error:
                # If we failed to connect, we try the next server
                pass

    raise InterfaceError("Unable to connect to any of the target hosts")


def connect(
    *args: Any, **kwargs: Any
) -> Union[PooledMySQLConnection, MySQLConnection, CMySQLConnection]:
    """Create or get a MySQL connection object.

    In its simpliest form, connect() will open a connection to a
    MySQL server and return a MySQLConnection object.

    When any connection pooling arguments are given, for example pool_name
    or pool_size, a pool is created or a previously one is used to return
    a PooledMySQLConnection.

    Returns MySQLConnection or PooledMySQLConnection.
    """
    # DNS SRV
    dns_srv = kwargs.pop("dns_srv") if "dns_srv" in kwargs else False

    if not isinstance(dns_srv, bool):
        raise InterfaceError("The value of 'dns-srv' must be a boolean")

    if dns_srv:
        if not HAVE_DNSPYTHON:
            raise InterfaceError(
                "MySQL host configuration requested DNS "
                "SRV. This requires the Python dnspython "
                "module. Please refer to documentation"
            )
        if "unix_socket" in kwargs:
            raise InterfaceError(
                "Using Unix domain sockets with DNS SRV lookup is not allowed"
            )
        if "port" in kwargs:
            raise InterfaceError(
                "Specifying a port number with DNS SRV lookup is not allowed"
            )
        if "failover" in kwargs:
            raise InterfaceError(
                "Specifying multiple hostnames with DNS SRV look up is not allowed"
            )
        if "host" not in kwargs:
            kwargs["host"] = DEFAULT_CONFIGURATION["host"]

        try:
            srv_records = dns.resolver.query(kwargs["host"], "SRV")
        except dns.exception.DNSException:
            raise InterfaceError(
                f"Unable to locate any hosts for '{kwargs['host']}'"
            ) from None

        failover = []
        for srv in srv_records:
            failover.append(
                {
                    "host": srv.target.to_text(omit_final_dot=True),
                    "port": srv.port,
                    "priority": srv.priority,
                    "weight": srv.weight,
                }
            )

        failover.sort(key=lambda x: (x["priority"], -x["weight"]))
        kwargs["failover"] = [
            {"host": srv["host"], "port": srv["port"]} for srv in failover
        ]

    # Option files
    if "read_default_file" in kwargs:
        kwargs["option_files"] = kwargs["read_default_file"]
        kwargs.pop("read_default_file")

    if "option_files" in kwargs:
        new_config = read_option_files(**kwargs)
        return connect(**new_config)

    # Failover
    if "failover" in kwargs:
        return _get_failover_connection(**kwargs)

    # Pooled connections
    try:
        if any(key in kwargs for key in CNX_POOL_ARGS):
            return _get_pooled_connection(**kwargs)
    except NameError:
        # No pooling
        pass

    # Use C Extension by default
    use_pure = kwargs.get("use_pure", False)
    if "use_pure" in kwargs:
        del kwargs["use_pure"]  # Remove 'use_pure' from kwargs
        if not use_pure and CMySQLConnection is None:
            raise ImportError(ERROR_NO_CEXT)

    if CMySQLConnection and not use_pure:
        return CMySQLConnection(*args, **kwargs)
    return MySQLConnection(*args, **kwargs)


def generate_pool_name(**kwargs: Any) -> str:
    """Generate a pool name

    This function takes keyword arguments, usually the connection
    arguments for MySQLConnection, and tries to generate a name for
    a pool.

    Raises PoolError when no name can be generated.

    Returns a string.
    """
    parts = []
    for key in ("host", "port", "user", "database"):
        try:
            parts.append(str(kwargs[key]))
        except KeyError:
            pass

    if not parts:
        raise PoolError("Failed generating pool name; specify pool_name")

    return "_".join(parts)


class PooledMySQLConnection:
    """Class holding a MySQL Connection in a pool

    PooledMySQLConnection is used by MySQLConnectionPool to return an
    instance holding a MySQL connection. It works like a MySQLConnection
    except for methods like close() and config().

    The close()-method will add the connection back to the pool rather
    than disconnecting from the MySQL server.

    Configuring the connection have to be done through the MySQLConnectionPool
    method set_config(). Using config() on pooled connection will raise a
    PoolError.
    """

    def __init__(
        self, pool: MySQLConnectionPool, cnx: Union[MySQLConnection, CMySQLConnection]
    ) -> None:
        """Initialize

        The pool argument must be an instance of MySQLConnectionPoll. cnx
        if an instance of MySQLConnection.
        """
        if not isinstance(pool, MySQLConnectionPool):
            raise AttributeError("pool should be a MySQLConnectionPool")
        if not isinstance(cnx, MYSQL_CNX_CLASS):
            raise AttributeError("cnx should be a MySQLConnection")
        self._cnx_pool: MySQLConnectionPool = pool
        self._cnx: Union[MySQLConnection, CMySQLConnection] = cnx

    def __enter__(self) -> PooledMySQLConnection:
        return self

    def __exit__(
        self,
        exc_type: Type[BaseException],
        exc_value: BaseException,
        traceback: TracebackType,
    ) -> None:
        self.close()

    def __getattr__(self, attr: Any) -> Any:
        """Calls attributes of the MySQLConnection instance"""
        return getattr(self._cnx, attr)

    def close(self) -> None:
        """Do not close, but add connection back to pool

        The close() method does not close the connection with the
        MySQL server. The connection is added back to the pool so it
        can be reused.

        When the pool is configured to reset the session, the session
        state will be cleared by re-authenticating the user.
        """
        try:
            cnx = self._cnx
            if self._cnx_pool.reset_session:
                cnx.reset_session()
        finally:
            self._cnx_pool.add_connection(cnx)
            self._cnx = None

    @staticmethod
    def config(**kwargs: Any) -> NoReturn:
        """Configuration is done through the pool"""
        raise PoolError(
            "Configuration for pooled connections should be done through the "
            "pool itself"
        )

    @property
    def pool_name(self) -> str:
        """Return the name of the connection pool"""
        return self._cnx_pool.pool_name


class MySQLConnectionPool:
    """Class defining a pool of MySQL connections"""

    def __init__(
        self,
        pool_size: int = 5,
        pool_name: Optional[str] = None,
        pool_reset_session: bool = True,
        **kwargs: Any,
    ) -> None:
        """Initialize

        Initialize a MySQL connection pool with a maximum number of
        connections set to pool_size. The rest of the keywords
        arguments, kwargs, are configuration arguments for MySQLConnection
        instances.
        """
        self._pool_size: Optional[int] = None
        self._pool_name: Optional[str] = None
        self._reset_session = pool_reset_session
        self._set_pool_size(pool_size)
        self._set_pool_name(pool_name or generate_pool_name(**kwargs))
        self._cnx_config: Dict[str, Any] = {}
        self._cnx_queue: queue.Queue[
            Union[MySQLConnection, CMySQLConnection]
        ] = queue.Queue(self._pool_size)
        self._config_version = uuid4()

        if kwargs:
            self.set_config(**kwargs)
            cnt = 0
            while cnt < self._pool_size:
                self.add_connection()
                cnt += 1

    @property
    def pool_name(self) -> str:
        """Return the name of the connection pool"""
        return self._pool_name

    @property
    def pool_size(self) -> int:
        """Return number of connections managed by the pool"""
        return self._pool_size

    @property
    def reset_session(self) -> bool:
        """Return whether to reset session"""
        return self._reset_session

    def set_config(self, **kwargs: Any) -> None:
        """Set the connection configuration for MySQLConnection instances

        This method sets the configuration used for creating MySQLConnection
        instances. See MySQLConnection for valid connection arguments.

        Raises PoolError when a connection argument is not valid, missing
        or not supported by MySQLConnection.
        """
        if not kwargs:
            return

        with CONNECTION_POOL_LOCK:
            try:
                test_cnx = connect()
                test_cnx.config(**kwargs)
                self._cnx_config = kwargs
                self._config_version = uuid4()
            except AttributeError as err:
                raise PoolError(f"Connection configuration not valid: {err}") from err

    def _set_pool_size(self, pool_size: int) -> None:
        """Set the size of the pool

        This method sets the size of the pool but it will not resize the pool.

        Raises an AttributeError when the pool_size is not valid. Invalid size
        is 0, negative or higher than pooling.CNX_POOL_MAXSIZE.
        """
        if pool_size <= 0 or pool_size > CNX_POOL_MAXSIZE:
            raise AttributeError(
                "Pool size should be higher than 0 and lower or equal to "
                f"{CNX_POOL_MAXSIZE}"
            )
        self._pool_size = pool_size

    def _set_pool_name(self, pool_name: str) -> None:
        r"""Set the name of the pool.

        This method checks the validity and sets the name of the pool.

        Raises an AttributeError when pool_name contains illegal characters
        ([^a-zA-Z0-9._\-*$#]) or is longer than pooling.CNX_POOL_MAXNAMESIZE.
        """
        if CNX_POOL_NAMEREGEX.search(pool_name):
            raise AttributeError(f"Pool name '{pool_name}' contains illegal characters")
        if len(pool_name) > CNX_POOL_MAXNAMESIZE:
            raise AttributeError(f"Pool name '{pool_name}' is too long")
        self._pool_name = pool_name

    def _queue_connection(self, cnx: Union[MySQLConnection, CMySQLConnection]) -> None:
        """Put connection back in the queue

        This method is putting a connection back in the queue. It will not
        acquire a lock as the methods using _queue_connection() will have it
        set.

        Raises PoolError on errors.
        """
        if not isinstance(cnx, MYSQL_CNX_CLASS):
            raise PoolError("Connection instance not subclass of MySQLConnection")

        try:
            self._cnx_queue.put(cnx, block=False)
        except queue.Full as err:
            raise PoolError("Failed adding connection; queue is full") from err

    def add_connection(
        self, cnx: Optional[Union[MySQLConnection, CMySQLConnection]] = None
    ) -> None:
        """Add a connection to the pool

        This method instantiates a MySQLConnection using the configuration
        passed when initializing the MySQLConnectionPool instance or using
        the set_config() method.
        If cnx is a MySQLConnection instance, it will be added to the
        queue.

        Raises PoolError when no configuration is set, when no more
        connection can be added (maximum reached) or when the connection
        can not be instantiated.
        """
        with CONNECTION_POOL_LOCK:
            if not self._cnx_config:
                raise PoolError("Connection configuration not available")

            if self._cnx_queue.full():
                raise PoolError("Failed adding connection; queue is full")

            if not cnx:
                cnx = connect(**self._cnx_config)  # type: ignore[assignment]
                try:
                    if (
                        self._reset_session
                        and self._cnx_config["compress"]
                        and cnx.get_server_version() < (5, 7, 3)
                    ):
                        raise NotSupportedError(
                            "Pool reset session is not supported with "
                            "compression for MySQL server version 5.7.2 "
                            "or earlier"
                        )
                except KeyError:
                    pass

                cnx.pool_config_version = self._config_version
            else:
                if not isinstance(cnx, MYSQL_CNX_CLASS):
                    raise PoolError(
                        "Connection instance not subclass of MySQLConnection"
                    )

            self._queue_connection(cnx)

    def get_connection(self) -> PooledMySQLConnection:
        """Get a connection from the pool

        This method returns an PooledMySQLConnection instance which
        has a reference to the pool that created it, and the next available
        MySQL connection.

        When the MySQL connection is not connect, a reconnect is attempted.

        Raises PoolError on errors.

        Returns a PooledMySQLConnection instance.
        """
        with CONNECTION_POOL_LOCK:
            try:
                cnx = self._cnx_queue.get(block=False)
            except queue.Empty as err:
                raise PoolError("Failed getting connection; pool exhausted") from err

            if (
                not cnx.is_connected()
                or self._config_version != cnx.pool_config_version
            ):
                cnx.config(**self._cnx_config)
                try:
                    cnx.reconnect()
                except InterfaceError:
                    # Failed to reconnect, give connection back to pool
                    self._queue_connection(cnx)
                    raise
                cnx.pool_config_version = self._config_version

            return PooledMySQLConnection(self, cnx)

    def _remove_connections(self) -> int:
        """Close all connections

        This method closes all connections. It returns the number
        of connections it closed.

        Used mostly for tests.

        Returns int.
        """
        with CONNECTION_POOL_LOCK:
            cnt = 0
            cnxq = self._cnx_queue
            while cnxq.qsize():
                try:
                    cnx = cnxq.get(block=False)
                    cnx.disconnect()
                    cnt += 1
                except queue.Empty:
                    return cnt
                except PoolError:
                    raise
                except Error:
                    # Any other error when closing means connection is closed
                    pass

            return cnt

virt/lib/python3.9/site-packages/mysql/connector/pooling 2.py
PrinceDisant/LostInNL
# Copyright (c) 2013, 2022, Oracle and/or its affiliates. All rights reserved.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

"""Implementing pooling of connections to MySQL servers."""
from __future__ import annotations

import queue
import random
import re
import threading

from types import TracebackType
from typing import Any, Dict, NoReturn, Optional, Tuple, Type, Union
from uuid import uuid4

try:
    import dns.exception
    import dns.resolver
except ImportError:
    HAVE_DNSPYTHON = False
else:
    HAVE_DNSPYTHON = True

try:
    from .connection_cext import CMySQLConnection
except ImportError:
    CMySQLConnection = None  # type: ignore[misc]

from .connection import MySQLConnection
from .constants import CNX_POOL_ARGS, DEFAULT_CONFIGURATION
from .errors import (
    Error,
    InterfaceError,
    NotSupportedError,
    PoolError,
    ProgrammingError,
)
from .optionfiles import read_option_files

CONNECTION_POOL_LOCK = threading.RLock()
CNX_POOL_MAXSIZE = 32
CNX_POOL_MAXNAMESIZE = 64
CNX_POOL_NAMEREGEX = re.compile(r"[^a-zA-Z0-9._:\-*$#]")
ERROR_NO_CEXT = "MySQL Connector/Python C Extension not available"
MYSQL_CNX_CLASS: Union[type, Tuple[type, ...]] = (
    MySQLConnection if CMySQLConnection is None else (MySQLConnection, CMySQLConnection)
)

_CONNECTION_POOLS: Dict[str, MySQLConnectionPool] = {}


def _get_pooled_connection(**kwargs: Any) -> PooledMySQLConnection:
    """Return a pooled MySQL connection."""
    # If no pool name specified, generate one
    pool_name = (
        kwargs["pool_name"] if "pool_name" in kwargs else generate_pool_name(**kwargs)
    )

    if kwargs.get("use_pure") is False and CMySQLConnection is None:
        raise ImportError(ERROR_NO_CEXT)

    # Setup the pool, ensuring only 1 thread can update at a time
    with CONNECTION_POOL_LOCK:
        if pool_name not in _CONNECTION_POOLS:
            _CONNECTION_POOLS[pool_name] = MySQLConnectionPool(**kwargs)
        elif isinstance(_CONNECTION_POOLS[pool_name], MySQLConnectionPool):
            # pool_size must be the same
            check_size = _CONNECTION_POOLS[pool_name].pool_size
            if "pool_size" in kwargs and kwargs["pool_size"] != check_size:
                raise PoolError("Size can not be changed for active pools.")

    # Return pooled connection
    try:
        return _CONNECTION_POOLS[pool_name].get_connection()
    except AttributeError:
        raise InterfaceError(
            f"Failed getting connection from pool '{pool_name}'"
        ) from None


def _get_failover_connection(
    **kwargs: Any,
) -> Union[PooledMySQLConnection, MySQLConnection, CMySQLConnection]:
    """Return a MySQL connection and try to failover if needed.

    An InterfaceError is raise when no MySQL is available. ValueError is
    raised when the failover server configuration contains an illegal
    connection argument. Supported arguments are user, password, host, port,
    unix_socket and database. ValueError is also raised when the failover
    argument was not provided.

    Returns MySQLConnection instance.
    """
    config = kwargs.copy()
    try:
        failover = config["failover"]
    except KeyError:
        raise ValueError("failover argument not provided") from None
    del config["failover"]

    support_cnx_args = set(
        [
            "user",
            "password",
            "host",
            "port",
            "unix_socket",
            "database",
            "pool_name",
            "pool_size",
            "priority",
        ]
    )

    # First check if we can add all use the configuration
    priority_count = 0
    for server in failover:
        diff = set(server.keys()) - support_cnx_args
        if diff:
            arg = "s" if len(diff) > 1 else ""
            lst = ", ".join(diff)
            raise ValueError(
                f"Unsupported connection argument {arg} in failover: {lst}"
            )
        if hasattr(server, "priority"):
            priority_count += 1

        server["priority"] = server.get("priority", 100)
        if server["priority"] < 0 or server["priority"] > 100:
            raise InterfaceError(
                "Priority value should be in the range of 0 to 100, "
                f"got : {server['priority']}"
            )
        if not isinstance(server["priority"], int):
            raise InterfaceError(
                "Priority value should be an integer in the range of 0 to "
                f"100, got : {server['priority']}"
            )

    if 0 < priority_count < len(failover):
        raise ProgrammingError(
            "You must either assign no priority to any "
            "of the routers or give a priority for "
            "every router"
        )

    server_directory = {}
    server_priority_list = []
    for server in sorted(failover, key=lambda x: x["priority"], reverse=True):
        if server["priority"] not in server_directory:
            server_directory[server["priority"]] = [server]
            server_priority_list.append(server["priority"])
        else:
            server_directory[server["priority"]].append(server)

    for priority in server_priority_list:
        failover_list = server_directory[priority]
        for _ in range(len(failover_list)):
            last = len(failover_list) - 1
            index = random.randint(0, last)
            server = failover_list.pop(index)
            new_config = config.copy()
            new_config.update(server)
            new_config.pop("priority", None)
            try:
                return connect(**new_config)
            except Error:
                # If we failed to connect, we try the next server
                pass

    raise InterfaceError("Unable to connect to any of the target hosts")


def connect(
    *args: Any, **kwargs: Any
) -> Union[PooledMySQLConnection, MySQLConnection, CMySQLConnection]:
    """Create or get a MySQL connection object.

    In its simpliest form, connect() will open a connection to a
    MySQL server and return a MySQLConnection object.

    When any connection pooling arguments are given, for example pool_name
    or pool_size, a pool is created or a previously one is used to return
    a PooledMySQLConnection.

    Returns MySQLConnection or PooledMySQLConnection.
    """
    # DNS SRV
    dns_srv = kwargs.pop("dns_srv") if "dns_srv" in kwargs else False

    if not isinstance(dns_srv, bool):
        raise InterfaceError("The value of 'dns-srv' must be a boolean")

    if dns_srv:
        if not HAVE_DNSPYTHON:
            raise InterfaceError(
                "MySQL host configuration requested DNS "
                "SRV. This requires the Python dnspython "
                "module. Please refer to documentation"
            )
        if "unix_socket" in kwargs:
            raise InterfaceError(
                "Using Unix domain sockets with DNS SRV lookup is not allowed"
            )
        if "port" in kwargs:
            raise InterfaceError(
                "Specifying a port number with DNS SRV lookup is not allowed"
            )
        if "failover" in kwargs:
            raise InterfaceError(
                "Specifying multiple hostnames with DNS SRV look up is not allowed"
            )
        if "host" not in kwargs:
            kwargs["host"] = DEFAULT_CONFIGURATION["host"]

        try:
            srv_records = dns.resolver.query(kwargs["host"], "SRV")
        except dns.exception.DNSException:
            raise InterfaceError(
                f"Unable to locate any hosts for '{kwargs['host']}'"
            ) from None

        failover = []
        for srv in srv_records:
            failover.append(
                {
                    "host": srv.target.to_text(omit_final_dot=True),
                    "port": srv.port,
                    "priority": srv.priority,
                    "weight": srv.weight,
                }
            )

        failover.sort(key=lambda x: (x["priority"], -x["weight"]))
        kwargs["failover"] = [
            {"host": srv["host"], "port": srv["port"]} for srv in failover
        ]

    # Option files
    if "read_default_file" in kwargs:
        kwargs["option_files"] = kwargs["read_default_file"]
        kwargs.pop("read_default_file")

    if "option_files" in kwargs:
        new_config = read_option_files(**kwargs)
        return connect(**new_config)

    # Failover
    if "failover" in kwargs:
        return _get_failover_connection(**kwargs)

    # Pooled connections
    try:
        if any(key in kwargs for key in CNX_POOL_ARGS):
            return _get_pooled_connection(**kwargs)
    except NameError:
        # No pooling
        pass

    # Use C Extension by default
    use_pure = kwargs.get("use_pure", False)
    if "use_pure" in kwargs:
        del kwargs["use_pure"]  # Remove 'use_pure' from kwargs
        if not use_pure and CMySQLConnection is None:
            raise ImportError(ERROR_NO_CEXT)

    if CMySQLConnection and not use_pure:
        return CMySQLConnection(*args, **kwargs)
    return MySQLConnection(*args, **kwargs)


def generate_pool_name(**kwargs: Any) -> str:
    """Generate a pool name

    This function takes keyword arguments, usually the connection
    arguments for MySQLConnection, and tries to generate a name for
    a pool.

    Raises PoolError when no name can be generated.

    Returns a string.
    """
    parts = []
    for key in ("host", "port", "user", "database"):
        try:
            parts.append(str(kwargs[key]))
        except KeyError:
            pass

    if not parts:
        raise PoolError("Failed generating pool name; specify pool_name")

    return "_".join(parts)


class PooledMySQLConnection:
    """Class holding a MySQL Connection in a pool

    PooledMySQLConnection is used by MySQLConnectionPool to return an
    instance holding a MySQL connection. It works like a MySQLConnection
    except for methods like close() and config().

    The close()-method will add the connection back to the pool rather
    than disconnecting from the MySQL server.

    Configuring the connection have to be done through the MySQLConnectionPool
    method set_config(). Using config() on pooled connection will raise a
    PoolError.
    """

    def __init__(
        self, pool: MySQLConnectionPool, cnx: Union[MySQLConnection, CMySQLConnection]
    ) -> None:
        """Initialize

        The pool argument must be an instance of MySQLConnectionPoll. cnx
        if an instance of MySQLConnection.
        """
        if not isinstance(pool, MySQLConnectionPool):
            raise AttributeError("pool should be a MySQLConnectionPool")
        if not isinstance(cnx, MYSQL_CNX_CLASS):
            raise AttributeError("cnx should be a MySQLConnection")
        self._cnx_pool: MySQLConnectionPool = pool
        self._cnx: Union[MySQLConnection, CMySQLConnection] = cnx

    def __enter__(self) -> PooledMySQLConnection:
        return self

    def __exit__(
        self,
        exc_type: Type[BaseException],
        exc_value: BaseException,
        traceback: TracebackType,
    ) -> None:
        self.close()

    def __getattr__(self, attr: Any) -> Any:
        """Calls attributes of the MySQLConnection instance"""
        return getattr(self._cnx, attr)

    def close(self) -> None:
        """Do not close, but add connection back to pool

        The close() method does not close the connection with the
        MySQL server. The connection is added back to the pool so it
        can be reused.

        When the pool is configured to reset the session, the session
        state will be cleared by re-authenticating the user.
        """
        try:
            cnx = self._cnx
            if self._cnx_pool.reset_session:
                cnx.reset_session()
        finally:
            self._cnx_pool.add_connection(cnx)
            self._cnx = None

    @staticmethod
    def config(**kwargs: Any) -> NoReturn:
        """Configuration is done through the pool"""
        raise PoolError(
            "Configuration for pooled connections should be done through the "
            "pool itself"
        )

    @property
    def pool_name(self) -> str:
        """Return the name of the connection pool"""
        return self._cnx_pool.pool_name


class MySQLConnectionPool:
    """Class defining a pool of MySQL connections"""

    def __init__(
        self,
        pool_size: int = 5,
        pool_name: Optional[str] = None,
        pool_reset_session: bool = True,
        **kwargs: Any,
    ) -> None:
        """Initialize

        Initialize a MySQL connection pool with a maximum number of
        connections set to pool_size. The rest of the keywords
        arguments, kwargs, are configuration arguments for MySQLConnection
        instances.
        """
        self._pool_size: Optional[int] = None
        self._pool_name: Optional[str] = None
        self._reset_session = pool_reset_session
        self._set_pool_size(pool_size)
        self._set_pool_name(pool_name or generate_pool_name(**kwargs))
        self._cnx_config: Dict[str, Any] = {}
        self._cnx_queue: queue.Queue[
            Union[MySQLConnection, CMySQLConnection]
        ] = queue.Queue(self._pool_size)
        self._config_version = uuid4()

        if kwargs:
            self.set_config(**kwargs)
            cnt = 0
            while cnt < self._pool_size:
                self.add_connection()
                cnt += 1

    @property
    def pool_name(self) -> str:
        """Return the name of the connection pool"""
        return self._pool_name

    @property
    def pool_size(self) -> int:
        """Return number of connections managed by the pool"""
        return self._pool_size

    @property
    def reset_session(self) -> bool:
        """Return whether to reset session"""
        return self._reset_session

    def set_config(self, **kwargs: Any) -> None:
        """Set the connection configuration for MySQLConnection instances

        This method sets the configuration used for creating MySQLConnection
        instances. See MySQLConnection for valid connection arguments.

        Raises PoolError when a connection argument is not valid, missing
        or not supported by MySQLConnection.
        """
        if not kwargs:
            return

        with CONNECTION_POOL_LOCK:
            try:
                test_cnx = connect()
                test_cnx.config(**kwargs)
                self._cnx_config = kwargs
                self._config_version = uuid4()
            except AttributeError as err:
                raise PoolError(f"Connection configuration not valid: {err}") from err

    def _set_pool_size(self, pool_size: int) -> None:
        """Set the size of the pool

        This method sets the size of the pool but it will not resize the pool.

        Raises an AttributeError when the pool_size is not valid. Invalid size
        is 0, negative or higher than pooling.CNX_POOL_MAXSIZE.
        """
        if pool_size <= 0 or pool_size > CNX_POOL_MAXSIZE:
            raise AttributeError(
                "Pool size should be higher than 0 and lower or equal to "
                f"{CNX_POOL_MAXSIZE}"
            )
        self._pool_size = pool_size

    def _set_pool_name(self, pool_name: str) -> None:
        r"""Set the name of the pool.

        This method checks the validity and sets the name of the pool.

        Raises an AttributeError when pool_name contains illegal characters
        ([^a-zA-Z0-9._\-*$#]) or is longer than pooling.CNX_POOL_MAXNAMESIZE.
        """
        if CNX_POOL_NAMEREGEX.search(pool_name):
            raise AttributeError(f"Pool name '{pool_name}' contains illegal characters")
        if len(pool_name) > CNX_POOL_MAXNAMESIZE:
            raise AttributeError(f"Pool name '{pool_name}' is too long")
        self._pool_name = pool_name

    def _queue_connection(self, cnx: Union[MySQLConnection, CMySQLConnection]) -> None:
        """Put connection back in the queue

        This method is putting a connection back in the queue. It will not
        acquire a lock as the methods using _queue_connection() will have it
        set.

        Raises PoolError on errors.
        """
        if not isinstance(cnx, MYSQL_CNX_CLASS):
            raise PoolError("Connection instance not subclass of MySQLConnection")

        try:
            self._cnx_queue.put(cnx, block=False)
        except queue.Full as err:
            raise PoolError("Failed adding connection; queue is full") from err

    def add_connection(
        self, cnx: Optional[Union[MySQLConnection, CMySQLConnection]] = None
    ) -> None:
        """Add a connection to the pool

        This method instantiates a MySQLConnection using the configuration
        passed when initializing the MySQLConnectionPool instance or using
        the set_config() method.
        If cnx is a MySQLConnection instance, it will be added to the
        queue.

        Raises PoolError when no configuration is set, when no more
        connection can be added (maximum reached) or when the connection
        can not be instantiated.
        """
        with CONNECTION_POOL_LOCK:
            if not self._cnx_config:
                raise PoolError("Connection configuration not available")

            if self._cnx_queue.full():
                raise PoolError("Failed adding connection; queue is full")

            if not cnx:
                cnx = connect(**self._cnx_config)  # type: ignore[assignment]
                try:
                    if (
                        self._reset_session
                        and self._cnx_config["compress"]
                        and cnx.get_server_version() < (5, 7, 3)
                    ):
                        raise NotSupportedError(
                            "Pool reset session is not supported with "
                            "compression for MySQL server version 5.7.2 "
                            "or earlier"
                        )
                except KeyError:
                    pass

                cnx.pool_config_version = self._config_version
            else:
                if not isinstance(cnx, MYSQL_CNX_CLASS):
                    raise PoolError(
                        "Connection instance not subclass of MySQLConnection"
                    )

            self._queue_connection(cnx)

    def get_connection(self) -> PooledMySQLConnection:
        """Get a connection from the pool

        This method returns an PooledMySQLConnection instance which
        has a reference to the pool that created it, and the next available
        MySQL connection.

        When the MySQL connection is not connect, a reconnect is attempted.

        Raises PoolError on errors.

        Returns a PooledMySQLConnection instance.
        """
        with CONNECTION_POOL_LOCK:
            try:
                cnx = self._cnx_queue.get(block=False)
            except queue.Empty as err:
                raise PoolError("Failed getting connection; pool exhausted") from err

            if (
                not cnx.is_connected()
                or self._config_version != cnx.pool_config_version
            ):
                cnx.config(**self._cnx_config)
                try:
                    cnx.reconnect()
                except InterfaceError:
                    # Failed to reconnect, give connection back to pool
                    self._queue_connection(cnx)
                    raise
                cnx.pool_config_version = self._config_version

            return PooledMySQLConnection(self, cnx)

    def _remove_connections(self) -> int:
        """Close all connections

        This method closes all connections. It returns the number
        of connections it closed.

        Used mostly for tests.

        Returns int.
        """
        with CONNECTION_POOL_LOCK:
            cnt = 0
            cnxq = self._cnx_queue
            while cnxq.qsize():
                try:
                    cnx = cnxq.get(block=False)
                    cnx.disconnect()
                    cnt += 1
                except queue.Empty:
                    return cnt
                except PoolError:
                    raise
                except Error:
                    # Any other error when closing means connection is closed
                    pass

            return cnt

virt/lib/python3.9/site-packages/mysql/connector/pooling 3.py
PrinceDisant/LostInNL
# Copyright (c) 2013, 2022, Oracle and/or its affiliates. All rights reserved.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

"""Implementing pooling of connections to MySQL servers."""
from __future__ import annotations

import queue
import random
import re
import threading

from types import TracebackType
from typing import Any, Dict, NoReturn, Optional, Tuple, Type, Union
from uuid import uuid4

try:
    import dns.exception
    import dns.resolver
except ImportError:
    HAVE_DNSPYTHON = False
else:
    HAVE_DNSPYTHON = True

try:
    from .connection_cext import CMySQLConnection
except ImportError:
    CMySQLConnection = None  # type: ignore[misc]

from .connection import MySQLConnection
from .constants import CNX_POOL_ARGS, DEFAULT_CONFIGURATION
from .errors import (
    Error,
    InterfaceError,
    NotSupportedError,
    PoolError,
    ProgrammingError,
)
from .optionfiles import read_option_files

CONNECTION_POOL_LOCK = threading.RLock()
CNX_POOL_MAXSIZE = 32
CNX_POOL_MAXNAMESIZE = 64
CNX_POOL_NAMEREGEX = re.compile(r"[^a-zA-Z0-9._:\-*$#]")
ERROR_NO_CEXT = "MySQL Connector/Python C Extension not available"
MYSQL_CNX_CLASS: Union[type, Tuple[type, ...]] = (
    MySQLConnection if CMySQLConnection is None else (MySQLConnection, CMySQLConnection)
)

_CONNECTION_POOLS: Dict[str, MySQLConnectionPool] = {}


def _get_pooled_connection(**kwargs: Any) -> PooledMySQLConnection:
    """Return a pooled MySQL connection."""
    # If no pool name specified, generate one
    pool_name = (
        kwargs["pool_name"] if "pool_name" in kwargs else generate_pool_name(**kwargs)
    )

    if kwargs.get("use_pure") is False and CMySQLConnection is None:
        raise ImportError(ERROR_NO_CEXT)

    # Setup the pool, ensuring only 1 thread can update at a time
    with CONNECTION_POOL_LOCK:
        if pool_name not in _CONNECTION_POOLS:
            _CONNECTION_POOLS[pool_name] = MySQLConnectionPool(**kwargs)
        elif isinstance(_CONNECTION_POOLS[pool_name], MySQLConnectionPool):
            # pool_size must be the same
            check_size = _CONNECTION_POOLS[pool_name].pool_size
            if "pool_size" in kwargs and kwargs["pool_size"] != check_size:
                raise PoolError("Size can not be changed for active pools.")

    # Return pooled connection
    try:
        return _CONNECTION_POOLS[pool_name].get_connection()
    except AttributeError:
        raise InterfaceError(
            f"Failed getting connection from pool '{pool_name}'"
        ) from None


def _get_failover_connection(
    **kwargs: Any,
) -> Union[PooledMySQLConnection, MySQLConnection, CMySQLConnection]:
    """Return a MySQL connection and try to failover if needed.

    An InterfaceError is raise when no MySQL is available. ValueError is
    raised when the failover server configuration contains an illegal
    connection argument. Supported arguments are user, password, host, port,
    unix_socket and database. ValueError is also raised when the failover
    argument was not provided.

    Returns MySQLConnection instance.
    """
    config = kwargs.copy()
    try:
        failover = config["failover"]
    except KeyError:
        raise ValueError("failover argument not provided") from None
    del config["failover"]

    support_cnx_args = set(
        [
            "user",
            "password",
            "host",
            "port",
            "unix_socket",
            "database",
            "pool_name",
            "pool_size",
            "priority",
        ]
    )

    # First check if we can add all use the configuration
    priority_count = 0
    for server in failover:
        diff = set(server.keys()) - support_cnx_args
        if diff:
            arg = "s" if len(diff) > 1 else ""
            lst = ", ".join(diff)
            raise ValueError(
                f"Unsupported connection argument {arg} in failover: {lst}"
            )
        if hasattr(server, "priority"):
            priority_count += 1

        server["priority"] = server.get("priority", 100)
        if server["priority"] < 0 or server["priority"] > 100:
            raise InterfaceError(
                "Priority value should be in the range of 0 to 100, "
                f"got : {server['priority']}"
            )
        if not isinstance(server["priority"], int):
            raise InterfaceError(
                "Priority value should be an integer in the range of 0 to "
                f"100, got : {server['priority']}"
            )

    if 0 < priority_count < len(failover):
        raise ProgrammingError(
            "You must either assign no priority to any "
            "of the routers or give a priority for "
            "every router"
        )

    server_directory = {}
    server_priority_list = []
    for server in sorted(failover, key=lambda x: x["priority"], reverse=True):
        if server["priority"] not in server_directory:
            server_directory[server["priority"]] = [server]
            server_priority_list.append(server["priority"])
        else:
            server_directory[server["priority"]].append(server)

    for priority in server_priority_list:
        failover_list = server_directory[priority]
        for _ in range(len(failover_list)):
            last = len(failover_list) - 1
            index = random.randint(0, last)
            server = failover_list.pop(index)
            new_config = config.copy()
            new_config.update(server)
            new_config.pop("priority", None)
            try:
                return connect(**new_config)
            except Error:
                # If we failed to connect, we try the next server
                pass

    raise InterfaceError("Unable to connect to any of the target hosts")


def connect(
    *args: Any, **kwargs: Any
) -> Union[PooledMySQLConnection, MySQLConnection, CMySQLConnection]:
    """Create or get a MySQL connection object.

    In its simpliest form, connect() will open a connection to a
    MySQL server and return a MySQLConnection object.

    When any connection pooling arguments are given, for example pool_name
    or pool_size, a pool is created or a previously one is used to return
    a PooledMySQLConnection.

    Returns MySQLConnection or PooledMySQLConnection.
    """
    # DNS SRV
    dns_srv = kwargs.pop("dns_srv") if "dns_srv" in kwargs else False

    if not isinstance(dns_srv, bool):
        raise InterfaceError("The value of 'dns-srv' must be a boolean")

    if dns_srv:
        if not HAVE_DNSPYTHON:
            raise InterfaceError(
                "MySQL host configuration requested DNS "
                "SRV. This requires the Python dnspython "
                "module. Please refer to documentation"
            )
        if "unix_socket" in kwargs:
            raise InterfaceError(
                "Using Unix domain sockets with DNS SRV lookup is not allowed"
            )
        if "port" in kwargs:
            raise InterfaceError(
                "Specifying a port number with DNS SRV lookup is not allowed"
            )
        if "failover" in kwargs:
            raise InterfaceError(
                "Specifying multiple hostnames with DNS SRV look up is not allowed"
            )
        if "host" not in kwargs:
            kwargs["host"] = DEFAULT_CONFIGURATION["host"]

        try:
            srv_records = dns.resolver.query(kwargs["host"], "SRV")
        except dns.exception.DNSException:
            raise InterfaceError(
                f"Unable to locate any hosts for '{kwargs['host']}'"
            ) from None

        failover = []
        for srv in srv_records:
            failover.append(
                {
                    "host": srv.target.to_text(omit_final_dot=True),
                    "port": srv.port,
                    "priority": srv.priority,
                    "weight": srv.weight,
                }
            )

        failover.sort(key=lambda x: (x["priority"], -x["weight"]))
        kwargs["failover"] = [
            {"host": srv["host"], "port": srv["port"]} for srv in failover
        ]

    # Option files
    if "read_default_file" in kwargs:
        kwargs["option_files"] = kwargs["read_default_file"]
        kwargs.pop("read_default_file")

    if "option_files" in kwargs:
        new_config = read_option_files(**kwargs)
        return connect(**new_config)

    # Failover
    if "failover" in kwargs:
        return _get_failover_connection(**kwargs)

    # Pooled connections
    try:
        if any(key in kwargs for key in CNX_POOL_ARGS):
            return _get_pooled_connection(**kwargs)
    except NameError:
        # No pooling
        pass

    # Use C Extension by default
    use_pure = kwargs.get("use_pure", False)
    if "use_pure" in kwargs:
        del kwargs["use_pure"]  # Remove 'use_pure' from kwargs
        if not use_pure and CMySQLConnection is None:
            raise ImportError(ERROR_NO_CEXT)

    if CMySQLConnection and not use_pure:
        return CMySQLConnection(*args, **kwargs)
    return MySQLConnection(*args, **kwargs)


def generate_pool_name(**kwargs: Any) -> str:
    """Generate a pool name

    This function takes keyword arguments, usually the connection
    arguments for MySQLConnection, and tries to generate a name for
    a pool.

    Raises PoolError when no name can be generated.

    Returns a string.
    """
    parts = []
    for key in ("host", "port", "user", "database"):
        try:
            parts.append(str(kwargs[key]))
        except KeyError:
            pass

    if not parts:
        raise PoolError("Failed generating pool name; specify pool_name")

    return "_".join(parts)


class PooledMySQLConnection:
    """Class holding a MySQL Connection in a pool

    PooledMySQLConnection is used by MySQLConnectionPool to return an
    instance holding a MySQL connection. It works like a MySQLConnection
    except for methods like close() and config().

    The close()-method will add the connection back to the pool rather
    than disconnecting from the MySQL server.

    Configuring the connection have to be done through the MySQLConnectionPool
    method set_config(). Using config() on pooled connection will raise a
    PoolError.
    """

    def __init__(
        self, pool: MySQLConnectionPool, cnx: Union[MySQLConnection, CMySQLConnection]
    ) -> None:
        """Initialize

        The pool argument must be an instance of MySQLConnectionPoll. cnx
        if an instance of MySQLConnection.
        """
        if not isinstance(pool, MySQLConnectionPool):
            raise AttributeError("pool should be a MySQLConnectionPool")
        if not isinstance(cnx, MYSQL_CNX_CLASS):
            raise AttributeError("cnx should be a MySQLConnection")
        self._cnx_pool: MySQLConnectionPool = pool
        self._cnx: Union[MySQLConnection, CMySQLConnection] = cnx

    def __enter__(self) -> PooledMySQLConnection:
        return self

    def __exit__(
        self,
        exc_type: Type[BaseException],
        exc_value: BaseException,
        traceback: TracebackType,
    ) -> None:
        self.close()

    def __getattr__(self, attr: Any) -> Any:
        """Calls attributes of the MySQLConnection instance"""
        return getattr(self._cnx, attr)

    def close(self) -> None:
        """Do not close, but add connection back to pool

        The close() method does not close the connection with the
        MySQL server. The connection is added back to the pool so it
        can be reused.

        When the pool is configured to reset the session, the session
        state will be cleared by re-authenticating the user.
        """
        try:
            cnx = self._cnx
            if self._cnx_pool.reset_session:
                cnx.reset_session()
        finally:
            self._cnx_pool.add_connection(cnx)
            self._cnx = None

    @staticmethod
    def config(**kwargs: Any) -> NoReturn:
        """Configuration is done through the pool"""
        raise PoolError(
            "Configuration for pooled connections should be done through the "
            "pool itself"
        )

    @property
    def pool_name(self) -> str:
        """Return the name of the connection pool"""
        return self._cnx_pool.pool_name


class MySQLConnectionPool:
    """Class defining a pool of MySQL connections"""

    def __init__(
        self,
        pool_size: int = 5,
        pool_name: Optional[str] = None,
        pool_reset_session: bool = True,
        **kwargs: Any,
    ) -> None:
        """Initialize

        Initialize a MySQL connection pool with a maximum number of
        connections set to pool_size. The rest of the keywords
        arguments, kwargs, are configuration arguments for MySQLConnection
        instances.
        """
        self._pool_size: Optional[int] = None
        self._pool_name: Optional[str] = None
        self._reset_session = pool_reset_session
        self._set_pool_size(pool_size)
        self._set_pool_name(pool_name or generate_pool_name(**kwargs))
        self._cnx_config: Dict[str, Any] = {}
        self._cnx_queue: queue.Queue[
            Union[MySQLConnection, CMySQLConnection]
        ] = queue.Queue(self._pool_size)
        self._config_version = uuid4()

        if kwargs:
            self.set_config(**kwargs)
            cnt = 0
            while cnt < self._pool_size:
                self.add_connection()
                cnt += 1

    @property
    def pool_name(self) -> str:
        """Return the name of the connection pool"""
        return self._pool_name

    @property
    def pool_size(self) -> int:
        """Return number of connections managed by the pool"""
        return self._pool_size

    @property
    def reset_session(self) -> bool:
        """Return whether to reset session"""
        return self._reset_session

    def set_config(self, **kwargs: Any) -> None:
        """Set the connection configuration for MySQLConnection instances

        This method sets the configuration used for creating MySQLConnection
        instances. See MySQLConnection for valid connection arguments.

        Raises PoolError when a connection argument is not valid, missing
        or not supported by MySQLConnection.
        """
        if not kwargs:
            return

        with CONNECTION_POOL_LOCK:
            try:
                test_cnx = connect()
                test_cnx.config(**kwargs)
                self._cnx_config = kwargs
                self._config_version = uuid4()
            except AttributeError as err:
                raise PoolError(f"Connection configuration not valid: {err}") from err

    def _set_pool_size(self, pool_size: int) -> None:
        """Set the size of the pool

        This method sets the size of the pool but it will not resize the pool.

        Raises an AttributeError when the pool_size is not valid. Invalid size
        is 0, negative or higher than pooling.CNX_POOL_MAXSIZE.
        """
        if pool_size <= 0 or pool_size > CNX_POOL_MAXSIZE:
            raise AttributeError(
                "Pool size should be higher than 0 and lower or equal to "
                f"{CNX_POOL_MAXSIZE}"
            )
        self._pool_size = pool_size

    def _set_pool_name(self, pool_name: str) -> None:
        r"""Set the name of the pool.

        This method checks the validity and sets the name of the pool.

        Raises an AttributeError when pool_name contains illegal characters
        ([^a-zA-Z0-9._\-*$#]) or is longer than pooling.CNX_POOL_MAXNAMESIZE.
        """
        if CNX_POOL_NAMEREGEX.search(pool_name):
            raise AttributeError(f"Pool name '{pool_name}' contains illegal characters")
        if len(pool_name) > CNX_POOL_MAXNAMESIZE:
            raise AttributeError(f"Pool name '{pool_name}' is too long")
        self._pool_name = pool_name

    def _queue_connection(self, cnx: Union[MySQLConnection, CMySQLConnection]) -> None:
        """Put connection back in the queue

        This method is putting a connection back in the queue. It will not
        acquire a lock as the methods using _queue_connection() will have it
        set.

        Raises PoolError on errors.
        """
        if not isinstance(cnx, MYSQL_CNX_CLASS):
            raise PoolError("Connection instance not subclass of MySQLConnection")

        try:
            self._cnx_queue.put(cnx, block=False)
        except queue.Full as err:
            raise PoolError("Failed adding connection; queue is full") from err

    def add_connection(
        self, cnx: Optional[Union[MySQLConnection, CMySQLConnection]] = None
    ) -> None:
        """Add a connection to the pool

        This method instantiates a MySQLConnection using the configuration
        passed when initializing the MySQLConnectionPool instance or using
        the set_config() method.
        If cnx is a MySQLConnection instance, it will be added to the
        queue.

        Raises PoolError when no configuration is set, when no more
        connection can be added (maximum reached) or when the connection
        can not be instantiated.
        """
        with CONNECTION_POOL_LOCK:
            if not self._cnx_config:
                raise PoolError("Connection configuration not available")

            if self._cnx_queue.full():
                raise PoolError("Failed adding connection; queue is full")

            if not cnx:
                cnx = connect(**self._cnx_config)  # type: ignore[assignment]
                try:
                    if (
                        self._reset_session
                        and self._cnx_config["compress"]
                        and cnx.get_server_version() < (5, 7, 3)
                    ):
                        raise NotSupportedError(
                            "Pool reset session is not supported with "
                            "compression for MySQL server version 5.7.2 "
                            "or earlier"
                        )
                except KeyError:
                    pass

                cnx.pool_config_version = self._config_version
            else:
                if not isinstance(cnx, MYSQL_CNX_CLASS):
                    raise PoolError(
                        "Connection instance not subclass of MySQLConnection"
                    )

            self._queue_connection(cnx)

    def get_connection(self) -> PooledMySQLConnection:
        """Get a connection from the pool

        This method returns an PooledMySQLConnection instance which
        has a reference to the pool that created it, and the next available
        MySQL connection.

        When the MySQL connection is not connect, a reconnect is attempted.

        Raises PoolError on errors.

        Returns a PooledMySQLConnection instance.
        """
        with CONNECTION_POOL_LOCK:
            try:
                cnx = self._cnx_queue.get(block=False)
            except queue.Empty as err:
                raise PoolError("Failed getting connection; pool exhausted") from err

            if (
                not cnx.is_connected()
                or self._config_version != cnx.pool_config_version
            ):
                cnx.config(**self._cnx_config)
                try:
                    cnx.reconnect()
                except InterfaceError:
                    # Failed to reconnect, give connection back to pool
                    self._queue_connection(cnx)
                    raise
                cnx.pool_config_version = self._config_version

            return PooledMySQLConnection(self, cnx)

    def _remove_connections(self) -> int:
        """Close all connections

        This method closes all connections. It returns the number
        of connections it closed.

        Used mostly for tests.

        Returns int.
        """
        with CONNECTION_POOL_LOCK:
            cnt = 0
            cnxq = self._cnx_queue
            while cnxq.qsize():
                try:
                    cnx = cnxq.get(block=False)
                    cnx.disconnect()
                    cnt += 1
                except queue.Empty:
                    return cnt
                except PoolError:
                    raise
                except Error:
                    # Any other error when closing means connection is closed
                    pass

            return cnt

virt/lib/python3.9/site-packages/mysql/connector/pooling.py
PrinceDisant/LostInNL
# Copyright (c) 2009, 2022, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

# mypy: disable-error-code="assignment"

"""Implements the MySQL Client/Server protocol."""

import datetime
import struct

from decimal import Decimal, DecimalException
from typing import Any, Dict, List, Optional, Sequence, Tuple, Union

from . import utils
from .authentication import get_auth_plugin
from .constants import (
    PARAMETER_COUNT_AVAILABLE,
    ClientFlag,
    FieldFlag,
    FieldType,
    ServerCmd,
)
from .errors import DatabaseError, InterfaceError, ProgrammingError, get_exception
from .types import (
    ConnAttrsType,
    DescriptionType,
    EofPacketType,
    HandShakeType,
    OkPacketType,
    ParseValueFromBinaryResultPacketTypes,
    SocketType,
    StatsPacketType,
    StrOrBytes,
    SupportedMysqlBinaryProtocolTypes,
)

PROTOCOL_VERSION: int = 10


class MySQLProtocol:
    """Implements MySQL client/server protocol

    Create and parses MySQL packets.
    """

    @staticmethod
    def _connect_with_db(client_flags: int, database: Optional[str]) -> bytes:
        """Prepare database string for handshake response"""
        if client_flags & ClientFlag.CONNECT_WITH_DB and database:
            return database.encode("utf8") + b"\x00"
        return b"\x00"

    @staticmethod
    def _auth_response(
        client_flags: int,
        username: Optional[StrOrBytes],
        password: Optional[str],
        database: Optional[str],
        auth_plugin_class: str,
        auth_plugin: str,
        auth_data: Optional[bytes],
        ssl_enabled: bool,
    ) -> bytes:
        """Prepare the authentication response"""
        if not password:
            return b"\x00"

        try:
            auth = get_auth_plugin(auth_plugin, auth_plugin_class)(
                auth_data,
                username=username,
                password=password,
                database=database,
                ssl_enabled=ssl_enabled,
            )
            plugin_auth_response = auth.auth_response()
            # # We could receive a NULL response, an Interface error is called when so
            # if plugin_auth_response is None:
            #     raise InterfaceError
        except (TypeError, InterfaceError) as err:
            raise InterfaceError(f"Failed authentication: {err}") from err

        if client_flags & ClientFlag.SECURE_CONNECTION:
            resplen = len(plugin_auth_response)
            auth_response = struct.pack("<B", resplen) + plugin_auth_response
        else:
            auth_response = plugin_auth_response + b"\x00"
        return auth_response

    def make_auth(
        self,
        handshake: Optional[HandShakeType],
        username: Optional[StrOrBytes] = None,
        password: Optional[str] = None,
        database: Optional[str] = None,
        charset: int = 45,
        client_flags: int = 0,
        max_allowed_packet: int = 1073741824,
        ssl_enabled: bool = False,
        auth_plugin: Optional[str] = None,
        conn_attrs: Optional[ConnAttrsType] = None,
        auth_plugin_class: Optional[str] = None,
    ) -> bytes:
        """Make a MySQL Authentication packet"""
        if handshake is None:
            handshake = {}
        try:
            auth_data: Optional[bytes] = handshake["auth_data"]
            auth_plugin = auth_plugin or handshake["auth_plugin"]
        except (TypeError, KeyError) as err:
            raise ProgrammingError(
                f"Handshake misses authentication info ({err})"
            ) from None

        if not username:
            username = b""
        try:
            username_bytes = username.encode("utf8")  # type: ignore[union-attr]
        except AttributeError:
            # Username is already bytes
            username_bytes = username

        filler = "x" * 22
        username_len = len(username_bytes)

        packet = struct.pack(
            f"<IIH{filler}{username_len}sx",
            client_flags,
            max_allowed_packet,
            charset,
            username_bytes,
        )

        packet += self._auth_response(
            client_flags,
            username,
            password,
            database,
            auth_plugin_class,
            auth_plugin,
            auth_data,
            ssl_enabled,
        )

        packet += self._connect_with_db(client_flags, database)

        if client_flags & ClientFlag.PLUGIN_AUTH:
            packet += auth_plugin.encode("utf8") + b"\x00"

        if (client_flags & ClientFlag.CONNECT_ARGS) and conn_attrs is not None:
            packet += self.make_conn_attrs(conn_attrs)

        return packet

    @staticmethod
    def make_conn_attrs(conn_attrs: ConnAttrsType) -> bytes:
        """Encode the connection attributes"""
        for attr_name in conn_attrs:
            if conn_attrs[attr_name] is None:
                conn_attrs[attr_name] = ""
        conn_attrs_len = (
            sum(len(x) + len(conn_attrs[x]) for x in conn_attrs)
            + len(conn_attrs.keys())
            + len(conn_attrs.values())
        )

        conn_attrs_packet = struct.pack("<B", conn_attrs_len)
        for attr_name in conn_attrs:
            conn_attrs_packet += struct.pack("<B", len(attr_name))
            conn_attrs_packet += attr_name.encode("utf8")
            conn_attrs_packet += struct.pack("<B", len(conn_attrs[attr_name]))
            conn_attrs_packet += conn_attrs[attr_name].encode("utf8")  # type: ignore[union-attr]
        return conn_attrs_packet

    @staticmethod
    def make_auth_ssl(
        charset: int = 45, client_flags: int = 0, max_allowed_packet: int = 1073741824
    ) -> bytearray:
        """Make a SSL authentication packet"""
        return (
            utils.int4store(client_flags)
            + utils.int4store(max_allowed_packet)
            + utils.int2store(charset)
            + b"\x00" * 22
        )

    @staticmethod
    def make_command(command: int, argument: Optional[bytes] = None) -> bytearray:
        """Make a MySQL packet containing a command"""
        data = utils.int1store(command)
        if argument is not None:
            data += argument
        return data

    @staticmethod
    def make_stmt_fetch(statement_id: int, rows: int = 1) -> bytearray:
        """Make a MySQL packet with Fetch Statement command"""
        return utils.int4store(statement_id) + utils.int4store(rows)

    def make_change_user(
        self,
        handshake: HandShakeType,
        username: Optional[StrOrBytes] = None,
        password: Optional[str] = None,
        database: Optional[str] = None,
        charset: int = 45,
        client_flags: int = 0,
        ssl_enabled: bool = False,
        auth_plugin: Optional[str] = None,
        conn_attrs: Optional[ConnAttrsType] = None,
        auth_plugin_class: Optional[str] = None,
    ) -> bytes:
        """Make a MySQL packet with the Change User command"""

        try:
            auth_data: Optional[bytes] = handshake["auth_data"]
            auth_plugin = auth_plugin or handshake["auth_plugin"]
        except (TypeError, KeyError) as err:
            raise ProgrammingError(
                f"Handshake misses authentication info ({err})"
            ) from None

        if not username:
            username = b""
        try:
            username_bytes = username.encode("utf8")  # type: ignore[union-attr]
        except AttributeError:
            # Username is already bytes
            username_bytes = username

        username_len = len(username_bytes)
        packet = struct.pack(
            f"<B{username_len}sx",
            ServerCmd.CHANGE_USER,
            username_bytes,
        )

        packet += self._auth_response(
            client_flags,
            username,
            password,
            database,
            auth_plugin_class,
            auth_plugin,
            auth_data,
            ssl_enabled,
        )

        packet += self._connect_with_db(client_flags, database)

        packet += struct.pack("<H", charset)

        if client_flags & ClientFlag.PLUGIN_AUTH:
            packet += auth_plugin.encode("utf8") + b"\x00"

        if (client_flags & ClientFlag.CONNECT_ARGS) and conn_attrs is not None:
            packet += self.make_conn_attrs(conn_attrs)

        return packet

    @staticmethod
    def parse_handshake(packet: bytes) -> HandShakeType:
        """Parse a MySQL Handshake-packet"""
        res = {}
        res["protocol"] = struct.unpack("<xxxxB", packet[0:5])[0]
        if res["protocol"] != PROTOCOL_VERSION:
            raise DatabaseError(
                f"Protocol mismatch; server version = {res['protocol']}, "
                f"client version = {PROTOCOL_VERSION}"
            )
        packet, res["server_version_original"] = utils.read_string(
            packet[5:], end=b"\x00"
        )

        (
            res["server_threadid"],
            auth_data1,
            capabilities1,
            res["charset"],
            res["server_status"],
            capabilities2,
            auth_data_length,
        ) = struct.unpack("<I8sx2sBH2sBxxxxxxxxxx", packet[0:31])
        res["server_version_original"] = res["server_version_original"].decode()

        packet = packet[31:]

        capabilities = utils.intread(capabilities1 + capabilities2)
        auth_data2 = b""
        if capabilities & ClientFlag.SECURE_CONNECTION:
            size = min(13, auth_data_length - 8) if auth_data_length else 13
            auth_data2 = packet[0:size]
            packet = packet[size:]
            if auth_data2[-1] == 0:
                auth_data2 = auth_data2[:-1]

        if capabilities & ClientFlag.PLUGIN_AUTH:
            if b"\x00" not in packet and res["server_version_original"].startswith(
                "5.5.8"
            ):
                # MySQL server 5.5.8 has a bug where end byte is not send
                (packet, res["auth_plugin"]) = (b"", packet)
            else:
                (packet, res["auth_plugin"]) = utils.read_string(packet, end=b"\x00")
            res["auth_plugin"] = res["auth_plugin"].decode("utf-8")
        else:
            res["auth_plugin"] = "mysql_native_password"

        res["auth_data"] = auth_data1 + auth_data2
        res["capabilities"] = capabilities
        return res

    @staticmethod
    def parse_auth_next_factor(packet: bytes) -> Tuple[bytes, str]:
        """Parse a MySQL AuthNextFactor packet."""
        packet, status = utils.read_int(packet, 1)
        if status != 2:
            raise InterfaceError("Failed parsing AuthNextFactor packet (invalid)")
        packet, auth_plugin = utils.read_string(packet, end=b"\x00")
        return packet, auth_plugin.decode("utf-8")

    @staticmethod
    def parse_ok(packet: bytes) -> OkPacketType:
        """Parse a MySQL OK-packet"""
        if not packet[4] == 0:
            raise InterfaceError("Failed parsing OK packet (invalid).")

        ok_packet = {}
        try:
            ok_packet["field_count"] = struct.unpack("<xxxxB", packet[0:5])[0]
            packet, ok_packet["affected_rows"] = utils.read_lc_int(packet[5:])
            packet, ok_packet["insert_id"] = utils.read_lc_int(packet)
            (
                ok_packet["status_flag"],
                ok_packet["warning_count"],
            ) = struct.unpack("<HH", packet[0:4])
            packet = packet[4:]
            if packet:
                packet, ok_packet["info_msg"] = utils.read_lc_string(packet)
                ok_packet["info_msg"] = ok_packet["info_msg"].decode("utf-8")
        except ValueError as err:
            raise InterfaceError("Failed parsing OK packet.") from err
        return ok_packet

    @staticmethod
    def parse_column_count(packet: bytes) -> Optional[int]:
        """Parse a MySQL packet with the number of columns in result set"""
        try:
            count = utils.read_lc_int(packet[4:])[1]
            return count
        except (struct.error, ValueError) as err:
            raise InterfaceError("Failed parsing column count") from err

    @staticmethod
    def parse_column(packet: bytes, encoding: str = "utf-8") -> DescriptionType:
        """Parse a MySQL column-packet"""
        packet, _ = utils.read_lc_string(packet[4:])  # catalog
        packet, _ = utils.read_lc_string(packet)  # db
        packet, _ = utils.read_lc_string(packet)  # table
        packet, _ = utils.read_lc_string(packet)  # org_table
        packet, name = utils.read_lc_string(packet)  # name
        packet, _ = utils.read_lc_string(packet)  # org_name

        try:
            (
                charset,
                _,
                column_type,
                flags,
                _,
            ) = struct.unpack("<xHIBHBxx", packet)
        except struct.error:
            raise InterfaceError("Failed parsing column information") from None

        return (
            name.decode(encoding),
            column_type,
            None,  # display_size
            None,  # internal_size
            None,  # precision
            None,  # scale
            ~flags & FieldFlag.NOT_NULL,  # null_ok
            flags,  # MySQL specific
            charset,
        )

    def parse_eof(self, packet: bytes) -> EofPacketType:
        """Parse a MySQL EOF-packet"""
        if packet[4] == 0:
            # EOF packet deprecation
            return self.parse_ok(packet)

        err_msg = "Failed parsing EOF packet."
        res = {}
        try:
            unpacked = struct.unpack("<xxxBBHH", packet)
        except struct.error as err:
            raise InterfaceError(err_msg) from err

        if not (unpacked[1] == 254 and len(packet) <= 9):
            raise InterfaceError(err_msg)

        res["warning_count"] = unpacked[2]
        res["status_flag"] = unpacked[3]
        return res

    @staticmethod
    def parse_statistics(packet: bytes, with_header: bool = True) -> StatsPacketType:
        """Parse the statistics packet"""
        errmsg = "Failed getting COM_STATISTICS information"
        res: Dict[str, Union[int, Decimal]] = {}
        # Information is separated by 2 spaces
        pairs: List[bytes] = [b""]
        lbl: StrOrBytes = b""
        if with_header:
            pairs = packet[4:].split(b"\x20\x20")
        else:
            pairs = packet.split(b"\x20\x20")
        for pair in pairs:
            try:
                lbl, val = [v.strip() for v in pair.split(b":", 2)]
            except ValueError as err:
                raise InterfaceError(errmsg) from err

            # It's either an integer or a decimal
            lbl = lbl.decode("utf-8")
            try:
                res[lbl] = int(val)
            except (KeyError, ValueError):
                try:
                    res[lbl] = Decimal(val.decode("utf-8"))
                except DecimalException as err:
                    raise InterfaceError(f"{errmsg} ({lbl}:{repr(val)})") from err
        return res

    def read_text_result(
        self, sock: SocketType, version: Tuple[int, ...], count: int = 1
    ) -> Tuple[List[Tuple[Optional[bytes], ...]], Optional[EofPacketType],]:
        """Read MySQL text result

        Reads all or given number of rows from the socket.

        Returns a tuple with 2 elements: a list with all rows and
        the EOF packet.
        """
        # Keep unused 'version' for API backward compatibility
        _ = version
        rows = []
        eof = None
        rowdata = None
        i = 0
        while True:
            if eof or i == count:
                break
            packet = sock.recv()
            if packet.startswith(b"\xff\xff\xff"):
                datas = [packet[4:]]
                packet = sock.recv()
                while packet.startswith(b"\xff\xff\xff"):
                    datas.append(packet[4:])
                    packet = sock.recv()
                datas.append(packet[4:])
                rowdata = utils.read_lc_string_list(bytearray(b"").join(datas))
            elif packet[4] == 254 and packet[0] < 7:
                eof = self.parse_eof(packet)
                rowdata = None
            else:
                eof = None
                rowdata = utils.read_lc_string_list(packet[4:])
            if eof is None and rowdata is not None:
                rows.append(rowdata)
            elif eof is None and rowdata is None:
                raise get_exception(packet)
            i += 1
        return rows, eof

    @staticmethod
    def _parse_binary_integer(
        packet: bytes, field: DescriptionType
    ) -> Tuple[bytes, int]:
        """Parse an integer from a binary packet"""
        if field[1] == FieldType.TINY:
            format_ = "<b"
            length = 1
        elif field[1] == FieldType.SHORT:
            format_ = "<h"
            length = 2
        elif field[1] in (FieldType.INT24, FieldType.LONG):
            format_ = "<i"
            length = 4
        elif field[1] == FieldType.LONGLONG:
            format_ = "<q"
            length = 8

        if field[7] & FieldFlag.UNSIGNED:
            format_ = format_.upper()

        return (packet[length:], struct.unpack(format_, packet[0:length])[0])

    @staticmethod
    def _parse_binary_float(
        packet: bytes, field: DescriptionType
    ) -> Tuple[bytes, float]:
        """Parse a float/double from a binary packet"""
        if field[1] == FieldType.DOUBLE:
            length = 8
            format_ = "<d"
        else:
            length = 4
            format_ = "<f"

        return (packet[length:], struct.unpack(format_, packet[0:length])[0])

    @staticmethod
    def _parse_binary_new_decimal(
        packet: bytes, charset: str = "utf8"
    ) -> Tuple[bytes, Decimal]:
        """Parse a New Decimal from a binary packet"""
        (packet, value) = utils.read_lc_string(packet)
        return (packet, Decimal(value.decode(charset)))

    @staticmethod
    def _parse_binary_timestamp(
        packet: bytes,
        field_type: int,
    ) -> Tuple[bytes, Optional[Union[datetime.date, datetime.datetime]]]:
        """Parse a timestamp from a binary packet"""
        length = packet[0]
        value = None
        if length == 4:
            year = struct.unpack("<H", packet[1:3])[0]
            month = packet[3]
            day = packet[4]
            if field_type in (FieldType.DATETIME, FieldType.TIMESTAMP):
                value = datetime.datetime(year=year, month=month, day=day)
            else:
                value = datetime.date(year=year, month=month, day=day)
        elif length >= 7:
            mcs = 0
            if length == 11:
                mcs = struct.unpack("<I", packet[8 : length + 1])[0]
            value = datetime.datetime(
                year=struct.unpack("<H", packet[1:3])[0],
                month=packet[3],
                day=packet[4],
                hour=packet[5],
                minute=packet[6],
                second=packet[7],
                microsecond=mcs,
            )

        return (packet[length + 1 :], value)

    @staticmethod
    def _parse_binary_time(packet: bytes) -> Tuple[bytes, datetime.timedelta]:
        """Parse a time value from a binary packet"""
        length = packet[0]
        if not length:
            return (packet[1:], datetime.timedelta())
        data = packet[1 : length + 1]
        mcs = 0
        if length > 8:
            mcs = struct.unpack("<I", data[8:])[0]
        days = struct.unpack("<I", data[1:5])[0]
        if data[0] == 1:
            days *= -1
        tmp = datetime.timedelta(
            days=days,
            seconds=data[7],
            microseconds=mcs,
            minutes=data[6],
            hours=data[5],
        )

        return (packet[length + 1 :], tmp)

    def _parse_binary_values(
        self,
        fields: List[DescriptionType],
        packet: bytes,
        charset: str = "utf-8",
    ) -> Tuple[ParseValueFromBinaryResultPacketTypes, ...]:
        """Parse values from a binary result packet"""
        null_bitmap_length = (len(fields) + 7 + 2) // 8
        null_bitmap = [int(i) for i in packet[0:null_bitmap_length]]
        packet = packet[null_bitmap_length:]

        values: List[Any] = []
        value: Any = None
        for pos, field in enumerate(fields):
            if null_bitmap[int((pos + 2) / 8)] & (1 << (pos + 2) % 8):
                values.append(None)
                continue
            if field[1] in (
                FieldType.TINY,
                FieldType.SHORT,
                FieldType.INT24,
                FieldType.LONG,
                FieldType.LONGLONG,
            ):
                packet, value = self._parse_binary_integer(packet, field)
                values.append(value)
            elif field[1] in (FieldType.DOUBLE, FieldType.FLOAT):
                packet, value = self._parse_binary_float(packet, field)
                values.append(value)
            elif field[1] in (FieldType.DECIMAL, FieldType.NEWDECIMAL):
                packet, value = self._parse_binary_new_decimal(packet, charset)
                values.append(value)
            elif field[1] in (
                FieldType.DATETIME,
                FieldType.DATE,
                FieldType.TIMESTAMP,
            ):
                (packet, value) = self._parse_binary_timestamp(packet, field[1])
                values.append(value)
            elif field[1] == FieldType.TIME:
                (packet, value) = self._parse_binary_time(packet)
                values.append(value)
            else:
                (packet, value) = utils.read_lc_string(packet)
                try:
                    values.append(value.decode(charset))
                except UnicodeDecodeError:
                    values.append(value)

        return tuple(values)

    def read_binary_result(
        self,
        sock: SocketType,
        columns: List[DescriptionType],
        count: int = 1,
        charset: str = "utf-8",
    ) -> Tuple[
        List[Tuple[ParseValueFromBinaryResultPacketTypes, ...]],
        Optional[EofPacketType],
    ]:
        """Read MySQL binary protocol result

        Reads all or given number of binary resultset rows from the socket.
        """
        rows = []
        eof = None
        values = None
        i = 0
        while True:
            if eof is not None:
                break
            if i == count:
                break
            packet = sock.recv()
            if packet[4] == 254:
                eof = self.parse_eof(packet)
                values = None
            elif packet[4] == 0:
                eof = None
                values = self._parse_binary_values(columns, packet[5:], charset)
            if eof is None and values is not None:
                rows.append(values)
            elif eof is None and values is None:
                raise get_exception(packet)
            i += 1
        return (rows, eof)

    @staticmethod
    def parse_binary_prepare_ok(packet: bytes) -> Dict[str, int]:
        """Parse a MySQL Binary Protocol OK packet"""
        if not packet[4] == 0:
            raise InterfaceError("Failed parsing Binary OK packet")

        ok_pkt = {}
        try:
            packet, ok_pkt["statement_id"] = utils.read_int(packet[5:], 4)
            packet, ok_pkt["num_columns"] = utils.read_int(packet, 2)
            packet, ok_pkt["num_params"] = utils.read_int(packet, 2)
            packet = packet[1:]  # Filler 1 * \x00
            packet, ok_pkt["warning_count"] = utils.read_int(packet, 2)
        except ValueError as err:
            raise InterfaceError("Failed parsing Binary OK packet") from err

        return ok_pkt

    @staticmethod
    def prepare_binary_integer(value: int) -> Tuple[bytes, int, int]:
        """Prepare an integer for the MySQL binary protocol"""
        field_type = None
        flags = 0
        if value < 0:
            if value >= -128:
                format_ = "<b"
                field_type = FieldType.TINY
            elif value >= -32768:
                format_ = "<h"
                field_type = FieldType.SHORT
            elif value >= -2147483648:
                format_ = "<i"
                field_type = FieldType.LONG
            else:
                format_ = "<q"
                field_type = FieldType.LONGLONG
        else:
            flags = 128
            if value <= 255:
                format_ = "<B"
                field_type = FieldType.TINY
            elif value <= 65535:
                format_ = "<H"
                field_type = FieldType.SHORT
            elif value <= 4294967295:
                format_ = "<I"
                field_type = FieldType.LONG
            else:
                field_type = FieldType.LONGLONG
                format_ = "<Q"
        return (struct.pack(format_, value), field_type, flags)

    @staticmethod
    def prepare_binary_timestamp(
        value: Union[datetime.date, datetime.datetime]
    ) -> Tuple[bytearray, int]:
        """Prepare a timestamp object for the MySQL binary protocol

        This method prepares a timestamp of type datetime.datetime or
        datetime.date for sending over the MySQL binary protocol.
        A tuple is returned with the prepared value and field type
        as elements.

        Raises ValueError when the argument value is of invalid type.

        Returns a tuple.
        """
        if isinstance(value, datetime.datetime):
            field_type = FieldType.DATETIME
        elif isinstance(value, datetime.date):
            field_type = FieldType.DATE
        else:
            raise ValueError("Argument must a datetime.datetime or datetime.date")

        packed = (
            utils.int2store(value.year)
            + utils.int1store(value.month)
            + utils.int1store(value.day)
        )

        if isinstance(value, datetime.datetime):
            packed = (
                packed
                + utils.int1store(value.hour)
                + utils.int1store(value.minute)
                + utils.int1store(value.second)
            )
            if value.microsecond > 0:
                packed += utils.int4store(value.microsecond)

        packed = utils.int1store(len(packed)) + packed
        return (packed, field_type)

    @staticmethod
    def prepare_binary_time(
        value: Union[datetime.timedelta, datetime.time]
    ) -> Tuple[bytearray, int]:
        """Prepare a time object for the MySQL binary protocol

        This method prepares a time object of type datetime.timedelta or
        datetime.time for sending over the MySQL binary protocol.
        A tuple is returned with the prepared value and field type
        as elements.

        Raises ValueError when the argument value is of invalid type.

        Returns a tuple.
        """
        if not isinstance(value, (datetime.timedelta, datetime.time)):
            raise ValueError("Argument must a datetime.timedelta or datetime.time")

        field_type = FieldType.TIME
        negative = 0
        mcs = None
        packed = b""

        if isinstance(value, datetime.timedelta):
            if value.days < 0:
                negative = 1
            (hours, remainder) = divmod(value.seconds, 3600)
            (mins, secs) = divmod(remainder, 60)
            packed += (
                utils.int4store(abs(value.days))
                + utils.int1store(hours)
                + utils.int1store(mins)
                + utils.int1store(secs)
            )
            mcs = value.microseconds
        else:
            packed += (
                utils.int4store(0)
                + utils.int1store(value.hour)
                + utils.int1store(value.minute)
                + utils.int1store(value.second)
            )
            mcs = value.microsecond
        if mcs:
            packed += utils.int4store(mcs)

        packed = utils.int1store(negative) + packed
        packed = utils.int1store(len(packed)) + packed

        return (packed, field_type)

    @staticmethod
    def prepare_stmt_send_long_data(
        statement: int, param: int, data: bytes
    ) -> bytearray:
        """Prepare long data for prepared statements

        Returns a string.
        """
        packet: bytearray = utils.int4store(statement) + utils.int2store(param) + data
        return packet

    def make_stmt_execute(
        self,
        statement_id: int,
        data: Sequence[SupportedMysqlBinaryProtocolTypes] = (),
        parameters: Sequence[Any] = (),
        flags: int = 0,
        long_data_used: Optional[Dict[int, Tuple[bool]]] = None,
        charset: str = "utf8",
        query_attrs: Optional[List[Tuple[str, Any]]] = None,
        converter_str_fallback: bool = False,
    ) -> bytearray:
        """Make a MySQL packet with the Statement Execute command"""
        iteration_count = 1
        null_bitmap = [0] * ((len(data) + 7) // 8)
        values = []
        types = []
        packed = b""
        data_len = len(data)
        query_attr_names = []
        flags = flags if not query_attrs else flags + PARAMETER_COUNT_AVAILABLE
        if charset == "utf8mb4":
            charset = "utf8"
        if long_data_used is None:
            long_data_used = {}
        if query_attrs:
            data = list(data)
            for _, attr_val in query_attrs:
                data.append(attr_val)
            null_bitmap = [0] * ((len(data) + 7) // 8)
        if parameters or data:
            if data_len != len(parameters):
                raise InterfaceError(
                    "Failed executing prepared statement: data values does not"
                    " match number of parameters"
                )
            for pos, value in enumerate(data):
                _flags = 0
                if value is None:
                    null_bitmap[(pos // 8)] |= 1 << (pos % 8)
                    types.append(
                        utils.int1store(FieldType.NULL) + utils.int1store(_flags)
                    )
                    continue
                if pos in long_data_used:
                    if long_data_used[pos][0]:
                        # We suppose binary data
                        field_type = FieldType.BLOB
                    else:
                        # We suppose text data
                        field_type = FieldType.STRING
                elif isinstance(value, int):
                    (
                        packed,
                        field_type,
                        _flags,
                    ) = self.prepare_binary_integer(value)
                    values.append(packed)
                elif isinstance(value, str):
                    value = value.encode(charset)
                    values.append(utils.lc_int(len(value)) + value)
                    field_type = FieldType.STRING
                elif isinstance(value, bytes):
                    values.append(utils.lc_int(len(value)) + value)
                    field_type = FieldType.STRING
                elif isinstance(value, Decimal):
                    values.append(
                        utils.lc_int(len(str(value).encode(charset)))
                        + str(value).encode(charset)
                    )
                    field_type = FieldType.DECIMAL
                elif isinstance(value, float):
                    values.append(struct.pack("<d", value))
                    field_type = FieldType.DOUBLE
                elif isinstance(value, (datetime.datetime, datetime.date)):
                    (packed, field_type) = self.prepare_binary_timestamp(value)
                    values.append(packed)
                elif isinstance(value, (datetime.timedelta, datetime.time)):
                    (packed, field_type) = self.prepare_binary_time(value)
                    values.append(packed)
                elif converter_str_fallback:
                    value = str(value).encode(charset)
                    values.append(utils.lc_int(len(value)) + value)
                    field_type = FieldType.STRING
                else:
                    raise ProgrammingError(
                        "MySQL binary protocol can not handle "
                        f"'{value.__class__.__name__}' objects"
                    )
                types.append(utils.int1store(field_type) + utils.int1store(_flags))
                if query_attrs and pos + 1 > data_len:
                    name = query_attrs[pos - data_len][0].encode(charset)
                    query_attr_names.append(utils.lc_int(len(name)) + name)
        packet = (
            utils.int4store(statement_id)
            + utils.int1store(flags)
            + utils.int4store(iteration_count)
        )

        # if (num_params > 0 || (CLIENT_QUERY_ATTRIBUTES \
        #                        && (flags & PARAMETER_COUNT_AVAILABLE)) {
        if query_attrs is not None:
            parameter_count = data_len + len(query_attrs)
        else:
            parameter_count = data_len
        if parameter_count:
            # if CLIENT_QUERY_ATTRIBUTES is on
            if query_attrs is not None:
                packet += utils.lc_int(parameter_count)

            packet += b"".join(
                [struct.pack("B", bit) for bit in null_bitmap]
            ) + utils.int1store(1)
            count = 0
            for a_type in types:
                packet += a_type
                # if CLIENT_QUERY_ATTRIBUTES is on {
                #    string<lenenc>    parameter_name    Name of the parameter
                # or empty if not present
                # } if CLIENT_QUERY_ATTRIBUTES is on
                if query_attrs is not None:
                    if count + 1 > data_len:
                        packet += query_attr_names[count - data_len]
                    else:
                        packet += b"\x00"
                count += 1

            for a_value in values:
                packet += a_value

        return packet

    @staticmethod
    def parse_auth_switch_request(packet: bytes) -> Tuple[str, bytes]:
        """Parse a MySQL AuthSwitchRequest-packet"""
        if not packet[4] == 254:
            raise InterfaceError("Failed parsing AuthSwitchRequest packet")

        packet, plugin_name = utils.read_string(packet[5:], end=b"\x00")
        if packet and packet[-1] == 0:
            packet = packet[:-1]

        return plugin_name.decode("utf8"), packet

    @staticmethod
    def parse_auth_more_data(packet: bytes) -> bytes:
        """Parse a MySQL AuthMoreData-packet"""
        if not packet[4] == 1:
            raise InterfaceError("Failed parsing AuthMoreData packet")

        return packet[5:]

virt/lib/python3.9/site-packages/mysql/connector/protocol 2.py
PrinceDisant/LostInNL
# Copyright (c) 2009, 2022, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

# mypy: disable-error-code="assignment"

"""Implements the MySQL Client/Server protocol."""

import datetime
import struct

from decimal import Decimal, DecimalException
from typing import Any, Dict, List, Optional, Sequence, Tuple, Union

from . import utils
from .authentication import get_auth_plugin
from .constants import (
    PARAMETER_COUNT_AVAILABLE,
    ClientFlag,
    FieldFlag,
    FieldType,
    ServerCmd,
)
from .errors import DatabaseError, InterfaceError, ProgrammingError, get_exception
from .types import (
    ConnAttrsType,
    DescriptionType,
    EofPacketType,
    HandShakeType,
    OkPacketType,
    ParseValueFromBinaryResultPacketTypes,
    SocketType,
    StatsPacketType,
    StrOrBytes,
    SupportedMysqlBinaryProtocolTypes,
)

PROTOCOL_VERSION: int = 10


class MySQLProtocol:
    """Implements MySQL client/server protocol

    Create and parses MySQL packets.
    """

    @staticmethod
    def _connect_with_db(client_flags: int, database: Optional[str]) -> bytes:
        """Prepare database string for handshake response"""
        if client_flags & ClientFlag.CONNECT_WITH_DB and database:
            return database.encode("utf8") + b"\x00"
        return b"\x00"

    @staticmethod
    def _auth_response(
        client_flags: int,
        username: Optional[StrOrBytes],
        password: Optional[str],
        database: Optional[str],
        auth_plugin_class: str,
        auth_plugin: str,
        auth_data: Optional[bytes],
        ssl_enabled: bool,
    ) -> bytes:
        """Prepare the authentication response"""
        if not password:
            return b"\x00"

        try:
            auth = get_auth_plugin(auth_plugin, auth_plugin_class)(
                auth_data,
                username=username,
                password=password,
                database=database,
                ssl_enabled=ssl_enabled,
            )
            plugin_auth_response = auth.auth_response()
            # # We could receive a NULL response, an Interface error is called when so
            # if plugin_auth_response is None:
            #     raise InterfaceError
        except (TypeError, InterfaceError) as err:
            raise InterfaceError(f"Failed authentication: {err}") from err

        if client_flags & ClientFlag.SECURE_CONNECTION:
            resplen = len(plugin_auth_response)
            auth_response = struct.pack("<B", resplen) + plugin_auth_response
        else:
            auth_response = plugin_auth_response + b"\x00"
        return auth_response

    def make_auth(
        self,
        handshake: Optional[HandShakeType],
        username: Optional[StrOrBytes] = None,
        password: Optional[str] = None,
        database: Optional[str] = None,
        charset: int = 45,
        client_flags: int = 0,
        max_allowed_packet: int = 1073741824,
        ssl_enabled: bool = False,
        auth_plugin: Optional[str] = None,
        conn_attrs: Optional[ConnAttrsType] = None,
        auth_plugin_class: Optional[str] = None,
    ) -> bytes:
        """Make a MySQL Authentication packet"""
        if handshake is None:
            handshake = {}
        try:
            auth_data: Optional[bytes] = handshake["auth_data"]
            auth_plugin = auth_plugin or handshake["auth_plugin"]
        except (TypeError, KeyError) as err:
            raise ProgrammingError(
                f"Handshake misses authentication info ({err})"
            ) from None

        if not username:
            username = b""
        try:
            username_bytes = username.encode("utf8")  # type: ignore[union-attr]
        except AttributeError:
            # Username is already bytes
            username_bytes = username

        filler = "x" * 22
        username_len = len(username_bytes)

        packet = struct.pack(
            f"<IIH{filler}{username_len}sx",
            client_flags,
            max_allowed_packet,
            charset,
            username_bytes,
        )

        packet += self._auth_response(
            client_flags,
            username,
            password,
            database,
            auth_plugin_class,
            auth_plugin,
            auth_data,
            ssl_enabled,
        )

        packet += self._connect_with_db(client_flags, database)

        if client_flags & ClientFlag.PLUGIN_AUTH:
            packet += auth_plugin.encode("utf8") + b"\x00"

        if (client_flags & ClientFlag.CONNECT_ARGS) and conn_attrs is not None:
            packet += self.make_conn_attrs(conn_attrs)

        return packet

    @staticmethod
    def make_conn_attrs(conn_attrs: ConnAttrsType) -> bytes:
        """Encode the connection attributes"""
        for attr_name in conn_attrs:
            if conn_attrs[attr_name] is None:
                conn_attrs[attr_name] = ""
        conn_attrs_len = (
            sum(len(x) + len(conn_attrs[x]) for x in conn_attrs)
            + len(conn_attrs.keys())
            + len(conn_attrs.values())
        )

        conn_attrs_packet = struct.pack("<B", conn_attrs_len)
        for attr_name in conn_attrs:
            conn_attrs_packet += struct.pack("<B", len(attr_name))
            conn_attrs_packet += attr_name.encode("utf8")
            conn_attrs_packet += struct.pack("<B", len(conn_attrs[attr_name]))
            conn_attrs_packet += conn_attrs[attr_name].encode("utf8")  # type: ignore[union-attr]
        return conn_attrs_packet

    @staticmethod
    def make_auth_ssl(
        charset: int = 45, client_flags: int = 0, max_allowed_packet: int = 1073741824
    ) -> bytearray:
        """Make a SSL authentication packet"""
        return (
            utils.int4store(client_flags)
            + utils.int4store(max_allowed_packet)
            + utils.int2store(charset)
            + b"\x00" * 22
        )

    @staticmethod
    def make_command(command: int, argument: Optional[bytes] = None) -> bytearray:
        """Make a MySQL packet containing a command"""
        data = utils.int1store(command)
        if argument is not None:
            data += argument
        return data

    @staticmethod
    def make_stmt_fetch(statement_id: int, rows: int = 1) -> bytearray:
        """Make a MySQL packet with Fetch Statement command"""
        return utils.int4store(statement_id) + utils.int4store(rows)

    def make_change_user(
        self,
        handshake: HandShakeType,
        username: Optional[StrOrBytes] = None,
        password: Optional[str] = None,
        database: Optional[str] = None,
        charset: int = 45,
        client_flags: int = 0,
        ssl_enabled: bool = False,
        auth_plugin: Optional[str] = None,
        conn_attrs: Optional[ConnAttrsType] = None,
        auth_plugin_class: Optional[str] = None,
    ) -> bytes:
        """Make a MySQL packet with the Change User command"""

        try:
            auth_data: Optional[bytes] = handshake["auth_data"]
            auth_plugin = auth_plugin or handshake["auth_plugin"]
        except (TypeError, KeyError) as err:
            raise ProgrammingError(
                f"Handshake misses authentication info ({err})"
            ) from None

        if not username:
            username = b""
        try:
            username_bytes = username.encode("utf8")  # type: ignore[union-attr]
        except AttributeError:
            # Username is already bytes
            username_bytes = username

        username_len = len(username_bytes)
        packet = struct.pack(
            f"<B{username_len}sx",
            ServerCmd.CHANGE_USER,
            username_bytes,
        )

        packet += self._auth_response(
            client_flags,
            username,
            password,
            database,
            auth_plugin_class,
            auth_plugin,
            auth_data,
            ssl_enabled,
        )

        packet += self._connect_with_db(client_flags, database)

        packet += struct.pack("<H", charset)

        if client_flags & ClientFlag.PLUGIN_AUTH:
            packet += auth_plugin.encode("utf8") + b"\x00"

        if (client_flags & ClientFlag.CONNECT_ARGS) and conn_attrs is not None:
            packet += self.make_conn_attrs(conn_attrs)

        return packet

    @staticmethod
    def parse_handshake(packet: bytes) -> HandShakeType:
        """Parse a MySQL Handshake-packet"""
        res = {}
        res["protocol"] = struct.unpack("<xxxxB", packet[0:5])[0]
        if res["protocol"] != PROTOCOL_VERSION:
            raise DatabaseError(
                f"Protocol mismatch; server version = {res['protocol']}, "
                f"client version = {PROTOCOL_VERSION}"
            )
        packet, res["server_version_original"] = utils.read_string(
            packet[5:], end=b"\x00"
        )

        (
            res["server_threadid"],
            auth_data1,
            capabilities1,
            res["charset"],
            res["server_status"],
            capabilities2,
            auth_data_length,
        ) = struct.unpack("<I8sx2sBH2sBxxxxxxxxxx", packet[0:31])
        res["server_version_original"] = res["server_version_original"].decode()

        packet = packet[31:]

        capabilities = utils.intread(capabilities1 + capabilities2)
        auth_data2 = b""
        if capabilities & ClientFlag.SECURE_CONNECTION:
            size = min(13, auth_data_length - 8) if auth_data_length else 13
            auth_data2 = packet[0:size]
            packet = packet[size:]
            if auth_data2[-1] == 0:
                auth_data2 = auth_data2[:-1]

        if capabilities & ClientFlag.PLUGIN_AUTH:
            if b"\x00" not in packet and res["server_version_original"].startswith(
                "5.5.8"
            ):
                # MySQL server 5.5.8 has a bug where end byte is not send
                (packet, res["auth_plugin"]) = (b"", packet)
            else:
                (packet, res["auth_plugin"]) = utils.read_string(packet, end=b"\x00")
            res["auth_plugin"] = res["auth_plugin"].decode("utf-8")
        else:
            res["auth_plugin"] = "mysql_native_password"

        res["auth_data"] = auth_data1 + auth_data2
        res["capabilities"] = capabilities
        return res

    @staticmethod
    def parse_auth_next_factor(packet: bytes) -> Tuple[bytes, str]:
        """Parse a MySQL AuthNextFactor packet."""
        packet, status = utils.read_int(packet, 1)
        if status != 2:
            raise InterfaceError("Failed parsing AuthNextFactor packet (invalid)")
        packet, auth_plugin = utils.read_string(packet, end=b"\x00")
        return packet, auth_plugin.decode("utf-8")

    @staticmethod
    def parse_ok(packet: bytes) -> OkPacketType:
        """Parse a MySQL OK-packet"""
        if not packet[4] == 0:
            raise InterfaceError("Failed parsing OK packet (invalid).")

        ok_packet = {}
        try:
            ok_packet["field_count"] = struct.unpack("<xxxxB", packet[0:5])[0]
            packet, ok_packet["affected_rows"] = utils.read_lc_int(packet[5:])
            packet, ok_packet["insert_id"] = utils.read_lc_int(packet)
            (
                ok_packet["status_flag"],
                ok_packet["warning_count"],
            ) = struct.unpack("<HH", packet[0:4])
            packet = packet[4:]
            if packet:
                packet, ok_packet["info_msg"] = utils.read_lc_string(packet)
                ok_packet["info_msg"] = ok_packet["info_msg"].decode("utf-8")
        except ValueError as err:
            raise InterfaceError("Failed parsing OK packet.") from err
        return ok_packet

    @staticmethod
    def parse_column_count(packet: bytes) -> Optional[int]:
        """Parse a MySQL packet with the number of columns in result set"""
        try:
            count = utils.read_lc_int(packet[4:])[1]
            return count
        except (struct.error, ValueError) as err:
            raise InterfaceError("Failed parsing column count") from err

    @staticmethod
    def parse_column(packet: bytes, encoding: str = "utf-8") -> DescriptionType:
        """Parse a MySQL column-packet"""
        packet, _ = utils.read_lc_string(packet[4:])  # catalog
        packet, _ = utils.read_lc_string(packet)  # db
        packet, _ = utils.read_lc_string(packet)  # table
        packet, _ = utils.read_lc_string(packet)  # org_table
        packet, name = utils.read_lc_string(packet)  # name
        packet, _ = utils.read_lc_string(packet)  # org_name

        try:
            (
                charset,
                _,
                column_type,
                flags,
                _,
            ) = struct.unpack("<xHIBHBxx", packet)
        except struct.error:
            raise InterfaceError("Failed parsing column information") from None

        return (
            name.decode(encoding),
            column_type,
            None,  # display_size
            None,  # internal_size
            None,  # precision
            None,  # scale
            ~flags & FieldFlag.NOT_NULL,  # null_ok
            flags,  # MySQL specific
            charset,
        )

    def parse_eof(self, packet: bytes) -> EofPacketType:
        """Parse a MySQL EOF-packet"""
        if packet[4] == 0:
            # EOF packet deprecation
            return self.parse_ok(packet)

        err_msg = "Failed parsing EOF packet."
        res = {}
        try:
            unpacked = struct.unpack("<xxxBBHH", packet)
        except struct.error as err:
            raise InterfaceError(err_msg) from err

        if not (unpacked[1] == 254 and len(packet) <= 9):
            raise InterfaceError(err_msg)

        res["warning_count"] = unpacked[2]
        res["status_flag"] = unpacked[3]
        return res

    @staticmethod
    def parse_statistics(packet: bytes, with_header: bool = True) -> StatsPacketType:
        """Parse the statistics packet"""
        errmsg = "Failed getting COM_STATISTICS information"
        res: Dict[str, Union[int, Decimal]] = {}
        # Information is separated by 2 spaces
        pairs: List[bytes] = [b""]
        lbl: StrOrBytes = b""
        if with_header:
            pairs = packet[4:].split(b"\x20\x20")
        else:
            pairs = packet.split(b"\x20\x20")
        for pair in pairs:
            try:
                lbl, val = [v.strip() for v in pair.split(b":", 2)]
            except ValueError as err:
                raise InterfaceError(errmsg) from err

            # It's either an integer or a decimal
            lbl = lbl.decode("utf-8")
            try:
                res[lbl] = int(val)
            except (KeyError, ValueError):
                try:
                    res[lbl] = Decimal(val.decode("utf-8"))
                except DecimalException as err:
                    raise InterfaceError(f"{errmsg} ({lbl}:{repr(val)})") from err
        return res

    def read_text_result(
        self, sock: SocketType, version: Tuple[int, ...], count: int = 1
    ) -> Tuple[List[Tuple[Optional[bytes], ...]], Optional[EofPacketType],]:
        """Read MySQL text result

        Reads all or given number of rows from the socket.

        Returns a tuple with 2 elements: a list with all rows and
        the EOF packet.
        """
        # Keep unused 'version' for API backward compatibility
        _ = version
        rows = []
        eof = None
        rowdata = None
        i = 0
        while True:
            if eof or i == count:
                break
            packet = sock.recv()
            if packet.startswith(b"\xff\xff\xff"):
                datas = [packet[4:]]
                packet = sock.recv()
                while packet.startswith(b"\xff\xff\xff"):
                    datas.append(packet[4:])
                    packet = sock.recv()
                datas.append(packet[4:])
                rowdata = utils.read_lc_string_list(bytearray(b"").join(datas))
            elif packet[4] == 254 and packet[0] < 7:
                eof = self.parse_eof(packet)
                rowdata = None
            else:
                eof = None
                rowdata = utils.read_lc_string_list(packet[4:])
            if eof is None and rowdata is not None:
                rows.append(rowdata)
            elif eof is None and rowdata is None:
                raise get_exception(packet)
            i += 1
        return rows, eof

    @staticmethod
    def _parse_binary_integer(
        packet: bytes, field: DescriptionType
    ) -> Tuple[bytes, int]:
        """Parse an integer from a binary packet"""
        if field[1] == FieldType.TINY:
            format_ = "<b"
            length = 1
        elif field[1] == FieldType.SHORT:
            format_ = "<h"
            length = 2
        elif field[1] in (FieldType.INT24, FieldType.LONG):
            format_ = "<i"
            length = 4
        elif field[1] == FieldType.LONGLONG:
            format_ = "<q"
            length = 8

        if field[7] & FieldFlag.UNSIGNED:
            format_ = format_.upper()

        return (packet[length:], struct.unpack(format_, packet[0:length])[0])

    @staticmethod
    def _parse_binary_float(
        packet: bytes, field: DescriptionType
    ) -> Tuple[bytes, float]:
        """Parse a float/double from a binary packet"""
        if field[1] == FieldType.DOUBLE:
            length = 8
            format_ = "<d"
        else:
            length = 4
            format_ = "<f"

        return (packet[length:], struct.unpack(format_, packet[0:length])[0])

    @staticmethod
    def _parse_binary_new_decimal(
        packet: bytes, charset: str = "utf8"
    ) -> Tuple[bytes, Decimal]:
        """Parse a New Decimal from a binary packet"""
        (packet, value) = utils.read_lc_string(packet)
        return (packet, Decimal(value.decode(charset)))

    @staticmethod
    def _parse_binary_timestamp(
        packet: bytes,
        field_type: int,
    ) -> Tuple[bytes, Optional[Union[datetime.date, datetime.datetime]]]:
        """Parse a timestamp from a binary packet"""
        length = packet[0]
        value = None
        if length == 4:
            year = struct.unpack("<H", packet[1:3])[0]
            month = packet[3]
            day = packet[4]
            if field_type in (FieldType.DATETIME, FieldType.TIMESTAMP):
                value = datetime.datetime(year=year, month=month, day=day)
            else:
                value = datetime.date(year=year, month=month, day=day)
        elif length >= 7:
            mcs = 0
            if length == 11:
                mcs = struct.unpack("<I", packet[8 : length + 1])[0]
            value = datetime.datetime(
                year=struct.unpack("<H", packet[1:3])[0],
                month=packet[3],
                day=packet[4],
                hour=packet[5],
                minute=packet[6],
                second=packet[7],
                microsecond=mcs,
            )

        return (packet[length + 1 :], value)

    @staticmethod
    def _parse_binary_time(packet: bytes) -> Tuple[bytes, datetime.timedelta]:
        """Parse a time value from a binary packet"""
        length = packet[0]
        if not length:
            return (packet[1:], datetime.timedelta())
        data = packet[1 : length + 1]
        mcs = 0
        if length > 8:
            mcs = struct.unpack("<I", data[8:])[0]
        days = struct.unpack("<I", data[1:5])[0]
        if data[0] == 1:
            days *= -1
        tmp = datetime.timedelta(
            days=days,
            seconds=data[7],
            microseconds=mcs,
            minutes=data[6],
            hours=data[5],
        )

        return (packet[length + 1 :], tmp)

    def _parse_binary_values(
        self,
        fields: List[DescriptionType],
        packet: bytes,
        charset: str = "utf-8",
    ) -> Tuple[ParseValueFromBinaryResultPacketTypes, ...]:
        """Parse values from a binary result packet"""
        null_bitmap_length = (len(fields) + 7 + 2) // 8
        null_bitmap = [int(i) for i in packet[0:null_bitmap_length]]
        packet = packet[null_bitmap_length:]

        values: List[Any] = []
        value: Any = None
        for pos, field in enumerate(fields):
            if null_bitmap[int((pos + 2) / 8)] & (1 << (pos + 2) % 8):
                values.append(None)
                continue
            if field[1] in (
                FieldType.TINY,
                FieldType.SHORT,
                FieldType.INT24,
                FieldType.LONG,
                FieldType.LONGLONG,
            ):
                packet, value = self._parse_binary_integer(packet, field)
                values.append(value)
            elif field[1] in (FieldType.DOUBLE, FieldType.FLOAT):
                packet, value = self._parse_binary_float(packet, field)
                values.append(value)
            elif field[1] in (FieldType.DECIMAL, FieldType.NEWDECIMAL):
                packet, value = self._parse_binary_new_decimal(packet, charset)
                values.append(value)
            elif field[1] in (
                FieldType.DATETIME,
                FieldType.DATE,
                FieldType.TIMESTAMP,
            ):
                (packet, value) = self._parse_binary_timestamp(packet, field[1])
                values.append(value)
            elif field[1] == FieldType.TIME:
                (packet, value) = self._parse_binary_time(packet)
                values.append(value)
            else:
                (packet, value) = utils.read_lc_string(packet)
                try:
                    values.append(value.decode(charset))
                except UnicodeDecodeError:
                    values.append(value)

        return tuple(values)

    def read_binary_result(
        self,
        sock: SocketType,
        columns: List[DescriptionType],
        count: int = 1,
        charset: str = "utf-8",
    ) -> Tuple[
        List[Tuple[ParseValueFromBinaryResultPacketTypes, ...]],
        Optional[EofPacketType],
    ]:
        """Read MySQL binary protocol result

        Reads all or given number of binary resultset rows from the socket.
        """
        rows = []
        eof = None
        values = None
        i = 0
        while True:
            if eof is not None:
                break
            if i == count:
                break
            packet = sock.recv()
            if packet[4] == 254:
                eof = self.parse_eof(packet)
                values = None
            elif packet[4] == 0:
                eof = None
                values = self._parse_binary_values(columns, packet[5:], charset)
            if eof is None and values is not None:
                rows.append(values)
            elif eof is None and values is None:
                raise get_exception(packet)
            i += 1
        return (rows, eof)

    @staticmethod
    def parse_binary_prepare_ok(packet: bytes) -> Dict[str, int]:
        """Parse a MySQL Binary Protocol OK packet"""
        if not packet[4] == 0:
            raise InterfaceError("Failed parsing Binary OK packet")

        ok_pkt = {}
        try:
            packet, ok_pkt["statement_id"] = utils.read_int(packet[5:], 4)
            packet, ok_pkt["num_columns"] = utils.read_int(packet, 2)
            packet, ok_pkt["num_params"] = utils.read_int(packet, 2)
            packet = packet[1:]  # Filler 1 * \x00
            packet, ok_pkt["warning_count"] = utils.read_int(packet, 2)
        except ValueError as err:
            raise InterfaceError("Failed parsing Binary OK packet") from err

        return ok_pkt

    @staticmethod
    def prepare_binary_integer(value: int) -> Tuple[bytes, int, int]:
        """Prepare an integer for the MySQL binary protocol"""
        field_type = None
        flags = 0
        if value < 0:
            if value >= -128:
                format_ = "<b"
                field_type = FieldType.TINY
            elif value >= -32768:
                format_ = "<h"
                field_type = FieldType.SHORT
            elif value >= -2147483648:
                format_ = "<i"
                field_type = FieldType.LONG
            else:
                format_ = "<q"
                field_type = FieldType.LONGLONG
        else:
            flags = 128
            if value <= 255:
                format_ = "<B"
                field_type = FieldType.TINY
            elif value <= 65535:
                format_ = "<H"
                field_type = FieldType.SHORT
            elif value <= 4294967295:
                format_ = "<I"
                field_type = FieldType.LONG
            else:
                field_type = FieldType.LONGLONG
                format_ = "<Q"
        return (struct.pack(format_, value), field_type, flags)

    @staticmethod
    def prepare_binary_timestamp(
        value: Union[datetime.date, datetime.datetime]
    ) -> Tuple[bytearray, int]:
        """Prepare a timestamp object for the MySQL binary protocol

        This method prepares a timestamp of type datetime.datetime or
        datetime.date for sending over the MySQL binary protocol.
        A tuple is returned with the prepared value and field type
        as elements.

        Raises ValueError when the argument value is of invalid type.

        Returns a tuple.
        """
        if isinstance(value, datetime.datetime):
            field_type = FieldType.DATETIME
        elif isinstance(value, datetime.date):
            field_type = FieldType.DATE
        else:
            raise ValueError("Argument must a datetime.datetime or datetime.date")

        packed = (
            utils.int2store(value.year)
            + utils.int1store(value.month)
            + utils.int1store(value.day)
        )

        if isinstance(value, datetime.datetime):
            packed = (
                packed
                + utils.int1store(value.hour)
                + utils.int1store(value.minute)
                + utils.int1store(value.second)
            )
            if value.microsecond > 0:
                packed += utils.int4store(value.microsecond)

        packed = utils.int1store(len(packed)) + packed
        return (packed, field_type)

    @staticmethod
    def prepare_binary_time(
        value: Union[datetime.timedelta, datetime.time]
    ) -> Tuple[bytearray, int]:
        """Prepare a time object for the MySQL binary protocol

        This method prepares a time object of type datetime.timedelta or
        datetime.time for sending over the MySQL binary protocol.
        A tuple is returned with the prepared value and field type
        as elements.

        Raises ValueError when the argument value is of invalid type.

        Returns a tuple.
        """
        if not isinstance(value, (datetime.timedelta, datetime.time)):
            raise ValueError("Argument must a datetime.timedelta or datetime.time")

        field_type = FieldType.TIME
        negative = 0
        mcs = None
        packed = b""

        if isinstance(value, datetime.timedelta):
            if value.days < 0:
                negative = 1
            (hours, remainder) = divmod(value.seconds, 3600)
            (mins, secs) = divmod(remainder, 60)
            packed += (
                utils.int4store(abs(value.days))
                + utils.int1store(hours)
                + utils.int1store(mins)
                + utils.int1store(secs)
            )
            mcs = value.microseconds
        else:
            packed += (
                utils.int4store(0)
                + utils.int1store(value.hour)
                + utils.int1store(value.minute)
                + utils.int1store(value.second)
            )
            mcs = value.microsecond
        if mcs:
            packed += utils.int4store(mcs)

        packed = utils.int1store(negative) + packed
        packed = utils.int1store(len(packed)) + packed

        return (packed, field_type)

    @staticmethod
    def prepare_stmt_send_long_data(
        statement: int, param: int, data: bytes
    ) -> bytearray:
        """Prepare long data for prepared statements

        Returns a string.
        """
        packet: bytearray = utils.int4store(statement) + utils.int2store(param) + data
        return packet

    def make_stmt_execute(
        self,
        statement_id: int,
        data: Sequence[SupportedMysqlBinaryProtocolTypes] = (),
        parameters: Sequence[Any] = (),
        flags: int = 0,
        long_data_used: Optional[Dict[int, Tuple[bool]]] = None,
        charset: str = "utf8",
        query_attrs: Optional[List[Tuple[str, Any]]] = None,
        converter_str_fallback: bool = False,
    ) -> bytearray:
        """Make a MySQL packet with the Statement Execute command"""
        iteration_count = 1
        null_bitmap = [0] * ((len(data) + 7) // 8)
        values = []
        types = []
        packed = b""
        data_len = len(data)
        query_attr_names = []
        flags = flags if not query_attrs else flags + PARAMETER_COUNT_AVAILABLE
        if charset == "utf8mb4":
            charset = "utf8"
        if long_data_used is None:
            long_data_used = {}
        if query_attrs:
            data = list(data)
            for _, attr_val in query_attrs:
                data.append(attr_val)
            null_bitmap = [0] * ((len(data) + 7) // 8)
        if parameters or data:
            if data_len != len(parameters):
                raise InterfaceError(
                    "Failed executing prepared statement: data values does not"
                    " match number of parameters"
                )
            for pos, value in enumerate(data):
                _flags = 0
                if value is None:
                    null_bitmap[(pos // 8)] |= 1 << (pos % 8)
                    types.append(
                        utils.int1store(FieldType.NULL) + utils.int1store(_flags)
                    )
                    continue
                if pos in long_data_used:
                    if long_data_used[pos][0]:
                        # We suppose binary data
                        field_type = FieldType.BLOB
                    else:
                        # We suppose text data
                        field_type = FieldType.STRING
                elif isinstance(value, int):
                    (
                        packed,
                        field_type,
                        _flags,
                    ) = self.prepare_binary_integer(value)
                    values.append(packed)
                elif isinstance(value, str):
                    value = value.encode(charset)
                    values.append(utils.lc_int(len(value)) + value)
                    field_type = FieldType.STRING
                elif isinstance(value, bytes):
                    values.append(utils.lc_int(len(value)) + value)
                    field_type = FieldType.STRING
                elif isinstance(value, Decimal):
                    values.append(
                        utils.lc_int(len(str(value).encode(charset)))
                        + str(value).encode(charset)
                    )
                    field_type = FieldType.DECIMAL
                elif isinstance(value, float):
                    values.append(struct.pack("<d", value))
                    field_type = FieldType.DOUBLE
                elif isinstance(value, (datetime.datetime, datetime.date)):
                    (packed, field_type) = self.prepare_binary_timestamp(value)
                    values.append(packed)
                elif isinstance(value, (datetime.timedelta, datetime.time)):
                    (packed, field_type) = self.prepare_binary_time(value)
                    values.append(packed)
                elif converter_str_fallback:
                    value = str(value).encode(charset)
                    values.append(utils.lc_int(len(value)) + value)
                    field_type = FieldType.STRING
                else:
                    raise ProgrammingError(
                        "MySQL binary protocol can not handle "
                        f"'{value.__class__.__name__}' objects"
                    )
                types.append(utils.int1store(field_type) + utils.int1store(_flags))
                if query_attrs and pos + 1 > data_len:
                    name = query_attrs[pos - data_len][0].encode(charset)
                    query_attr_names.append(utils.lc_int(len(name)) + name)
        packet = (
            utils.int4store(statement_id)
            + utils.int1store(flags)
            + utils.int4store(iteration_count)
        )

        # if (num_params > 0 || (CLIENT_QUERY_ATTRIBUTES \
        #                        && (flags & PARAMETER_COUNT_AVAILABLE)) {
        if query_attrs is not None:
            parameter_count = data_len + len(query_attrs)
        else:
            parameter_count = data_len
        if parameter_count:
            # if CLIENT_QUERY_ATTRIBUTES is on
            if query_attrs is not None:
                packet += utils.lc_int(parameter_count)

            packet += b"".join(
                [struct.pack("B", bit) for bit in null_bitmap]
            ) + utils.int1store(1)
            count = 0
            for a_type in types:
                packet += a_type
                # if CLIENT_QUERY_ATTRIBUTES is on {
                #    string<lenenc>    parameter_name    Name of the parameter
                # or empty if not present
                # } if CLIENT_QUERY_ATTRIBUTES is on
                if query_attrs is not None:
                    if count + 1 > data_len:
                        packet += query_attr_names[count - data_len]
                    else:
                        packet += b"\x00"
                count += 1

            for a_value in values:
                packet += a_value

        return packet

    @staticmethod
    def parse_auth_switch_request(packet: bytes) -> Tuple[str, bytes]:
        """Parse a MySQL AuthSwitchRequest-packet"""
        if not packet[4] == 254:
            raise InterfaceError("Failed parsing AuthSwitchRequest packet")

        packet, plugin_name = utils.read_string(packet[5:], end=b"\x00")
        if packet and packet[-1] == 0:
            packet = packet[:-1]

        return plugin_name.decode("utf8"), packet

    @staticmethod
    def parse_auth_more_data(packet: bytes) -> bytes:
        """Parse a MySQL AuthMoreData-packet"""
        if not packet[4] == 1:
            raise InterfaceError("Failed parsing AuthMoreData packet")

        return packet[5:]

virt/lib/python3.9/site-packages/mysql/connector/protocol 3.py
PrinceDisant/LostInNL
# Copyright (c) 2009, 2022, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

# mypy: disable-error-code="assignment"

"""Implements the MySQL Client/Server protocol."""

import datetime
import struct

from decimal import Decimal, DecimalException
from typing import Any, Dict, List, Optional, Sequence, Tuple, Union

from . import utils
from .authentication import get_auth_plugin
from .constants import (
    PARAMETER_COUNT_AVAILABLE,
    ClientFlag,
    FieldFlag,
    FieldType,
    ServerCmd,
)
from .errors import DatabaseError, InterfaceError, ProgrammingError, get_exception
from .types import (
    ConnAttrsType,
    DescriptionType,
    EofPacketType,
    HandShakeType,
    OkPacketType,
    ParseValueFromBinaryResultPacketTypes,
    SocketType,
    StatsPacketType,
    StrOrBytes,
    SupportedMysqlBinaryProtocolTypes,
)

PROTOCOL_VERSION: int = 10


class MySQLProtocol:
    """Implements MySQL client/server protocol

    Create and parses MySQL packets.
    """

    @staticmethod
    def _connect_with_db(client_flags: int, database: Optional[str]) -> bytes:
        """Prepare database string for handshake response"""
        if client_flags & ClientFlag.CONNECT_WITH_DB and database:
            return database.encode("utf8") + b"\x00"
        return b"\x00"

    @staticmethod
    def _auth_response(
        client_flags: int,
        username: Optional[StrOrBytes],
        password: Optional[str],
        database: Optional[str],
        auth_plugin_class: str,
        auth_plugin: str,
        auth_data: Optional[bytes],
        ssl_enabled: bool,
    ) -> bytes:
        """Prepare the authentication response"""
        if not password:
            return b"\x00"

        try:
            auth = get_auth_plugin(auth_plugin, auth_plugin_class)(
                auth_data,
                username=username,
                password=password,
                database=database,
                ssl_enabled=ssl_enabled,
            )
            plugin_auth_response = auth.auth_response()
            # # We could receive a NULL response, an Interface error is called when so
            # if plugin_auth_response is None:
            #     raise InterfaceError
        except (TypeError, InterfaceError) as err:
            raise InterfaceError(f"Failed authentication: {err}") from err

        if client_flags & ClientFlag.SECURE_CONNECTION:
            resplen = len(plugin_auth_response)
            auth_response = struct.pack("<B", resplen) + plugin_auth_response
        else:
            auth_response = plugin_auth_response + b"\x00"
        return auth_response

    def make_auth(
        self,
        handshake: Optional[HandShakeType],
        username: Optional[StrOrBytes] = None,
        password: Optional[str] = None,
        database: Optional[str] = None,
        charset: int = 45,
        client_flags: int = 0,
        max_allowed_packet: int = 1073741824,
        ssl_enabled: bool = False,
        auth_plugin: Optional[str] = None,
        conn_attrs: Optional[ConnAttrsType] = None,
        auth_plugin_class: Optional[str] = None,
    ) -> bytes:
        """Make a MySQL Authentication packet"""
        if handshake is None:
            handshake = {}
        try:
            auth_data: Optional[bytes] = handshake["auth_data"]
            auth_plugin = auth_plugin or handshake["auth_plugin"]
        except (TypeError, KeyError) as err:
            raise ProgrammingError(
                f"Handshake misses authentication info ({err})"
            ) from None

        if not username:
            username = b""
        try:
            username_bytes = username.encode("utf8")  # type: ignore[union-attr]
        except AttributeError:
            # Username is already bytes
            username_bytes = username

        filler = "x" * 22
        username_len = len(username_bytes)

        packet = struct.pack(
            f"<IIH{filler}{username_len}sx",
            client_flags,
            max_allowed_packet,
            charset,
            username_bytes,
        )

        packet += self._auth_response(
            client_flags,
            username,
            password,
            database,
            auth_plugin_class,
            auth_plugin,
            auth_data,
            ssl_enabled,
        )

        packet += self._connect_with_db(client_flags, database)

        if client_flags & ClientFlag.PLUGIN_AUTH:
            packet += auth_plugin.encode("utf8") + b"\x00"

        if (client_flags & ClientFlag.CONNECT_ARGS) and conn_attrs is not None:
            packet += self.make_conn_attrs(conn_attrs)

        return packet

    @staticmethod
    def make_conn_attrs(conn_attrs: ConnAttrsType) -> bytes:
        """Encode the connection attributes"""
        for attr_name in conn_attrs:
            if conn_attrs[attr_name] is None:
                conn_attrs[attr_name] = ""
        conn_attrs_len = (
            sum(len(x) + len(conn_attrs[x]) for x in conn_attrs)
            + len(conn_attrs.keys())
            + len(conn_attrs.values())
        )

        conn_attrs_packet = struct.pack("<B", conn_attrs_len)
        for attr_name in conn_attrs:
            conn_attrs_packet += struct.pack("<B", len(attr_name))
            conn_attrs_packet += attr_name.encode("utf8")
            conn_attrs_packet += struct.pack("<B", len(conn_attrs[attr_name]))
            conn_attrs_packet += conn_attrs[attr_name].encode("utf8")  # type: ignore[union-attr]
        return conn_attrs_packet

    @staticmethod
    def make_auth_ssl(
        charset: int = 45, client_flags: int = 0, max_allowed_packet: int = 1073741824
    ) -> bytearray:
        """Make a SSL authentication packet"""
        return (
            utils.int4store(client_flags)
            + utils.int4store(max_allowed_packet)
            + utils.int2store(charset)
            + b"\x00" * 22
        )

    @staticmethod
    def make_command(command: int, argument: Optional[bytes] = None) -> bytearray:
        """Make a MySQL packet containing a command"""
        data = utils.int1store(command)
        if argument is not None:
            data += argument
        return data

    @staticmethod
    def make_stmt_fetch(statement_id: int, rows: int = 1) -> bytearray:
        """Make a MySQL packet with Fetch Statement command"""
        return utils.int4store(statement_id) + utils.int4store(rows)

    def make_change_user(
        self,
        handshake: HandShakeType,
        username: Optional[StrOrBytes] = None,
        password: Optional[str] = None,
        database: Optional[str] = None,
        charset: int = 45,
        client_flags: int = 0,
        ssl_enabled: bool = False,
        auth_plugin: Optional[str] = None,
        conn_attrs: Optional[ConnAttrsType] = None,
        auth_plugin_class: Optional[str] = None,
    ) -> bytes:
        """Make a MySQL packet with the Change User command"""

        try:
            auth_data: Optional[bytes] = handshake["auth_data"]
            auth_plugin = auth_plugin or handshake["auth_plugin"]
        except (TypeError, KeyError) as err:
            raise ProgrammingError(
                f"Handshake misses authentication info ({err})"
            ) from None

        if not username:
            username = b""
        try:
            username_bytes = username.encode("utf8")  # type: ignore[union-attr]
        except AttributeError:
            # Username is already bytes
            username_bytes = username

        username_len = len(username_bytes)
        packet = struct.pack(
            f"<B{username_len}sx",
            ServerCmd.CHANGE_USER,
            username_bytes,
        )

        packet += self._auth_response(
            client_flags,
            username,
            password,
            database,
            auth_plugin_class,
            auth_plugin,
            auth_data,
            ssl_enabled,
        )

        packet += self._connect_with_db(client_flags, database)

        packet += struct.pack("<H", charset)

        if client_flags & ClientFlag.PLUGIN_AUTH:
            packet += auth_plugin.encode("utf8") + b"\x00"

        if (client_flags & ClientFlag.CONNECT_ARGS) and conn_attrs is not None:
            packet += self.make_conn_attrs(conn_attrs)

        return packet

    @staticmethod
    def parse_handshake(packet: bytes) -> HandShakeType:
        """Parse a MySQL Handshake-packet"""
        res = {}
        res["protocol"] = struct.unpack("<xxxxB", packet[0:5])[0]
        if res["protocol"] != PROTOCOL_VERSION:
            raise DatabaseError(
                f"Protocol mismatch; server version = {res['protocol']}, "
                f"client version = {PROTOCOL_VERSION}"
            )
        packet, res["server_version_original"] = utils.read_string(
            packet[5:], end=b"\x00"
        )

        (
            res["server_threadid"],
            auth_data1,
            capabilities1,
            res["charset"],
            res["server_status"],
            capabilities2,
            auth_data_length,
        ) = struct.unpack("<I8sx2sBH2sBxxxxxxxxxx", packet[0:31])
        res["server_version_original"] = res["server_version_original"].decode()

        packet = packet[31:]

        capabilities = utils.intread(capabilities1 + capabilities2)
        auth_data2 = b""
        if capabilities & ClientFlag.SECURE_CONNECTION:
            size = min(13, auth_data_length - 8) if auth_data_length else 13
            auth_data2 = packet[0:size]
            packet = packet[size:]
            if auth_data2[-1] == 0:
                auth_data2 = auth_data2[:-1]

        if capabilities & ClientFlag.PLUGIN_AUTH:
            if b"\x00" not in packet and res["server_version_original"].startswith(
                "5.5.8"
            ):
                # MySQL server 5.5.8 has a bug where end byte is not send
                (packet, res["auth_plugin"]) = (b"", packet)
            else:
                (packet, res["auth_plugin"]) = utils.read_string(packet, end=b"\x00")
            res["auth_plugin"] = res["auth_plugin"].decode("utf-8")
        else:
            res["auth_plugin"] = "mysql_native_password"

        res["auth_data"] = auth_data1 + auth_data2
        res["capabilities"] = capabilities
        return res

    @staticmethod
    def parse_auth_next_factor(packet: bytes) -> Tuple[bytes, str]:
        """Parse a MySQL AuthNextFactor packet."""
        packet, status = utils.read_int(packet, 1)
        if status != 2:
            raise InterfaceError("Failed parsing AuthNextFactor packet (invalid)")
        packet, auth_plugin = utils.read_string(packet, end=b"\x00")
        return packet, auth_plugin.decode("utf-8")

    @staticmethod
    def parse_ok(packet: bytes) -> OkPacketType:
        """Parse a MySQL OK-packet"""
        if not packet[4] == 0:
            raise InterfaceError("Failed parsing OK packet (invalid).")

        ok_packet = {}
        try:
            ok_packet["field_count"] = struct.unpack("<xxxxB", packet[0:5])[0]
            packet, ok_packet["affected_rows"] = utils.read_lc_int(packet[5:])
            packet, ok_packet["insert_id"] = utils.read_lc_int(packet)
            (
                ok_packet["status_flag"],
                ok_packet["warning_count"],
            ) = struct.unpack("<HH", packet[0:4])
            packet = packet[4:]
            if packet:
                packet, ok_packet["info_msg"] = utils.read_lc_string(packet)
                ok_packet["info_msg"] = ok_packet["info_msg"].decode("utf-8")
        except ValueError as err:
            raise InterfaceError("Failed parsing OK packet.") from err
        return ok_packet

    @staticmethod
    def parse_column_count(packet: bytes) -> Optional[int]:
        """Parse a MySQL packet with the number of columns in result set"""
        try:
            count = utils.read_lc_int(packet[4:])[1]
            return count
        except (struct.error, ValueError) as err:
            raise InterfaceError("Failed parsing column count") from err

    @staticmethod
    def parse_column(packet: bytes, encoding: str = "utf-8") -> DescriptionType:
        """Parse a MySQL column-packet"""
        packet, _ = utils.read_lc_string(packet[4:])  # catalog
        packet, _ = utils.read_lc_string(packet)  # db
        packet, _ = utils.read_lc_string(packet)  # table
        packet, _ = utils.read_lc_string(packet)  # org_table
        packet, name = utils.read_lc_string(packet)  # name
        packet, _ = utils.read_lc_string(packet)  # org_name

        try:
            (
                charset,
                _,
                column_type,
                flags,
                _,
            ) = struct.unpack("<xHIBHBxx", packet)
        except struct.error:
            raise InterfaceError("Failed parsing column information") from None

        return (
            name.decode(encoding),
            column_type,
            None,  # display_size
            None,  # internal_size
            None,  # precision
            None,  # scale
            ~flags & FieldFlag.NOT_NULL,  # null_ok
            flags,  # MySQL specific
            charset,
        )

    def parse_eof(self, packet: bytes) -> EofPacketType:
        """Parse a MySQL EOF-packet"""
        if packet[4] == 0:
            # EOF packet deprecation
            return self.parse_ok(packet)

        err_msg = "Failed parsing EOF packet."
        res = {}
        try:
            unpacked = struct.unpack("<xxxBBHH", packet)
        except struct.error as err:
            raise InterfaceError(err_msg) from err

        if not (unpacked[1] == 254 and len(packet) <= 9):
            raise InterfaceError(err_msg)

        res["warning_count"] = unpacked[2]
        res["status_flag"] = unpacked[3]
        return res

    @staticmethod
    def parse_statistics(packet: bytes, with_header: bool = True) -> StatsPacketType:
        """Parse the statistics packet"""
        errmsg = "Failed getting COM_STATISTICS information"
        res: Dict[str, Union[int, Decimal]] = {}
        # Information is separated by 2 spaces
        pairs: List[bytes] = [b""]
        lbl: StrOrBytes = b""
        if with_header:
            pairs = packet[4:].split(b"\x20\x20")
        else:
            pairs = packet.split(b"\x20\x20")
        for pair in pairs:
            try:
                lbl, val = [v.strip() for v in pair.split(b":", 2)]
            except ValueError as err:
                raise InterfaceError(errmsg) from err

            # It's either an integer or a decimal
            lbl = lbl.decode("utf-8")
            try:
                res[lbl] = int(val)
            except (KeyError, ValueError):
                try:
                    res[lbl] = Decimal(val.decode("utf-8"))
                except DecimalException as err:
                    raise InterfaceError(f"{errmsg} ({lbl}:{repr(val)})") from err
        return res

    def read_text_result(
        self, sock: SocketType, version: Tuple[int, ...], count: int = 1
    ) -> Tuple[List[Tuple[Optional[bytes], ...]], Optional[EofPacketType],]:
        """Read MySQL text result

        Reads all or given number of rows from the socket.

        Returns a tuple with 2 elements: a list with all rows and
        the EOF packet.
        """
        # Keep unused 'version' for API backward compatibility
        _ = version
        rows = []
        eof = None
        rowdata = None
        i = 0
        while True:
            if eof or i == count:
                break
            packet = sock.recv()
            if packet.startswith(b"\xff\xff\xff"):
                datas = [packet[4:]]
                packet = sock.recv()
                while packet.startswith(b"\xff\xff\xff"):
                    datas.append(packet[4:])
                    packet = sock.recv()
                datas.append(packet[4:])
                rowdata = utils.read_lc_string_list(bytearray(b"").join(datas))
            elif packet[4] == 254 and packet[0] < 7:
                eof = self.parse_eof(packet)
                rowdata = None
            else:
                eof = None
                rowdata = utils.read_lc_string_list(packet[4:])
            if eof is None and rowdata is not None:
                rows.append(rowdata)
            elif eof is None and rowdata is None:
                raise get_exception(packet)
            i += 1
        return rows, eof

    @staticmethod
    def _parse_binary_integer(
        packet: bytes, field: DescriptionType
    ) -> Tuple[bytes, int]:
        """Parse an integer from a binary packet"""
        if field[1] == FieldType.TINY:
            format_ = "<b"
            length = 1
        elif field[1] == FieldType.SHORT:
            format_ = "<h"
            length = 2
        elif field[1] in (FieldType.INT24, FieldType.LONG):
            format_ = "<i"
            length = 4
        elif field[1] == FieldType.LONGLONG:
            format_ = "<q"
            length = 8

        if field[7] & FieldFlag.UNSIGNED:
            format_ = format_.upper()

        return (packet[length:], struct.unpack(format_, packet[0:length])[0])

    @staticmethod
    def _parse_binary_float(
        packet: bytes, field: DescriptionType
    ) -> Tuple[bytes, float]:
        """Parse a float/double from a binary packet"""
        if field[1] == FieldType.DOUBLE:
            length = 8
            format_ = "<d"
        else:
            length = 4
            format_ = "<f"

        return (packet[length:], struct.unpack(format_, packet[0:length])[0])

    @staticmethod
    def _parse_binary_new_decimal(
        packet: bytes, charset: str = "utf8"
    ) -> Tuple[bytes, Decimal]:
        """Parse a New Decimal from a binary packet"""
        (packet, value) = utils.read_lc_string(packet)
        return (packet, Decimal(value.decode(charset)))

    @staticmethod
    def _parse_binary_timestamp(
        packet: bytes,
        field_type: int,
    ) -> Tuple[bytes, Optional[Union[datetime.date, datetime.datetime]]]:
        """Parse a timestamp from a binary packet"""
        length = packet[0]
        value = None
        if length == 4:
            year = struct.unpack("<H", packet[1:3])[0]
            month = packet[3]
            day = packet[4]
            if field_type in (FieldType.DATETIME, FieldType.TIMESTAMP):
                value = datetime.datetime(year=year, month=month, day=day)
            else:
                value = datetime.date(year=year, month=month, day=day)
        elif length >= 7:
            mcs = 0
            if length == 11:
                mcs = struct.unpack("<I", packet[8 : length + 1])[0]
            value = datetime.datetime(
                year=struct.unpack("<H", packet[1:3])[0],
                month=packet[3],
                day=packet[4],
                hour=packet[5],
                minute=packet[6],
                second=packet[7],
                microsecond=mcs,
            )

        return (packet[length + 1 :], value)

    @staticmethod
    def _parse_binary_time(packet: bytes) -> Tuple[bytes, datetime.timedelta]:
        """Parse a time value from a binary packet"""
        length = packet[0]
        if not length:
            return (packet[1:], datetime.timedelta())
        data = packet[1 : length + 1]
        mcs = 0
        if length > 8:
            mcs = struct.unpack("<I", data[8:])[0]
        days = struct.unpack("<I", data[1:5])[0]
        if data[0] == 1:
            days *= -1
        tmp = datetime.timedelta(
            days=days,
            seconds=data[7],
            microseconds=mcs,
            minutes=data[6],
            hours=data[5],
        )

        return (packet[length + 1 :], tmp)

    def _parse_binary_values(
        self,
        fields: List[DescriptionType],
        packet: bytes,
        charset: str = "utf-8",
    ) -> Tuple[ParseValueFromBinaryResultPacketTypes, ...]:
        """Parse values from a binary result packet"""
        null_bitmap_length = (len(fields) + 7 + 2) // 8
        null_bitmap = [int(i) for i in packet[0:null_bitmap_length]]
        packet = packet[null_bitmap_length:]

        values: List[Any] = []
        value: Any = None
        for pos, field in enumerate(fields):
            if null_bitmap[int((pos + 2) / 8)] & (1 << (pos + 2) % 8):
                values.append(None)
                continue
            if field[1] in (
                FieldType.TINY,
                FieldType.SHORT,
                FieldType.INT24,
                FieldType.LONG,
                FieldType.LONGLONG,
            ):
                packet, value = self._parse_binary_integer(packet, field)
                values.append(value)
            elif field[1] in (FieldType.DOUBLE, FieldType.FLOAT):
                packet, value = self._parse_binary_float(packet, field)
                values.append(value)
            elif field[1] in (FieldType.DECIMAL, FieldType.NEWDECIMAL):
                packet, value = self._parse_binary_new_decimal(packet, charset)
                values.append(value)
            elif field[1] in (
                FieldType.DATETIME,
                FieldType.DATE,
                FieldType.TIMESTAMP,
            ):
                (packet, value) = self._parse_binary_timestamp(packet, field[1])
                values.append(value)
            elif field[1] == FieldType.TIME:
                (packet, value) = self._parse_binary_time(packet)
                values.append(value)
            else:
                (packet, value) = utils.read_lc_string(packet)
                try:
                    values.append(value.decode(charset))
                except UnicodeDecodeError:
                    values.append(value)

        return tuple(values)

    def read_binary_result(
        self,
        sock: SocketType,
        columns: List[DescriptionType],
        count: int = 1,
        charset: str = "utf-8",
    ) -> Tuple[
        List[Tuple[ParseValueFromBinaryResultPacketTypes, ...]],
        Optional[EofPacketType],
    ]:
        """Read MySQL binary protocol result

        Reads all or given number of binary resultset rows from the socket.
        """
        rows = []
        eof = None
        values = None
        i = 0
        while True:
            if eof is not None:
                break
            if i == count:
                break
            packet = sock.recv()
            if packet[4] == 254:
                eof = self.parse_eof(packet)
                values = None
            elif packet[4] == 0:
                eof = None
                values = self._parse_binary_values(columns, packet[5:], charset)
            if eof is None and values is not None:
                rows.append(values)
            elif eof is None and values is None:
                raise get_exception(packet)
            i += 1
        return (rows, eof)

    @staticmethod
    def parse_binary_prepare_ok(packet: bytes) -> Dict[str, int]:
        """Parse a MySQL Binary Protocol OK packet"""
        if not packet[4] == 0:
            raise InterfaceError("Failed parsing Binary OK packet")

        ok_pkt = {}
        try:
            packet, ok_pkt["statement_id"] = utils.read_int(packet[5:], 4)
            packet, ok_pkt["num_columns"] = utils.read_int(packet, 2)
            packet, ok_pkt["num_params"] = utils.read_int(packet, 2)
            packet = packet[1:]  # Filler 1 * \x00
            packet, ok_pkt["warning_count"] = utils.read_int(packet, 2)
        except ValueError as err:
            raise InterfaceError("Failed parsing Binary OK packet") from err

        return ok_pkt

    @staticmethod
    def prepare_binary_integer(value: int) -> Tuple[bytes, int, int]:
        """Prepare an integer for the MySQL binary protocol"""
        field_type = None
        flags = 0
        if value < 0:
            if value >= -128:
                format_ = "<b"
                field_type = FieldType.TINY
            elif value >= -32768:
                format_ = "<h"
                field_type = FieldType.SHORT
            elif value >= -2147483648:
                format_ = "<i"
                field_type = FieldType.LONG
            else:
                format_ = "<q"
                field_type = FieldType.LONGLONG
        else:
            flags = 128
            if value <= 255:
                format_ = "<B"
                field_type = FieldType.TINY
            elif value <= 65535:
                format_ = "<H"
                field_type = FieldType.SHORT
            elif value <= 4294967295:
                format_ = "<I"
                field_type = FieldType.LONG
            else:
                field_type = FieldType.LONGLONG
                format_ = "<Q"
        return (struct.pack(format_, value), field_type, flags)

    @staticmethod
    def prepare_binary_timestamp(
        value: Union[datetime.date, datetime.datetime]
    ) -> Tuple[bytearray, int]:
        """Prepare a timestamp object for the MySQL binary protocol

        This method prepares a timestamp of type datetime.datetime or
        datetime.date for sending over the MySQL binary protocol.
        A tuple is returned with the prepared value and field type
        as elements.

        Raises ValueError when the argument value is of invalid type.

        Returns a tuple.
        """
        if isinstance(value, datetime.datetime):
            field_type = FieldType.DATETIME
        elif isinstance(value, datetime.date):
            field_type = FieldType.DATE
        else:
            raise ValueError("Argument must a datetime.datetime or datetime.date")

        packed = (
            utils.int2store(value.year)
            + utils.int1store(value.month)
            + utils.int1store(value.day)
        )

        if isinstance(value, datetime.datetime):
            packed = (
                packed
                + utils.int1store(value.hour)
                + utils.int1store(value.minute)
                + utils.int1store(value.second)
            )
            if value.microsecond > 0:
                packed += utils.int4store(value.microsecond)

        packed = utils.int1store(len(packed)) + packed
        return (packed, field_type)

    @staticmethod
    def prepare_binary_time(
        value: Union[datetime.timedelta, datetime.time]
    ) -> Tuple[bytearray, int]:
        """Prepare a time object for the MySQL binary protocol

        This method prepares a time object of type datetime.timedelta or
        datetime.time for sending over the MySQL binary protocol.
        A tuple is returned with the prepared value and field type
        as elements.

        Raises ValueError when the argument value is of invalid type.

        Returns a tuple.
        """
        if not isinstance(value, (datetime.timedelta, datetime.time)):
            raise ValueError("Argument must a datetime.timedelta or datetime.time")

        field_type = FieldType.TIME
        negative = 0
        mcs = None
        packed = b""

        if isinstance(value, datetime.timedelta):
            if value.days < 0:
                negative = 1
            (hours, remainder) = divmod(value.seconds, 3600)
            (mins, secs) = divmod(remainder, 60)
            packed += (
                utils.int4store(abs(value.days))
                + utils.int1store(hours)
                + utils.int1store(mins)
                + utils.int1store(secs)
            )
            mcs = value.microseconds
        else:
            packed += (
                utils.int4store(0)
                + utils.int1store(value.hour)
                + utils.int1store(value.minute)
                + utils.int1store(value.second)
            )
            mcs = value.microsecond
        if mcs:
            packed += utils.int4store(mcs)

        packed = utils.int1store(negative) + packed
        packed = utils.int1store(len(packed)) + packed

        return (packed, field_type)

    @staticmethod
    def prepare_stmt_send_long_data(
        statement: int, param: int, data: bytes
    ) -> bytearray:
        """Prepare long data for prepared statements

        Returns a string.
        """
        packet: bytearray = utils.int4store(statement) + utils.int2store(param) + data
        return packet

    def make_stmt_execute(
        self,
        statement_id: int,
        data: Sequence[SupportedMysqlBinaryProtocolTypes] = (),
        parameters: Sequence[Any] = (),
        flags: int = 0,
        long_data_used: Optional[Dict[int, Tuple[bool]]] = None,
        charset: str = "utf8",
        query_attrs: Optional[List[Tuple[str, Any]]] = None,
        converter_str_fallback: bool = False,
    ) -> bytearray:
        """Make a MySQL packet with the Statement Execute command"""
        iteration_count = 1
        null_bitmap = [0] * ((len(data) + 7) // 8)
        values = []
        types = []
        packed = b""
        data_len = len(data)
        query_attr_names = []
        flags = flags if not query_attrs else flags + PARAMETER_COUNT_AVAILABLE
        if charset == "utf8mb4":
            charset = "utf8"
        if long_data_used is None:
            long_data_used = {}
        if query_attrs:
            data = list(data)
            for _, attr_val in query_attrs:
                data.append(attr_val)
            null_bitmap = [0] * ((len(data) + 7) // 8)
        if parameters or data:
            if data_len != len(parameters):
                raise InterfaceError(
                    "Failed executing prepared statement: data values does not"
                    " match number of parameters"
                )
            for pos, value in enumerate(data):
                _flags = 0
                if value is None:
                    null_bitmap[(pos // 8)] |= 1 << (pos % 8)
                    types.append(
                        utils.int1store(FieldType.NULL) + utils.int1store(_flags)
                    )
                    continue
                if pos in long_data_used:
                    if long_data_used[pos][0]:
                        # We suppose binary data
                        field_type = FieldType.BLOB
                    else:
                        # We suppose text data
                        field_type = FieldType.STRING
                elif isinstance(value, int):
                    (
                        packed,
                        field_type,
                        _flags,
                    ) = self.prepare_binary_integer(value)
                    values.append(packed)
                elif isinstance(value, str):
                    value = value.encode(charset)
                    values.append(utils.lc_int(len(value)) + value)
                    field_type = FieldType.STRING
                elif isinstance(value, bytes):
                    values.append(utils.lc_int(len(value)) + value)
                    field_type = FieldType.STRING
                elif isinstance(value, Decimal):
                    values.append(
                        utils.lc_int(len(str(value).encode(charset)))
                        + str(value).encode(charset)
                    )
                    field_type = FieldType.DECIMAL
                elif isinstance(value, float):
                    values.append(struct.pack("<d", value))
                    field_type = FieldType.DOUBLE
                elif isinstance(value, (datetime.datetime, datetime.date)):
                    (packed, field_type) = self.prepare_binary_timestamp(value)
                    values.append(packed)
                elif isinstance(value, (datetime.timedelta, datetime.time)):
                    (packed, field_type) = self.prepare_binary_time(value)
                    values.append(packed)
                elif converter_str_fallback:
                    value = str(value).encode(charset)
                    values.append(utils.lc_int(len(value)) + value)
                    field_type = FieldType.STRING
                else:
                    raise ProgrammingError(
                        "MySQL binary protocol can not handle "
                        f"'{value.__class__.__name__}' objects"
                    )
                types.append(utils.int1store(field_type) + utils.int1store(_flags))
                if query_attrs and pos + 1 > data_len:
                    name = query_attrs[pos - data_len][0].encode(charset)
                    query_attr_names.append(utils.lc_int(len(name)) + name)
        packet = (
            utils.int4store(statement_id)
            + utils.int1store(flags)
            + utils.int4store(iteration_count)
        )

        # if (num_params > 0 || (CLIENT_QUERY_ATTRIBUTES \
        #                        && (flags & PARAMETER_COUNT_AVAILABLE)) {
        if query_attrs is not None:
            parameter_count = data_len + len(query_attrs)
        else:
            parameter_count = data_len
        if parameter_count:
            # if CLIENT_QUERY_ATTRIBUTES is on
            if query_attrs is not None:
                packet += utils.lc_int(parameter_count)

            packet += b"".join(
                [struct.pack("B", bit) for bit in null_bitmap]
            ) + utils.int1store(1)
            count = 0
            for a_type in types:
                packet += a_type
                # if CLIENT_QUERY_ATTRIBUTES is on {
                #    string<lenenc>    parameter_name    Name of the parameter
                # or empty if not present
                # } if CLIENT_QUERY_ATTRIBUTES is on
                if query_attrs is not None:
                    if count + 1 > data_len:
                        packet += query_attr_names[count - data_len]
                    else:
                        packet += b"\x00"
                count += 1

            for a_value in values:
                packet += a_value

        return packet

    @staticmethod
    def parse_auth_switch_request(packet: bytes) -> Tuple[str, bytes]:
        """Parse a MySQL AuthSwitchRequest-packet"""
        if not packet[4] == 254:
            raise InterfaceError("Failed parsing AuthSwitchRequest packet")

        packet, plugin_name = utils.read_string(packet[5:], end=b"\x00")
        if packet and packet[-1] == 0:
            packet = packet[:-1]

        return plugin_name.decode("utf8"), packet

    @staticmethod
    def parse_auth_more_data(packet: bytes) -> bytes:
        """Parse a MySQL AuthMoreData-packet"""
        if not packet[4] == 1:
            raise InterfaceError("Failed parsing AuthMoreData packet")

        return packet[5:]

virt/lib/python3.9/site-packages/mysql/connector/protocol.py
PrinceDisant/LostInNL
"""
Type hint aliases hub
"""
import os
import typing

from datetime import date, datetime, time, timedelta
from decimal import Decimal
from time import struct_time
from typing import (
    TYPE_CHECKING,
    Dict,
    List,
    Mapping,
    Optional,
    Sequence,
    Set,
    Tuple,
    Union,
)

if hasattr(typing, "TypeAlias"):
    # pylint: disable=no-name-in-module
    from typing import TypeAlias  # type: ignore[attr-defined]
else:
    try:
        from typing_extensions import TypeAlias
    except ModuleNotFoundError:
        # pylint: disable=reimported
        from typing import Any as TypeAlias


if TYPE_CHECKING:
    from .custom_types import HexLiteral
    from .network import MySQLSocket


StrOrBytes = Union[str, bytes]
StrOrBytesPath = Union[StrOrBytes, os.PathLike]
SocketType: TypeAlias = "MySQLSocket"


""" Conversion """
ToPythonOutputTypes = Optional[
    Union[
        float,
        int,
        Decimal,
        StrOrBytes,
        date,
        timedelta,
        datetime,
        Set[str],
    ]
]
ToMysqlInputTypes = Optional[
    Union[
        int,
        float,
        Decimal,
        StrOrBytes,
        bool,
        datetime,
        date,
        time,
        struct_time,
        timedelta,
    ]
]
ToMysqlOutputTypes = Optional[Union[int, float, bytes, "HexLiteral"]]


""" Protocol """
HandShakeType = Dict[str, Optional[Union[int, StrOrBytes]]]
OkPacketType = Dict[str, Optional[Union[int, str]]]
EofPacketType = OkPacketType
StatsPacketType = Dict[str, Union[int, Decimal]]
SupportedMysqlBinaryProtocolTypes = Optional[
    Union[int, StrOrBytes, Decimal, float, datetime, date, timedelta, time]
]
QueryAttrType = List[
    # 2-Tuple: (str, attr_types)
    Tuple[str, SupportedMysqlBinaryProtocolTypes]
]
ParseValueFromBinaryResultPacketTypes = Optional[
    Union[
        int,
        float,
        Decimal,
        date,
        datetime,
        timedelta,
        str,
    ]
]
DescriptionType = Tuple[
    # Sometimes it can be represented as a 2-Tuple of the form:
    # Tuple[str, int],  # field name, field type,
    # but we will stick with the 9-Tuple format produced by
    # the protocol module.
    str,  # field name
    int,  # field type
    None,  # you can ignore it or take a look at protocol.parse_column()
    None,
    None,
    None,
    Union[bool, int],  # null ok
    int,  # field flags
    int,  # MySQL charset ID
]


""" Connection """
ConnAttrsType = Dict[str, Optional[Union[str, Tuple[str, str]]]]
ResultType = Mapping[
    str, Optional[Union[int, str, EofPacketType, List[DescriptionType]]]
]


""" Connection C-EXT """
CextEofPacketType = Dict[str, int]
CextResultType = Dict[str, Union[CextEofPacketType, List[DescriptionType]]]


""" Cursor """
ParamsSequenceType = Sequence[ToMysqlInputTypes]
ParamsDictType = Dict[str, ToMysqlInputTypes]
ParamsSequenceOrDictType = Union[ParamsDictType, ParamsSequenceType]
RowType = Tuple[ToPythonOutputTypes, ...]
WarningType = Tuple[str, int, str]

virt/lib/python3.9/site-packages/mysql/connector/types.py
PrinceDisant/LostInNL
# Copyright (c) 2009, 2022, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

"""Utilities."""

import os
import platform
import struct
import subprocess
import sys
import unicodedata

from decimal import Decimal
from functools import lru_cache
from stringprep import (
    in_table_a1,
    in_table_b1,
    in_table_c3,
    in_table_c4,
    in_table_c5,
    in_table_c6,
    in_table_c7,
    in_table_c8,
    in_table_c9,
    in_table_c11,
    in_table_c12,
    in_table_c21_c22,
    in_table_d1,
    in_table_d2,
)
from typing import Callable, Dict, List, Optional, Tuple, Type, Union

from .custom_types import HexLiteral
from .types import StrOrBytes

__MYSQL_DEBUG__: bool = False

NUMERIC_TYPES: Tuple[Type[int], Type[float], Type[Decimal], Type[HexLiteral]] = (
    int,
    float,
    Decimal,
    HexLiteral,
)


def intread(buf: Union[int, bytes]) -> int:
    """Unpacks the given buffer to an integer"""
    if isinstance(buf, int):
        return buf
    length = len(buf)
    if length == 1:
        return buf[0]
    if length <= 4:
        tmp = buf + b"\x00" * (4 - length)
        return int(struct.unpack("<I", tmp)[0])
    tmp = buf + b"\x00" * (8 - length)
    return int(struct.unpack("<Q", tmp)[0])


def int1store(i: int) -> bytearray:
    """
    Takes an unsigned byte (1 byte) and packs it as a bytes-object.

    Returns string.
    """
    if i < 0 or i > 255:
        raise ValueError("int1store requires 0 <= i <= 255")
    return bytearray(struct.pack("<B", i))


def int2store(i: int) -> bytearray:
    """
    Takes an unsigned short (2 bytes) and packs it as a bytes-object.

    Returns string.
    """
    if i < 0 or i > 65535:
        raise ValueError("int2store requires 0 <= i <= 65535")
    return bytearray(struct.pack("<H", i))


def int3store(i: int) -> bytearray:
    """
    Takes an unsigned integer (3 bytes) and packs it as a bytes-object.

    Returns string.
    """
    if i < 0 or i > 16777215:
        raise ValueError("int3store requires 0 <= i <= 16777215")
    return bytearray(struct.pack("<I", i)[0:3])


def int4store(i: int) -> bytearray:
    """
    Takes an unsigned integer (4 bytes) and packs it as a bytes-object.

    Returns string.
    """
    if i < 0 or i > 4294967295:
        raise ValueError("int4store requires 0 <= i <= 4294967295")
    return bytearray(struct.pack("<I", i))


def int8store(i: int) -> bytearray:
    """
    Takes an unsigned integer (8 bytes) and packs it as string.

    Returns string.
    """
    if i < 0 or i > 18446744073709551616:
        raise ValueError("int8store requires 0 <= i <= 2^64")
    return bytearray(struct.pack("<Q", i))


def intstore(i: int) -> bytearray:
    """
    Takes an unsigned integers and packs it as a bytes-object.

    This function uses int1store, int2store, int3store,
    int4store or int8store depending on the integer value.

    returns string.
    """
    if i < 0 or i > 18446744073709551616:
        raise ValueError("intstore requires 0 <= i <=  2^64")

    if i <= 255:
        formed_string = int1store
    elif i <= 65535:
        formed_string = int2store
    elif i <= 16777215:
        formed_string = int3store
    elif i <= 4294967295:
        formed_string = int4store
    else:
        formed_string = int8store

    return formed_string(i)


def lc_int(i: int) -> bytes:
    """
    Takes an unsigned integer and packs it as bytes,
    with the information of how much bytes the encoded int takes.
    """
    if i < 0 or i > 18446744073709551616:
        raise ValueError("Requires 0 <= i <= 2^64")

    if i < 251:
        return bytearray(struct.pack("<B", i))
    if i <= 65535:
        return b"\xfc" + bytearray(struct.pack("<H", i))
    if i <= 16777215:
        return b"\xfd" + bytearray(struct.pack("<I", i)[0:3])

    return b"\xfe" + bytearray(struct.pack("<Q", i))


def read_bytes(buf: bytes, size: int) -> Tuple[bytes, bytes]:
    """
    Reads bytes from a buffer.

    Returns a tuple with buffer less the read bytes, and the bytes.
    """
    res = buf[0:size]
    return (buf[size:], res)


def read_lc_string(buf: bytes) -> Tuple[bytes, Optional[bytes]]:
    """
    Takes a buffer and reads a length coded string from the start.

    This is how Length coded strings work

    If the string is 250 bytes long or smaller, then it looks like this:

      <-- 1b  -->
      +----------+-------------------------
      |  length  | a string goes here
      +----------+-------------------------

    If the string is bigger than 250, then it looks like this:

      <- 1b -><- 2/3/8 ->
      +------+-----------+-------------------------
      | type |  length   | a string goes here
      +------+-----------+-------------------------

      if type == \xfc:
          length is code in next 2 bytes
      elif type == \xfd:
          length is code in next 3 bytes
      elif type == \xfe:
          length is code in next 8 bytes

    NULL has a special value. If the buffer starts with \xfb then
    it's a NULL and we return None as value.

    Returns a tuple (trucated buffer, bytes).
    """
    if buf[0] == 251:  # \xfb
        # NULL value
        return (buf[1:], None)

    length = lsize = 0
    fst = buf[0]

    if fst <= 250:  # \xFA
        length = fst
        return (buf[1 + length :], buf[1 : length + 1])
    if fst == 252:
        lsize = 2
    elif fst == 253:
        lsize = 3
    elif fst == 254:
        lsize = 8

    length = intread(buf[1 : lsize + 1])
    return (buf[lsize + length + 1 :], buf[lsize + 1 : length + lsize + 1])


def read_lc_string_list(buf: bytes) -> Optional[Tuple[Optional[bytes], ...]]:
    """Reads all length encoded strings from the given buffer

    Returns a list of bytes
    """
    byteslst: List[Optional[bytes]] = []

    sizes = {252: 2, 253: 3, 254: 8}

    buf_len = len(buf)
    pos = 0

    while pos < buf_len:
        first = buf[pos]
        if first == 255:
            # Special case when MySQL error 1317 is returned by MySQL.
            # We simply return None.
            return None
        if first == 251:
            # NULL value
            byteslst.append(None)
            pos += 1
        else:
            if first <= 250:
                length = first
                byteslst.append(buf[(pos + 1) : length + (pos + 1)])
                pos += 1 + length
            else:
                lsize = 0
                try:
                    lsize = sizes[first]
                except KeyError:
                    return None
                length = intread(buf[(pos + 1) : lsize + (pos + 1)])
                byteslst.append(buf[pos + 1 + lsize : length + lsize + (pos + 1)])
                pos += 1 + lsize + length

    return tuple(byteslst)


def read_string(
    buf: bytes,
    end: Optional[bytes] = None,
    size: Optional[int] = None,
) -> Tuple[bytes, bytes]:
    """
    Reads a string up until a character or for a given size.

    Returns a tuple (trucated buffer, string).
    """
    if end is None and size is None:
        raise ValueError("read_string() needs either end or size")

    if end is not None:
        try:
            idx = buf.index(end)
        except ValueError as err:
            raise ValueError("end byte not present in buffer") from err
        return (buf[idx + 1 :], buf[0:idx])
    if size is not None:
        return read_bytes(buf, size)

    raise ValueError("read_string() needs either end or size (weird)")


def read_int(buf: bytes, size: int) -> Tuple[bytes, int]:
    """Read an integer from buffer

    Returns a tuple (truncated buffer, int)
    """
    res = intread(buf[0:size])
    return (buf[size:], res)


def read_lc_int(buf: bytes) -> Tuple[bytes, Optional[int]]:
    """
    Takes a buffer and reads an length code string from the start.

    Returns a tuple with buffer less the integer and the integer read.
    """
    if not buf:
        raise ValueError("Empty buffer.")

    lcbyte = buf[0]
    if lcbyte == 251:
        return (buf[1:], None)
    if lcbyte < 251:
        return (buf[1:], int(lcbyte))
    if lcbyte == 252:
        return (buf[3:], struct.unpack("<xH", buf[0:3])[0])
    if lcbyte == 253:
        return (buf[4:], struct.unpack("<I", buf[1:4] + b"\x00")[0])
    if lcbyte == 254:
        return (buf[9:], struct.unpack("<xQ", buf[0:9])[0])
    raise ValueError("Failed reading length encoded integer")


#
# For debugging
#
def _digest_buffer(buf: StrOrBytes) -> str:
    """Debug function for showing buffers"""
    if not isinstance(buf, str):
        return "".join([f"\\x{c:02x}" for c in buf])
    return "".join([f"\\x{ord(c):02x}" for c in buf])


def print_buffer(
    abuffer: StrOrBytes, prefix: Optional[str] = None, limit: int = 30
) -> None:
    """Debug function printing output of _digest_buffer()"""
    if prefix:
        if limit and limit > 0:
            digest = _digest_buffer(abuffer[0:limit])
        else:
            digest = _digest_buffer(abuffer)
        print(prefix + ": " + digest)
    else:
        print(_digest_buffer(abuffer))


def _parse_os_release() -> Dict[str, str]:
    """Parse the contents of /etc/os-release file.

    Returns:
        A dictionary containing release information.
    """
    distro: Dict[str, str] = {}
    os_release_file = os.path.join("/etc", "os-release")
    if not os.path.exists(os_release_file):
        return distro
    with open(os_release_file, encoding="utf-8") as file_obj:
        for line in file_obj:
            key_value = line.split("=")
            if len(key_value) != 2:
                continue
            key = key_value[0].lower()
            value = key_value[1].rstrip("\n").strip('"')
            distro[key] = value
    return distro


def _parse_lsb_release() -> Dict[str, str]:
    """Parse the contents of /etc/lsb-release file.

    Returns:
        A dictionary containing release information.
    """
    distro = {}
    lsb_release_file = os.path.join("/etc", "lsb-release")
    if os.path.exists(lsb_release_file):
        with open(lsb_release_file, encoding="utf-8") as file_obj:
            for line in file_obj:
                key_value = line.split("=")
                if len(key_value) != 2:
                    continue
                key = key_value[0].lower()
                value = key_value[1].rstrip("\n").strip('"')
                distro[key] = value
    return distro


def _parse_lsb_release_command() -> Optional[Dict[str, str]]:
    """Parse the output of the lsb_release command.

    Returns:
        A dictionary containing release information.
    """
    distro = {}
    with open(os.devnull, "w", encoding="utf-8") as devnull:
        try:
            stdout = subprocess.check_output(("lsb_release", "-a"), stderr=devnull)
        except OSError:
            return None
        lines = stdout.decode(sys.getfilesystemencoding()).splitlines()
        for line in lines:
            key_value = line.split(":")
            if len(key_value) != 2:
                continue
            key = key_value[0].replace(" ", "_").lower()
            value = key_value[1].strip("\t")
            distro[key] = value
    return distro


def linux_distribution() -> Tuple[str, str, str]:
    """Tries to determine the name of the Linux OS distribution name.

    First tries to get information from ``/etc/os-release`` file.
    If fails, tries to get the information of ``/etc/lsb-release`` file.
    And finally the information of ``lsb-release`` command.

    Returns:
        A tuple with (`name`, `version`, `codename`)
    """
    distro: Optional[Dict[str, str]] = _parse_lsb_release()
    if distro:
        return (
            distro.get("distrib_id", ""),
            distro.get("distrib_release", ""),
            distro.get("distrib_codename", ""),
        )

    distro = _parse_lsb_release_command()
    if distro:
        return (
            distro.get("distributor_id", ""),
            distro.get("release", ""),
            distro.get("codename", ""),
        )

    distro = _parse_os_release()
    if distro:
        return (
            distro.get("name", ""),
            distro.get("version_id", ""),
            distro.get("version_codename", ""),
        )

    return ("", "", "")


def _get_unicode_read_direction(unicode_str: str) -> str:
    """Get the readiness direction of the unicode string.

    We assume that the direction is "L-to-R" if the first character does not
    indicate the direction is "R-to-L" or an "AL" (Arabic Letter).
    """
    if unicode_str and unicodedata.bidirectional(unicode_str[0]) in (
        "R",
        "AL",
    ):
        return "R-to-L"
    return "L-to-R"


def _get_unicode_direction_rule(unicode_str: str) -> Dict[str, Callable[[str], bool]]:
    """
    1) The characters in section 5.8 MUST be prohibited.

    2) If a string contains any RandALCat character, the string MUST NOT
       contain any LCat character.

    3) If a string contains any RandALCat character, a RandALCat
       character MUST be the first character of the string, and a
       RandALCat character MUST be the last character of the string.
    """
    read_dir = _get_unicode_read_direction(unicode_str)

    # point 3)
    if read_dir == "R-to-L":
        if not (in_table_d1(unicode_str[0]) and in_table_d1(unicode_str[-1])):
            raise ValueError(
                "Invalid unicode Bidirectional sequence, if the "
                "first character is RandALCat, the final character"
                "must be RandALCat too."
            )
        # characters from in_table_d2 are prohibited.
        return {"Bidirectional Characters requirement 2 [StringPrep, d2]": in_table_d2}

    # characters from in_table_d1 are prohibited.
    return {"Bidirectional Characters requirement 2 [StringPrep, d2]": in_table_d1}


def validate_normalized_unicode_string(
    normalized_str: str,
) -> Optional[Tuple[str, str]]:
    """Check for Prohibited Output according to rfc4013 profile.

    This profile specifies the following characters as prohibited input:

       - Non-ASCII space characters [StringPrep, C.1.2]
       - ASCII control characters [StringPrep, C.2.1]
       - Non-ASCII control characters [StringPrep, C.2.2]
       - Private Use characters [StringPrep, C.3]
       - Non-character code points [StringPrep, C.4]
       - Surrogate code points [StringPrep, C.5]
       - Inappropriate for plain text characters [StringPrep, C.6]
       - Inappropriate for canonical representation characters [StringPrep, C.7]
       - Change display properties or deprecated characters [StringPrep, C.8]
       - Tagging characters [StringPrep, C.9]

    In addition of checking of Bidirectional Characters [StringPrep, Section 6]
    and the Unassigned Code Points [StringPrep, A.1].

    Returns:
        A tuple with ("probited character", "breaked_rule")
    """
    rules = {
        "Space characters that contains the ASCII code points": in_table_c11,
        "Space characters non-ASCII code points": in_table_c12,
        "Unassigned Code Points [StringPrep, A.1]": in_table_a1,
        "Non-ASCII space characters [StringPrep, C.1.2]": in_table_c12,
        "ASCII control characters [StringPrep, C.2.1]": in_table_c21_c22,
        "Private Use characters [StringPrep, C.3]": in_table_c3,
        "Non-character code points [StringPrep, C.4]": in_table_c4,
        "Surrogate code points [StringPrep, C.5]": in_table_c5,
        "Inappropriate for plain text characters [StringPrep, C.6]": in_table_c6,
        "Inappropriate for canonical representation characters [StringPrep, C.7]": in_table_c7,
        "Change display properties or deprecated characters [StringPrep, C.8]": in_table_c8,
        "Tagging characters [StringPrep, C.9]": in_table_c9,
    }

    try:
        rules.update(_get_unicode_direction_rule(normalized_str))
    except ValueError as err:
        return normalized_str, str(err)

    for char in normalized_str:
        for rule, func in rules.items():
            if func(char) and char != " ":
                return char, rule

    return None


def normalize_unicode_string(a_string: str) -> str:
    """normalizes a unicode string according to rfc4013

    Normalization of a unicode string according to rfc4013: The SASLprep profile
    of the "stringprep" algorithm.

    Normalization Unicode equivalence is the specification by the Unicode
    character encoding standard that some sequences of code points represent
    essentially the same character.

    This method normalizes using the Normalization Form Compatibility
    Composition (NFKC), as described in rfc4013 2.2.

    Returns:
        Normalized unicode string according to rfc4013.
    """
    # Per rfc4013 2.1. Mapping
    # non-ASCII space characters [StringPrep, C.1.2] are mapped to ' ' (U+0020)
    # "commonly mapped to nothing" characters [StringPrep, B.1] are mapped to ''
    nstr_list = [
        " " if in_table_c12(char) else "" if in_table_b1(char) else char
        for char in a_string
    ]

    nstr = "".join(nstr_list)

    # Per rfc4013 2.2. Use NFKC Normalization Form Compatibility Composition
    # Characters are decomposed by compatibility, then recomposed by canonical
    # equivalence.
    nstr = unicodedata.normalize("NFKC", nstr)
    if not nstr:
        # Normilization results in empty string.
        return ""

    return nstr


def init_bytearray(
    payload: Union[int, StrOrBytes] = b"", encoding: str = "utf-8"
) -> bytearray:
    """Initialize a bytearray from the payload."""
    if isinstance(payload, bytearray):
        return payload
    if isinstance(payload, int):
        return bytearray(payload)
    if not isinstance(payload, bytes):
        try:
            return bytearray(payload.encode(encoding=encoding))
        except AttributeError as err:
            raise ValueError("payload must be a str or bytes") from err

    return bytearray(payload)


@lru_cache()
def get_platform() -> Dict[str, Union[str, Tuple[str, str]]]:
    """Return a dict with the platform arch and OS version."""
    plat: Dict[str, Union[str, Tuple[str, str]]] = {"arch": "", "version": ""}
    if os.name == "nt":
        if "64" in platform.architecture()[0]:
            plat["arch"] = "x86_64"
        elif "32" in platform.architecture()[0]:
            plat["arch"] = "i386"
        else:
            plat["arch"] = platform.architecture()
        plat["version"] = f"Windows-{platform.win32_ver()[1]}"
    else:
        plat["arch"] = platform.machine()
        if platform.system() == "Darwin":
            plat["version"] = f"macOS-{platform.mac_ver()[0]}"
        else:
            plat["version"] = "-".join(linux_distribution()[0:2])

    return plat

virt/lib/python3.9/site-packages/mysql/connector/utils 2.py
PrinceDisant/LostInNL
# Copyright (c) 2009, 2022, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

"""Utilities."""

import os
import platform
import struct
import subprocess
import sys
import unicodedata

from decimal import Decimal
from functools import lru_cache
from stringprep import (
    in_table_a1,
    in_table_b1,
    in_table_c3,
    in_table_c4,
    in_table_c5,
    in_table_c6,
    in_table_c7,
    in_table_c8,
    in_table_c9,
    in_table_c11,
    in_table_c12,
    in_table_c21_c22,
    in_table_d1,
    in_table_d2,
)
from typing import Callable, Dict, List, Optional, Tuple, Type, Union

from .custom_types import HexLiteral
from .types import StrOrBytes

__MYSQL_DEBUG__: bool = False

NUMERIC_TYPES: Tuple[Type[int], Type[float], Type[Decimal], Type[HexLiteral]] = (
    int,
    float,
    Decimal,
    HexLiteral,
)


def intread(buf: Union[int, bytes]) -> int:
    """Unpacks the given buffer to an integer"""
    if isinstance(buf, int):
        return buf
    length = len(buf)
    if length == 1:
        return buf[0]
    if length <= 4:
        tmp = buf + b"\x00" * (4 - length)
        return int(struct.unpack("<I", tmp)[0])
    tmp = buf + b"\x00" * (8 - length)
    return int(struct.unpack("<Q", tmp)[0])


def int1store(i: int) -> bytearray:
    """
    Takes an unsigned byte (1 byte) and packs it as a bytes-object.

    Returns string.
    """
    if i < 0 or i > 255:
        raise ValueError("int1store requires 0 <= i <= 255")
    return bytearray(struct.pack("<B", i))


def int2store(i: int) -> bytearray:
    """
    Takes an unsigned short (2 bytes) and packs it as a bytes-object.

    Returns string.
    """
    if i < 0 or i > 65535:
        raise ValueError("int2store requires 0 <= i <= 65535")
    return bytearray(struct.pack("<H", i))


def int3store(i: int) -> bytearray:
    """
    Takes an unsigned integer (3 bytes) and packs it as a bytes-object.

    Returns string.
    """
    if i < 0 or i > 16777215:
        raise ValueError("int3store requires 0 <= i <= 16777215")
    return bytearray(struct.pack("<I", i)[0:3])


def int4store(i: int) -> bytearray:
    """
    Takes an unsigned integer (4 bytes) and packs it as a bytes-object.

    Returns string.
    """
    if i < 0 or i > 4294967295:
        raise ValueError("int4store requires 0 <= i <= 4294967295")
    return bytearray(struct.pack("<I", i))


def int8store(i: int) -> bytearray:
    """
    Takes an unsigned integer (8 bytes) and packs it as string.

    Returns string.
    """
    if i < 0 or i > 18446744073709551616:
        raise ValueError("int8store requires 0 <= i <= 2^64")
    return bytearray(struct.pack("<Q", i))


def intstore(i: int) -> bytearray:
    """
    Takes an unsigned integers and packs it as a bytes-object.

    This function uses int1store, int2store, int3store,
    int4store or int8store depending on the integer value.

    returns string.
    """
    if i < 0 or i > 18446744073709551616:
        raise ValueError("intstore requires 0 <= i <=  2^64")

    if i <= 255:
        formed_string = int1store
    elif i <= 65535:
        formed_string = int2store
    elif i <= 16777215:
        formed_string = int3store
    elif i <= 4294967295:
        formed_string = int4store
    else:
        formed_string = int8store

    return formed_string(i)


def lc_int(i: int) -> bytes:
    """
    Takes an unsigned integer and packs it as bytes,
    with the information of how much bytes the encoded int takes.
    """
    if i < 0 or i > 18446744073709551616:
        raise ValueError("Requires 0 <= i <= 2^64")

    if i < 251:
        return bytearray(struct.pack("<B", i))
    if i <= 65535:
        return b"\xfc" + bytearray(struct.pack("<H", i))
    if i <= 16777215:
        return b"\xfd" + bytearray(struct.pack("<I", i)[0:3])

    return b"\xfe" + bytearray(struct.pack("<Q", i))


def read_bytes(buf: bytes, size: int) -> Tuple[bytes, bytes]:
    """
    Reads bytes from a buffer.

    Returns a tuple with buffer less the read bytes, and the bytes.
    """
    res = buf[0:size]
    return (buf[size:], res)


def read_lc_string(buf: bytes) -> Tuple[bytes, Optional[bytes]]:
    """
    Takes a buffer and reads a length coded string from the start.

    This is how Length coded strings work

    If the string is 250 bytes long or smaller, then it looks like this:

      <-- 1b  -->
      +----------+-------------------------
      |  length  | a string goes here
      +----------+-------------------------

    If the string is bigger than 250, then it looks like this:

      <- 1b -><- 2/3/8 ->
      +------+-----------+-------------------------
      | type |  length   | a string goes here
      +------+-----------+-------------------------

      if type == \xfc:
          length is code in next 2 bytes
      elif type == \xfd:
          length is code in next 3 bytes
      elif type == \xfe:
          length is code in next 8 bytes

    NULL has a special value. If the buffer starts with \xfb then
    it's a NULL and we return None as value.

    Returns a tuple (trucated buffer, bytes).
    """
    if buf[0] == 251:  # \xfb
        # NULL value
        return (buf[1:], None)

    length = lsize = 0
    fst = buf[0]

    if fst <= 250:  # \xFA
        length = fst
        return (buf[1 + length :], buf[1 : length + 1])
    if fst == 252:
        lsize = 2
    elif fst == 253:
        lsize = 3
    elif fst == 254:
        lsize = 8

    length = intread(buf[1 : lsize + 1])
    return (buf[lsize + length + 1 :], buf[lsize + 1 : length + lsize + 1])


def read_lc_string_list(buf: bytes) -> Optional[Tuple[Optional[bytes], ...]]:
    """Reads all length encoded strings from the given buffer

    Returns a list of bytes
    """
    byteslst: List[Optional[bytes]] = []

    sizes = {252: 2, 253: 3, 254: 8}

    buf_len = len(buf)
    pos = 0

    while pos < buf_len:
        first = buf[pos]
        if first == 255:
            # Special case when MySQL error 1317 is returned by MySQL.
            # We simply return None.
            return None
        if first == 251:
            # NULL value
            byteslst.append(None)
            pos += 1
        else:
            if first <= 250:
                length = first
                byteslst.append(buf[(pos + 1) : length + (pos + 1)])
                pos += 1 + length
            else:
                lsize = 0
                try:
                    lsize = sizes[first]
                except KeyError:
                    return None
                length = intread(buf[(pos + 1) : lsize + (pos + 1)])
                byteslst.append(buf[pos + 1 + lsize : length + lsize + (pos + 1)])
                pos += 1 + lsize + length

    return tuple(byteslst)


def read_string(
    buf: bytes,
    end: Optional[bytes] = None,
    size: Optional[int] = None,
) -> Tuple[bytes, bytes]:
    """
    Reads a string up until a character or for a given size.

    Returns a tuple (trucated buffer, string).
    """
    if end is None and size is None:
        raise ValueError("read_string() needs either end or size")

    if end is not None:
        try:
            idx = buf.index(end)
        except ValueError as err:
            raise ValueError("end byte not present in buffer") from err
        return (buf[idx + 1 :], buf[0:idx])
    if size is not None:
        return read_bytes(buf, size)

    raise ValueError("read_string() needs either end or size (weird)")


def read_int(buf: bytes, size: int) -> Tuple[bytes, int]:
    """Read an integer from buffer

    Returns a tuple (truncated buffer, int)
    """
    res = intread(buf[0:size])
    return (buf[size:], res)


def read_lc_int(buf: bytes) -> Tuple[bytes, Optional[int]]:
    """
    Takes a buffer and reads an length code string from the start.

    Returns a tuple with buffer less the integer and the integer read.
    """
    if not buf:
        raise ValueError("Empty buffer.")

    lcbyte = buf[0]
    if lcbyte == 251:
        return (buf[1:], None)
    if lcbyte < 251:
        return (buf[1:], int(lcbyte))
    if lcbyte == 252:
        return (buf[3:], struct.unpack("<xH", buf[0:3])[0])
    if lcbyte == 253:
        return (buf[4:], struct.unpack("<I", buf[1:4] + b"\x00")[0])
    if lcbyte == 254:
        return (buf[9:], struct.unpack("<xQ", buf[0:9])[0])
    raise ValueError("Failed reading length encoded integer")


#
# For debugging
#
def _digest_buffer(buf: StrOrBytes) -> str:
    """Debug function for showing buffers"""
    if not isinstance(buf, str):
        return "".join([f"\\x{c:02x}" for c in buf])
    return "".join([f"\\x{ord(c):02x}" for c in buf])


def print_buffer(
    abuffer: StrOrBytes, prefix: Optional[str] = None, limit: int = 30
) -> None:
    """Debug function printing output of _digest_buffer()"""
    if prefix:
        if limit and limit > 0:
            digest = _digest_buffer(abuffer[0:limit])
        else:
            digest = _digest_buffer(abuffer)
        print(prefix + ": " + digest)
    else:
        print(_digest_buffer(abuffer))


def _parse_os_release() -> Dict[str, str]:
    """Parse the contents of /etc/os-release file.

    Returns:
        A dictionary containing release information.
    """
    distro: Dict[str, str] = {}
    os_release_file = os.path.join("/etc", "os-release")
    if not os.path.exists(os_release_file):
        return distro
    with open(os_release_file, encoding="utf-8") as file_obj:
        for line in file_obj:
            key_value = line.split("=")
            if len(key_value) != 2:
                continue
            key = key_value[0].lower()
            value = key_value[1].rstrip("\n").strip('"')
            distro[key] = value
    return distro


def _parse_lsb_release() -> Dict[str, str]:
    """Parse the contents of /etc/lsb-release file.

    Returns:
        A dictionary containing release information.
    """
    distro = {}
    lsb_release_file = os.path.join("/etc", "lsb-release")
    if os.path.exists(lsb_release_file):
        with open(lsb_release_file, encoding="utf-8") as file_obj:
            for line in file_obj:
                key_value = line.split("=")
                if len(key_value) != 2:
                    continue
                key = key_value[0].lower()
                value = key_value[1].rstrip("\n").strip('"')
                distro[key] = value
    return distro


def _parse_lsb_release_command() -> Optional[Dict[str, str]]:
    """Parse the output of the lsb_release command.

    Returns:
        A dictionary containing release information.
    """
    distro = {}
    with open(os.devnull, "w", encoding="utf-8") as devnull:
        try:
            stdout = subprocess.check_output(("lsb_release", "-a"), stderr=devnull)
        except OSError:
            return None
        lines = stdout.decode(sys.getfilesystemencoding()).splitlines()
        for line in lines:
            key_value = line.split(":")
            if len(key_value) != 2:
                continue
            key = key_value[0].replace(" ", "_").lower()
            value = key_value[1].strip("\t")
            distro[key] = value
    return distro


def linux_distribution() -> Tuple[str, str, str]:
    """Tries to determine the name of the Linux OS distribution name.

    First tries to get information from ``/etc/os-release`` file.
    If fails, tries to get the information of ``/etc/lsb-release`` file.
    And finally the information of ``lsb-release`` command.

    Returns:
        A tuple with (`name`, `version`, `codename`)
    """
    distro: Optional[Dict[str, str]] = _parse_lsb_release()
    if distro:
        return (
            distro.get("distrib_id", ""),
            distro.get("distrib_release", ""),
            distro.get("distrib_codename", ""),
        )

    distro = _parse_lsb_release_command()
    if distro:
        return (
            distro.get("distributor_id", ""),
            distro.get("release", ""),
            distro.get("codename", ""),
        )

    distro = _parse_os_release()
    if distro:
        return (
            distro.get("name", ""),
            distro.get("version_id", ""),
            distro.get("version_codename", ""),
        )

    return ("", "", "")


def _get_unicode_read_direction(unicode_str: str) -> str:
    """Get the readiness direction of the unicode string.

    We assume that the direction is "L-to-R" if the first character does not
    indicate the direction is "R-to-L" or an "AL" (Arabic Letter).
    """
    if unicode_str and unicodedata.bidirectional(unicode_str[0]) in (
        "R",
        "AL",
    ):
        return "R-to-L"
    return "L-to-R"


def _get_unicode_direction_rule(unicode_str: str) -> Dict[str, Callable[[str], bool]]:
    """
    1) The characters in section 5.8 MUST be prohibited.

    2) If a string contains any RandALCat character, the string MUST NOT
       contain any LCat character.

    3) If a string contains any RandALCat character, a RandALCat
       character MUST be the first character of the string, and a
       RandALCat character MUST be the last character of the string.
    """
    read_dir = _get_unicode_read_direction(unicode_str)

    # point 3)
    if read_dir == "R-to-L":
        if not (in_table_d1(unicode_str[0]) and in_table_d1(unicode_str[-1])):
            raise ValueError(
                "Invalid unicode Bidirectional sequence, if the "
                "first character is RandALCat, the final character"
                "must be RandALCat too."
            )
        # characters from in_table_d2 are prohibited.
        return {"Bidirectional Characters requirement 2 [StringPrep, d2]": in_table_d2}

    # characters from in_table_d1 are prohibited.
    return {"Bidirectional Characters requirement 2 [StringPrep, d2]": in_table_d1}


def validate_normalized_unicode_string(
    normalized_str: str,
) -> Optional[Tuple[str, str]]:
    """Check for Prohibited Output according to rfc4013 profile.

    This profile specifies the following characters as prohibited input:

       - Non-ASCII space characters [StringPrep, C.1.2]
       - ASCII control characters [StringPrep, C.2.1]
       - Non-ASCII control characters [StringPrep, C.2.2]
       - Private Use characters [StringPrep, C.3]
       - Non-character code points [StringPrep, C.4]
       - Surrogate code points [StringPrep, C.5]
       - Inappropriate for plain text characters [StringPrep, C.6]
       - Inappropriate for canonical representation characters [StringPrep, C.7]
       - Change display properties or deprecated characters [StringPrep, C.8]
       - Tagging characters [StringPrep, C.9]

    In addition of checking of Bidirectional Characters [StringPrep, Section 6]
    and the Unassigned Code Points [StringPrep, A.1].

    Returns:
        A tuple with ("probited character", "breaked_rule")
    """
    rules = {
        "Space characters that contains the ASCII code points": in_table_c11,
        "Space characters non-ASCII code points": in_table_c12,
        "Unassigned Code Points [StringPrep, A.1]": in_table_a1,
        "Non-ASCII space characters [StringPrep, C.1.2]": in_table_c12,
        "ASCII control characters [StringPrep, C.2.1]": in_table_c21_c22,
        "Private Use characters [StringPrep, C.3]": in_table_c3,
        "Non-character code points [StringPrep, C.4]": in_table_c4,
        "Surrogate code points [StringPrep, C.5]": in_table_c5,
        "Inappropriate for plain text characters [StringPrep, C.6]": in_table_c6,
        "Inappropriate for canonical representation characters [StringPrep, C.7]": in_table_c7,
        "Change display properties or deprecated characters [StringPrep, C.8]": in_table_c8,
        "Tagging characters [StringPrep, C.9]": in_table_c9,
    }

    try:
        rules.update(_get_unicode_direction_rule(normalized_str))
    except ValueError as err:
        return normalized_str, str(err)

    for char in normalized_str:
        for rule, func in rules.items():
            if func(char) and char != " ":
                return char, rule

    return None


def normalize_unicode_string(a_string: str) -> str:
    """normalizes a unicode string according to rfc4013

    Normalization of a unicode string according to rfc4013: The SASLprep profile
    of the "stringprep" algorithm.

    Normalization Unicode equivalence is the specification by the Unicode
    character encoding standard that some sequences of code points represent
    essentially the same character.

    This method normalizes using the Normalization Form Compatibility
    Composition (NFKC), as described in rfc4013 2.2.

    Returns:
        Normalized unicode string according to rfc4013.
    """
    # Per rfc4013 2.1. Mapping
    # non-ASCII space characters [StringPrep, C.1.2] are mapped to ' ' (U+0020)
    # "commonly mapped to nothing" characters [StringPrep, B.1] are mapped to ''
    nstr_list = [
        " " if in_table_c12(char) else "" if in_table_b1(char) else char
        for char in a_string
    ]

    nstr = "".join(nstr_list)

    # Per rfc4013 2.2. Use NFKC Normalization Form Compatibility Composition
    # Characters are decomposed by compatibility, then recomposed by canonical
    # equivalence.
    nstr = unicodedata.normalize("NFKC", nstr)
    if not nstr:
        # Normilization results in empty string.
        return ""

    return nstr


def init_bytearray(
    payload: Union[int, StrOrBytes] = b"", encoding: str = "utf-8"
) -> bytearray:
    """Initialize a bytearray from the payload."""
    if isinstance(payload, bytearray):
        return payload
    if isinstance(payload, int):
        return bytearray(payload)
    if not isinstance(payload, bytes):
        try:
            return bytearray(payload.encode(encoding=encoding))
        except AttributeError as err:
            raise ValueError("payload must be a str or bytes") from err

    return bytearray(payload)


@lru_cache()
def get_platform() -> Dict[str, Union[str, Tuple[str, str]]]:
    """Return a dict with the platform arch and OS version."""
    plat: Dict[str, Union[str, Tuple[str, str]]] = {"arch": "", "version": ""}
    if os.name == "nt":
        if "64" in platform.architecture()[0]:
            plat["arch"] = "x86_64"
        elif "32" in platform.architecture()[0]:
            plat["arch"] = "i386"
        else:
            plat["arch"] = platform.architecture()
        plat["version"] = f"Windows-{platform.win32_ver()[1]}"
    else:
        plat["arch"] = platform.machine()
        if platform.system() == "Darwin":
            plat["version"] = f"macOS-{platform.mac_ver()[0]}"
        else:
            plat["version"] = "-".join(linux_distribution()[0:2])

    return plat

virt/lib/python3.9/site-packages/mysql/connector/utils 3.py
PrinceDisant/LostInNL
# Copyright (c) 2009, 2022, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

"""Utilities."""

import os
import platform
import struct
import subprocess
import sys
import unicodedata

from decimal import Decimal
from functools import lru_cache
from stringprep import (
    in_table_a1,
    in_table_b1,
    in_table_c3,
    in_table_c4,
    in_table_c5,
    in_table_c6,
    in_table_c7,
    in_table_c8,
    in_table_c9,
    in_table_c11,
    in_table_c12,
    in_table_c21_c22,
    in_table_d1,
    in_table_d2,
)
from typing import Callable, Dict, List, Optional, Tuple, Type, Union

from .custom_types import HexLiteral
from .types import StrOrBytes

__MYSQL_DEBUG__: bool = False

NUMERIC_TYPES: Tuple[Type[int], Type[float], Type[Decimal], Type[HexLiteral]] = (
    int,
    float,
    Decimal,
    HexLiteral,
)


def intread(buf: Union[int, bytes]) -> int:
    """Unpacks the given buffer to an integer"""
    if isinstance(buf, int):
        return buf
    length = len(buf)
    if length == 1:
        return buf[0]
    if length <= 4:
        tmp = buf + b"\x00" * (4 - length)
        return int(struct.unpack("<I", tmp)[0])
    tmp = buf + b"\x00" * (8 - length)
    return int(struct.unpack("<Q", tmp)[0])


def int1store(i: int) -> bytearray:
    """
    Takes an unsigned byte (1 byte) and packs it as a bytes-object.

    Returns string.
    """
    if i < 0 or i > 255:
        raise ValueError("int1store requires 0 <= i <= 255")
    return bytearray(struct.pack("<B", i))


def int2store(i: int) -> bytearray:
    """
    Takes an unsigned short (2 bytes) and packs it as a bytes-object.

    Returns string.
    """
    if i < 0 or i > 65535:
        raise ValueError("int2store requires 0 <= i <= 65535")
    return bytearray(struct.pack("<H", i))


def int3store(i: int) -> bytearray:
    """
    Takes an unsigned integer (3 bytes) and packs it as a bytes-object.

    Returns string.
    """
    if i < 0 or i > 16777215:
        raise ValueError("int3store requires 0 <= i <= 16777215")
    return bytearray(struct.pack("<I", i)[0:3])


def int4store(i: int) -> bytearray:
    """
    Takes an unsigned integer (4 bytes) and packs it as a bytes-object.

    Returns string.
    """
    if i < 0 or i > 4294967295:
        raise ValueError("int4store requires 0 <= i <= 4294967295")
    return bytearray(struct.pack("<I", i))


def int8store(i: int) -> bytearray:
    """
    Takes an unsigned integer (8 bytes) and packs it as string.

    Returns string.
    """
    if i < 0 or i > 18446744073709551616:
        raise ValueError("int8store requires 0 <= i <= 2^64")
    return bytearray(struct.pack("<Q", i))


def intstore(i: int) -> bytearray:
    """
    Takes an unsigned integers and packs it as a bytes-object.

    This function uses int1store, int2store, int3store,
    int4store or int8store depending on the integer value.

    returns string.
    """
    if i < 0 or i > 18446744073709551616:
        raise ValueError("intstore requires 0 <= i <=  2^64")

    if i <= 255:
        formed_string = int1store
    elif i <= 65535:
        formed_string = int2store
    elif i <= 16777215:
        formed_string = int3store
    elif i <= 4294967295:
        formed_string = int4store
    else:
        formed_string = int8store

    return formed_string(i)


def lc_int(i: int) -> bytes:
    """
    Takes an unsigned integer and packs it as bytes,
    with the information of how much bytes the encoded int takes.
    """
    if i < 0 or i > 18446744073709551616:
        raise ValueError("Requires 0 <= i <= 2^64")

    if i < 251:
        return bytearray(struct.pack("<B", i))
    if i <= 65535:
        return b"\xfc" + bytearray(struct.pack("<H", i))
    if i <= 16777215:
        return b"\xfd" + bytearray(struct.pack("<I", i)[0:3])

    return b"\xfe" + bytearray(struct.pack("<Q", i))


def read_bytes(buf: bytes, size: int) -> Tuple[bytes, bytes]:
    """
    Reads bytes from a buffer.

    Returns a tuple with buffer less the read bytes, and the bytes.
    """
    res = buf[0:size]
    return (buf[size:], res)


def read_lc_string(buf: bytes) -> Tuple[bytes, Optional[bytes]]:
    """
    Takes a buffer and reads a length coded string from the start.

    This is how Length coded strings work

    If the string is 250 bytes long or smaller, then it looks like this:

      <-- 1b  -->
      +----------+-------------------------
      |  length  | a string goes here
      +----------+-------------------------

    If the string is bigger than 250, then it looks like this:

      <- 1b -><- 2/3/8 ->
      +------+-----------+-------------------------
      | type |  length   | a string goes here
      +------+-----------+-------------------------

      if type == \xfc:
          length is code in next 2 bytes
      elif type == \xfd:
          length is code in next 3 bytes
      elif type == \xfe:
          length is code in next 8 bytes

    NULL has a special value. If the buffer starts with \xfb then
    it's a NULL and we return None as value.

    Returns a tuple (trucated buffer, bytes).
    """
    if buf[0] == 251:  # \xfb
        # NULL value
        return (buf[1:], None)

    length = lsize = 0
    fst = buf[0]

    if fst <= 250:  # \xFA
        length = fst
        return (buf[1 + length :], buf[1 : length + 1])
    if fst == 252:
        lsize = 2
    elif fst == 253:
        lsize = 3
    elif fst == 254:
        lsize = 8

    length = intread(buf[1 : lsize + 1])
    return (buf[lsize + length + 1 :], buf[lsize + 1 : length + lsize + 1])


def read_lc_string_list(buf: bytes) -> Optional[Tuple[Optional[bytes], ...]]:
    """Reads all length encoded strings from the given buffer

    Returns a list of bytes
    """
    byteslst: List[Optional[bytes]] = []

    sizes = {252: 2, 253: 3, 254: 8}

    buf_len = len(buf)
    pos = 0

    while pos < buf_len:
        first = buf[pos]
        if first == 255:
            # Special case when MySQL error 1317 is returned by MySQL.
            # We simply return None.
            return None
        if first == 251:
            # NULL value
            byteslst.append(None)
            pos += 1
        else:
            if first <= 250:
                length = first
                byteslst.append(buf[(pos + 1) : length + (pos + 1)])
                pos += 1 + length
            else:
                lsize = 0
                try:
                    lsize = sizes[first]
                except KeyError:
                    return None
                length = intread(buf[(pos + 1) : lsize + (pos + 1)])
                byteslst.append(buf[pos + 1 + lsize : length + lsize + (pos + 1)])
                pos += 1 + lsize + length

    return tuple(byteslst)


def read_string(
    buf: bytes,
    end: Optional[bytes] = None,
    size: Optional[int] = None,
) -> Tuple[bytes, bytes]:
    """
    Reads a string up until a character or for a given size.

    Returns a tuple (trucated buffer, string).
    """
    if end is None and size is None:
        raise ValueError("read_string() needs either end or size")

    if end is not None:
        try:
            idx = buf.index(end)
        except ValueError as err:
            raise ValueError("end byte not present in buffer") from err
        return (buf[idx + 1 :], buf[0:idx])
    if size is not None:
        return read_bytes(buf, size)

    raise ValueError("read_string() needs either end or size (weird)")


def read_int(buf: bytes, size: int) -> Tuple[bytes, int]:
    """Read an integer from buffer

    Returns a tuple (truncated buffer, int)
    """
    res = intread(buf[0:size])
    return (buf[size:], res)


def read_lc_int(buf: bytes) -> Tuple[bytes, Optional[int]]:
    """
    Takes a buffer and reads an length code string from the start.

    Returns a tuple with buffer less the integer and the integer read.
    """
    if not buf:
        raise ValueError("Empty buffer.")

    lcbyte = buf[0]
    if lcbyte == 251:
        return (buf[1:], None)
    if lcbyte < 251:
        return (buf[1:], int(lcbyte))
    if lcbyte == 252:
        return (buf[3:], struct.unpack("<xH", buf[0:3])[0])
    if lcbyte == 253:
        return (buf[4:], struct.unpack("<I", buf[1:4] + b"\x00")[0])
    if lcbyte == 254:
        return (buf[9:], struct.unpack("<xQ", buf[0:9])[0])
    raise ValueError("Failed reading length encoded integer")


#
# For debugging
#
def _digest_buffer(buf: StrOrBytes) -> str:
    """Debug function for showing buffers"""
    if not isinstance(buf, str):
        return "".join([f"\\x{c:02x}" for c in buf])
    return "".join([f"\\x{ord(c):02x}" for c in buf])


def print_buffer(
    abuffer: StrOrBytes, prefix: Optional[str] = None, limit: int = 30
) -> None:
    """Debug function printing output of _digest_buffer()"""
    if prefix:
        if limit and limit > 0:
            digest = _digest_buffer(abuffer[0:limit])
        else:
            digest = _digest_buffer(abuffer)
        print(prefix + ": " + digest)
    else:
        print(_digest_buffer(abuffer))


def _parse_os_release() -> Dict[str, str]:
    """Parse the contents of /etc/os-release file.

    Returns:
        A dictionary containing release information.
    """
    distro: Dict[str, str] = {}
    os_release_file = os.path.join("/etc", "os-release")
    if not os.path.exists(os_release_file):
        return distro
    with open(os_release_file, encoding="utf-8") as file_obj:
        for line in file_obj:
            key_value = line.split("=")
            if len(key_value) != 2:
                continue
            key = key_value[0].lower()
            value = key_value[1].rstrip("\n").strip('"')
            distro[key] = value
    return distro


def _parse_lsb_release() -> Dict[str, str]:
    """Parse the contents of /etc/lsb-release file.

    Returns:
        A dictionary containing release information.
    """
    distro = {}
    lsb_release_file = os.path.join("/etc", "lsb-release")
    if os.path.exists(lsb_release_file):
        with open(lsb_release_file, encoding="utf-8") as file_obj:
            for line in file_obj:
                key_value = line.split("=")
                if len(key_value) != 2:
                    continue
                key = key_value[0].lower()
                value = key_value[1].rstrip("\n").strip('"')
                distro[key] = value
    return distro


def _parse_lsb_release_command() -> Optional[Dict[str, str]]:
    """Parse the output of the lsb_release command.

    Returns:
        A dictionary containing release information.
    """
    distro = {}
    with open(os.devnull, "w", encoding="utf-8") as devnull:
        try:
            stdout = subprocess.check_output(("lsb_release", "-a"), stderr=devnull)
        except OSError:
            return None
        lines = stdout.decode(sys.getfilesystemencoding()).splitlines()
        for line in lines:
            key_value = line.split(":")
            if len(key_value) != 2:
                continue
            key = key_value[0].replace(" ", "_").lower()
            value = key_value[1].strip("\t")
            distro[key] = value
    return distro


def linux_distribution() -> Tuple[str, str, str]:
    """Tries to determine the name of the Linux OS distribution name.

    First tries to get information from ``/etc/os-release`` file.
    If fails, tries to get the information of ``/etc/lsb-release`` file.
    And finally the information of ``lsb-release`` command.

    Returns:
        A tuple with (`name`, `version`, `codename`)
    """
    distro: Optional[Dict[str, str]] = _parse_lsb_release()
    if distro:
        return (
            distro.get("distrib_id", ""),
            distro.get("distrib_release", ""),
            distro.get("distrib_codename", ""),
        )

    distro = _parse_lsb_release_command()
    if distro:
        return (
            distro.get("distributor_id", ""),
            distro.get("release", ""),
            distro.get("codename", ""),
        )

    distro = _parse_os_release()
    if distro:
        return (
            distro.get("name", ""),
            distro.get("version_id", ""),
            distro.get("version_codename", ""),
        )

    return ("", "", "")


def _get_unicode_read_direction(unicode_str: str) -> str:
    """Get the readiness direction of the unicode string.

    We assume that the direction is "L-to-R" if the first character does not
    indicate the direction is "R-to-L" or an "AL" (Arabic Letter).
    """
    if unicode_str and unicodedata.bidirectional(unicode_str[0]) in (
        "R",
        "AL",
    ):
        return "R-to-L"
    return "L-to-R"


def _get_unicode_direction_rule(unicode_str: str) -> Dict[str, Callable[[str], bool]]:
    """
    1) The characters in section 5.8 MUST be prohibited.

    2) If a string contains any RandALCat character, the string MUST NOT
       contain any LCat character.

    3) If a string contains any RandALCat character, a RandALCat
       character MUST be the first character of the string, and a
       RandALCat character MUST be the last character of the string.
    """
    read_dir = _get_unicode_read_direction(unicode_str)

    # point 3)
    if read_dir == "R-to-L":
        if not (in_table_d1(unicode_str[0]) and in_table_d1(unicode_str[-1])):
            raise ValueError(
                "Invalid unicode Bidirectional sequence, if the "
                "first character is RandALCat, the final character"
                "must be RandALCat too."
            )
        # characters from in_table_d2 are prohibited.
        return {"Bidirectional Characters requirement 2 [StringPrep, d2]": in_table_d2}

    # characters from in_table_d1 are prohibited.
    return {"Bidirectional Characters requirement 2 [StringPrep, d2]": in_table_d1}


def validate_normalized_unicode_string(
    normalized_str: str,
) -> Optional[Tuple[str, str]]:
    """Check for Prohibited Output according to rfc4013 profile.

    This profile specifies the following characters as prohibited input:

       - Non-ASCII space characters [StringPrep, C.1.2]
       - ASCII control characters [StringPrep, C.2.1]
       - Non-ASCII control characters [StringPrep, C.2.2]
       - Private Use characters [StringPrep, C.3]
       - Non-character code points [StringPrep, C.4]
       - Surrogate code points [StringPrep, C.5]
       - Inappropriate for plain text characters [StringPrep, C.6]
       - Inappropriate for canonical representation characters [StringPrep, C.7]
       - Change display properties or deprecated characters [StringPrep, C.8]
       - Tagging characters [StringPrep, C.9]

    In addition of checking of Bidirectional Characters [StringPrep, Section 6]
    and the Unassigned Code Points [StringPrep, A.1].

    Returns:
        A tuple with ("probited character", "breaked_rule")
    """
    rules = {
        "Space characters that contains the ASCII code points": in_table_c11,
        "Space characters non-ASCII code points": in_table_c12,
        "Unassigned Code Points [StringPrep, A.1]": in_table_a1,
        "Non-ASCII space characters [StringPrep, C.1.2]": in_table_c12,
        "ASCII control characters [StringPrep, C.2.1]": in_table_c21_c22,
        "Private Use characters [StringPrep, C.3]": in_table_c3,
        "Non-character code points [StringPrep, C.4]": in_table_c4,
        "Surrogate code points [StringPrep, C.5]": in_table_c5,
        "Inappropriate for plain text characters [StringPrep, C.6]": in_table_c6,
        "Inappropriate for canonical representation characters [StringPrep, C.7]": in_table_c7,
        "Change display properties or deprecated characters [StringPrep, C.8]": in_table_c8,
        "Tagging characters [StringPrep, C.9]": in_table_c9,
    }

    try:
        rules.update(_get_unicode_direction_rule(normalized_str))
    except ValueError as err:
        return normalized_str, str(err)

    for char in normalized_str:
        for rule, func in rules.items():
            if func(char) and char != " ":
                return char, rule

    return None


def normalize_unicode_string(a_string: str) -> str:
    """normalizes a unicode string according to rfc4013

    Normalization of a unicode string according to rfc4013: The SASLprep profile
    of the "stringprep" algorithm.

    Normalization Unicode equivalence is the specification by the Unicode
    character encoding standard that some sequences of code points represent
    essentially the same character.

    This method normalizes using the Normalization Form Compatibility
    Composition (NFKC), as described in rfc4013 2.2.

    Returns:
        Normalized unicode string according to rfc4013.
    """
    # Per rfc4013 2.1. Mapping
    # non-ASCII space characters [StringPrep, C.1.2] are mapped to ' ' (U+0020)
    # "commonly mapped to nothing" characters [StringPrep, B.1] are mapped to ''
    nstr_list = [
        " " if in_table_c12(char) else "" if in_table_b1(char) else char
        for char in a_string
    ]

    nstr = "".join(nstr_list)

    # Per rfc4013 2.2. Use NFKC Normalization Form Compatibility Composition
    # Characters are decomposed by compatibility, then recomposed by canonical
    # equivalence.
    nstr = unicodedata.normalize("NFKC", nstr)
    if not nstr:
        # Normilization results in empty string.
        return ""

    return nstr


def init_bytearray(
    payload: Union[int, StrOrBytes] = b"", encoding: str = "utf-8"
) -> bytearray:
    """Initialize a bytearray from the payload."""
    if isinstance(payload, bytearray):
        return payload
    if isinstance(payload, int):
        return bytearray(payload)
    if not isinstance(payload, bytes):
        try:
            return bytearray(payload.encode(encoding=encoding))
        except AttributeError as err:
            raise ValueError("payload must be a str or bytes") from err

    return bytearray(payload)


@lru_cache()
def get_platform() -> Dict[str, Union[str, Tuple[str, str]]]:
    """Return a dict with the platform arch and OS version."""
    plat: Dict[str, Union[str, Tuple[str, str]]] = {"arch": "", "version": ""}
    if os.name == "nt":
        if "64" in platform.architecture()[0]:
            plat["arch"] = "x86_64"
        elif "32" in platform.architecture()[0]:
            plat["arch"] = "i386"
        else:
            plat["arch"] = platform.architecture()
        plat["version"] = f"Windows-{platform.win32_ver()[1]}"
    else:
        plat["arch"] = platform.machine()
        if platform.system() == "Darwin":
            plat["version"] = f"macOS-{platform.mac_ver()[0]}"
        else:
            plat["version"] = "-".join(linux_distribution()[0:2])

    return plat

virt/lib/python3.9/site-packages/mysql/connector/utils.py
PrinceDisant/LostInNL
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
The OpenTelemetry logging API describes the classes used to generate logs and events.

The :class:`.LoggerProvider` provides users access to the :class:`.Logger` which in
turn is used to create :class:`.Event` and :class:`.Log` objects.

This module provides abstract (i.e. unimplemented) classes required for
logging, and a concrete no-op implementation :class:`.NoOpLogger` that allows applications
to use the API package alone without a supporting implementation.

To get a logger, you need to provide the package name from which you are
calling the logging APIs to OpenTelemetry by calling `LoggerProvider.get_logger`
with the calling module name and the version of your package.

The following code shows how to obtain a logger using the global :class:`.LoggerProvider`::

    from opentelemetry._logs import get_logger

    logger = get_logger("example-logger")

.. versionadded:: 1.15.0
"""

from mysql.opentelemetry._logs._internal import (
    Logger,
    LoggerProvider,
    LogRecord,
    NoOpLogger,
    NoOpLoggerProvider,
    get_logger,
    get_logger_provider,
    set_logger_provider,
)
from mysql.opentelemetry._logs.severity import SeverityNumber, std_to_otel

__all__ = [
    "Logger",
    "LoggerProvider",
    "LogRecord",
    "NoOpLogger",
    "NoOpLoggerProvider",
    "get_logger",
    "get_logger_provider",
    "set_logger_provider",
    "SeverityNumber",
    "std_to_otel",
]

virt/lib/python3.9/site-packages/mysql/opentelemetry/_logs/__init__.py
PrinceDisant/LostInNL
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
The OpenTelemetry logging API describes the classes used to generate logs and events.

The :class:`.LoggerProvider` provides users access to the :class:`.Logger` which in
turn is used to create :class:`.Event` and :class:`.Log` objects.

This module provides abstract (i.e. unimplemented) classes required for
logging, and a concrete no-op implementation :class:`.NoOpLogger` that allows applications
to use the API package alone without a supporting implementation.

To get a logger, you need to provide the package name from which you are
calling the logging APIs to OpenTelemetry by calling `LoggerProvider.get_logger`
with the calling module name and the version of your package.

The following code shows how to obtain a logger using the global :class:`.LoggerProvider`::

    from mysql.opentelemetry._logs import get_logger

    logger = get_logger("example-logger")

.. versionadded:: 1.15.0
"""

from abc import ABC, abstractmethod
from logging import getLogger
from os import environ
from typing import Any, Optional, cast

from mysql.opentelemetry._logs.severity import SeverityNumber
from mysql.opentelemetry.environment_variables import _OTEL_PYTHON_LOGGER_PROVIDER
from mysql.opentelemetry.trace.span import TraceFlags
from mysql.opentelemetry.util._once import Once
from mysql.opentelemetry.util._providers import _load_provider
from mysql.opentelemetry.util.types import Attributes

_logger = getLogger(__name__)


class LogRecord(ABC):
    """A LogRecord instance represents an event being logged.

    LogRecord instances are created and emitted via `Logger`
    every time something is logged. They contain all the information
    pertinent to the event being logged.
    """

    def __init__(
        self,
        timestamp: Optional[int] = None,
        observed_timestamp: Optional[int] = None,
        trace_id: Optional[int] = None,
        span_id: Optional[int] = None,
        trace_flags: Optional["TraceFlags"] = None,
        severity_text: Optional[str] = None,
        severity_number: Optional[SeverityNumber] = None,
        body: Optional[Any] = None,
        attributes: Optional["Attributes"] = None,
    ):
        self.timestamp = timestamp
        self.observed_timestamp = observed_timestamp
        self.trace_id = trace_id
        self.span_id = span_id
        self.trace_flags = trace_flags
        self.severity_text = severity_text
        self.severity_number = severity_number
        self.body = body  # type: ignore
        self.attributes = attributes


class Logger(ABC):
    """Handles emitting events and logs via `LogRecord`."""

    def __init__(
        self,
        name: str,
        version: Optional[str] = None,
        schema_url: Optional[str] = None,
    ) -> None:
        super().__init__()
        self._name = name
        self._version = version
        self._schema_url = schema_url

    @abstractmethod
    def emit(self, record: "LogRecord") -> None:
        """Emits a :class:`LogRecord` representing a log to the processing pipeline."""


class NoOpLogger(Logger):
    """The default Logger used when no Logger implementation is available.

    All operations are no-op.
    """

    def emit(self, record: "LogRecord") -> None:
        pass


class LoggerProvider(ABC):
    """
    LoggerProvider is the entry point of the API. It provides access to Logger instances.
    """

    @abstractmethod
    def get_logger(
        self,
        name: str,
        version: Optional[str] = None,
        schema_url: Optional[str] = None,
    ) -> Logger:
        """Returns a `Logger` for use by the given instrumentation library.

        For any two calls it is undefined whether the same or different
        `Logger` instances are returned, even for different library names.

        This function may return different `Logger` types (e.g. a no-op logger
        vs. a functional logger).

        Args:
            name: The name of the instrumenting module.
                ``__name__`` may not be used as this can result in
                different logger names if the loggers are in different files.
                It is better to use a fixed string that can be imported where
                needed and used consistently as the name of the logger.

                This should *not* be the name of the module that is
                instrumented but the name of the module doing the instrumentation.
                E.g., instead of ``"requests"``, use
                ``"mysql.opentelemetry.instrumentation.requests"``.

            version: Optional. The version string of the
                instrumenting library.  Usually this should be the same as
                ``pkg_resources.get_distribution(instrumenting_library_name).version``.

            schema_url: Optional. Specifies the Schema URL of the emitted telemetry.
        """


class NoOpLoggerProvider(LoggerProvider):
    """The default LoggerProvider used when no LoggerProvider implementation is available."""

    def get_logger(
        self,
        name: str,
        version: Optional[str] = None,
        schema_url: Optional[str] = None,
    ) -> Logger:
        """Returns a NoOpLogger."""
        super().get_logger(name, version=version, schema_url=schema_url)
        return NoOpLogger(name, version=version, schema_url=schema_url)


# TODO: ProxyLoggerProvider


_LOGGER_PROVIDER_SET_ONCE = Once()
_LOGGER_PROVIDER = None


def get_logger_provider() -> LoggerProvider:
    """Gets the current global :class:`~.LoggerProvider` object."""
    global _LOGGER_PROVIDER  # pylint: disable=global-statement
    if _LOGGER_PROVIDER is None:
        if _OTEL_PYTHON_LOGGER_PROVIDER not in environ.keys():
            # TODO: return proxy
            _LOGGER_PROVIDER = NoOpLoggerProvider()
            return _LOGGER_PROVIDER

        logger_provider: LoggerProvider = _load_provider(  # type: ignore
            _OTEL_PYTHON_LOGGER_PROVIDER, "logger_provider"
        )
        _set_logger_provider(logger_provider, log=False)

    # _LOGGER_PROVIDER will have been set by one thread
    return cast("LoggerProvider", _LOGGER_PROVIDER)


def _set_logger_provider(logger_provider: LoggerProvider, log: bool) -> None:
    def set_lp() -> None:
        global _LOGGER_PROVIDER  # pylint: disable=global-statement
        _LOGGER_PROVIDER = logger_provider  # type: ignore

    did_set = _LOGGER_PROVIDER_SET_ONCE.do_once(set_lp)

    if log and not did_set:
        _logger.warning("Overriding of current LoggerProvider is not allowed")


def set_logger_provider(meter_provider: LoggerProvider) -> None:
    """Sets the current global :class:`~.LoggerProvider` object.

    This can only be done once, a warning will be logged if any further attempt
    is made.
    """
    _set_logger_provider(meter_provider, log=True)


def get_logger(
    instrumenting_module_name: str,
    instrumenting_library_version: str = "",
    logger_provider: Optional[LoggerProvider] = None,
) -> "Logger":
    """Returns a `Logger` for use within a python process.

    This function is a convenience wrapper for
    mysql.opentelemetry.sdk._logs.LoggerProvider.get_logger.

    If logger_provider param is omitted the current configured one is used.
    """
    if logger_provider is None:
        logger_provider = get_logger_provider()
    return logger_provider.get_logger(
        instrumenting_module_name, instrumenting_library_version
    )

virt/lib/python3.9/site-packages/mysql/opentelemetry/_logs/_internal/__init__.py
PrinceDisant/LostInNL
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# type: ignore

import logging
import threading

from collections import OrderedDict
from collections.abc import MutableMapping
from typing import Optional, Sequence, Union

from mysql.opentelemetry.util import types

# bytes are accepted as a user supplied value for attributes but
# decoded to strings internally.
_VALID_ATTR_VALUE_TYPES = (bool, str, bytes, int, float)


_logger = logging.getLogger(__name__)


def _clean_attribute(
    key: str, value: types.AttributeValue, max_len: Optional[int]
) -> Optional[types.AttributeValue]:
    """Checks if attribute value is valid and cleans it if required.

    The function returns the cleaned value or None if the value is not valid.

    An attribute value is valid if it is either:
        - A primitive type: string, boolean, double precision floating
            point (IEEE 754-1985) or integer.
        - An array of primitive type values. The array MUST be homogeneous,
            i.e. it MUST NOT contain values of different types.

    An attribute needs cleansing if:
        - Its length is greater than the maximum allowed length.
        - It needs to be encoded/decoded e.g, bytes to strings.
    """

    if not (key and isinstance(key, str)):
        _logger.warning("invalid key `%s`. must be non-empty string.", key)
        return None

    if isinstance(value, _VALID_ATTR_VALUE_TYPES):
        return _clean_attribute_value(value, max_len)

    if isinstance(value, Sequence):
        sequence_first_valid_type = None
        cleaned_seq = []

        for element in value:
            element = _clean_attribute_value(element, max_len)
            if element is None:
                cleaned_seq.append(element)
                continue

            element_type = type(element)
            # Reject attribute value if sequence contains a value with an incompatible type.
            if element_type not in _VALID_ATTR_VALUE_TYPES:
                _logger.warning(
                    "Invalid type %s in attribute value sequence. Expected one of "
                    "%s or None",
                    element_type.__name__,
                    [valid_type.__name__ for valid_type in _VALID_ATTR_VALUE_TYPES],
                )
                return None

            # The type of the sequence must be homogeneous. The first non-None
            # element determines the type of the sequence
            if sequence_first_valid_type is None:
                sequence_first_valid_type = element_type
            # use equality instead of isinstance as isinstance(True, int) evaluates to True
            elif element_type != sequence_first_valid_type:
                _logger.warning(
                    "Attribute %r mixes types %s and %s in attribute value sequence",
                    key,
                    sequence_first_valid_type.__name__,
                    type(element).__name__,
                )
                return None

            cleaned_seq.append(element)

        # Freeze mutable sequences defensively
        return tuple(cleaned_seq)

    _logger.warning(
        "Invalid type %s for attribute '%s' value. Expected one of %s or a "
        "sequence of those types",
        type(value).__name__,
        key,
        [valid_type.__name__ for valid_type in _VALID_ATTR_VALUE_TYPES],
    )
    return None


def _clean_attribute_value(
    value: types.AttributeValue, limit: Optional[int]
) -> Union[types.AttributeValue, None]:
    if value is None:
        return None

    if isinstance(value, bytes):
        try:
            value = value.decode()
        except UnicodeDecodeError:
            _logger.warning("Byte attribute could not be decoded.")
            return None

    if limit is not None and isinstance(value, str):
        value = value[:limit]
    return value


class BoundedAttributes(MutableMapping):
    """An ordered dict with a fixed max capacity.

    Oldest elements are dropped when the dict is full and a new element is
    added.
    """

    def __init__(
        self,
        maxlen: Optional[int] = None,
        attributes: types.Attributes = None,
        immutable: bool = True,
        max_value_len: Optional[int] = None,
    ):
        if maxlen is not None:
            if not isinstance(maxlen, int) or maxlen < 0:
                raise ValueError("maxlen must be valid int greater or equal to 0")
        self.maxlen = maxlen
        self.dropped = 0
        self.max_value_len = max_value_len
        self._dict = OrderedDict()  # type: OrderedDict
        self._lock = threading.Lock()  # type: threading.Lock
        if attributes:
            for key, value in attributes.items():
                self[key] = value
        self._immutable = immutable

    def __repr__(self):
        return f"{type(self).__name__}({dict(self._dict)}, maxlen={self.maxlen})"

    def __getitem__(self, key):
        return self._dict[key]

    def __setitem__(self, key, value):
        if getattr(self, "_immutable", False):
            raise TypeError
        with self._lock:
            if self.maxlen is not None and self.maxlen == 0:
                self.dropped += 1
                return

            value = _clean_attribute(key, value, self.max_value_len)
            if value is not None:
                if key in self._dict:
                    del self._dict[key]
                elif self.maxlen is not None and len(self._dict) == self.maxlen:
                    self._dict.popitem(last=False)
                    self.dropped += 1

                self._dict[key] = value

    def __delitem__(self, key):
        if getattr(self, "_immutable", False):
            raise TypeError
        with self._lock:
            del self._dict[key]

    def __iter__(self):
        with self._lock:
            return iter(self._dict.copy())

    def __len__(self):
        return len(self._dict)

    def copy(self):
        return self._dict.copy()

virt/lib/python3.9/site-packages/mysql/opentelemetry/attributes/__init__.py
PrinceDisant/LostInNL
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from logging import getLogger
from re import compile
from types import MappingProxyType
from typing import Mapping, Optional

from mysql.opentelemetry.context import create_key, get_value, set_value
from mysql.opentelemetry.context.context import Context
from mysql.opentelemetry.util.re import (
    _BAGGAGE_PROPERTY_FORMAT,
    _KEY_FORMAT,
    _VALUE_FORMAT,
)

_BAGGAGE_KEY = create_key("baggage")
_logger = getLogger(__name__)

_KEY_PATTERN = compile(_KEY_FORMAT)
_VALUE_PATTERN = compile(_VALUE_FORMAT)
_PROPERT_PATTERN = compile(_BAGGAGE_PROPERTY_FORMAT)


def get_all(
    context: Optional[Context] = None,
) -> Mapping[str, object]:
    """Returns the name/value pairs in the Baggage

    Args:
        context: The Context to use. If not set, uses current Context

    Returns:
        The name/value pairs in the Baggage
    """
    baggage = get_value(_BAGGAGE_KEY, context=context)
    if isinstance(baggage, dict):
        return MappingProxyType(baggage)
    return MappingProxyType({})


def get_baggage(name: str, context: Optional[Context] = None) -> Optional[object]:
    """Provides access to the value for a name/value pair in the
    Baggage

    Args:
        name: The name of the value to retrieve
        context: The Context to use. If not set, uses current Context

    Returns:
        The value associated with the given name, or null if the given name is
        not present.
    """
    return get_all(context=context).get(name)


def set_baggage(name: str, value: object, context: Optional[Context] = None) -> Context:
    """Sets a value in the Baggage

    Args:
        name: The name of the value to set
        value: The value to set
        context: The Context to use. If not set, uses current Context

    Returns:
        A Context with the value updated
    """
    baggage = dict(get_all(context=context))
    baggage[name] = value
    return set_value(_BAGGAGE_KEY, baggage, context=context)


def remove_baggage(name: str, context: Optional[Context] = None) -> Context:
    """Removes a value from the Baggage

    Args:
        name: The name of the value to remove
        context: The Context to use. If not set, uses current Context

    Returns:
        A Context with the name/value removed
    """
    baggage = dict(get_all(context=context))
    baggage.pop(name, None)

    return set_value(_BAGGAGE_KEY, baggage, context=context)


def clear(context: Optional[Context] = None) -> Context:
    """Removes all values from the Baggage

    Args:
        context: The Context to use. If not set, uses current Context

    Returns:
        A Context with all baggage entries removed
    """
    return set_value(_BAGGAGE_KEY, {}, context=context)


def _is_valid_key(name: str) -> bool:
    return _KEY_PATTERN.fullmatch(str(name)) is not None


def _is_valid_value(value: object) -> bool:
    parts = str(value).split(";")
    is_valid_value = _VALUE_PATTERN.fullmatch(parts[0]) is not None
    if len(parts) > 1:  # one or more properties metadata
        for property in parts[1:]:
            if _PROPERT_PATTERN.fullmatch(property) is None:
                is_valid_value = False
                break
    return is_valid_value


def _is_valid_pair(key: str, value: str) -> bool:
    return _is_valid_key(key) and _is_valid_value(value)

virt/lib/python3.9/site-packages/mysql/opentelemetry/baggage/__init__.py
PrinceDisant/LostInNL
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
from logging import getLogger
from re import split
from typing import Iterable, List, Mapping, Optional, Set
from urllib.parse import quote_plus, unquote_plus

from mysql.opentelemetry.baggage import _is_valid_pair, get_all, set_baggage
from mysql.opentelemetry.context import get_current
from mysql.opentelemetry.context.context import Context
from mysql.opentelemetry.propagators import textmap
from mysql.opentelemetry.util.re import _DELIMITER_PATTERN

_logger = getLogger(__name__)


class W3CBaggagePropagator(textmap.TextMapPropagator):
    """Extracts and injects Baggage which is used to annotate telemetry."""

    _MAX_HEADER_LENGTH = 8192
    _MAX_PAIR_LENGTH = 4096
    _MAX_PAIRS = 180
    _BAGGAGE_HEADER_NAME = "baggage"

    def extract(
        self,
        carrier: textmap.CarrierT,
        context: Optional[Context] = None,
        getter: textmap.Getter[textmap.CarrierT] = textmap.default_getter,
    ) -> Context:
        """Extract Baggage from the carrier.

        See
        `mysql.opentelemetry.propagators.textmap.TextMapPropagator.extract`
        """

        if context is None:
            context = get_current()

        header = _extract_first_element(getter.get(carrier, self._BAGGAGE_HEADER_NAME))

        if not header:
            return context

        if len(header) > self._MAX_HEADER_LENGTH:
            _logger.warning(
                "Baggage header `%s` exceeded the maximum number of bytes per baggage-string",
                header,
            )
            return context

        baggage_entries: List[str] = split(_DELIMITER_PATTERN, header)
        total_baggage_entries = self._MAX_PAIRS

        if len(baggage_entries) > self._MAX_PAIRS:
            _logger.warning(
                "Baggage header `%s` exceeded the maximum number of list-members",
                header,
            )

        for entry in baggage_entries:
            if len(entry) > self._MAX_PAIR_LENGTH:
                _logger.warning(
                    "Baggage entry `%s` exceeded the maximum number of bytes per list-member",
                    entry,
                )
                continue
            if not entry:  # empty string
                continue
            try:
                name, value = entry.split("=", 1)
            except Exception:  # pylint: disable=broad-except
                _logger.warning(
                    "Baggage list-member `%s` doesn't match the format", entry
                )
                continue

            if not _is_valid_pair(name, value):
                _logger.warning("Invalid baggage entry: `%s`", entry)
                continue

            name = unquote_plus(name).strip()
            value = unquote_plus(value).strip()

            context = set_baggage(
                name,
                value,
                context=context,
            )
            total_baggage_entries -= 1
            if total_baggage_entries == 0:
                break

        return context

    def inject(
        self,
        carrier: textmap.CarrierT,
        context: Optional[Context] = None,
        setter: textmap.Setter[textmap.CarrierT] = textmap.default_setter,
    ) -> None:
        """Injects Baggage into the carrier.

        See
        `mysql.opentelemetry.propagators.textmap.TextMapPropagator.inject`
        """
        baggage_entries = get_all(context=context)
        if not baggage_entries:
            return

        baggage_string = _format_baggage(baggage_entries)
        setter.set(carrier, self._BAGGAGE_HEADER_NAME, baggage_string)

    @property
    def fields(self) -> Set[str]:
        """Returns a set with the fields set in `inject`."""
        return {self._BAGGAGE_HEADER_NAME}


def _format_baggage(baggage_entries: Mapping[str, object]) -> str:
    return ",".join(
        quote_plus(str(key)) + "=" + quote_plus(str(value))
        for key, value in baggage_entries.items()
    )


def _extract_first_element(
    items: Optional[Iterable[textmap.CarrierT]],
) -> Optional[textmap.CarrierT]:
    if items is None:
        return None
    return next(iter(items), None)

virt/lib/python3.9/site-packages/mysql/opentelemetry/baggage/propagation/__init__.py
PrinceDisant/LostInNL
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import logging
import threading
import typing

from functools import wraps
from os import environ
from uuid import uuid4

# pylint: disable=wrong-import-position
from mysql.opentelemetry.context.context import Context, _RuntimeContext  # noqa
from mysql.opentelemetry.environment_variables import OTEL_PYTHON_CONTEXT
from mysql.opentelemetry.util._importlib_metadata import entry_points

logger = logging.getLogger(__name__)
_RUNTIME_CONTEXT = None  # type: typing.Optional[_RuntimeContext]
_RUNTIME_CONTEXT_LOCK = threading.Lock()

_F = typing.TypeVar("_F", bound=typing.Callable[..., typing.Any])


def _load_runtime_context(func: _F) -> _F:
    """A decorator used to initialize the global RuntimeContext

    Returns:
        A wrapper of the decorated method.
    """

    @wraps(func)  # type: ignore[misc]
    def wrapper(  # type: ignore[misc]
        *args: typing.Tuple[typing.Any, typing.Any],
        **kwargs: typing.Dict[typing.Any, typing.Any],
    ) -> typing.Optional[typing.Any]:
        global _RUNTIME_CONTEXT  # pylint: disable=global-statement

        with _RUNTIME_CONTEXT_LOCK:
            if _RUNTIME_CONTEXT is None:
                # FIXME use a better implementation of a configuration manager
                # to avoid having to get configuration values straight from
                # environment variables
                default_context = "contextvars_context"

                configured_context = environ.get(
                    OTEL_PYTHON_CONTEXT, default_context
                )  # type: str
                try:
                    _RUNTIME_CONTEXT = next(  # type: ignore
                        iter(  # type: ignore
                            entry_points(  # type: ignore
                                group="opentelemetry_context",
                                name=configured_context,
                            )
                        )
                    ).load()()

                except Exception:  # pylint: disable=broad-except
                    logger.exception("Failed to load context: %s", configured_context)
        return func(*args, **kwargs)  # type: ignore[misc]

    return typing.cast(_F, wrapper)  # type: ignore[misc]


def create_key(keyname: str) -> str:
    """To allow cross-cutting concern to control access to their local state,
    the RuntimeContext API provides a function which takes a keyname as input,
    and returns a unique key.
    Args:
        keyname: The key name is for debugging purposes and is not required to be unique.
    Returns:
        A unique string representing the newly created key.
    """
    return keyname + "-" + str(uuid4())


def get_value(key: str, context: typing.Optional[Context] = None) -> "object":
    """To access the local state of a concern, the RuntimeContext API
    provides a function which takes a context and a key as input,
    and returns a value.

    Args:
        key: The key of the value to retrieve.
        context: The context from which to retrieve the value, if None, the current context is used.

    Returns:
        The value associated with the key.
    """
    return context.get(key) if context is not None else get_current().get(key)


def set_value(
    key: str, value: "object", context: typing.Optional[Context] = None
) -> Context:
    """To record the local state of a cross-cutting concern, the
    RuntimeContext API provides a function which takes a context, a
    key, and a value as input, and returns an updated context
    which contains the new value.

    Args:
        key: The key of the entry to set.
        value: The value of the entry to set.
        context: The context to copy, if None, the current context is used.

    Returns:
        A new `Context` containing the value set.
    """
    if context is None:
        context = get_current()
    new_values = context.copy()
    new_values[key] = value
    return Context(new_values)


@_load_runtime_context  # type: ignore
def get_current() -> Context:
    """To access the context associated with program execution,
    the Context API provides a function which takes no arguments
    and returns a Context.

    Returns:
        The current `Context` object.
    """
    return _RUNTIME_CONTEXT.get_current()  # type:ignore


@_load_runtime_context  # type: ignore
def attach(context: Context) -> object:
    """Associates a Context with the caller's current execution unit. Returns
    a token that can be used to restore the previous Context.

    Args:
        context: The Context to set as current.

    Returns:
        A token that can be used with `detach` to reset the context.
    """
    return _RUNTIME_CONTEXT.attach(context)  # type:ignore


@_load_runtime_context  # type: ignore
def detach(token: object) -> None:
    """Resets the Context associated with the caller's current execution unit
    to the value it had before attaching a specified Context.

    Args:
        token: The Token that was returned by a previous call to attach a Context.
    """
    try:
        _RUNTIME_CONTEXT.detach(token)  # type: ignore
    except Exception:  # pylint: disable=broad-except
        logger.exception("Failed to detach context")


# FIXME This is a temporary location for the suppress instrumentation key.
# Once the decision around how to suppress instrumentation is made in the
# spec, this key should be moved accordingly.
_SUPPRESS_INSTRUMENTATION_KEY = create_key("suppress_instrumentation")
_SUPPRESS_HTTP_INSTRUMENTATION_KEY = create_key("suppress_http_instrumentation")

virt/lib/python3.9/site-packages/mysql/opentelemetry/context/__init__.py
PrinceDisant/LostInNL
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from contextvars import ContextVar

from mysql.opentelemetry.context.context import Context, _RuntimeContext


class ContextVarsRuntimeContext(_RuntimeContext):
    """An implementation of the RuntimeContext interface which wraps ContextVar under
    the hood. This is the preferred implementation for usage with Python 3.5+
    """

    _CONTEXT_KEY = "current_context"

    def __init__(self) -> None:
        self._current_context = ContextVar(self._CONTEXT_KEY, default=Context())

    def attach(self, context: Context) -> object:
        """Sets the current `Context` object. Returns a
        token that can be used to reset to the previous `Context`.

        Args:
            context: The Context to set.
        """
        return self._current_context.set(context)

    def get_current(self) -> Context:
        """Returns the current `Context` object."""
        return self._current_context.get()

    def detach(self, token: object) -> None:
        """Resets Context to a previous value

        Args:
            token: A reference to a previous Context.
        """
        self._current_context.reset(token)  # type: ignore


__all__ = ["ContextVarsRuntimeContext"]

virt/lib/python3.9/site-packages/mysql/opentelemetry/context/contextvars_context.py
PrinceDisant/LostInNL
import abc
import collections
import contextlib
import csv
import email
import functools
import inspect
import itertools
import operator
import os
import pathlib
import posixpath
import re
import sys
import textwrap
import warnings

from contextlib import suppress
from importlib import import_module
from importlib.abc import MetaPathFinder
from itertools import starmap
from typing import Iterable, List, Mapping, Optional, Set, cast

import zipp

from . import _adapters, _meta, _py39compat
from ._collections import FreezableDefaultDict, Pair
from ._compat import NullFinder, StrPath, install, pypy_partial
from ._functools import method_cache, pass_none
from ._itertools import always_iterable, unique_everseen
from ._meta import PackageMetadata, SimplePath

__all__ = [
    "Distribution",
    "DistributionFinder",
    "PackageMetadata",
    "PackageNotFoundError",
    "distribution",
    "distributions",
    "entry_points",
    "files",
    "metadata",
    "packages_distributions",
    "requires",
    "version",
]


class PackageNotFoundError(ModuleNotFoundError):
    """The package was not found."""

    def __str__(self) -> str:
        return f"No package metadata was found for {self.name}"

    @property
    def name(self) -> str:  # type: ignore[override]
        (name,) = self.args
        return name


class Sectioned:
    """
    A simple entry point config parser for performance

    >>> for item in Sectioned.read(Sectioned._sample):
    ...     print(item)
    Pair(name='sec1', value='# comments ignored')
    Pair(name='sec1', value='a = 1')
    Pair(name='sec1', value='b = 2')
    Pair(name='sec2', value='a = 2')

    >>> res = Sectioned.section_pairs(Sectioned._sample)
    >>> item = next(res)
    >>> item.name
    'sec1'
    >>> item.value
    Pair(name='a', value='1')
    >>> item = next(res)
    >>> item.value
    Pair(name='b', value='2')
    >>> item = next(res)
    >>> item.name
    'sec2'
    >>> item.value
    Pair(name='a', value='2')
    >>> list(res)
    []
    """

    _sample = textwrap.dedent(
        """
        [sec1]
        # comments ignored
        a = 1
        b = 2

        [sec2]
        a = 2
        """
    ).lstrip()

    @classmethod
    def section_pairs(cls, text):
        return (
            section._replace(value=Pair.parse(section.value))
            for section in cls.read(text, filter_=cls.valid)
            if section.name is not None
        )

    @staticmethod
    def read(text, filter_=None):
        lines = filter(filter_, map(str.strip, text.splitlines()))
        name = None
        for value in lines:
            section_match = value.startswith("[") and value.endswith("]")
            if section_match:
                name = value.strip("[]")
                continue
            yield Pair(name, value)

    @staticmethod
    def valid(line: str):
        return line and not line.startswith("#")


class DeprecatedTuple:
    """
    Provide subscript item access for backward compatibility.

    >>> recwarn = getfixture('recwarn')
    >>> ep = EntryPoint(name='name', value='value', group='group')
    >>> ep[:]
    ('name', 'value', 'group')
    >>> ep[0]
    'name'
    >>> len(recwarn)
    1
    """

    # Do not remove prior to 2023-05-01 or Python 3.13
    _warn = functools.partial(
        warnings.warn,
        "EntryPoint tuple interface is deprecated. Access members by name.",
        DeprecationWarning,
        stacklevel=pypy_partial(2),
    )

    def __getitem__(self, item):
        self._warn()
        return self._key()[item]


class EntryPoint(DeprecatedTuple):
    """An entry point as defined by Python packaging conventions.

    See `the packaging docs on entry points
    <https://packaging.python.org/specifications/entry-points/>`_
    for more information.

    >>> ep = EntryPoint(
    ...     name=None, group=None, value='package.module:attr [extra1, extra2]')
    >>> ep.module
    'package.module'
    >>> ep.attr
    'attr'
    >>> ep.extras
    ['extra1', 'extra2']
    """

    pattern = re.compile(
        r"(?P<module>[\w.]+)\s*"
        r"(:\s*(?P<attr>[\w.]+)\s*)?"
        r"((?P<extras>\[.*\])\s*)?$"
    )
    """
    A regular expression describing the syntax for an entry point,
    which might look like:

        - module
        - package.module
        - package.module:attribute
        - package.module:object.attribute
        - package.module:attr [extra1, extra2]

    Other combinations are possible as well.

    The expression is lenient about whitespace around the ':',
    following the attr, and following any extras.
    """

    name: str
    value: str
    group: str

    dist: Optional["Distribution"] = None

    def __init__(self, name: str, value: str, group: str) -> None:
        vars(self).update(name=name, value=value, group=group)

    def load(self):
        """Load the entry point from its definition. If only a module
        is indicated by the value, return that module. Otherwise,
        return the named object.
        """
        match = self.pattern.match(self.value)
        module = import_module(match.group("module"))
        attrs = filter(None, (match.group("attr") or "").split("."))
        return functools.reduce(getattr, attrs, module)

    @property
    def module(self) -> str:
        match = self.pattern.match(self.value)
        assert match is not None
        return match.group("module")

    @property
    def attr(self) -> str:
        match = self.pattern.match(self.value)
        assert match is not None
        return match.group("attr")

    @property
    def extras(self) -> List[str]:
        match = self.pattern.match(self.value)
        assert match is not None
        return re.findall(r"\w+", match.group("extras") or "")

    def _for(self, dist):
        vars(self).update(dist=dist)
        return self

    def matches(self, **params):
        """
        EntryPoint matches the given parameters.

        >>> ep = EntryPoint(group='foo', name='bar', value='bing:bong [extra1, extra2]')
        >>> ep.matches(group='foo')
        True
        >>> ep.matches(name='bar', value='bing:bong [extra1, extra2]')
        True
        >>> ep.matches(group='foo', name='other')
        False
        >>> ep.matches()
        True
        >>> ep.matches(extras=['extra1', 'extra2'])
        True
        >>> ep.matches(module='bing')
        True
        >>> ep.matches(attr='bong')
        True
        """
        attrs = (getattr(self, param) for param in params)
        return all(map(operator.eq, params.values(), attrs))

    def _key(self):
        return self.name, self.value, self.group

    def __lt__(self, other):
        return self._key() < other._key()

    def __eq__(self, other):
        return self._key() == other._key()

    def __setattr__(self, name, value):
        raise AttributeError("EntryPoint objects are immutable.")

    def __repr__(self):
        return (
            f"EntryPoint(name={self.name!r}, value={self.value!r}, "
            f"group={self.group!r})"
        )

    def __hash__(self) -> int:
        return hash(self._key())


class EntryPoints(tuple):
    """
    An immutable collection of selectable EntryPoint objects.
    """

    __slots__ = ()

    def __getitem__(self, name: str) -> EntryPoint:  # type: ignore[override]
        """
        Get the EntryPoint in self matching name.
        """
        try:
            return next(iter(self.select(name=name)))
        except StopIteration:
            raise KeyError(name)

    def select(self, **params):
        """
        Select entry points from self that match the
        given parameters (typically group and/or name).
        """
        return EntryPoints(ep for ep in self if _py39compat.ep_matches(ep, **params))

    @property
    def names(self) -> Set[str]:
        """
        Return the set of all names of all entry points.
        """
        return {ep.name for ep in self}

    @property
    def groups(self) -> Set[str]:
        """
        Return the set of all groups of all entry points.
        """
        return {ep.group for ep in self}

    @classmethod
    def _from_text_for(cls, text, dist):
        return cls(ep._for(dist) for ep in cls._from_text(text))

    @staticmethod
    def _from_text(text):
        return (
            EntryPoint(name=item.value.name, value=item.value.value, group=item.name)
            for item in Sectioned.section_pairs(text or "")
        )


class PackagePath(pathlib.PurePosixPath):
    """A reference to a path in a package"""

    hash: Optional["FileHash"]
    size: int
    dist: "Distribution"

    def read_text(self, encoding: str = "utf-8") -> str:  # type: ignore[override]
        with self.locate().open(encoding=encoding) as stream:
            return stream.read()

    def read_binary(self) -> bytes:
        with self.locate().open("rb") as stream:
            return stream.read()

    def locate(self) -> pathlib.Path:
        """Return a path-like object for this path"""
        return self.dist.locate_file(self)


class FileHash:
    def __init__(self, spec: str) -> None:
        self.mode, _, self.value = spec.partition("=")

    def __repr__(self) -> str:
        return f"<FileHash mode: {self.mode} value: {self.value}>"


class DeprecatedNonAbstract:
    def __new__(cls, *args, **kwargs):
        all_names = {
            name for subclass in inspect.getmro(cls) for name in vars(subclass)
        }
        abstract = {
            name
            for name in all_names
            if getattr(getattr(cls, name), "__isabstractmethod__", False)
        }
        if abstract:
            warnings.warn(
                f"Unimplemented abstract methods {abstract}",
                DeprecationWarning,
                stacklevel=2,
            )
        return super().__new__(cls)


class Distribution(DeprecatedNonAbstract):
    """A Python distribution package."""

    @abc.abstractmethod
    def read_text(self, filename) -> Optional[str]:
        """Attempt to load metadata file given by the name.

        :param filename: The name of the file in the distribution info.
        :return: The text if found, otherwise None.
        """

    @abc.abstractmethod
    def locate_file(self, path: StrPath) -> pathlib.Path:
        """
        Given a path to a file in this distribution, return a path
        to it.
        """

    @classmethod
    def from_name(cls, name: str) -> "Distribution":
        """Return the Distribution for the given package name.

        :param name: The name of the distribution package to search for.
        :return: The Distribution instance (or subclass thereof) for the named
            package, if found.
        :raises PackageNotFoundError: When the named package's distribution
            metadata cannot be found.
        :raises ValueError: When an invalid value is supplied for name.
        """
        if not name:
            raise ValueError("A distribution name is required.")
        try:
            return next(iter(cls.discover(name=name)))
        except StopIteration:
            raise PackageNotFoundError(name)

    @classmethod
    def discover(cls, **kwargs) -> Iterable["Distribution"]:
        """Return an iterable of Distribution objects for all packages.

        Pass a ``context`` or pass keyword arguments for constructing
        a context.

        :context: A ``DistributionFinder.Context`` object.
        :return: Iterable of Distribution objects for all packages.
        """
        context = kwargs.pop("context", None)
        if context and kwargs:
            raise ValueError("cannot accept context and kwargs")
        context = context or DistributionFinder.Context(**kwargs)
        return itertools.chain.from_iterable(
            resolver(context) for resolver in cls._discover_resolvers()
        )

    @staticmethod
    def at(path: StrPath) -> "Distribution":
        """Return a Distribution for the indicated metadata path

        :param path: a string or path-like object
        :return: a concrete Distribution instance for the path
        """
        return PathDistribution(pathlib.Path(path))

    @staticmethod
    def _discover_resolvers():
        """Search the meta_path for resolvers."""
        declared = (
            getattr(finder, "find_distributions", None) for finder in sys.meta_path
        )
        return filter(None, declared)

    @property
    def metadata(self) -> _meta.PackageMetadata:
        """Return the parsed metadata for this Distribution.

        The returned object will have keys that name the various bits of
        metadata.  See PEP 566 for details.
        """
        opt_text = (
            self.read_text("METADATA")
            or self.read_text("PKG-INFO")
            # This last clause is here to support old egg-info files.  Its
            # effect is to just end up using the PathDistribution's self._path
            # (which points to the egg-info file) attribute unchanged.
            or self.read_text("")
        )
        text = cast(str, opt_text)
        return _adapters.Message(email.message_from_string(text))

    @property
    def name(self) -> str:
        """Return the 'Name' metadata for the distribution package."""
        return self.metadata["Name"]

    @property
    def _normalized_name(self):
        """Return a normalized version of the name."""
        return Prepared.normalize(self.name)

    @property
    def version(self) -> str:
        """Return the 'Version' metadata for the distribution package."""
        return self.metadata["Version"]

    @property
    def entry_points(self) -> EntryPoints:
        return EntryPoints._from_text_for(self.read_text("entry_points.txt"), self)

    @property
    def files(self) -> Optional[List[PackagePath]]:
        """Files in this distribution.

        :return: List of PackagePath for this distribution or None

        Result is `None` if the metadata file that enumerates files
        (i.e. RECORD for dist-info, or installed-files.txt or
        SOURCES.txt for egg-info) is missing.
        Result may be empty if the metadata exists but is empty.
        """

        def make_file(name, hash=None, size_str=None):
            result = PackagePath(name)
            result.hash = FileHash(hash) if hash else None
            result.size = int(size_str) if size_str else None
            result.dist = self
            return result

        @pass_none
        def make_files(lines):
            return starmap(make_file, csv.reader(lines))

        @pass_none
        def skip_missing_files(package_paths):
            return list(filter(lambda path: path.locate().exists(), package_paths))

        return skip_missing_files(
            make_files(
                self._read_files_distinfo()
                or self._read_files_egginfo_installed()
                or self._read_files_egginfo_sources()
            )
        )

    def _read_files_distinfo(self):
        """
        Read the lines of RECORD
        """
        text = self.read_text("RECORD")
        return text and text.splitlines()

    def _read_files_egginfo_installed(self):
        """
        Read installed-files.txt and return lines in a similar
        CSV-parsable format as RECORD: each file must be placed
        relative to the site-packages directory and must also be
        quoted (since file names can contain literal commas).

        This file is written when the package is installed by pip,
        but it might not be written for other installation methods.
        Assume the file is accurate if it exists.
        """
        text = self.read_text("installed-files.txt")
        # Prepend the .egg-info/ subdir to the lines in this file.
        # But this subdir is only available from PathDistribution's
        # self._path.
        subdir = getattr(self, "_path", None)
        if not text or not subdir:
            return

        paths = (
            (subdir / name)
            .resolve()
            .relative_to(self.locate_file("").resolve())
            .as_posix()
            for name in text.splitlines()
        )
        return map('"{}"'.format, paths)

    def _read_files_egginfo_sources(self):
        """
        Read SOURCES.txt and return lines in a similar CSV-parsable
        format as RECORD: each file name must be quoted (since it
        might contain literal commas).

        Note that SOURCES.txt is not a reliable source for what
        files are installed by a package. This file is generated
        for a source archive, and the files that are present
        there (e.g. setup.py) may not correctly reflect the files
        that are present after the package has been installed.
        """
        text = self.read_text("SOURCES.txt")
        return text and map('"{}"'.format, text.splitlines())

    @property
    def requires(self) -> Optional[List[str]]:
        """Generated requirements specified for this Distribution"""
        reqs = self._read_dist_info_reqs() or self._read_egg_info_reqs()
        return reqs and list(reqs)

    def _read_dist_info_reqs(self):
        return self.metadata.get_all("Requires-Dist")

    def _read_egg_info_reqs(self):
        source = self.read_text("requires.txt")
        return pass_none(self._deps_from_requires_text)(source)

    @classmethod
    def _deps_from_requires_text(cls, source):
        return cls._convert_egg_info_reqs_to_simple_reqs(Sectioned.read(source))

    @staticmethod
    def _convert_egg_info_reqs_to_simple_reqs(sections):
        """
        Historically, setuptools would solicit and store 'extra'
        requirements, including those with environment markers,
        in separate sections. More modern tools expect each
        dependency to be defined separately, with any relevant
        extras and environment markers attached directly to that
        requirement. This method converts the former to the
        latter. See _test_deps_from_requires_text for an example.
        """

        def make_condition(name):
            return name and f'extra == "{name}"'

        def quoted_marker(section):
            section = section or ""
            extra, sep, markers = section.partition(":")
            if extra and markers:
                markers = f"({markers})"
            conditions = list(filter(None, [markers, make_condition(extra)]))
            return "; " + " and ".join(conditions) if conditions else ""

        def url_req_space(req):
            """
            PEP 508 requires a space between the url_spec and the quoted_marker.
            Ref python/importlib_metadata#357.
            """
            # '@' is uniquely indicative of a url_req.
            return " " * ("@" in req)

        for section in sections:
            space = url_req_space(section.value)
            yield section.value + space + quoted_marker(section.name)


class DistributionFinder(MetaPathFinder):
    """
    A MetaPathFinder capable of discovering installed distributions.
    """

    class Context:
        """
        Keyword arguments presented by the caller to
        ``distributions()`` or ``Distribution.discover()``
        to narrow the scope of a search for distributions
        in all DistributionFinders.

        Each DistributionFinder may expect any parameters
        and should attempt to honor the canonical
        parameters defined below when appropriate.
        """

        name = None
        """
        Specific name for which a distribution finder should match.
        A name of ``None`` matches all distributions.
        """

        def __init__(self, **kwargs):
            vars(self).update(kwargs)

        @property
        def path(self) -> List[str]:
            """
            The sequence of directory path that a distribution finder
            should search.

            Typically refers to Python installed package paths such as
            "site-packages" directories and defaults to ``sys.path``.
            """
            return vars(self).get("path", sys.path)

    @abc.abstractmethod
    def find_distributions(self, context=Context()) -> Iterable[Distribution]:
        """
        Find distributions.

        Return an iterable of all Distribution instances capable of
        loading the metadata for packages matching the ``context``,
        a DistributionFinder.Context instance.
        """


class FastPath:
    """
    Micro-optimized class for searching a path for
    children.

    >>> FastPath('').children()
    ['...']
    """

    @functools.lru_cache()  # type: ignore
    def __new__(cls, root):
        return super().__new__(cls)

    def __init__(self, root):
        self.root = root

    def joinpath(self, child):
        return pathlib.Path(self.root, child)

    def children(self):
        with suppress(Exception):
            return os.listdir(self.root or ".")
        with suppress(Exception):
            return self.zip_children()
        return []

    def zip_children(self):
        zip_path = zipp.Path(self.root)
        names = zip_path.root.namelist()
        self.joinpath = zip_path.joinpath

        return dict.fromkeys(child.split(posixpath.sep, 1)[0] for child in names)

    def search(self, name):
        return self.lookup(self.mtime).search(name)

    @property
    def mtime(self):
        with suppress(OSError):
            return os.stat(self.root).st_mtime
        self.lookup.cache_clear()

    @method_cache
    def lookup(self, mtime):
        return Lookup(self)


class Lookup:
    def __init__(self, path: FastPath):
        base = os.path.basename(path.root).lower()
        base_is_egg = base.endswith(".egg")
        self.infos = FreezableDefaultDict(list)
        self.eggs = FreezableDefaultDict(list)

        for child in path.children():
            low = child.lower()
            if low.endswith((".dist-info", ".egg-info")):
                # rpartition is faster than splitext and suitable for this purpose.
                name = low.rpartition(".")[0].partition("-")[0]
                normalized = Prepared.normalize(name)
                self.infos[normalized].append(path.joinpath(child))
            elif base_is_egg and low == "egg-info":
                name = base.rpartition(".")[0].partition("-")[0]
                legacy_normalized = Prepared.legacy_normalize(name)
                self.eggs[legacy_normalized].append(path.joinpath(child))

        self.infos.freeze()
        self.eggs.freeze()

    def search(self, prepared):
        infos = (
            self.infos[prepared.normalized]
            if prepared
            else itertools.chain.from_iterable(self.infos.values())
        )
        eggs = (
            self.eggs[prepared.legacy_normalized]
            if prepared
            else itertools.chain.from_iterable(self.eggs.values())
        )
        return itertools.chain(infos, eggs)


class Prepared:
    """
    A prepared search for metadata on a possibly-named package.
    """

    normalized = None
    legacy_normalized = None

    def __init__(self, name):
        self.name = name
        if name is None:
            return
        self.normalized = self.normalize(name)
        self.legacy_normalized = self.legacy_normalize(name)

    @staticmethod
    def normalize(name):
        """
        PEP 503 normalization plus dashes as underscores.
        """
        return re.sub(r"[-_.]+", "-", name).lower().replace("-", "_")

    @staticmethod
    def legacy_normalize(name):
        """
        Normalize the package name as found in the convention in
        older packaging tools versions and specs.
        """
        return name.lower().replace("-", "_")

    def __bool__(self):
        return bool(self.name)


@install
class MetadataPathFinder(NullFinder, DistributionFinder):
    """A degenerate finder for distribution packages on the file system.

    This finder supplies only a find_distributions() method for versions
    of Python that do not have a PathFinder find_distributions().
    """

    def find_distributions(
        self, context=DistributionFinder.Context()
    ) -> Iterable["PathDistribution"]:
        """
        Find distributions.

        Return an iterable of all Distribution instances capable of
        loading the metadata for packages matching ``context.name``
        (or all names if ``None`` indicated) along the paths in the list
        of directories ``context.path``.
        """
        found = self._search_paths(context.name, context.path)
        return map(PathDistribution, found)

    @classmethod
    def _search_paths(cls, name, paths):
        """Find metadata directories in paths heuristically."""
        prepared = Prepared(name)
        return itertools.chain.from_iterable(
            path.search(prepared) for path in map(FastPath, paths)
        )

    def invalidate_caches(cls) -> None:
        FastPath.__new__.cache_clear()


class PathDistribution(Distribution):
    def __init__(self, path: SimplePath) -> None:
        """Construct a distribution.

        :param path: SimplePath indicating the metadata directory.
        """
        self._path = path

    def read_text(self, filename: StrPath) -> Optional[str]:
        with suppress(
            FileNotFoundError,
            IsADirectoryError,
            KeyError,
            NotADirectoryError,
            PermissionError,
        ):
            return self._path.joinpath(filename).read_text(encoding="utf-8")

        return None

    read_text.__doc__ = Distribution.read_text.__doc__

    def locate_file(self, path: StrPath) -> pathlib.Path:
        return self._path.parent / path

    @property
    def _normalized_name(self):
        """
        Performance optimization: where possible, resolve the
        normalized name from the file system path.
        """
        stem = os.path.basename(str(self._path))
        return (
            pass_none(Prepared.normalize)(self._name_from_stem(stem))
            or super()._normalized_name
        )

    @staticmethod
    def _name_from_stem(stem):
        """
        >>> PathDistribution._name_from_stem('foo-3.0.egg-info')
        'foo'
        >>> PathDistribution._name_from_stem('CherryPy-3.0.dist-info')
        'CherryPy'
        >>> PathDistribution._name_from_stem('face.egg-info')
        'face'
        >>> PathDistribution._name_from_stem('foo.bar')
        """
        filename, ext = os.path.splitext(stem)
        if ext not in (".dist-info", ".egg-info"):
            return
        name, sep, rest = filename.partition("-")
        return name


def distribution(distribution_name) -> Distribution:
    """Get the ``Distribution`` instance for the named package.

    :param distribution_name: The name of the distribution package as a string.
    :return: A ``Distribution`` instance (or subclass thereof).
    """
    return Distribution.from_name(distribution_name)


def distributions(**kwargs) -> Iterable[Distribution]:
    """Get all ``Distribution`` instances in the current environment.

    :return: An iterable of ``Distribution`` instances.
    """
    return Distribution.discover(**kwargs)


def metadata(distribution_name) -> _meta.PackageMetadata:
    """Get the metadata for the named package.

    :param distribution_name: The name of the distribution package to query.
    :return: A PackageMetadata containing the parsed metadata.
    """
    return Distribution.from_name(distribution_name).metadata


def version(distribution_name) -> str:
    """Get the version string for the named package.

    :param distribution_name: The name of the distribution package to query.
    :return: The version string for the package as defined in the package's
        "Version" metadata key.
    """
    return distribution(distribution_name).version


_unique = functools.partial(
    unique_everseen,
    key=_py39compat.normalized_name,
)
"""
Wrapper for ``distributions`` to return unique distributions by name.
"""


def entry_points(**params) -> EntryPoints:
    """Return EntryPoint objects for all installed packages.

    Pass selection parameters (group or name) to filter the
    result to entry points matching those properties (see
    EntryPoints.select()).

    :return: EntryPoints for all installed packages.
    """
    eps = itertools.chain.from_iterable(
        dist.entry_points for dist in _unique(distributions())
    )
    return EntryPoints(eps).select(**params)


def files(distribution_name) -> Optional[List[PackagePath]]:
    """Return a list of files for the named package.

    :param distribution_name: The name of the distribution package to query.
    :return: List of files composing the distribution.
    """
    return distribution(distribution_name).files


def requires(distribution_name) -> Optional[List[str]]:
    """
    Return a list of requirements for the named package.

    :return: An iterable of requirements, suitable for
        packaging.requirement.Requirement.
    """
    return distribution(distribution_name).requires


def packages_distributions() -> Mapping[str, List[str]]:
    """
    Return a mapping of top-level packages to their
    distributions.

    >>> import collections.abc
    >>> pkgs = packages_distributions()
    >>> all(isinstance(dist, collections.abc.Sequence) for dist in pkgs.values())
    True
    """
    pkg_to_dist = collections.defaultdict(list)
    for dist in distributions():
        for pkg in _top_level_declared(dist) or _top_level_inferred(dist):
            pkg_to_dist[pkg].append(dist.metadata["Name"])
    return dict(pkg_to_dist)


def _top_level_declared(dist):
    return (dist.read_text("top_level.txt") or "").split()


def _top_level_inferred(dist):
    opt_names = {
        f.parts[0] if len(f.parts) > 1 else inspect.getmodulename(f)
        for f in always_iterable(dist.files)
    }

    @pass_none
    def importable_name(name):
        return "." not in name

    return filter(importable_name, opt_names)

virt/lib/python3.9/site-packages/mysql/opentelemetry/importlib_metadata/__init__ 2.py
PrinceDisant/LostInNL
import abc
import collections
import contextlib
import csv
import email
import functools
import inspect
import itertools
import operator
import os
import pathlib
import posixpath
import re
import sys
import textwrap
import warnings

from contextlib import suppress
from importlib import import_module
from importlib.abc import MetaPathFinder
from itertools import starmap
from typing import Iterable, List, Mapping, Optional, Set, cast

import zipp

from . import _adapters, _meta, _py39compat
from ._collections import FreezableDefaultDict, Pair
from ._compat import NullFinder, StrPath, install, pypy_partial
from ._functools import method_cache, pass_none
from ._itertools import always_iterable, unique_everseen
from ._meta import PackageMetadata, SimplePath

__all__ = [
    "Distribution",
    "DistributionFinder",
    "PackageMetadata",
    "PackageNotFoundError",
    "distribution",
    "distributions",
    "entry_points",
    "files",
    "metadata",
    "packages_distributions",
    "requires",
    "version",
]


class PackageNotFoundError(ModuleNotFoundError):
    """The package was not found."""

    def __str__(self) -> str:
        return f"No package metadata was found for {self.name}"

    @property
    def name(self) -> str:  # type: ignore[override]
        (name,) = self.args
        return name


class Sectioned:
    """
    A simple entry point config parser for performance

    >>> for item in Sectioned.read(Sectioned._sample):
    ...     print(item)
    Pair(name='sec1', value='# comments ignored')
    Pair(name='sec1', value='a = 1')
    Pair(name='sec1', value='b = 2')
    Pair(name='sec2', value='a = 2')

    >>> res = Sectioned.section_pairs(Sectioned._sample)
    >>> item = next(res)
    >>> item.name
    'sec1'
    >>> item.value
    Pair(name='a', value='1')
    >>> item = next(res)
    >>> item.value
    Pair(name='b', value='2')
    >>> item = next(res)
    >>> item.name
    'sec2'
    >>> item.value
    Pair(name='a', value='2')
    >>> list(res)
    []
    """

    _sample = textwrap.dedent(
        """
        [sec1]
        # comments ignored
        a = 1
        b = 2

        [sec2]
        a = 2
        """
    ).lstrip()

    @classmethod
    def section_pairs(cls, text):
        return (
            section._replace(value=Pair.parse(section.value))
            for section in cls.read(text, filter_=cls.valid)
            if section.name is not None
        )

    @staticmethod
    def read(text, filter_=None):
        lines = filter(filter_, map(str.strip, text.splitlines()))
        name = None
        for value in lines:
            section_match = value.startswith("[") and value.endswith("]")
            if section_match:
                name = value.strip("[]")
                continue
            yield Pair(name, value)

    @staticmethod
    def valid(line: str):
        return line and not line.startswith("#")


class DeprecatedTuple:
    """
    Provide subscript item access for backward compatibility.

    >>> recwarn = getfixture('recwarn')
    >>> ep = EntryPoint(name='name', value='value', group='group')
    >>> ep[:]
    ('name', 'value', 'group')
    >>> ep[0]
    'name'
    >>> len(recwarn)
    1
    """

    # Do not remove prior to 2023-05-01 or Python 3.13
    _warn = functools.partial(
        warnings.warn,
        "EntryPoint tuple interface is deprecated. Access members by name.",
        DeprecationWarning,
        stacklevel=pypy_partial(2),
    )

    def __getitem__(self, item):
        self._warn()
        return self._key()[item]


class EntryPoint(DeprecatedTuple):
    """An entry point as defined by Python packaging conventions.

    See `the packaging docs on entry points
    <https://packaging.python.org/specifications/entry-points/>`_
    for more information.

    >>> ep = EntryPoint(
    ...     name=None, group=None, value='package.module:attr [extra1, extra2]')
    >>> ep.module
    'package.module'
    >>> ep.attr
    'attr'
    >>> ep.extras
    ['extra1', 'extra2']
    """

    pattern = re.compile(
        r"(?P<module>[\w.]+)\s*"
        r"(:\s*(?P<attr>[\w.]+)\s*)?"
        r"((?P<extras>\[.*\])\s*)?$"
    )
    """
    A regular expression describing the syntax for an entry point,
    which might look like:

        - module
        - package.module
        - package.module:attribute
        - package.module:object.attribute
        - package.module:attr [extra1, extra2]

    Other combinations are possible as well.

    The expression is lenient about whitespace around the ':',
    following the attr, and following any extras.
    """

    name: str
    value: str
    group: str

    dist: Optional["Distribution"] = None

    def __init__(self, name: str, value: str, group: str) -> None:
        vars(self).update(name=name, value=value, group=group)

    def load(self):
        """Load the entry point from its definition. If only a module
        is indicated by the value, return that module. Otherwise,
        return the named object.
        """
        match = self.pattern.match(self.value)
        module = import_module(match.group("module"))
        attrs = filter(None, (match.group("attr") or "").split("."))
        return functools.reduce(getattr, attrs, module)

    @property
    def module(self) -> str:
        match = self.pattern.match(self.value)
        assert match is not None
        return match.group("module")

    @property
    def attr(self) -> str:
        match = self.pattern.match(self.value)
        assert match is not None
        return match.group("attr")

    @property
    def extras(self) -> List[str]:
        match = self.pattern.match(self.value)
        assert match is not None
        return re.findall(r"\w+", match.group("extras") or "")

    def _for(self, dist):
        vars(self).update(dist=dist)
        return self

    def matches(self, **params):
        """
        EntryPoint matches the given parameters.

        >>> ep = EntryPoint(group='foo', name='bar', value='bing:bong [extra1, extra2]')
        >>> ep.matches(group='foo')
        True
        >>> ep.matches(name='bar', value='bing:bong [extra1, extra2]')
        True
        >>> ep.matches(group='foo', name='other')
        False
        >>> ep.matches()
        True
        >>> ep.matches(extras=['extra1', 'extra2'])
        True
        >>> ep.matches(module='bing')
        True
        >>> ep.matches(attr='bong')
        True
        """
        attrs = (getattr(self, param) for param in params)
        return all(map(operator.eq, params.values(), attrs))

    def _key(self):
        return self.name, self.value, self.group

    def __lt__(self, other):
        return self._key() < other._key()

    def __eq__(self, other):
        return self._key() == other._key()

    def __setattr__(self, name, value):
        raise AttributeError("EntryPoint objects are immutable.")

    def __repr__(self):
        return (
            f"EntryPoint(name={self.name!r}, value={self.value!r}, "
            f"group={self.group!r})"
        )

    def __hash__(self) -> int:
        return hash(self._key())


class EntryPoints(tuple):
    """
    An immutable collection of selectable EntryPoint objects.
    """

    __slots__ = ()

    def __getitem__(self, name: str) -> EntryPoint:  # type: ignore[override]
        """
        Get the EntryPoint in self matching name.
        """
        try:
            return next(iter(self.select(name=name)))
        except StopIteration:
            raise KeyError(name)

    def select(self, **params):
        """
        Select entry points from self that match the
        given parameters (typically group and/or name).
        """
        return EntryPoints(ep for ep in self if _py39compat.ep_matches(ep, **params))

    @property
    def names(self) -> Set[str]:
        """
        Return the set of all names of all entry points.
        """
        return {ep.name for ep in self}

    @property
    def groups(self) -> Set[str]:
        """
        Return the set of all groups of all entry points.
        """
        return {ep.group for ep in self}

    @classmethod
    def _from_text_for(cls, text, dist):
        return cls(ep._for(dist) for ep in cls._from_text(text))

    @staticmethod
    def _from_text(text):
        return (
            EntryPoint(name=item.value.name, value=item.value.value, group=item.name)
            for item in Sectioned.section_pairs(text or "")
        )


class PackagePath(pathlib.PurePosixPath):
    """A reference to a path in a package"""

    hash: Optional["FileHash"]
    size: int
    dist: "Distribution"

    def read_text(self, encoding: str = "utf-8") -> str:  # type: ignore[override]
        with self.locate().open(encoding=encoding) as stream:
            return stream.read()

    def read_binary(self) -> bytes:
        with self.locate().open("rb") as stream:
            return stream.read()

    def locate(self) -> pathlib.Path:
        """Return a path-like object for this path"""
        return self.dist.locate_file(self)


class FileHash:
    def __init__(self, spec: str) -> None:
        self.mode, _, self.value = spec.partition("=")

    def __repr__(self) -> str:
        return f"<FileHash mode: {self.mode} value: {self.value}>"


class DeprecatedNonAbstract:
    def __new__(cls, *args, **kwargs):
        all_names = {
            name for subclass in inspect.getmro(cls) for name in vars(subclass)
        }
        abstract = {
            name
            for name in all_names
            if getattr(getattr(cls, name), "__isabstractmethod__", False)
        }
        if abstract:
            warnings.warn(
                f"Unimplemented abstract methods {abstract}",
                DeprecationWarning,
                stacklevel=2,
            )
        return super().__new__(cls)


class Distribution(DeprecatedNonAbstract):
    """A Python distribution package."""

    @abc.abstractmethod
    def read_text(self, filename) -> Optional[str]:
        """Attempt to load metadata file given by the name.

        :param filename: The name of the file in the distribution info.
        :return: The text if found, otherwise None.
        """

    @abc.abstractmethod
    def locate_file(self, path: StrPath) -> pathlib.Path:
        """
        Given a path to a file in this distribution, return a path
        to it.
        """

    @classmethod
    def from_name(cls, name: str) -> "Distribution":
        """Return the Distribution for the given package name.

        :param name: The name of the distribution package to search for.
        :return: The Distribution instance (or subclass thereof) for the named
            package, if found.
        :raises PackageNotFoundError: When the named package's distribution
            metadata cannot be found.
        :raises ValueError: When an invalid value is supplied for name.
        """
        if not name:
            raise ValueError("A distribution name is required.")
        try:
            return next(iter(cls.discover(name=name)))
        except StopIteration:
            raise PackageNotFoundError(name)

    @classmethod
    def discover(cls, **kwargs) -> Iterable["Distribution"]:
        """Return an iterable of Distribution objects for all packages.

        Pass a ``context`` or pass keyword arguments for constructing
        a context.

        :context: A ``DistributionFinder.Context`` object.
        :return: Iterable of Distribution objects for all packages.
        """
        context = kwargs.pop("context", None)
        if context and kwargs:
            raise ValueError("cannot accept context and kwargs")
        context = context or DistributionFinder.Context(**kwargs)
        return itertools.chain.from_iterable(
            resolver(context) for resolver in cls._discover_resolvers()
        )

    @staticmethod
    def at(path: StrPath) -> "Distribution":
        """Return a Distribution for the indicated metadata path

        :param path: a string or path-like object
        :return: a concrete Distribution instance for the path
        """
        return PathDistribution(pathlib.Path(path))

    @staticmethod
    def _discover_resolvers():
        """Search the meta_path for resolvers."""
        declared = (
            getattr(finder, "find_distributions", None) for finder in sys.meta_path
        )
        return filter(None, declared)

    @property
    def metadata(self) -> _meta.PackageMetadata:
        """Return the parsed metadata for this Distribution.

        The returned object will have keys that name the various bits of
        metadata.  See PEP 566 for details.
        """
        opt_text = (
            self.read_text("METADATA")
            or self.read_text("PKG-INFO")
            # This last clause is here to support old egg-info files.  Its
            # effect is to just end up using the PathDistribution's self._path
            # (which points to the egg-info file) attribute unchanged.
            or self.read_text("")
        )
        text = cast(str, opt_text)
        return _adapters.Message(email.message_from_string(text))

    @property
    def name(self) -> str:
        """Return the 'Name' metadata for the distribution package."""
        return self.metadata["Name"]

    @property
    def _normalized_name(self):
        """Return a normalized version of the name."""
        return Prepared.normalize(self.name)

    @property
    def version(self) -> str:
        """Return the 'Version' metadata for the distribution package."""
        return self.metadata["Version"]

    @property
    def entry_points(self) -> EntryPoints:
        return EntryPoints._from_text_for(self.read_text("entry_points.txt"), self)

    @property
    def files(self) -> Optional[List[PackagePath]]:
        """Files in this distribution.

        :return: List of PackagePath for this distribution or None

        Result is `None` if the metadata file that enumerates files
        (i.e. RECORD for dist-info, or installed-files.txt or
        SOURCES.txt for egg-info) is missing.
        Result may be empty if the metadata exists but is empty.
        """

        def make_file(name, hash=None, size_str=None):
            result = PackagePath(name)
            result.hash = FileHash(hash) if hash else None
            result.size = int(size_str) if size_str else None
            result.dist = self
            return result

        @pass_none
        def make_files(lines):
            return starmap(make_file, csv.reader(lines))

        @pass_none
        def skip_missing_files(package_paths):
            return list(filter(lambda path: path.locate().exists(), package_paths))

        return skip_missing_files(
            make_files(
                self._read_files_distinfo()
                or self._read_files_egginfo_installed()
                or self._read_files_egginfo_sources()
            )
        )

    def _read_files_distinfo(self):
        """
        Read the lines of RECORD
        """
        text = self.read_text("RECORD")
        return text and text.splitlines()

    def _read_files_egginfo_installed(self):
        """
        Read installed-files.txt and return lines in a similar
        CSV-parsable format as RECORD: each file must be placed
        relative to the site-packages directory and must also be
        quoted (since file names can contain literal commas).

        This file is written when the package is installed by pip,
        but it might not be written for other installation methods.
        Assume the file is accurate if it exists.
        """
        text = self.read_text("installed-files.txt")
        # Prepend the .egg-info/ subdir to the lines in this file.
        # But this subdir is only available from PathDistribution's
        # self._path.
        subdir = getattr(self, "_path", None)
        if not text or not subdir:
            return

        paths = (
            (subdir / name)
            .resolve()
            .relative_to(self.locate_file("").resolve())
            .as_posix()
            for name in text.splitlines()
        )
        return map('"{}"'.format, paths)

    def _read_files_egginfo_sources(self):
        """
        Read SOURCES.txt and return lines in a similar CSV-parsable
        format as RECORD: each file name must be quoted (since it
        might contain literal commas).

        Note that SOURCES.txt is not a reliable source for what
        files are installed by a package. This file is generated
        for a source archive, and the files that are present
        there (e.g. setup.py) may not correctly reflect the files
        that are present after the package has been installed.
        """
        text = self.read_text("SOURCES.txt")
        return text and map('"{}"'.format, text.splitlines())

    @property
    def requires(self) -> Optional[List[str]]:
        """Generated requirements specified for this Distribution"""
        reqs = self._read_dist_info_reqs() or self._read_egg_info_reqs()
        return reqs and list(reqs)

    def _read_dist_info_reqs(self):
        return self.metadata.get_all("Requires-Dist")

    def _read_egg_info_reqs(self):
        source = self.read_text("requires.txt")
        return pass_none(self._deps_from_requires_text)(source)

    @classmethod
    def _deps_from_requires_text(cls, source):
        return cls._convert_egg_info_reqs_to_simple_reqs(Sectioned.read(source))

    @staticmethod
    def _convert_egg_info_reqs_to_simple_reqs(sections):
        """
        Historically, setuptools would solicit and store 'extra'
        requirements, including those with environment markers,
        in separate sections. More modern tools expect each
        dependency to be defined separately, with any relevant
        extras and environment markers attached directly to that
        requirement. This method converts the former to the
        latter. See _test_deps_from_requires_text for an example.
        """

        def make_condition(name):
            return name and f'extra == "{name}"'

        def quoted_marker(section):
            section = section or ""
            extra, sep, markers = section.partition(":")
            if extra and markers:
                markers = f"({markers})"
            conditions = list(filter(None, [markers, make_condition(extra)]))
            return "; " + " and ".join(conditions) if conditions else ""

        def url_req_space(req):
            """
            PEP 508 requires a space between the url_spec and the quoted_marker.
            Ref python/importlib_metadata#357.
            """
            # '@' is uniquely indicative of a url_req.
            return " " * ("@" in req)

        for section in sections:
            space = url_req_space(section.value)
            yield section.value + space + quoted_marker(section.name)


class DistributionFinder(MetaPathFinder):
    """
    A MetaPathFinder capable of discovering installed distributions.
    """

    class Context:
        """
        Keyword arguments presented by the caller to
        ``distributions()`` or ``Distribution.discover()``
        to narrow the scope of a search for distributions
        in all DistributionFinders.

        Each DistributionFinder may expect any parameters
        and should attempt to honor the canonical
        parameters defined below when appropriate.
        """

        name = None
        """
        Specific name for which a distribution finder should match.
        A name of ``None`` matches all distributions.
        """

        def __init__(self, **kwargs):
            vars(self).update(kwargs)

        @property
        def path(self) -> List[str]:
            """
            The sequence of directory path that a distribution finder
            should search.

            Typically refers to Python installed package paths such as
            "site-packages" directories and defaults to ``sys.path``.
            """
            return vars(self).get("path", sys.path)

    @abc.abstractmethod
    def find_distributions(self, context=Context()) -> Iterable[Distribution]:
        """
        Find distributions.

        Return an iterable of all Distribution instances capable of
        loading the metadata for packages matching the ``context``,
        a DistributionFinder.Context instance.
        """


class FastPath:
    """
    Micro-optimized class for searching a path for
    children.

    >>> FastPath('').children()
    ['...']
    """

    @functools.lru_cache()  # type: ignore
    def __new__(cls, root):
        return super().__new__(cls)

    def __init__(self, root):
        self.root = root

    def joinpath(self, child):
        return pathlib.Path(self.root, child)

    def children(self):
        with suppress(Exception):
            return os.listdir(self.root or ".")
        with suppress(Exception):
            return self.zip_children()
        return []

    def zip_children(self):
        zip_path = zipp.Path(self.root)
        names = zip_path.root.namelist()
        self.joinpath = zip_path.joinpath

        return dict.fromkeys(child.split(posixpath.sep, 1)[0] for child in names)

    def search(self, name):
        return self.lookup(self.mtime).search(name)

    @property
    def mtime(self):
        with suppress(OSError):
            return os.stat(self.root).st_mtime
        self.lookup.cache_clear()

    @method_cache
    def lookup(self, mtime):
        return Lookup(self)


class Lookup:
    def __init__(self, path: FastPath):
        base = os.path.basename(path.root).lower()
        base_is_egg = base.endswith(".egg")
        self.infos = FreezableDefaultDict(list)
        self.eggs = FreezableDefaultDict(list)

        for child in path.children():
            low = child.lower()
            if low.endswith((".dist-info", ".egg-info")):
                # rpartition is faster than splitext and suitable for this purpose.
                name = low.rpartition(".")[0].partition("-")[0]
                normalized = Prepared.normalize(name)
                self.infos[normalized].append(path.joinpath(child))
            elif base_is_egg and low == "egg-info":
                name = base.rpartition(".")[0].partition("-")[0]
                legacy_normalized = Prepared.legacy_normalize(name)
                self.eggs[legacy_normalized].append(path.joinpath(child))

        self.infos.freeze()
        self.eggs.freeze()

    def search(self, prepared):
        infos = (
            self.infos[prepared.normalized]
            if prepared
            else itertools.chain.from_iterable(self.infos.values())
        )
        eggs = (
            self.eggs[prepared.legacy_normalized]
            if prepared
            else itertools.chain.from_iterable(self.eggs.values())
        )
        return itertools.chain(infos, eggs)


class Prepared:
    """
    A prepared search for metadata on a possibly-named package.
    """

    normalized = None
    legacy_normalized = None

    def __init__(self, name):
        self.name = name
        if name is None:
            return
        self.normalized = self.normalize(name)
        self.legacy_normalized = self.legacy_normalize(name)

    @staticmethod
    def normalize(name):
        """
        PEP 503 normalization plus dashes as underscores.
        """
        return re.sub(r"[-_.]+", "-", name).lower().replace("-", "_")

    @staticmethod
    def legacy_normalize(name):
        """
        Normalize the package name as found in the convention in
        older packaging tools versions and specs.
        """
        return name.lower().replace("-", "_")

    def __bool__(self):
        return bool(self.name)


@install
class MetadataPathFinder(NullFinder, DistributionFinder):
    """A degenerate finder for distribution packages on the file system.

    This finder supplies only a find_distributions() method for versions
    of Python that do not have a PathFinder find_distributions().
    """

    def find_distributions(
        self, context=DistributionFinder.Context()
    ) -> Iterable["PathDistribution"]:
        """
        Find distributions.

        Return an iterable of all Distribution instances capable of
        loading the metadata for packages matching ``context.name``
        (or all names if ``None`` indicated) along the paths in the list
        of directories ``context.path``.
        """
        found = self._search_paths(context.name, context.path)
        return map(PathDistribution, found)

    @classmethod
    def _search_paths(cls, name, paths):
        """Find metadata directories in paths heuristically."""
        prepared = Prepared(name)
        return itertools.chain.from_iterable(
            path.search(prepared) for path in map(FastPath, paths)
        )

    def invalidate_caches(cls) -> None:
        FastPath.__new__.cache_clear()


class PathDistribution(Distribution):
    def __init__(self, path: SimplePath) -> None:
        """Construct a distribution.

        :param path: SimplePath indicating the metadata directory.
        """
        self._path = path

    def read_text(self, filename: StrPath) -> Optional[str]:
        with suppress(
            FileNotFoundError,
            IsADirectoryError,
            KeyError,
            NotADirectoryError,
            PermissionError,
        ):
            return self._path.joinpath(filename).read_text(encoding="utf-8")

        return None

    read_text.__doc__ = Distribution.read_text.__doc__

    def locate_file(self, path: StrPath) -> pathlib.Path:
        return self._path.parent / path

    @property
    def _normalized_name(self):
        """
        Performance optimization: where possible, resolve the
        normalized name from the file system path.
        """
        stem = os.path.basename(str(self._path))
        return (
            pass_none(Prepared.normalize)(self._name_from_stem(stem))
            or super()._normalized_name
        )

    @staticmethod
    def _name_from_stem(stem):
        """
        >>> PathDistribution._name_from_stem('foo-3.0.egg-info')
        'foo'
        >>> PathDistribution._name_from_stem('CherryPy-3.0.dist-info')
        'CherryPy'
        >>> PathDistribution._name_from_stem('face.egg-info')
        'face'
        >>> PathDistribution._name_from_stem('foo.bar')
        """
        filename, ext = os.path.splitext(stem)
        if ext not in (".dist-info", ".egg-info"):
            return
        name, sep, rest = filename.partition("-")
        return name


def distribution(distribution_name) -> Distribution:
    """Get the ``Distribution`` instance for the named package.

    :param distribution_name: The name of the distribution package as a string.
    :return: A ``Distribution`` instance (or subclass thereof).
    """
    return Distribution.from_name(distribution_name)


def distributions(**kwargs) -> Iterable[Distribution]:
    """Get all ``Distribution`` instances in the current environment.

    :return: An iterable of ``Distribution`` instances.
    """
    return Distribution.discover(**kwargs)


def metadata(distribution_name) -> _meta.PackageMetadata:
    """Get the metadata for the named package.

    :param distribution_name: The name of the distribution package to query.
    :return: A PackageMetadata containing the parsed metadata.
    """
    return Distribution.from_name(distribution_name).metadata


def version(distribution_name) -> str:
    """Get the version string for the named package.

    :param distribution_name: The name of the distribution package to query.
    :return: The version string for the package as defined in the package's
        "Version" metadata key.
    """
    return distribution(distribution_name).version


_unique = functools.partial(
    unique_everseen,
    key=_py39compat.normalized_name,
)
"""
Wrapper for ``distributions`` to return unique distributions by name.
"""


def entry_points(**params) -> EntryPoints:
    """Return EntryPoint objects for all installed packages.

    Pass selection parameters (group or name) to filter the
    result to entry points matching those properties (see
    EntryPoints.select()).

    :return: EntryPoints for all installed packages.
    """
    eps = itertools.chain.from_iterable(
        dist.entry_points for dist in _unique(distributions())
    )
    return EntryPoints(eps).select(**params)


def files(distribution_name) -> Optional[List[PackagePath]]:
    """Return a list of files for the named package.

    :param distribution_name: The name of the distribution package to query.
    :return: List of files composing the distribution.
    """
    return distribution(distribution_name).files


def requires(distribution_name) -> Optional[List[str]]:
    """
    Return a list of requirements for the named package.

    :return: An iterable of requirements, suitable for
        packaging.requirement.Requirement.
    """
    return distribution(distribution_name).requires


def packages_distributions() -> Mapping[str, List[str]]:
    """
    Return a mapping of top-level packages to their
    distributions.

    >>> import collections.abc
    >>> pkgs = packages_distributions()
    >>> all(isinstance(dist, collections.abc.Sequence) for dist in pkgs.values())
    True
    """
    pkg_to_dist = collections.defaultdict(list)
    for dist in distributions():
        for pkg in _top_level_declared(dist) or _top_level_inferred(dist):
            pkg_to_dist[pkg].append(dist.metadata["Name"])
    return dict(pkg_to_dist)


def _top_level_declared(dist):
    return (dist.read_text("top_level.txt") or "").split()


def _top_level_inferred(dist):
    opt_names = {
        f.parts[0] if len(f.parts) > 1 else inspect.getmodulename(f)
        for f in always_iterable(dist.files)
    }

    @pass_none
    def importable_name(name):
        return "." not in name

    return filter(importable_name, opt_names)

virt/lib/python3.9/site-packages/mysql/opentelemetry/importlib_metadata/__init__ 3.py
PrinceDisant/LostInNL
import abc
import collections
import contextlib
import csv
import email
import functools
import inspect
import itertools
import operator
import os
import pathlib
import posixpath
import re
import sys
import textwrap
import warnings

from contextlib import suppress
from importlib import import_module
from importlib.abc import MetaPathFinder
from itertools import starmap
from typing import Iterable, List, Mapping, Optional, Set, cast

import zipp

from . import _adapters, _meta, _py39compat
from ._collections import FreezableDefaultDict, Pair
from ._compat import NullFinder, StrPath, install, pypy_partial
from ._functools import method_cache, pass_none
from ._itertools import always_iterable, unique_everseen
from ._meta import PackageMetadata, SimplePath

__all__ = [
    "Distribution",
    "DistributionFinder",
    "PackageMetadata",
    "PackageNotFoundError",
    "distribution",
    "distributions",
    "entry_points",
    "files",
    "metadata",
    "packages_distributions",
    "requires",
    "version",
]


class PackageNotFoundError(ModuleNotFoundError):
    """The package was not found."""

    def __str__(self) -> str:
        return f"No package metadata was found for {self.name}"

    @property
    def name(self) -> str:  # type: ignore[override]
        (name,) = self.args
        return name


class Sectioned:
    """
    A simple entry point config parser for performance

    >>> for item in Sectioned.read(Sectioned._sample):
    ...     print(item)
    Pair(name='sec1', value='# comments ignored')
    Pair(name='sec1', value='a = 1')
    Pair(name='sec1', value='b = 2')
    Pair(name='sec2', value='a = 2')

    >>> res = Sectioned.section_pairs(Sectioned._sample)
    >>> item = next(res)
    >>> item.name
    'sec1'
    >>> item.value
    Pair(name='a', value='1')
    >>> item = next(res)
    >>> item.value
    Pair(name='b', value='2')
    >>> item = next(res)
    >>> item.name
    'sec2'
    >>> item.value
    Pair(name='a', value='2')
    >>> list(res)
    []
    """

    _sample = textwrap.dedent(
        """
        [sec1]
        # comments ignored
        a = 1
        b = 2

        [sec2]
        a = 2
        """
    ).lstrip()

    @classmethod
    def section_pairs(cls, text):
        return (
            section._replace(value=Pair.parse(section.value))
            for section in cls.read(text, filter_=cls.valid)
            if section.name is not None
        )

    @staticmethod
    def read(text, filter_=None):
        lines = filter(filter_, map(str.strip, text.splitlines()))
        name = None
        for value in lines:
            section_match = value.startswith("[") and value.endswith("]")
            if section_match:
                name = value.strip("[]")
                continue
            yield Pair(name, value)

    @staticmethod
    def valid(line: str):
        return line and not line.startswith("#")


class DeprecatedTuple:
    """
    Provide subscript item access for backward compatibility.

    >>> recwarn = getfixture('recwarn')
    >>> ep = EntryPoint(name='name', value='value', group='group')
    >>> ep[:]
    ('name', 'value', 'group')
    >>> ep[0]
    'name'
    >>> len(recwarn)
    1
    """

    # Do not remove prior to 2023-05-01 or Python 3.13
    _warn = functools.partial(
        warnings.warn,
        "EntryPoint tuple interface is deprecated. Access members by name.",
        DeprecationWarning,
        stacklevel=pypy_partial(2),
    )

    def __getitem__(self, item):
        self._warn()
        return self._key()[item]


class EntryPoint(DeprecatedTuple):
    """An entry point as defined by Python packaging conventions.

    See `the packaging docs on entry points
    <https://packaging.python.org/specifications/entry-points/>`_
    for more information.

    >>> ep = EntryPoint(
    ...     name=None, group=None, value='package.module:attr [extra1, extra2]')
    >>> ep.module
    'package.module'
    >>> ep.attr
    'attr'
    >>> ep.extras
    ['extra1', 'extra2']
    """

    pattern = re.compile(
        r"(?P<module>[\w.]+)\s*"
        r"(:\s*(?P<attr>[\w.]+)\s*)?"
        r"((?P<extras>\[.*\])\s*)?$"
    )
    """
    A regular expression describing the syntax for an entry point,
    which might look like:

        - module
        - package.module
        - package.module:attribute
        - package.module:object.attribute
        - package.module:attr [extra1, extra2]

    Other combinations are possible as well.

    The expression is lenient about whitespace around the ':',
    following the attr, and following any extras.
    """

    name: str
    value: str
    group: str

    dist: Optional["Distribution"] = None

    def __init__(self, name: str, value: str, group: str) -> None:
        vars(self).update(name=name, value=value, group=group)

    def load(self):
        """Load the entry point from its definition. If only a module
        is indicated by the value, return that module. Otherwise,
        return the named object.
        """
        match = self.pattern.match(self.value)
        module = import_module(match.group("module"))
        attrs = filter(None, (match.group("attr") or "").split("."))
        return functools.reduce(getattr, attrs, module)

    @property
    def module(self) -> str:
        match = self.pattern.match(self.value)
        assert match is not None
        return match.group("module")

    @property
    def attr(self) -> str:
        match = self.pattern.match(self.value)
        assert match is not None
        return match.group("attr")

    @property
    def extras(self) -> List[str]:
        match = self.pattern.match(self.value)
        assert match is not None
        return re.findall(r"\w+", match.group("extras") or "")

    def _for(self, dist):
        vars(self).update(dist=dist)
        return self

    def matches(self, **params):
        """
        EntryPoint matches the given parameters.

        >>> ep = EntryPoint(group='foo', name='bar', value='bing:bong [extra1, extra2]')
        >>> ep.matches(group='foo')
        True
        >>> ep.matches(name='bar', value='bing:bong [extra1, extra2]')
        True
        >>> ep.matches(group='foo', name='other')
        False
        >>> ep.matches()
        True
        >>> ep.matches(extras=['extra1', 'extra2'])
        True
        >>> ep.matches(module='bing')
        True
        >>> ep.matches(attr='bong')
        True
        """
        attrs = (getattr(self, param) for param in params)
        return all(map(operator.eq, params.values(), attrs))

    def _key(self):
        return self.name, self.value, self.group

    def __lt__(self, other):
        return self._key() < other._key()

    def __eq__(self, other):
        return self._key() == other._key()

    def __setattr__(self, name, value):
        raise AttributeError("EntryPoint objects are immutable.")

    def __repr__(self):
        return (
            f"EntryPoint(name={self.name!r}, value={self.value!r}, "
            f"group={self.group!r})"
        )

    def __hash__(self) -> int:
        return hash(self._key())


class EntryPoints(tuple):
    """
    An immutable collection of selectable EntryPoint objects.
    """

    __slots__ = ()

    def __getitem__(self, name: str) -> EntryPoint:  # type: ignore[override]
        """
        Get the EntryPoint in self matching name.
        """
        try:
            return next(iter(self.select(name=name)))
        except StopIteration:
            raise KeyError(name)

    def select(self, **params):
        """
        Select entry points from self that match the
        given parameters (typically group and/or name).
        """
        return EntryPoints(ep for ep in self if _py39compat.ep_matches(ep, **params))

    @property
    def names(self) -> Set[str]:
        """
        Return the set of all names of all entry points.
        """
        return {ep.name for ep in self}

    @property
    def groups(self) -> Set[str]:
        """
        Return the set of all groups of all entry points.
        """
        return {ep.group for ep in self}

    @classmethod
    def _from_text_for(cls, text, dist):
        return cls(ep._for(dist) for ep in cls._from_text(text))

    @staticmethod
    def _from_text(text):
        return (
            EntryPoint(name=item.value.name, value=item.value.value, group=item.name)
            for item in Sectioned.section_pairs(text or "")
        )


class PackagePath(pathlib.PurePosixPath):
    """A reference to a path in a package"""

    hash: Optional["FileHash"]
    size: int
    dist: "Distribution"

    def read_text(self, encoding: str = "utf-8") -> str:  # type: ignore[override]
        with self.locate().open(encoding=encoding) as stream:
            return stream.read()

    def read_binary(self) -> bytes:
        with self.locate().open("rb") as stream:
            return stream.read()

    def locate(self) -> pathlib.Path:
        """Return a path-like object for this path"""
        return self.dist.locate_file(self)


class FileHash:
    def __init__(self, spec: str) -> None:
        self.mode, _, self.value = spec.partition("=")

    def __repr__(self) -> str:
        return f"<FileHash mode: {self.mode} value: {self.value}>"


class DeprecatedNonAbstract:
    def __new__(cls, *args, **kwargs):
        all_names = {
            name for subclass in inspect.getmro(cls) for name in vars(subclass)
        }
        abstract = {
            name
            for name in all_names
            if getattr(getattr(cls, name), "__isabstractmethod__", False)
        }
        if abstract:
            warnings.warn(
                f"Unimplemented abstract methods {abstract}",
                DeprecationWarning,
                stacklevel=2,
            )
        return super().__new__(cls)


class Distribution(DeprecatedNonAbstract):
    """A Python distribution package."""

    @abc.abstractmethod
    def read_text(self, filename) -> Optional[str]:
        """Attempt to load metadata file given by the name.

        :param filename: The name of the file in the distribution info.
        :return: The text if found, otherwise None.
        """

    @abc.abstractmethod
    def locate_file(self, path: StrPath) -> pathlib.Path:
        """
        Given a path to a file in this distribution, return a path
        to it.
        """

    @classmethod
    def from_name(cls, name: str) -> "Distribution":
        """Return the Distribution for the given package name.

        :param name: The name of the distribution package to search for.
        :return: The Distribution instance (or subclass thereof) for the named
            package, if found.
        :raises PackageNotFoundError: When the named package's distribution
            metadata cannot be found.
        :raises ValueError: When an invalid value is supplied for name.
        """
        if not name:
            raise ValueError("A distribution name is required.")
        try:
            return next(iter(cls.discover(name=name)))
        except StopIteration:
            raise PackageNotFoundError(name)

    @classmethod
    def discover(cls, **kwargs) -> Iterable["Distribution"]:
        """Return an iterable of Distribution objects for all packages.

        Pass a ``context`` or pass keyword arguments for constructing
        a context.

        :context: A ``DistributionFinder.Context`` object.
        :return: Iterable of Distribution objects for all packages.
        """
        context = kwargs.pop("context", None)
        if context and kwargs:
            raise ValueError("cannot accept context and kwargs")
        context = context or DistributionFinder.Context(**kwargs)
        return itertools.chain.from_iterable(
            resolver(context) for resolver in cls._discover_resolvers()
        )

    @staticmethod
    def at(path: StrPath) -> "Distribution":
        """Return a Distribution for the indicated metadata path

        :param path: a string or path-like object
        :return: a concrete Distribution instance for the path
        """
        return PathDistribution(pathlib.Path(path))

    @staticmethod
    def _discover_resolvers():
        """Search the meta_path for resolvers."""
        declared = (
            getattr(finder, "find_distributions", None) for finder in sys.meta_path
        )
        return filter(None, declared)

    @property
    def metadata(self) -> _meta.PackageMetadata:
        """Return the parsed metadata for this Distribution.

        The returned object will have keys that name the various bits of
        metadata.  See PEP 566 for details.
        """
        opt_text = (
            self.read_text("METADATA")
            or self.read_text("PKG-INFO")
            # This last clause is here to support old egg-info files.  Its
            # effect is to just end up using the PathDistribution's self._path
            # (which points to the egg-info file) attribute unchanged.
            or self.read_text("")
        )
        text = cast(str, opt_text)
        return _adapters.Message(email.message_from_string(text))

    @property
    def name(self) -> str:
        """Return the 'Name' metadata for the distribution package."""
        return self.metadata["Name"]

    @property
    def _normalized_name(self):
        """Return a normalized version of the name."""
        return Prepared.normalize(self.name)

    @property
    def version(self) -> str:
        """Return the 'Version' metadata for the distribution package."""
        return self.metadata["Version"]

    @property
    def entry_points(self) -> EntryPoints:
        return EntryPoints._from_text_for(self.read_text("entry_points.txt"), self)

    @property
    def files(self) -> Optional[List[PackagePath]]:
        """Files in this distribution.

        :return: List of PackagePath for this distribution or None

        Result is `None` if the metadata file that enumerates files
        (i.e. RECORD for dist-info, or installed-files.txt or
        SOURCES.txt for egg-info) is missing.
        Result may be empty if the metadata exists but is empty.
        """

        def make_file(name, hash=None, size_str=None):
            result = PackagePath(name)
            result.hash = FileHash(hash) if hash else None
            result.size = int(size_str) if size_str else None
            result.dist = self
            return result

        @pass_none
        def make_files(lines):
            return starmap(make_file, csv.reader(lines))

        @pass_none
        def skip_missing_files(package_paths):
            return list(filter(lambda path: path.locate().exists(), package_paths))

        return skip_missing_files(
            make_files(
                self._read_files_distinfo()
                or self._read_files_egginfo_installed()
                or self._read_files_egginfo_sources()
            )
        )

    def _read_files_distinfo(self):
        """
        Read the lines of RECORD
        """
        text = self.read_text("RECORD")
        return text and text.splitlines()

    def _read_files_egginfo_installed(self):
        """
        Read installed-files.txt and return lines in a similar
        CSV-parsable format as RECORD: each file must be placed
        relative to the site-packages directory and must also be
        quoted (since file names can contain literal commas).

        This file is written when the package is installed by pip,
        but it might not be written for other installation methods.
        Assume the file is accurate if it exists.
        """
        text = self.read_text("installed-files.txt")
        # Prepend the .egg-info/ subdir to the lines in this file.
        # But this subdir is only available from PathDistribution's
        # self._path.
        subdir = getattr(self, "_path", None)
        if not text or not subdir:
            return

        paths = (
            (subdir / name)
            .resolve()
            .relative_to(self.locate_file("").resolve())
            .as_posix()
            for name in text.splitlines()
        )
        return map('"{}"'.format, paths)

    def _read_files_egginfo_sources(self):
        """
        Read SOURCES.txt and return lines in a similar CSV-parsable
        format as RECORD: each file name must be quoted (since it
        might contain literal commas).

        Note that SOURCES.txt is not a reliable source for what
        files are installed by a package. This file is generated
        for a source archive, and the files that are present
        there (e.g. setup.py) may not correctly reflect the files
        that are present after the package has been installed.
        """
        text = self.read_text("SOURCES.txt")
        return text and map('"{}"'.format, text.splitlines())

    @property
    def requires(self) -> Optional[List[str]]:
        """Generated requirements specified for this Distribution"""
        reqs = self._read_dist_info_reqs() or self._read_egg_info_reqs()
        return reqs and list(reqs)

    def _read_dist_info_reqs(self):
        return self.metadata.get_all("Requires-Dist")

    def _read_egg_info_reqs(self):
        source = self.read_text("requires.txt")
        return pass_none(self._deps_from_requires_text)(source)

    @classmethod
    def _deps_from_requires_text(cls, source):
        return cls._convert_egg_info_reqs_to_simple_reqs(Sectioned.read(source))

    @staticmethod
    def _convert_egg_info_reqs_to_simple_reqs(sections):
        """
        Historically, setuptools would solicit and store 'extra'
        requirements, including those with environment markers,
        in separate sections. More modern tools expect each
        dependency to be defined separately, with any relevant
        extras and environment markers attached directly to that
        requirement. This method converts the former to the
        latter. See _test_deps_from_requires_text for an example.
        """

        def make_condition(name):
            return name and f'extra == "{name}"'

        def quoted_marker(section):
            section = section or ""
            extra, sep, markers = section.partition(":")
            if extra and markers:
                markers = f"({markers})"
            conditions = list(filter(None, [markers, make_condition(extra)]))
            return "; " + " and ".join(conditions) if conditions else ""

        def url_req_space(req):
            """
            PEP 508 requires a space between the url_spec and the quoted_marker.
            Ref python/importlib_metadata#357.
            """
            # '@' is uniquely indicative of a url_req.
            return " " * ("@" in req)

        for section in sections:
            space = url_req_space(section.value)
            yield section.value + space + quoted_marker(section.name)


class DistributionFinder(MetaPathFinder):
    """
    A MetaPathFinder capable of discovering installed distributions.
    """

    class Context:
        """
        Keyword arguments presented by the caller to
        ``distributions()`` or ``Distribution.discover()``
        to narrow the scope of a search for distributions
        in all DistributionFinders.

        Each DistributionFinder may expect any parameters
        and should attempt to honor the canonical
        parameters defined below when appropriate.
        """

        name = None
        """
        Specific name for which a distribution finder should match.
        A name of ``None`` matches all distributions.
        """

        def __init__(self, **kwargs):
            vars(self).update(kwargs)

        @property
        def path(self) -> List[str]:
            """
            The sequence of directory path that a distribution finder
            should search.

            Typically refers to Python installed package paths such as
            "site-packages" directories and defaults to ``sys.path``.
            """
            return vars(self).get("path", sys.path)

    @abc.abstractmethod
    def find_distributions(self, context=Context()) -> Iterable[Distribution]:
        """
        Find distributions.

        Return an iterable of all Distribution instances capable of
        loading the metadata for packages matching the ``context``,
        a DistributionFinder.Context instance.
        """


class FastPath:
    """
    Micro-optimized class for searching a path for
    children.

    >>> FastPath('').children()
    ['...']
    """

    @functools.lru_cache()  # type: ignore
    def __new__(cls, root):
        return super().__new__(cls)

    def __init__(self, root):
        self.root = root

    def joinpath(self, child):
        return pathlib.Path(self.root, child)

    def children(self):
        with suppress(Exception):
            return os.listdir(self.root or ".")
        with suppress(Exception):
            return self.zip_children()
        return []

    def zip_children(self):
        zip_path = zipp.Path(self.root)
        names = zip_path.root.namelist()
        self.joinpath = zip_path.joinpath

        return dict.fromkeys(child.split(posixpath.sep, 1)[0] for child in names)

    def search(self, name):
        return self.lookup(self.mtime).search(name)

    @property
    def mtime(self):
        with suppress(OSError):
            return os.stat(self.root).st_mtime
        self.lookup.cache_clear()

    @method_cache
    def lookup(self, mtime):
        return Lookup(self)


class Lookup:
    def __init__(self, path: FastPath):
        base = os.path.basename(path.root).lower()
        base_is_egg = base.endswith(".egg")
        self.infos = FreezableDefaultDict(list)
        self.eggs = FreezableDefaultDict(list)

        for child in path.children():
            low = child.lower()
            if low.endswith((".dist-info", ".egg-info")):
                # rpartition is faster than splitext and suitable for this purpose.
                name = low.rpartition(".")[0].partition("-")[0]
                normalized = Prepared.normalize(name)
                self.infos[normalized].append(path.joinpath(child))
            elif base_is_egg and low == "egg-info":
                name = base.rpartition(".")[0].partition("-")[0]
                legacy_normalized = Prepared.legacy_normalize(name)
                self.eggs[legacy_normalized].append(path.joinpath(child))

        self.infos.freeze()
        self.eggs.freeze()

    def search(self, prepared):
        infos = (
            self.infos[prepared.normalized]
            if prepared
            else itertools.chain.from_iterable(self.infos.values())
        )
        eggs = (
            self.eggs[prepared.legacy_normalized]
            if prepared
            else itertools.chain.from_iterable(self.eggs.values())
        )
        return itertools.chain(infos, eggs)


class Prepared:
    """
    A prepared search for metadata on a possibly-named package.
    """

    normalized = None
    legacy_normalized = None

    def __init__(self, name):
        self.name = name
        if name is None:
            return
        self.normalized = self.normalize(name)
        self.legacy_normalized = self.legacy_normalize(name)

    @staticmethod
    def normalize(name):
        """
        PEP 503 normalization plus dashes as underscores.
        """
        return re.sub(r"[-_.]+", "-", name).lower().replace("-", "_")

    @staticmethod
    def legacy_normalize(name):
        """
        Normalize the package name as found in the convention in
        older packaging tools versions and specs.
        """
        return name.lower().replace("-", "_")

    def __bool__(self):
        return bool(self.name)


@install
class MetadataPathFinder(NullFinder, DistributionFinder):
    """A degenerate finder for distribution packages on the file system.

    This finder supplies only a find_distributions() method for versions
    of Python that do not have a PathFinder find_distributions().
    """

    def find_distributions(
        self, context=DistributionFinder.Context()
    ) -> Iterable["PathDistribution"]:
        """
        Find distributions.

        Return an iterable of all Distribution instances capable of
        loading the metadata for packages matching ``context.name``
        (or all names if ``None`` indicated) along the paths in the list
        of directories ``context.path``.
        """
        found = self._search_paths(context.name, context.path)
        return map(PathDistribution, found)

    @classmethod
    def _search_paths(cls, name, paths):
        """Find metadata directories in paths heuristically."""
        prepared = Prepared(name)
        return itertools.chain.from_iterable(
            path.search(prepared) for path in map(FastPath, paths)
        )

    def invalidate_caches(cls) -> None:
        FastPath.__new__.cache_clear()


class PathDistribution(Distribution):
    def __init__(self, path: SimplePath) -> None:
        """Construct a distribution.

        :param path: SimplePath indicating the metadata directory.
        """
        self._path = path

    def read_text(self, filename: StrPath) -> Optional[str]:
        with suppress(
            FileNotFoundError,
            IsADirectoryError,
            KeyError,
            NotADirectoryError,
            PermissionError,
        ):
            return self._path.joinpath(filename).read_text(encoding="utf-8")

        return None

    read_text.__doc__ = Distribution.read_text.__doc__

    def locate_file(self, path: StrPath) -> pathlib.Path:
        return self._path.parent / path

    @property
    def _normalized_name(self):
        """
        Performance optimization: where possible, resolve the
        normalized name from the file system path.
        """
        stem = os.path.basename(str(self._path))
        return (
            pass_none(Prepared.normalize)(self._name_from_stem(stem))
            or super()._normalized_name
        )

    @staticmethod
    def _name_from_stem(stem):
        """
        >>> PathDistribution._name_from_stem('foo-3.0.egg-info')
        'foo'
        >>> PathDistribution._name_from_stem('CherryPy-3.0.dist-info')
        'CherryPy'
        >>> PathDistribution._name_from_stem('face.egg-info')
        'face'
        >>> PathDistribution._name_from_stem('foo.bar')
        """
        filename, ext = os.path.splitext(stem)
        if ext not in (".dist-info", ".egg-info"):
            return
        name, sep, rest = filename.partition("-")
        return name


def distribution(distribution_name) -> Distribution:
    """Get the ``Distribution`` instance for the named package.

    :param distribution_name: The name of the distribution package as a string.
    :return: A ``Distribution`` instance (or subclass thereof).
    """
    return Distribution.from_name(distribution_name)


def distributions(**kwargs) -> Iterable[Distribution]:
    """Get all ``Distribution`` instances in the current environment.

    :return: An iterable of ``Distribution`` instances.
    """
    return Distribution.discover(**kwargs)


def metadata(distribution_name) -> _meta.PackageMetadata:
    """Get the metadata for the named package.

    :param distribution_name: The name of the distribution package to query.
    :return: A PackageMetadata containing the parsed metadata.
    """
    return Distribution.from_name(distribution_name).metadata


def version(distribution_name) -> str:
    """Get the version string for the named package.

    :param distribution_name: The name of the distribution package to query.
    :return: The version string for the package as defined in the package's
        "Version" metadata key.
    """
    return distribution(distribution_name).version


_unique = functools.partial(
    unique_everseen,
    key=_py39compat.normalized_name,
)
"""
Wrapper for ``distributions`` to return unique distributions by name.
"""


def entry_points(**params) -> EntryPoints:
    """Return EntryPoint objects for all installed packages.

    Pass selection parameters (group or name) to filter the
    result to entry points matching those properties (see
    EntryPoints.select()).

    :return: EntryPoints for all installed packages.
    """
    eps = itertools.chain.from_iterable(
        dist.entry_points for dist in _unique(distributions())
    )
    return EntryPoints(eps).select(**params)


def files(distribution_name) -> Optional[List[PackagePath]]:
    """Return a list of files for the named package.

    :param distribution_name: The name of the distribution package to query.
    :return: List of files composing the distribution.
    """
    return distribution(distribution_name).files


def requires(distribution_name) -> Optional[List[str]]:
    """
    Return a list of requirements for the named package.

    :return: An iterable of requirements, suitable for
        packaging.requirement.Requirement.
    """
    return distribution(distribution_name).requires


def packages_distributions() -> Mapping[str, List[str]]:
    """
    Return a mapping of top-level packages to their
    distributions.

    >>> import collections.abc
    >>> pkgs = packages_distributions()
    >>> all(isinstance(dist, collections.abc.Sequence) for dist in pkgs.values())
    True
    """
    pkg_to_dist = collections.defaultdict(list)
    for dist in distributions():
        for pkg in _top_level_declared(dist) or _top_level_inferred(dist):
            pkg_to_dist[pkg].append(dist.metadata["Name"])
    return dict(pkg_to_dist)


def _top_level_declared(dist):
    return (dist.read_text("top_level.txt") or "").split()


def _top_level_inferred(dist):
    opt_names = {
        f.parts[0] if len(f.parts) > 1 else inspect.getmodulename(f)
        for f in always_iterable(dist.files)
    }

    @pass_none
    def importable_name(name):
        return "." not in name

    return filter(importable_name, opt_names)

virt/lib/python3.9/site-packages/mysql/opentelemetry/importlib_metadata/__init__.py
PrinceDisant/LostInNL
import email.message
import functools
import re
import textwrap
import warnings

from ._compat import pypy_partial
from ._text import FoldedCase

# Do not remove prior to 2024-01-01 or Python 3.14
_warn = functools.partial(
    warnings.warn,
    "Implicit None on return values is deprecated and will raise KeyErrors.",
    DeprecationWarning,
    stacklevel=pypy_partial(2),
)


class Message(email.message.Message):
    multiple_use_keys = set(
        map(
            FoldedCase,
            [
                "Classifier",
                "Obsoletes-Dist",
                "Platform",
                "Project-URL",
                "Provides-Dist",
                "Provides-Extra",
                "Requires-Dist",
                "Requires-External",
                "Supported-Platform",
                "Dynamic",
            ],
        )
    )
    """
    Keys that may be indicated multiple times per PEP 566.
    """

    def __new__(cls, orig: email.message.Message):
        res = super().__new__(cls)
        vars(res).update(vars(orig))
        return res

    def __init__(self, *args, **kwargs):
        self._headers = self._repair_headers()

    # suppress spurious error from mypy
    def __iter__(self):
        return super().__iter__()

    def __getitem__(self, item):
        """
        Warn users that a ``KeyError`` can be expected when a
        mising key is supplied. Ref python/importlib_metadata#371.
        """
        res = super().__getitem__(item)
        if res is None:
            _warn()
        return res

    def _repair_headers(self):
        def redent(value):
            "Correct for RFC822 indentation"
            if not value or "\n" not in value:
                return value
            return textwrap.dedent(" " * 8 + value)

        headers = [(key, redent(value)) for key, value in vars(self)["_headers"]]
        if self._payload:
            headers.append(("Description", self.get_payload()))
        return headers

    @property
    def json(self):
        """
        Convert PackageMetadata to a JSON-compatible format
        per PEP 0566.
        """

        def transform(key):
            value = self.get_all(key) if key in self.multiple_use_keys else self[key]
            if key == "Keywords":
                value = re.split(r"\s+", value)
            tk = key.lower().replace("-", "_")
            return tk, value

        return dict(map(transform, map(FoldedCase, self)))

virt/lib/python3.9/site-packages/mysql/opentelemetry/importlib_metadata/_adapters.py
PrinceDisant/LostInNL
import os
import platform
import sys

from typing import Union

__all__ = ["install", "NullFinder", "Protocol"]


try:
    from typing import Protocol
except ImportError:  # pragma: no cover
    # Python 3.7 compatibility
    from typing_extensions import Protocol  # type: ignore


def install(cls):
    """
    Class decorator for installation on sys.meta_path.

    Adds the backport DistributionFinder to sys.meta_path and
    attempts to disable the finder functionality of the stdlib
    DistributionFinder.
    """
    sys.meta_path.append(cls())
    disable_stdlib_finder()
    return cls


def disable_stdlib_finder():
    """
    Give the backport primacy for discovering path-based distributions
    by monkey-patching the stdlib O_O.

    See #91 for more background for rationale on this sketchy
    behavior.
    """

    def matches(finder):
        return getattr(
            finder, "__module__", None
        ) == "_frozen_importlib_external" and hasattr(finder, "find_distributions")

    for finder in filter(matches, sys.meta_path):  # pragma: nocover
        del finder.find_distributions


class NullFinder:
    """
    A "Finder" (aka "MetaClassFinder") that never finds any modules,
    but may find distributions.
    """

    @staticmethod
    def find_spec(*args, **kwargs):
        return None

    # In Python 2, the import system requires finders
    # to have a find_module() method, but this usage
    # is deprecated in Python 3 in favor of find_spec().
    # For the purposes of this finder (i.e. being present
    # on sys.meta_path but having no other import
    # system functionality), the two methods are identical.
    find_module = find_spec


def pypy_partial(val):
    """
    Adjust for variable stacklevel on partial under PyPy.

    Workaround for #327.
    """
    is_pypy = platform.python_implementation() == "PyPy"
    return val + is_pypy


if sys.version_info >= (3, 9):
    StrPath = Union[str, os.PathLike[str]]
else:
    # PathLike is only subscriptable at runtime in 3.9+
    StrPath = Union[str, "os.PathLike[str]"]  # pragma: no cover

virt/lib/python3.9/site-packages/mysql/opentelemetry/importlib_metadata/_compat.py
PrinceDisant/LostInNL
import functools
import types


# from jaraco.functools 3.3
def method_cache(method, cache_wrapper=None):
    """
    Wrap lru_cache to support storing the cache data in the object instances.

    Abstracts the common paradigm where the method explicitly saves an
    underscore-prefixed protected property on first call and returns that
    subsequently.

    >>> class MyClass:
    ...     calls = 0
    ...
    ...     @method_cache
    ...     def method(self, value):
    ...         self.calls += 1
    ...         return value

    >>> a = MyClass()
    >>> a.method(3)
    3
    >>> for x in range(75):
    ...     res = a.method(x)
    >>> a.calls
    75

    Note that the apparent behavior will be exactly like that of lru_cache
    except that the cache is stored on each instance, so values in one
    instance will not flush values from another, and when an instance is
    deleted, so are the cached values for that instance.

    >>> b = MyClass()
    >>> for x in range(35):
    ...     res = b.method(x)
    >>> b.calls
    35
    >>> a.method(0)
    0
    >>> a.calls
    75

    Note that if method had been decorated with ``functools.lru_cache()``,
    a.calls would have been 76 (due to the cached value of 0 having been
    flushed by the 'b' instance).

    Clear the cache with ``.cache_clear()``

    >>> a.method.cache_clear()

    Same for a method that hasn't yet been called.

    >>> c = MyClass()
    >>> c.method.cache_clear()

    Another cache wrapper may be supplied:

    >>> cache = functools.lru_cache(maxsize=2)
    >>> MyClass.method2 = method_cache(lambda self: 3, cache_wrapper=cache)
    >>> a = MyClass()
    >>> a.method2()
    3

    Caution - do not subsequently wrap the method with another decorator, such
    as ``@property``, which changes the semantics of the function.

    See also
    http://code.activestate.com/recipes/577452-a-memoize-decorator-for-instance-methods/
    for another implementation and additional justification.
    """
    cache_wrapper = cache_wrapper or functools.lru_cache()

    def wrapper(self, *args, **kwargs):
        # it's the first call, replace the method with a cached, bound method
        bound_method = types.MethodType(method, self)
        cached_method = cache_wrapper(bound_method)
        setattr(self, method.__name__, cached_method)
        return cached_method(*args, **kwargs)

    # Support cache clear even before cache has been created.
    wrapper.cache_clear = lambda: None

    return wrapper


# From jaraco.functools 3.3
def pass_none(func):
    """
    Wrap func so it's not called if its first param is None

    >>> print_text = pass_none(print)
    >>> print_text('text')
    text
    >>> print_text(None)
    """

    @functools.wraps(func)
    def wrapper(param, *args, **kwargs):
        if param is not None:
            return func(param, *args, **kwargs)

    return wrapper

virt/lib/python3.9/site-packages/mysql/opentelemetry/importlib_metadata/_functools.py
PrinceDisant/LostInNL
from typing import Any, Dict, Iterator, List, Optional, TypeVar, Union, overload

from ._compat import Protocol

_T = TypeVar("_T")


class PackageMetadata(Protocol):
    def __len__(self) -> int:
        ...  # pragma: no cover

    def __contains__(self, item: str) -> bool:
        ...  # pragma: no cover

    def __getitem__(self, key: str) -> str:
        ...  # pragma: no cover

    def __iter__(self) -> Iterator[str]:
        ...  # pragma: no cover

    @overload
    def get(self, name: str, failobj: None = None) -> Optional[str]:
        ...  # pragma: no cover

    @overload
    def get(self, name: str, failobj: _T) -> Union[str, _T]:
        ...  # pragma: no cover

    # overload per python/importlib_metadata#435
    @overload
    def get_all(self, name: str, failobj: None = None) -> Optional[List[Any]]:
        ...  # pragma: no cover

    @overload
    def get_all(self, name: str, failobj: _T) -> Union[List[Any], _T]:
        """
        Return all values associated with a possibly multi-valued key.
        """

    @property
    def json(self) -> Dict[str, Union[str, List[str]]]:
        """
        A JSON-compatible form of the metadata.
        """


class SimplePath(Protocol[_T]):
    """
    A minimal subset of pathlib.Path required by PathDistribution.
    """

    def joinpath(self, other: Union[str, _T]) -> _T:
        ...  # pragma: no cover

    def __truediv__(self, other: Union[str, _T]) -> _T:
        ...  # pragma: no cover

    @property
    def parent(self) -> _T:
        ...  # pragma: no cover

    def read_text(self) -> str:
        ...  # pragma: no cover

virt/lib/python3.9/site-packages/mysql/opentelemetry/importlib_metadata/_meta.py
PrinceDisant/LostInNL
"""
Compatibility layer with Python 3.8/3.9
"""
from typing import TYPE_CHECKING, Any, Optional

if TYPE_CHECKING:  # pragma: no cover
    # Prevent circular imports on runtime.
    from . import Distribution, EntryPoint
else:
    Distribution = EntryPoint = Any


def normalized_name(dist: Distribution) -> Optional[str]:
    """
    Honor name normalization for distributions that don't provide ``_normalized_name``.
    """
    try:
        return dist._normalized_name
    except AttributeError:
        from . import Prepared  # -> delay to prevent circular imports.

        return Prepared.normalize(getattr(dist, "name", None) or dist.metadata["Name"])


def ep_matches(ep: EntryPoint, **params) -> bool:
    """
    Workaround for ``EntryPoint`` objects without the ``matches`` method.
    """
    try:
        return ep.matches(**params)
    except AttributeError:
        from . import EntryPoint  # -> delay to prevent circular imports.

        # Reconstruct the EntryPoint object to make sure it is compatible.
        return EntryPoint(ep.name, ep.value, ep.group).matches(**params)

virt/lib/python3.9/site-packages/mysql/opentelemetry/importlib_metadata/_py39compat.py
PrinceDisant/LostInNL
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""
The OpenTelemetry metrics API  describes the classes used to generate
metrics.

The :class:`.MeterProvider` provides users access to the :class:`.Meter` which in
turn is used to create :class:`.Instrument` objects. The :class:`.Instrument` objects are
used to record measurements.

This module provides abstract (i.e. unimplemented) classes required for
metrics, and a concrete no-op implementation :class:`.NoOpMeter` that allows applications
to use the API package alone without a supporting implementation.

To get a meter, you need to provide the package name from which you are
calling the meter APIs to OpenTelemetry by calling `MeterProvider.get_meter`
with the calling instrumentation name and the version of your package.

The following code shows how to obtain a meter using the global :class:`.MeterProvider`::

    from mysql.opentelemetry.metrics import get_meter

    meter = get_meter("example-meter")
    counter = meter.create_counter("example-counter")

.. versionadded:: 1.10.0
.. versionchanged:: 1.12.0rc
"""

from mysql.opentelemetry.metrics._internal import (
    Meter,
    MeterProvider,
    NoOpMeter,
    NoOpMeterProvider,
    get_meter,
    get_meter_provider,
    set_meter_provider,
)
from mysql.opentelemetry.metrics._internal.instrument import (
    Asynchronous,
    CallbackOptions,
    CallbackT,
    Counter,
    Histogram,
    Instrument,
    NoOpCounter,
    NoOpHistogram,
    NoOpObservableCounter,
    NoOpObservableGauge,
    NoOpObservableUpDownCounter,
    NoOpUpDownCounter,
    ObservableCounter,
    ObservableGauge,
    ObservableUpDownCounter,
    Synchronous,
    UpDownCounter,
)
from mysql.opentelemetry.metrics._internal.observation import Observation

for obj in [
    Counter,
    Synchronous,
    Asynchronous,
    CallbackOptions,
    get_meter_provider,
    get_meter,
    Histogram,
    Meter,
    MeterProvider,
    Instrument,
    NoOpCounter,
    NoOpHistogram,
    NoOpMeter,
    NoOpMeterProvider,
    NoOpObservableCounter,
    NoOpObservableGauge,
    NoOpObservableUpDownCounter,
    NoOpUpDownCounter,
    ObservableCounter,
    ObservableGauge,
    ObservableUpDownCounter,
    Observation,
    set_meter_provider,
    UpDownCounter,
]:
    obj.__module__ = __name__

__all__ = [
    "CallbackOptions",
    "MeterProvider",
    "NoOpMeterProvider",
    "Meter",
    "Counter",
    "NoOpCounter",
    "UpDownCounter",
    "NoOpUpDownCounter",
    "Histogram",
    "NoOpHistogram",
    "ObservableCounter",
    "NoOpObservableCounter",
    "ObservableUpDownCounter",
    "Instrument",
    "Synchronous",
    "Asynchronous",
    "NoOpObservableGauge",
    "ObservableGauge",
    "NoOpObservableUpDownCounter",
    "get_meter",
    "get_meter_provider",
    "set_meter_provider",
    "Observation",
    "CallbackT",
    "NoOpMeter",
]

virt/lib/python3.9/site-packages/mysql/opentelemetry/metrics/__init__.py
PrinceDisant/LostInNL
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# pylint: disable=too-many-ancestors

"""
The OpenTelemetry metrics API  describes the classes used to generate
metrics.

The :class:`.MeterProvider` provides users access to the :class:`.Meter` which in
turn is used to create :class:`.Instrument` objects. The :class:`.Instrument` objects are
used to record measurements.

This module provides abstract (i.e. unimplemented) classes required for
metrics, and a concrete no-op implementation :class:`.NoOpMeter` that allows applications
to use the API package alone without a supporting implementation.

To get a meter, you need to provide the package name from which you are
calling the meter APIs to OpenTelemetry by calling `MeterProvider.get_meter`
with the calling instrumentation name and the version of your package.

The following code shows how to obtain a meter using the global :class:`.MeterProvider`::

    from mysql.opentelemetry.metrics import get_meter

    meter = get_meter("example-meter")
    counter = meter.create_counter("example-counter")

.. versionadded:: 1.10.0
"""


from abc import ABC, abstractmethod
from logging import getLogger
from os import environ
from threading import Lock
from typing import List, Optional, Sequence, Set, Tuple, Union, cast

from mysql.opentelemetry.environment_variables import OTEL_PYTHON_METER_PROVIDER
from mysql.opentelemetry.metrics._internal.instrument import (
    CallbackT,
    Counter,
    Histogram,
    NoOpCounter,
    NoOpHistogram,
    NoOpObservableCounter,
    NoOpObservableGauge,
    NoOpObservableUpDownCounter,
    NoOpUpDownCounter,
    ObservableCounter,
    ObservableGauge,
    ObservableUpDownCounter,
    UpDownCounter,
    _ProxyCounter,
    _ProxyHistogram,
    _ProxyObservableCounter,
    _ProxyObservableGauge,
    _ProxyObservableUpDownCounter,
    _ProxyUpDownCounter,
)
from mysql.opentelemetry.util._once import Once
from mysql.opentelemetry.util._providers import _load_provider

_logger = getLogger(__name__)


_ProxyInstrumentT = Union[
    _ProxyCounter,
    _ProxyHistogram,
    _ProxyObservableCounter,
    _ProxyObservableGauge,
    _ProxyObservableUpDownCounter,
    _ProxyUpDownCounter,
]


class MeterProvider(ABC):
    """
    MeterProvider is the entry point of the API. It provides access to `Meter` instances.
    """

    @abstractmethod
    def get_meter(
        self,
        name: str,
        version: Optional[str] = None,
        schema_url: Optional[str] = None,
    ) -> "Meter":
        """Returns a `Meter` for use by the given instrumentation library.

        For any two calls it is undefined whether the same or different
        `Meter` instances are returned, even for different library names.

        This function may return different `Meter` types (e.g. a no-op meter
        vs. a functional meter).

        Args:
            name: The name of the instrumenting module.
                ``__name__`` may not be used as this can result in
                different meter names if the meters are in different files.
                It is better to use a fixed string that can be imported where
                needed and used consistently as the name of the meter.

                This should *not* be the name of the module that is
                instrumented but the name of the module doing the instrumentation.
                E.g., instead of ``"requests"``, use
                ``"mysql.opentelemetry.instrumentation.requests"``.

            version: Optional. The version string of the
                instrumenting library.  Usually this should be the same as
                ``importlib.metadata.version(instrumenting_library_name)``.

            schema_url: Optional. Specifies the Schema URL of the emitted telemetry.
        """


class NoOpMeterProvider(MeterProvider):
    """The default MeterProvider used when no MeterProvider implementation is available."""

    def get_meter(
        self,
        name: str,
        version: Optional[str] = None,
        schema_url: Optional[str] = None,
    ) -> "Meter":
        """Returns a NoOpMeter."""
        super().get_meter(name, version=version, schema_url=schema_url)
        return NoOpMeter(name, version=version, schema_url=schema_url)


class _ProxyMeterProvider(MeterProvider):
    def __init__(self) -> None:
        self._lock = Lock()
        self._meters: List[_ProxyMeter] = []
        self._real_meter_provider: Optional[MeterProvider] = None

    def get_meter(
        self,
        name: str,
        version: Optional[str] = None,
        schema_url: Optional[str] = None,
    ) -> "Meter":
        with self._lock:
            if self._real_meter_provider is not None:
                return self._real_meter_provider.get_meter(name, version, schema_url)

            meter = _ProxyMeter(name, version=version, schema_url=schema_url)
            self._meters.append(meter)
            return meter

    def on_set_meter_provider(self, meter_provider: MeterProvider) -> None:
        with self._lock:
            self._real_meter_provider = meter_provider
            for meter in self._meters:
                meter.on_set_meter_provider(meter_provider)


class Meter(ABC):
    """Handles instrument creation.

    This class provides methods for creating instruments which are then
    used to produce measurements.
    """

    def __init__(
        self,
        name: str,
        version: Optional[str] = None,
        schema_url: Optional[str] = None,
    ) -> None:
        super().__init__()
        self._name = name
        self._version = version
        self._schema_url = schema_url
        self._instrument_ids: Set[str] = set()
        self._instrument_ids_lock = Lock()

    @property
    def name(self) -> str:
        """
        The name of the instrumenting module.
        """
        return self._name

    @property
    def version(self) -> Optional[str]:
        """
        The version string of the instrumenting library.
        """
        return self._version

    @property
    def schema_url(self) -> Optional[str]:
        """
        Specifies the Schema URL of the emitted telemetry
        """
        return self._schema_url

    def _is_instrument_registered(
        self, name: str, type_: type, unit: str, description: str
    ) -> Tuple[bool, str]:
        """
        Check if an instrument with the same name, type, unit and description
        has been registered already.

        Returns a tuple. The first value is `True` if the instrument has been
        registered already, `False` otherwise. The second value is the
        instrument id.
        """

        instrument_id = ",".join(
            [name.strip().lower(), type_.__name__, unit, description]
        )

        result = False

        with self._instrument_ids_lock:
            if instrument_id in self._instrument_ids:
                result = True
            else:
                self._instrument_ids.add(instrument_id)

        return (result, instrument_id)

    @abstractmethod
    def create_counter(
        self,
        name: str,
        unit: str = "",
        description: str = "",
    ) -> Counter:
        """Creates a `Counter` instrument

        Args:
            name: The name of the instrument to be created
            unit: The unit for observations this instrument reports. For
                example, ``By`` for bytes. UCUM units are recommended.
            description: A description for this instrument and what it measures.
        """

    @abstractmethod
    def create_up_down_counter(
        self,
        name: str,
        unit: str = "",
        description: str = "",
    ) -> UpDownCounter:
        """Creates an `UpDownCounter` instrument

        Args:
            name: The name of the instrument to be created
            unit: The unit for observations this instrument reports. For
                example, ``By`` for bytes. UCUM units are recommended.
            description: A description for this instrument and what it measures.
        """

    @abstractmethod
    def create_observable_counter(
        self,
        name: str,
        callbacks: Optional[Sequence[CallbackT]] = None,
        unit: str = "",
        description: str = "",
    ) -> ObservableCounter:
        """Creates an `ObservableCounter` instrument

        An observable counter observes a monotonically increasing count by calling provided
        callbacks which accept a :class:`~mysql.opentelemetry.metrics.CallbackOptions` and return
        multiple :class:`~mysql.opentelemetry.metrics.Observation`.

        For example, an observable counter could be used to report system CPU
        time periodically. Here is a basic implementation::

            def cpu_time_callback(options: CallbackOptions) -> Iterable[Observation]:
                observations = []
                with open("/proc/stat") as procstat:
                    procstat.readline()  # skip the first line
                    for line in procstat:
                        if not line.startswith("cpu"): break
                        cpu, *states = line.split()
                        observations.append(Observation(int(states[0]) // 100, {"cpu": cpu, "state": "user"}))
                        observations.append(Observation(int(states[1]) // 100, {"cpu": cpu, "state": "nice"}))
                        observations.append(Observation(int(states[2]) // 100, {"cpu": cpu, "state": "system"}))
                        # ... other states
                return observations

            meter.create_observable_counter(
                "system.cpu.time",
                callbacks=[cpu_time_callback],
                unit="s",
                description="CPU time"
            )

        To reduce memory usage, you can use generator callbacks instead of
        building the full list::

            def cpu_time_callback(options: CallbackOptions) -> Iterable[Observation]:
                with open("/proc/stat") as procstat:
                    procstat.readline()  # skip the first line
                    for line in procstat:
                        if not line.startswith("cpu"): break
                        cpu, *states = line.split()
                        yield Observation(int(states[0]) // 100, {"cpu": cpu, "state": "user"})
                        yield Observation(int(states[1]) // 100, {"cpu": cpu, "state": "nice"})
                        # ... other states

        Alternatively, you can pass a sequence of generators directly instead of a sequence of
        callbacks, which each should return iterables of :class:`~mysql.opentelemetry.metrics.Observation`::

            def cpu_time_callback(states_to_include: set[str]) -> Iterable[Iterable[Observation]]:
                # accept options sent in from OpenTelemetry
                options = yield
                while True:
                    observations = []
                    with open("/proc/stat") as procstat:
                        procstat.readline()  # skip the first line
                        for line in procstat:
                            if not line.startswith("cpu"): break
                            cpu, *states = line.split()
                            if "user" in states_to_include:
                                observations.append(Observation(int(states[0]) // 100, {"cpu": cpu, "state": "user"}))
                            if "nice" in states_to_include:
                                observations.append(Observation(int(states[1]) // 100, {"cpu": cpu, "state": "nice"}))
                            # ... other states
                    # yield the observations and receive the options for next iteration
                    options = yield observations

            meter.create_observable_counter(
                "system.cpu.time",
                callbacks=[cpu_time_callback({"user", "system"})],
                unit="s",
                description="CPU time"
            )

        The :class:`~mysql.opentelemetry.metrics.CallbackOptions` contain a timeout which the
        callback should respect. For example if the callback does asynchronous work, like
        making HTTP requests, it should respect the timeout::

            def scrape_http_callback(options: CallbackOptions) -> Iterable[Observation]:
                r = requests.get('http://scrapethis.com', timeout=options.timeout_millis / 10**3)
                for value in r.json():
                    yield Observation(value)

        Args:
            name: The name of the instrument to be created
            callbacks: A sequence of callbacks that return an iterable of
                :class:`~mysql.opentelemetry.metrics.Observation`. Alternatively, can be a sequence of generators that each
                yields iterables of :class:`~mysql.opentelemetry.metrics.Observation`.
            unit: The unit for observations this instrument reports. For
                example, ``By`` for bytes. UCUM units are recommended.
            description: A description for this instrument and what it measures.
        """

    @abstractmethod
    def create_histogram(
        self,
        name: str,
        unit: str = "",
        description: str = "",
    ) -> Histogram:
        """Creates a :class:`~mysql.opentelemetry.metrics.Histogram` instrument

        Args:
            name: The name of the instrument to be created
            unit: The unit for observations this instrument reports. For
                example, ``By`` for bytes. UCUM units are recommended.
            description: A description for this instrument and what it measures.
        """

    @abstractmethod
    def create_observable_gauge(
        self,
        name: str,
        callbacks: Optional[Sequence[CallbackT]] = None,
        unit: str = "",
        description: str = "",
    ) -> ObservableGauge:
        """Creates an `ObservableGauge` instrument

        Args:
            name: The name of the instrument to be created
            callbacks: A sequence of callbacks that return an iterable of
                :class:`~mysql.opentelemetry.metrics.Observation`. Alternatively, can be a generator that yields iterables
                of :class:`~mysql.opentelemetry.metrics.Observation`.
            unit: The unit for observations this instrument reports. For
                example, ``By`` for bytes. UCUM units are recommended.
            description: A description for this instrument and what it measures.
        """

    @abstractmethod
    def create_observable_up_down_counter(
        self,
        name: str,
        callbacks: Optional[Sequence[CallbackT]] = None,
        unit: str = "",
        description: str = "",
    ) -> ObservableUpDownCounter:
        """Creates an `ObservableUpDownCounter` instrument

        Args:
            name: The name of the instrument to be created
            callbacks: A sequence of callbacks that return an iterable of
                :class:`~mysql.opentelemetry.metrics.Observation`. Alternatively, can be a generator that yields iterables
                of :class:`~mysql.opentelemetry.metrics.Observation`.
            unit: The unit for observations this instrument reports. For
                example, ``By`` for bytes. UCUM units are recommended.
            description: A description for this instrument and what it measures.
        """


class _ProxyMeter(Meter):
    def __init__(
        self,
        name: str,
        version: Optional[str] = None,
        schema_url: Optional[str] = None,
    ) -> None:
        super().__init__(name, version=version, schema_url=schema_url)
        self._lock = Lock()
        self._instruments: List[_ProxyInstrumentT] = []
        self._real_meter: Optional[Meter] = None

    def on_set_meter_provider(self, meter_provider: MeterProvider) -> None:
        """Called when a real meter provider is set on the creating _ProxyMeterProvider

        Creates a real backing meter for this instance and notifies all created
        instruments so they can create real backing instruments.
        """
        real_meter = meter_provider.get_meter(
            self._name, self._version, self._schema_url
        )

        with self._lock:
            self._real_meter = real_meter
            # notify all proxy instruments of the new meter so they can create
            # real instruments to back themselves
            for instrument in self._instruments:
                instrument.on_meter_set(real_meter)

    def create_counter(
        self,
        name: str,
        unit: str = "",
        description: str = "",
    ) -> Counter:
        with self._lock:
            if self._real_meter:
                return self._real_meter.create_counter(name, unit, description)
            proxy = _ProxyCounter(name, unit, description)
            self._instruments.append(proxy)
            return proxy

    def create_up_down_counter(
        self,
        name: str,
        unit: str = "",
        description: str = "",
    ) -> UpDownCounter:
        with self._lock:
            if self._real_meter:
                return self._real_meter.create_up_down_counter(name, unit, description)
            proxy = _ProxyUpDownCounter(name, unit, description)
            self._instruments.append(proxy)
            return proxy

    def create_observable_counter(
        self,
        name: str,
        callbacks: Optional[Sequence[CallbackT]] = None,
        unit: str = "",
        description: str = "",
    ) -> ObservableCounter:
        with self._lock:
            if self._real_meter:
                return self._real_meter.create_observable_counter(
                    name, callbacks, unit, description
                )
            proxy = _ProxyObservableCounter(
                name, callbacks, unit=unit, description=description
            )
            self._instruments.append(proxy)
            return proxy

    def create_histogram(
        self,
        name: str,
        unit: str = "",
        description: str = "",
    ) -> Histogram:
        with self._lock:
            if self._real_meter:
                return self._real_meter.create_histogram(name, unit, description)
            proxy = _ProxyHistogram(name, unit, description)
            self._instruments.append(proxy)
            return proxy

    def create_observable_gauge(
        self,
        name: str,
        callbacks: Optional[Sequence[CallbackT]] = None,
        unit: str = "",
        description: str = "",
    ) -> ObservableGauge:
        with self._lock:
            if self._real_meter:
                return self._real_meter.create_observable_gauge(
                    name, callbacks, unit, description
                )
            proxy = _ProxyObservableGauge(
                name, callbacks, unit=unit, description=description
            )
            self._instruments.append(proxy)
            return proxy

    def create_observable_up_down_counter(
        self,
        name: str,
        callbacks: Optional[Sequence[CallbackT]] = None,
        unit: str = "",
        description: str = "",
    ) -> ObservableUpDownCounter:
        with self._lock:
            if self._real_meter:
                return self._real_meter.create_observable_up_down_counter(
                    name,
                    callbacks,
                    unit,
                    description,
                )
            proxy = _ProxyObservableUpDownCounter(
                name, callbacks, unit=unit, description=description
            )
            self._instruments.append(proxy)
            return proxy


class NoOpMeter(Meter):
    """The default Meter used when no Meter implementation is available.

    All operations are no-op.
    """

    def create_counter(
        self,
        name: str,
        unit: str = "",
        description: str = "",
    ) -> Counter:
        """Returns a no-op Counter."""
        super().create_counter(name, unit=unit, description=description)
        if self._is_instrument_registered(name, NoOpCounter, unit, description)[0]:
            _logger.warning(
                "An instrument with name %s, type %s, unit %s and "
                "description %s has been created already.",
                name,
                Counter.__name__,
                unit,
                description,
            )
        return NoOpCounter(name, unit=unit, description=description)

    def create_up_down_counter(
        self,
        name: str,
        unit: str = "",
        description: str = "",
    ) -> UpDownCounter:
        """Returns a no-op UpDownCounter."""
        super().create_up_down_counter(name, unit=unit, description=description)
        if self._is_instrument_registered(name, NoOpUpDownCounter, unit, description)[
            0
        ]:
            _logger.warning(
                "An instrument with name %s, type %s, unit %s and "
                "description %s has been created already.",
                name,
                UpDownCounter.__name__,
                unit,
                description,
            )
        return NoOpUpDownCounter(name, unit=unit, description=description)

    def create_observable_counter(
        self,
        name: str,
        callbacks: Optional[Sequence[CallbackT]] = None,
        unit: str = "",
        description: str = "",
    ) -> ObservableCounter:
        """Returns a no-op ObservableCounter."""
        super().create_observable_counter(
            name, callbacks, unit=unit, description=description
        )
        if self._is_instrument_registered(
            name, NoOpObservableCounter, unit, description
        )[0]:
            _logger.warning(
                "An instrument with name %s, type %s, unit %s and "
                "description %s has been created already.",
                name,
                ObservableCounter.__name__,
                unit,
                description,
            )
        return NoOpObservableCounter(
            name,
            callbacks,
            unit=unit,
            description=description,
        )

    def create_histogram(
        self,
        name: str,
        unit: str = "",
        description: str = "",
    ) -> Histogram:
        """Returns a no-op Histogram."""
        super().create_histogram(name, unit=unit, description=description)
        if self._is_instrument_registered(name, NoOpHistogram, unit, description)[0]:
            _logger.warning(
                "An instrument with name %s, type %s, unit %s and "
                "description %s has been created already.",
                name,
                Histogram.__name__,
                unit,
                description,
            )
        return NoOpHistogram(name, unit=unit, description=description)

    def create_observable_gauge(
        self,
        name: str,
        callbacks: Optional[Sequence[CallbackT]] = None,
        unit: str = "",
        description: str = "",
    ) -> ObservableGauge:
        """Returns a no-op ObservableGauge."""
        super().create_observable_gauge(
            name, callbacks, unit=unit, description=description
        )
        if self._is_instrument_registered(name, NoOpObservableGauge, unit, description)[
            0
        ]:
            _logger.warning(
                "An instrument with name %s, type %s, unit %s and "
                "description %s has been created already.",
                name,
                ObservableGauge.__name__,
                unit,
                description,
            )
        return NoOpObservableGauge(
            name,
            callbacks,
            unit=unit,
            description=description,
        )

    def create_observable_up_down_counter(
        self,
        name: str,
        callbacks: Optional[Sequence[CallbackT]] = None,
        unit: str = "",
        description: str = "",
    ) -> ObservableUpDownCounter:
        """Returns a no-op ObservableUpDownCounter."""
        super().create_observable_up_down_counter(
            name, callbacks, unit=unit, description=description
        )
        if self._is_instrument_registered(
            name, NoOpObservableUpDownCounter, unit, description
        )[0]:
            _logger.warning(
                "An instrument with name %s, type %s, unit %s and "
                "description %s has been created already.",
                name,
                ObservableUpDownCounter.__name__,
                unit,
                description,
            )
        return NoOpObservableUpDownCounter(
            name,
            callbacks,
            unit=unit,
            description=description,
        )


_METER_PROVIDER_SET_ONCE = Once()
_METER_PROVIDER: Optional[MeterProvider] = None
_PROXY_METER_PROVIDER = _ProxyMeterProvider()


def get_meter(
    name: str,
    version: str = "",
    meter_provider: Optional[MeterProvider] = None,
) -> "Meter":
    """Returns a `Meter` for use by the given instrumentation library.

    This function is a convenience wrapper for
    `mysql.opentelemetry.metrics.MeterProvider.get_meter`.

    If meter_provider is omitted the current configured one is used.
    """
    if meter_provider is None:
        meter_provider = get_meter_provider()
    return meter_provider.get_meter(name, version)


def _set_meter_provider(meter_provider: MeterProvider, log: bool) -> None:
    def set_mp() -> None:
        global _METER_PROVIDER  # pylint: disable=global-statement
        _METER_PROVIDER = meter_provider

        # gives all proxies real instruments off the newly set meter provider
        _PROXY_METER_PROVIDER.on_set_meter_provider(meter_provider)

    did_set = _METER_PROVIDER_SET_ONCE.do_once(set_mp)

    if log and not did_set:
        _logger.warning("Overriding of current MeterProvider is not allowed")


def set_meter_provider(meter_provider: MeterProvider) -> None:
    """Sets the current global :class:`~.MeterProvider` object.

    This can only be done once, a warning will be logged if any further attempt
    is made.
    """
    _set_meter_provider(meter_provider, log=True)


def get_meter_provider() -> MeterProvider:
    """Gets the current global :class:`~.MeterProvider` object."""

    if _METER_PROVIDER is None:
        if OTEL_PYTHON_METER_PROVIDER not in environ.keys():
            return _PROXY_METER_PROVIDER

        meter_provider: MeterProvider = _load_provider(  # type: ignore
            OTEL_PYTHON_METER_PROVIDER, "meter_provider"
        )
        _set_meter_provider(meter_provider, log=False)

    # _METER_PROVIDER will have been set by one thread
    return cast("MeterProvider", _METER_PROVIDER)

virt/lib/python3.9/site-packages/mysql/opentelemetry/metrics/_internal/__init__ 2.py
PrinceDisant/LostInNL
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# pylint: disable=too-many-ancestors

"""
The OpenTelemetry metrics API  describes the classes used to generate
metrics.

The :class:`.MeterProvider` provides users access to the :class:`.Meter` which in
turn is used to create :class:`.Instrument` objects. The :class:`.Instrument` objects are
used to record measurements.

This module provides abstract (i.e. unimplemented) classes required for
metrics, and a concrete no-op implementation :class:`.NoOpMeter` that allows applications
to use the API package alone without a supporting implementation.

To get a meter, you need to provide the package name from which you are
calling the meter APIs to OpenTelemetry by calling `MeterProvider.get_meter`
with the calling instrumentation name and the version of your package.

The following code shows how to obtain a meter using the global :class:`.MeterProvider`::

    from mysql.opentelemetry.metrics import get_meter

    meter = get_meter("example-meter")
    counter = meter.create_counter("example-counter")

.. versionadded:: 1.10.0
"""


from abc import ABC, abstractmethod
from logging import getLogger
from os import environ
from threading import Lock
from typing import List, Optional, Sequence, Set, Tuple, Union, cast

from mysql.opentelemetry.environment_variables import OTEL_PYTHON_METER_PROVIDER
from mysql.opentelemetry.metrics._internal.instrument import (
    CallbackT,
    Counter,
    Histogram,
    NoOpCounter,
    NoOpHistogram,
    NoOpObservableCounter,
    NoOpObservableGauge,
    NoOpObservableUpDownCounter,
    NoOpUpDownCounter,
    ObservableCounter,
    ObservableGauge,
    ObservableUpDownCounter,
    UpDownCounter,
    _ProxyCounter,
    _ProxyHistogram,
    _ProxyObservableCounter,
    _ProxyObservableGauge,
    _ProxyObservableUpDownCounter,
    _ProxyUpDownCounter,
)
from mysql.opentelemetry.util._once import Once
from mysql.opentelemetry.util._providers import _load_provider

_logger = getLogger(__name__)


_ProxyInstrumentT = Union[
    _ProxyCounter,
    _ProxyHistogram,
    _ProxyObservableCounter,
    _ProxyObservableGauge,
    _ProxyObservableUpDownCounter,
    _ProxyUpDownCounter,
]


class MeterProvider(ABC):
    """
    MeterProvider is the entry point of the API. It provides access to `Meter` instances.
    """

    @abstractmethod
    def get_meter(
        self,
        name: str,
        version: Optional[str] = None,
        schema_url: Optional[str] = None,
    ) -> "Meter":
        """Returns a `Meter` for use by the given instrumentation library.

        For any two calls it is undefined whether the same or different
        `Meter` instances are returned, even for different library names.

        This function may return different `Meter` types (e.g. a no-op meter
        vs. a functional meter).

        Args:
            name: The name of the instrumenting module.
                ``__name__`` may not be used as this can result in
                different meter names if the meters are in different files.
                It is better to use a fixed string that can be imported where
                needed and used consistently as the name of the meter.

                This should *not* be the name of the module that is
                instrumented but the name of the module doing the instrumentation.
                E.g., instead of ``"requests"``, use
                ``"mysql.opentelemetry.instrumentation.requests"``.

            version: Optional. The version string of the
                instrumenting library.  Usually this should be the same as
                ``importlib.metadata.version(instrumenting_library_name)``.

            schema_url: Optional. Specifies the Schema URL of the emitted telemetry.
        """


class NoOpMeterProvider(MeterProvider):
    """The default MeterProvider used when no MeterProvider implementation is available."""

    def get_meter(
        self,
        name: str,
        version: Optional[str] = None,
        schema_url: Optional[str] = None,
    ) -> "Meter":
        """Returns a NoOpMeter."""
        super().get_meter(name, version=version, schema_url=schema_url)
        return NoOpMeter(name, version=version, schema_url=schema_url)


class _ProxyMeterProvider(MeterProvider):
    def __init__(self) -> None:
        self._lock = Lock()
        self._meters: List[_ProxyMeter] = []
        self._real_meter_provider: Optional[MeterProvider] = None

    def get_meter(
        self,
        name: str,
        version: Optional[str] = None,
        schema_url: Optional[str] = None,
    ) -> "Meter":
        with self._lock:
            if self._real_meter_provider is not None:
                return self._real_meter_provider.get_meter(name, version, schema_url)

            meter = _ProxyMeter(name, version=version, schema_url=schema_url)
            self._meters.append(meter)
            return meter

    def on_set_meter_provider(self, meter_provider: MeterProvider) -> None:
        with self._lock:
            self._real_meter_provider = meter_provider
            for meter in self._meters:
                meter.on_set_meter_provider(meter_provider)


class Meter(ABC):
    """Handles instrument creation.

    This class provides methods for creating instruments which are then
    used to produce measurements.
    """

    def __init__(
        self,
        name: str,
        version: Optional[str] = None,
        schema_url: Optional[str] = None,
    ) -> None:
        super().__init__()
        self._name = name
        self._version = version
        self._schema_url = schema_url
        self._instrument_ids: Set[str] = set()
        self._instrument_ids_lock = Lock()

    @property
    def name(self) -> str:
        """
        The name of the instrumenting module.
        """
        return self._name

    @property
    def version(self) -> Optional[str]:
        """
        The version string of the instrumenting library.
        """
        return self._version

    @property
    def schema_url(self) -> Optional[str]:
        """
        Specifies the Schema URL of the emitted telemetry
        """
        return self._schema_url

    def _is_instrument_registered(
        self, name: str, type_: type, unit: str, description: str
    ) -> Tuple[bool, str]:
        """
        Check if an instrument with the same name, type, unit and description
        has been registered already.

        Returns a tuple. The first value is `True` if the instrument has been
        registered already, `False` otherwise. The second value is the
        instrument id.
        """

        instrument_id = ",".join(
            [name.strip().lower(), type_.__name__, unit, description]
        )

        result = False

        with self._instrument_ids_lock:
            if instrument_id in self._instrument_ids:
                result = True
            else:
                self._instrument_ids.add(instrument_id)

        return (result, instrument_id)

    @abstractmethod
    def create_counter(
        self,
        name: str,
        unit: str = "",
        description: str = "",
    ) -> Counter:
        """Creates a `Counter` instrument

        Args:
            name: The name of the instrument to be created
            unit: The unit for observations this instrument reports. For
                example, ``By`` for bytes. UCUM units are recommended.
            description: A description for this instrument and what it measures.
        """

    @abstractmethod
    def create_up_down_counter(
        self,
        name: str,
        unit: str = "",
        description: str = "",
    ) -> UpDownCounter:
        """Creates an `UpDownCounter` instrument

        Args:
            name: The name of the instrument to be created
            unit: The unit for observations this instrument reports. For
                example, ``By`` for bytes. UCUM units are recommended.
            description: A description for this instrument and what it measures.
        """

    @abstractmethod
    def create_observable_counter(
        self,
        name: str,
        callbacks: Optional[Sequence[CallbackT]] = None,
        unit: str = "",
        description: str = "",
    ) -> ObservableCounter:
        """Creates an `ObservableCounter` instrument

        An observable counter observes a monotonically increasing count by calling provided
        callbacks which accept a :class:`~mysql.opentelemetry.metrics.CallbackOptions` and return
        multiple :class:`~mysql.opentelemetry.metrics.Observation`.

        For example, an observable counter could be used to report system CPU
        time periodically. Here is a basic implementation::

            def cpu_time_callback(options: CallbackOptions) -> Iterable[Observation]:
                observations = []
                with open("/proc/stat") as procstat:
                    procstat.readline()  # skip the first line
                    for line in procstat:
                        if not line.startswith("cpu"): break
                        cpu, *states = line.split()
                        observations.append(Observation(int(states[0]) // 100, {"cpu": cpu, "state": "user"}))
                        observations.append(Observation(int(states[1]) // 100, {"cpu": cpu, "state": "nice"}))
                        observations.append(Observation(int(states[2]) // 100, {"cpu": cpu, "state": "system"}))
                        # ... other states
                return observations

            meter.create_observable_counter(
                "system.cpu.time",
                callbacks=[cpu_time_callback],
                unit="s",
                description="CPU time"
            )

        To reduce memory usage, you can use generator callbacks instead of
        building the full list::

            def cpu_time_callback(options: CallbackOptions) -> Iterable[Observation]:
                with open("/proc/stat") as procstat:
                    procstat.readline()  # skip the first line
                    for line in procstat:
                        if not line.startswith("cpu"): break
                        cpu, *states = line.split()
                        yield Observation(int(states[0]) // 100, {"cpu": cpu, "state": "user"})
                        yield Observation(int(states[1]) // 100, {"cpu": cpu, "state": "nice"})
                        # ... other states

        Alternatively, you can pass a sequence of generators directly instead of a sequence of
        callbacks, which each should return iterables of :class:`~mysql.opentelemetry.metrics.Observation`::

            def cpu_time_callback(states_to_include: set[str]) -> Iterable[Iterable[Observation]]:
                # accept options sent in from OpenTelemetry
                options = yield
                while True:
                    observations = []
                    with open("/proc/stat") as procstat:
                        procstat.readline()  # skip the first line
                        for line in procstat:
                            if not line.startswith("cpu"): break
                            cpu, *states = line.split()
                            if "user" in states_to_include:
                                observations.append(Observation(int(states[0]) // 100, {"cpu": cpu, "state": "user"}))
                            if "nice" in states_to_include:
                                observations.append(Observation(int(states[1]) // 100, {"cpu": cpu, "state": "nice"}))
                            # ... other states
                    # yield the observations and receive the options for next iteration
                    options = yield observations

            meter.create_observable_counter(
                "system.cpu.time",
                callbacks=[cpu_time_callback({"user", "system"})],
                unit="s",
                description="CPU time"
            )

        The :class:`~mysql.opentelemetry.metrics.CallbackOptions` contain a timeout which the
        callback should respect. For example if the callback does asynchronous work, like
        making HTTP requests, it should respect the timeout::

            def scrape_http_callback(options: CallbackOptions) -> Iterable[Observation]:
                r = requests.get('http://scrapethis.com', timeout=options.timeout_millis / 10**3)
                for value in r.json():
                    yield Observation(value)

        Args:
            name: The name of the instrument to be created
            callbacks: A sequence of callbacks that return an iterable of
                :class:`~mysql.opentelemetry.metrics.Observation`. Alternatively, can be a sequence of generators that each
                yields iterables of :class:`~mysql.opentelemetry.metrics.Observation`.
            unit: The unit for observations this instrument reports. For
                example, ``By`` for bytes. UCUM units are recommended.
            description: A description for this instrument and what it measures.
        """

    @abstractmethod
    def create_histogram(
        self,
        name: str,
        unit: str = "",
        description: str = "",
    ) -> Histogram:
        """Creates a :class:`~mysql.opentelemetry.metrics.Histogram` instrument

        Args:
            name: The name of the instrument to be created
            unit: The unit for observations this instrument reports. For
                example, ``By`` for bytes. UCUM units are recommended.
            description: A description for this instrument and what it measures.
        """

    @abstractmethod
    def create_observable_gauge(
        self,
        name: str,
        callbacks: Optional[Sequence[CallbackT]] = None,
        unit: str = "",
        description: str = "",
    ) -> ObservableGauge:
        """Creates an `ObservableGauge` instrument

        Args:
            name: The name of the instrument to be created
            callbacks: A sequence of callbacks that return an iterable of
                :class:`~mysql.opentelemetry.metrics.Observation`. Alternatively, can be a generator that yields iterables
                of :class:`~mysql.opentelemetry.metrics.Observation`.
            unit: The unit for observations this instrument reports. For
                example, ``By`` for bytes. UCUM units are recommended.
            description: A description for this instrument and what it measures.
        """

    @abstractmethod
    def create_observable_up_down_counter(
        self,
        name: str,
        callbacks: Optional[Sequence[CallbackT]] = None,
        unit: str = "",
        description: str = "",
    ) -> ObservableUpDownCounter:
        """Creates an `ObservableUpDownCounter` instrument

        Args:
            name: The name of the instrument to be created
            callbacks: A sequence of callbacks that return an iterable of
                :class:`~mysql.opentelemetry.metrics.Observation`. Alternatively, can be a generator that yields iterables
                of :class:`~mysql.opentelemetry.metrics.Observation`.
            unit: The unit for observations this instrument reports. For
                example, ``By`` for bytes. UCUM units are recommended.
            description: A description for this instrument and what it measures.
        """


class _ProxyMeter(Meter):
    def __init__(
        self,
        name: str,
        version: Optional[str] = None,
        schema_url: Optional[str] = None,
    ) -> None:
        super().__init__(name, version=version, schema_url=schema_url)
        self._lock = Lock()
        self._instruments: List[_ProxyInstrumentT] = []
        self._real_meter: Optional[Meter] = None

    def on_set_meter_provider(self, meter_provider: MeterProvider) -> None:
        """Called when a real meter provider is set on the creating _ProxyMeterProvider

        Creates a real backing meter for this instance and notifies all created
        instruments so they can create real backing instruments.
        """
        real_meter = meter_provider.get_meter(
            self._name, self._version, self._schema_url
        )

        with self._lock:
            self._real_meter = real_meter
            # notify all proxy instruments of the new meter so they can create
            # real instruments to back themselves
            for instrument in self._instruments:
                instrument.on_meter_set(real_meter)

    def create_counter(
        self,
        name: str,
        unit: str = "",
        description: str = "",
    ) -> Counter:
        with self._lock:
            if self._real_meter:
                return self._real_meter.create_counter(name, unit, description)
            proxy = _ProxyCounter(name, unit, description)
            self._instruments.append(proxy)
            return proxy

    def create_up_down_counter(
        self,
        name: str,
        unit: str = "",
        description: str = "",
    ) -> UpDownCounter:
        with self._lock:
            if self._real_meter:
                return self._real_meter.create_up_down_counter(name, unit, description)
            proxy = _ProxyUpDownCounter(name, unit, description)
            self._instruments.append(proxy)
            return proxy

    def create_observable_counter(
        self,
        name: str,
        callbacks: Optional[Sequence[CallbackT]] = None,
        unit: str = "",
        description: str = "",
    ) -> ObservableCounter:
        with self._lock:
            if self._real_meter:
                return self._real_meter.create_observable_counter(
                    name, callbacks, unit, description
                )
            proxy = _ProxyObservableCounter(
                name, callbacks, unit=unit, description=description
            )
            self._instruments.append(proxy)
            return proxy

    def create_histogram(
        self,
        name: str,
        unit: str = "",
        description: str = "",
    ) -> Histogram:
        with self._lock:
            if self._real_meter:
                return self._real_meter.create_histogram(name, unit, description)
            proxy = _ProxyHistogram(name, unit, description)
            self._instruments.append(proxy)
            return proxy

    def create_observable_gauge(
        self,
        name: str,
        callbacks: Optional[Sequence[CallbackT]] = None,
        unit: str = "",
        description: str = "",
    ) -> ObservableGauge:
        with self._lock:
            if self._real_meter:
                return self._real_meter.create_observable_gauge(
                    name, callbacks, unit, description
                )
            proxy = _ProxyObservableGauge(
                name, callbacks, unit=unit, description=description
            )
            self._instruments.append(proxy)
            return proxy

    def create_observable_up_down_counter(
        self,
        name: str,
        callbacks: Optional[Sequence[CallbackT]] = None,
        unit: str = "",
        description: str = "",
    ) -> ObservableUpDownCounter:
        with self._lock:
            if self._real_meter:
                return self._real_meter.create_observable_up_down_counter(
                    name,
                    callbacks,
                    unit,
                    description,
                )
            proxy = _ProxyObservableUpDownCounter(
                name, callbacks, unit=unit, description=description
            )
            self._instruments.append(proxy)
            return proxy


class NoOpMeter(Meter):
    """The default Meter used when no Meter implementation is available.

    All operations are no-op.
    """

    def create_counter(
        self,
        name: str,
        unit: str = "",
        description: str = "",
    ) -> Counter:
        """Returns a no-op Counter."""
        super().create_counter(name, unit=unit, description=description)
        if self._is_instrument_registered(name, NoOpCounter, unit, description)[0]:
            _logger.warning(
                "An instrument with name %s, type %s, unit %s and "
                "description %s has been created already.",
                name,
                Counter.__name__,
                unit,
                description,
            )
        return NoOpCounter(name, unit=unit, description=description)

    def create_up_down_counter(
        self,
        name: str,
        unit: str = "",
        description: str = "",
    ) -> UpDownCounter:
        """Returns a no-op UpDownCounter."""
        super().create_up_down_counter(name, unit=unit, description=description)
        if self._is_instrument_registered(name, NoOpUpDownCounter, unit, description)[
            0
        ]:
            _logger.warning(
                "An instrument with name %s, type %s, unit %s and "
                "description %s has been created already.",
                name,
                UpDownCounter.__name__,
                unit,
                description,
            )
        return NoOpUpDownCounter(name, unit=unit, description=description)

    def create_observable_counter(
        self,
        name: str,
        callbacks: Optional[Sequence[CallbackT]] = None,
        unit: str = "",
        description: str = "",
    ) -> ObservableCounter:
        """Returns a no-op ObservableCounter."""
        super().create_observable_counter(
            name, callbacks, unit=unit, description=description
        )
        if self._is_instrument_registered(
            name, NoOpObservableCounter, unit, description
        )[0]:
            _logger.warning(
                "An instrument with name %s, type %s, unit %s and "
                "description %s has been created already.",
                name,
                ObservableCounter.__name__,
                unit,
                description,
            )
        return NoOpObservableCounter(
            name,
            callbacks,
            unit=unit,
            description=description,
        )

    def create_histogram(
        self,
        name: str,
        unit: str = "",
        description: str = "",
    ) -> Histogram:
        """Returns a no-op Histogram."""
        super().create_histogram(name, unit=unit, description=description)
        if self._is_instrument_registered(name, NoOpHistogram, unit, description)[0]:
            _logger.warning(
                "An instrument with name %s, type %s, unit %s and "
                "description %s has been created already.",
                name,
                Histogram.__name__,
                unit,
                description,
            )
        return NoOpHistogram(name, unit=unit, description=description)

    def create_observable_gauge(
        self,
        name: str,
        callbacks: Optional[Sequence[CallbackT]] = None,
        unit: str = "",
        description: str = "",
    ) -> ObservableGauge:
        """Returns a no-op ObservableGauge."""
        super().create_observable_gauge(
            name, callbacks, unit=unit, description=description
        )
        if self._is_instrument_registered(name, NoOpObservableGauge, unit, description)[
            0
        ]:
            _logger.warning(
                "An instrument with name %s, type %s, unit %s and "
                "description %s has been created already.",
                name,
                ObservableGauge.__name__,
                unit,
                description,
            )
        return NoOpObservableGauge(
            name,
            callbacks,
            unit=unit,
            description=description,
        )

    def create_observable_up_down_counter(
        self,
        name: str,
        callbacks: Optional[Sequence[CallbackT]] = None,
        unit: str = "",
        description: str = "",
    ) -> ObservableUpDownCounter:
        """Returns a no-op ObservableUpDownCounter."""
        super().create_observable_up_down_counter(
            name, callbacks, unit=unit, description=description
        )
        if self._is_instrument_registered(
            name, NoOpObservableUpDownCounter, unit, description
        )[0]:
            _logger.warning(
                "An instrument with name %s, type %s, unit %s and "
                "description %s has been created already.",
                name,
                ObservableUpDownCounter.__name__,
                unit,
                description,
            )
        return NoOpObservableUpDownCounter(
            name,
            callbacks,
            unit=unit,
            description=description,
        )


_METER_PROVIDER_SET_ONCE = Once()
_METER_PROVIDER: Optional[MeterProvider] = None
_PROXY_METER_PROVIDER = _ProxyMeterProvider()


def get_meter(
    name: str,
    version: str = "",
    meter_provider: Optional[MeterProvider] = None,
) -> "Meter":
    """Returns a `Meter` for use by the given instrumentation library.

    This function is a convenience wrapper for
    `mysql.opentelemetry.metrics.MeterProvider.get_meter`.

    If meter_provider is omitted the current configured one is used.
    """
    if meter_provider is None:
        meter_provider = get_meter_provider()
    return meter_provider.get_meter(name, version)


def _set_meter_provider(meter_provider: MeterProvider, log: bool) -> None:
    def set_mp() -> None:
        global _METER_PROVIDER  # pylint: disable=global-statement
        _METER_PROVIDER = meter_provider

        # gives all proxies real instruments off the newly set meter provider
        _PROXY_METER_PROVIDER.on_set_meter_provider(meter_provider)

    did_set = _METER_PROVIDER_SET_ONCE.do_once(set_mp)

    if log and not did_set:
        _logger.warning("Overriding of current MeterProvider is not allowed")


def set_meter_provider(meter_provider: MeterProvider) -> None:
    """Sets the current global :class:`~.MeterProvider` object.

    This can only be done once, a warning will be logged if any further attempt
    is made.
    """
    _set_meter_provider(meter_provider, log=True)


def get_meter_provider() -> MeterProvider:
    """Gets the current global :class:`~.MeterProvider` object."""

    if _METER_PROVIDER is None:
        if OTEL_PYTHON_METER_PROVIDER not in environ.keys():
            return _PROXY_METER_PROVIDER

        meter_provider: MeterProvider = _load_provider(  # type: ignore
            OTEL_PYTHON_METER_PROVIDER, "meter_provider"
        )
        _set_meter_provider(meter_provider, log=False)

    # _METER_PROVIDER will have been set by one thread
    return cast("MeterProvider", _METER_PROVIDER)

virt/lib/python3.9/site-packages/mysql/opentelemetry/metrics/_internal/__init__ 3.py
PrinceDisant/LostInNL
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# pylint: disable=too-many-ancestors

"""
The OpenTelemetry metrics API  describes the classes used to generate
metrics.

The :class:`.MeterProvider` provides users access to the :class:`.Meter` which in
turn is used to create :class:`.Instrument` objects. The :class:`.Instrument` objects are
used to record measurements.

This module provides abstract (i.e. unimplemented) classes required for
metrics, and a concrete no-op implementation :class:`.NoOpMeter` that allows applications
to use the API package alone without a supporting implementation.

To get a meter, you need to provide the package name from which you are
calling the meter APIs to OpenTelemetry by calling `MeterProvider.get_meter`
with the calling instrumentation name and the version of your package.

The following code shows how to obtain a meter using the global :class:`.MeterProvider`::

    from mysql.opentelemetry.metrics import get_meter

    meter = get_meter("example-meter")
    counter = meter.create_counter("example-counter")

.. versionadded:: 1.10.0
"""


from abc import ABC, abstractmethod
from logging import getLogger
from os import environ
from threading import Lock
from typing import List, Optional, Sequence, Set, Tuple, Union, cast

from mysql.opentelemetry.environment_variables import OTEL_PYTHON_METER_PROVIDER
from mysql.opentelemetry.metrics._internal.instrument import (
    CallbackT,
    Counter,
    Histogram,
    NoOpCounter,
    NoOpHistogram,
    NoOpObservableCounter,
    NoOpObservableGauge,
    NoOpObservableUpDownCounter,
    NoOpUpDownCounter,
    ObservableCounter,
    ObservableGauge,
    ObservableUpDownCounter,
    UpDownCounter,
    _ProxyCounter,
    _ProxyHistogram,
    _ProxyObservableCounter,
    _ProxyObservableGauge,
    _ProxyObservableUpDownCounter,
    _ProxyUpDownCounter,
)
from mysql.opentelemetry.util._once import Once
from mysql.opentelemetry.util._providers import _load_provider

_logger = getLogger(__name__)


_ProxyInstrumentT = Union[
    _ProxyCounter,
    _ProxyHistogram,
    _ProxyObservableCounter,
    _ProxyObservableGauge,
    _ProxyObservableUpDownCounter,
    _ProxyUpDownCounter,
]


class MeterProvider(ABC):
    """
    MeterProvider is the entry point of the API. It provides access to `Meter` instances.
    """

    @abstractmethod
    def get_meter(
        self,
        name: str,
        version: Optional[str] = None,
        schema_url: Optional[str] = None,
    ) -> "Meter":
        """Returns a `Meter` for use by the given instrumentation library.

        For any two calls it is undefined whether the same or different
        `Meter` instances are returned, even for different library names.

        This function may return different `Meter` types (e.g. a no-op meter
        vs. a functional meter).

        Args:
            name: The name of the instrumenting module.
                ``__name__`` may not be used as this can result in
                different meter names if the meters are in different files.
                It is better to use a fixed string that can be imported where
                needed and used consistently as the name of the meter.

                This should *not* be the name of the module that is
                instrumented but the name of the module doing the instrumentation.
                E.g., instead of ``"requests"``, use
                ``"mysql.opentelemetry.instrumentation.requests"``.

            version: Optional. The version string of the
                instrumenting library.  Usually this should be the same as
                ``importlib.metadata.version(instrumenting_library_name)``.

            schema_url: Optional. Specifies the Schema URL of the emitted telemetry.
        """


class NoOpMeterProvider(MeterProvider):
    """The default MeterProvider used when no MeterProvider implementation is available."""

    def get_meter(
        self,
        name: str,
        version: Optional[str] = None,
        schema_url: Optional[str] = None,
    ) -> "Meter":
        """Returns a NoOpMeter."""
        super().get_meter(name, version=version, schema_url=schema_url)
        return NoOpMeter(name, version=version, schema_url=schema_url)


class _ProxyMeterProvider(MeterProvider):
    def __init__(self) -> None:
        self._lock = Lock()
        self._meters: List[_ProxyMeter] = []
        self._real_meter_provider: Optional[MeterProvider] = None

    def get_meter(
        self,
        name: str,
        version: Optional[str] = None,
        schema_url: Optional[str] = None,
    ) -> "Meter":
        with self._lock:
            if self._real_meter_provider is not None:
                return self._real_meter_provider.get_meter(name, version, schema_url)

            meter = _ProxyMeter(name, version=version, schema_url=schema_url)
            self._meters.append(meter)
            return meter

    def on_set_meter_provider(self, meter_provider: MeterProvider) -> None:
        with self._lock:
            self._real_meter_provider = meter_provider
            for meter in self._meters:
                meter.on_set_meter_provider(meter_provider)


class Meter(ABC):
    """Handles instrument creation.

    This class provides methods for creating instruments which are then
    used to produce measurements.
    """

    def __init__(
        self,
        name: str,
        version: Optional[str] = None,
        schema_url: Optional[str] = None,
    ) -> None:
        super().__init__()
        self._name = name
        self._version = version
        self._schema_url = schema_url
        self._instrument_ids: Set[str] = set()
        self._instrument_ids_lock = Lock()

    @property
    def name(self) -> str:
        """
        The name of the instrumenting module.
        """
        return self._name

    @property
    def version(self) -> Optional[str]:
        """
        The version string of the instrumenting library.
        """
        return self._version

    @property
    def schema_url(self) -> Optional[str]:
        """
        Specifies the Schema URL of the emitted telemetry
        """
        return self._schema_url

    def _is_instrument_registered(
        self, name: str, type_: type, unit: str, description: str
    ) -> Tuple[bool, str]:
        """
        Check if an instrument with the same name, type, unit and description
        has been registered already.

        Returns a tuple. The first value is `True` if the instrument has been
        registered already, `False` otherwise. The second value is the
        instrument id.
        """

        instrument_id = ",".join(
            [name.strip().lower(), type_.__name__, unit, description]
        )

        result = False

        with self._instrument_ids_lock:
            if instrument_id in self._instrument_ids:
                result = True
            else:
                self._instrument_ids.add(instrument_id)

        return (result, instrument_id)

    @abstractmethod
    def create_counter(
        self,
        name: str,
        unit: str = "",
        description: str = "",
    ) -> Counter:
        """Creates a `Counter` instrument

        Args:
            name: The name of the instrument to be created
            unit: The unit for observations this instrument reports. For
                example, ``By`` for bytes. UCUM units are recommended.
            description: A description for this instrument and what it measures.
        """

    @abstractmethod
    def create_up_down_counter(
        self,
        name: str,
        unit: str = "",
        description: str = "",
    ) -> UpDownCounter:
        """Creates an `UpDownCounter` instrument

        Args:
            name: The name of the instrument to be created
            unit: The unit for observations this instrument reports. For
                example, ``By`` for bytes. UCUM units are recommended.
            description: A description for this instrument and what it measures.
        """

    @abstractmethod
    def create_observable_counter(
        self,
        name: str,
        callbacks: Optional[Sequence[CallbackT]] = None,
        unit: str = "",
        description: str = "",
    ) -> ObservableCounter:
        """Creates an `ObservableCounter` instrument

        An observable counter observes a monotonically increasing count by calling provided
        callbacks which accept a :class:`~mysql.opentelemetry.metrics.CallbackOptions` and return
        multiple :class:`~mysql.opentelemetry.metrics.Observation`.

        For example, an observable counter could be used to report system CPU
        time periodically. Here is a basic implementation::

            def cpu_time_callback(options: CallbackOptions) -> Iterable[Observation]:
                observations = []
                with open("/proc/stat") as procstat:
                    procstat.readline()  # skip the first line
                    for line in procstat:
                        if not line.startswith("cpu"): break
                        cpu, *states = line.split()
                        observations.append(Observation(int(states[0]) // 100, {"cpu": cpu, "state": "user"}))
                        observations.append(Observation(int(states[1]) // 100, {"cpu": cpu, "state": "nice"}))
                        observations.append(Observation(int(states[2]) // 100, {"cpu": cpu, "state": "system"}))
                        # ... other states
                return observations

            meter.create_observable_counter(
                "system.cpu.time",
                callbacks=[cpu_time_callback],
                unit="s",
                description="CPU time"
            )

        To reduce memory usage, you can use generator callbacks instead of
        building the full list::

            def cpu_time_callback(options: CallbackOptions) -> Iterable[Observation]:
                with open("/proc/stat") as procstat:
                    procstat.readline()  # skip the first line
                    for line in procstat:
                        if not line.startswith("cpu"): break
                        cpu, *states = line.split()
                        yield Observation(int(states[0]) // 100, {"cpu": cpu, "state": "user"})
                        yield Observation(int(states[1]) // 100, {"cpu": cpu, "state": "nice"})
                        # ... other states

        Alternatively, you can pass a sequence of generators directly instead of a sequence of
        callbacks, which each should return iterables of :class:`~mysql.opentelemetry.metrics.Observation`::

            def cpu_time_callback(states_to_include: set[str]) -> Iterable[Iterable[Observation]]:
                # accept options sent in from OpenTelemetry
                options = yield
                while True:
                    observations = []
                    with open("/proc/stat") as procstat:
                        procstat.readline()  # skip the first line
                        for line in procstat:
                            if not line.startswith("cpu"): break
                            cpu, *states = line.split()
                            if "user" in states_to_include:
                                observations.append(Observation(int(states[0]) // 100, {"cpu": cpu, "state": "user"}))
                            if "nice" in states_to_include:
                                observations.append(Observation(int(states[1]) // 100, {"cpu": cpu, "state": "nice"}))
                            # ... other states
                    # yield the observations and receive the options for next iteration
                    options = yield observations

            meter.create_observable_counter(
                "system.cpu.time",
                callbacks=[cpu_time_callback({"user", "system"})],
                unit="s",
                description="CPU time"
            )

        The :class:`~mysql.opentelemetry.metrics.CallbackOptions` contain a timeout which the
        callback should respect. For example if the callback does asynchronous work, like
        making HTTP requests, it should respect the timeout::

            def scrape_http_callback(options: CallbackOptions) -> Iterable[Observation]:
                r = requests.get('http://scrapethis.com', timeout=options.timeout_millis / 10**3)
                for value in r.json():
                    yield Observation(value)

        Args:
            name: The name of the instrument to be created
            callbacks: A sequence of callbacks that return an iterable of
                :class:`~mysql.opentelemetry.metrics.Observation`. Alternatively, can be a sequence of generators that each
                yields iterables of :class:`~mysql.opentelemetry.metrics.Observation`.
            unit: The unit for observations this instrument reports. For
                example, ``By`` for bytes. UCUM units are recommended.
            description: A description for this instrument and what it measures.
        """

    @abstractmethod
    def create_histogram(
        self,
        name: str,
        unit: str = "",
        description: str = "",
    ) -> Histogram:
        """Creates a :class:`~mysql.opentelemetry.metrics.Histogram` instrument

        Args:
            name: The name of the instrument to be created
            unit: The unit for observations this instrument reports. For
                example, ``By`` for bytes. UCUM units are recommended.
            description: A description for this instrument and what it measures.
        """

    @abstractmethod
    def create_observable_gauge(
        self,
        name: str,
        callbacks: Optional[Sequence[CallbackT]] = None,
        unit: str = "",
        description: str = "",
    ) -> ObservableGauge:
        """Creates an `ObservableGauge` instrument

        Args:
            name: The name of the instrument to be created
            callbacks: A sequence of callbacks that return an iterable of
                :class:`~mysql.opentelemetry.metrics.Observation`. Alternatively, can be a generator that yields iterables
                of :class:`~mysql.opentelemetry.metrics.Observation`.
            unit: The unit for observations this instrument reports. For
                example, ``By`` for bytes. UCUM units are recommended.
            description: A description for this instrument and what it measures.
        """

    @abstractmethod
    def create_observable_up_down_counter(
        self,
        name: str,
        callbacks: Optional[Sequence[CallbackT]] = None,
        unit: str = "",
        description: str = "",
    ) -> ObservableUpDownCounter:
        """Creates an `ObservableUpDownCounter` instrument

        Args:
            name: The name of the instrument to be created
            callbacks: A sequence of callbacks that return an iterable of
                :class:`~mysql.opentelemetry.metrics.Observation`. Alternatively, can be a generator that yields iterables
                of :class:`~mysql.opentelemetry.metrics.Observation`.
            unit: The unit for observations this instrument reports. For
                example, ``By`` for bytes. UCUM units are recommended.
            description: A description for this instrument and what it measures.
        """


class _ProxyMeter(Meter):
    def __init__(
        self,
        name: str,
        version: Optional[str] = None,
        schema_url: Optional[str] = None,
    ) -> None:
        super().__init__(name, version=version, schema_url=schema_url)
        self._lock = Lock()
        self._instruments: List[_ProxyInstrumentT] = []
        self._real_meter: Optional[Meter] = None

    def on_set_meter_provider(self, meter_provider: MeterProvider) -> None:
        """Called when a real meter provider is set on the creating _ProxyMeterProvider

        Creates a real backing meter for this instance and notifies all created
        instruments so they can create real backing instruments.
        """
        real_meter = meter_provider.get_meter(
            self._name, self._version, self._schema_url
        )

        with self._lock:
            self._real_meter = real_meter
            # notify all proxy instruments of the new meter so they can create
            # real instruments to back themselves
            for instrument in self._instruments:
                instrument.on_meter_set(real_meter)

    def create_counter(
        self,
        name: str,
        unit: str = "",
        description: str = "",
    ) -> Counter:
        with self._lock:
            if self._real_meter:
                return self._real_meter.create_counter(name, unit, description)
            proxy = _ProxyCounter(name, unit, description)
            self._instruments.append(proxy)
            return proxy

    def create_up_down_counter(
        self,
        name: str,
        unit: str = "",
        description: str = "",
    ) -> UpDownCounter:
        with self._lock:
            if self._real_meter:
                return self._real_meter.create_up_down_counter(name, unit, description)
            proxy = _ProxyUpDownCounter(name, unit, description)
            self._instruments.append(proxy)
            return proxy

    def create_observable_counter(
        self,
        name: str,
        callbacks: Optional[Sequence[CallbackT]] = None,
        unit: str = "",
        description: str = "",
    ) -> ObservableCounter:
        with self._lock:
            if self._real_meter:
                return self._real_meter.create_observable_counter(
                    name, callbacks, unit, description
                )
            proxy = _ProxyObservableCounter(
                name, callbacks, unit=unit, description=description
            )
            self._instruments.append(proxy)
            return proxy

    def create_histogram(
        self,
        name: str,
        unit: str = "",
        description: str = "",
    ) -> Histogram:
        with self._lock:
            if self._real_meter:
                return self._real_meter.create_histogram(name, unit, description)
            proxy = _ProxyHistogram(name, unit, description)
            self._instruments.append(proxy)
            return proxy

    def create_observable_gauge(
        self,
        name: str,
        callbacks: Optional[Sequence[CallbackT]] = None,
        unit: str = "",
        description: str = "",
    ) -> ObservableGauge:
        with self._lock:
            if self._real_meter:
                return self._real_meter.create_observable_gauge(
                    name, callbacks, unit, description
                )
            proxy = _ProxyObservableGauge(
                name, callbacks, unit=unit, description=description
            )
            self._instruments.append(proxy)
            return proxy

    def create_observable_up_down_counter(
        self,
        name: str,
        callbacks: Optional[Sequence[CallbackT]] = None,
        unit: str = "",
        description: str = "",
    ) -> ObservableUpDownCounter:
        with self._lock:
            if self._real_meter:
                return self._real_meter.create_observable_up_down_counter(
                    name,
                    callbacks,
                    unit,
                    description,
                )
            proxy = _ProxyObservableUpDownCounter(
                name, callbacks, unit=unit, description=description
            )
            self._instruments.append(proxy)
            return proxy


class NoOpMeter(Meter):
    """The default Meter used when no Meter implementation is available.

    All operations are no-op.
    """

    def create_counter(
        self,
        name: str,
        unit: str = "",
        description: str = "",
    ) -> Counter:
        """Returns a no-op Counter."""
        super().create_counter(name, unit=unit, description=description)
        if self._is_instrument_registered(name, NoOpCounter, unit, description)[0]:
            _logger.warning(
                "An instrument with name %s, type %s, unit %s and "
                "description %s has been created already.",
                name,
                Counter.__name__,
                unit,
                description,
            )
        return NoOpCounter(name, unit=unit, description=description)

    def create_up_down_counter(
        self,
        name: str,
        unit: str = "",
        description: str = "",
    ) -> UpDownCounter:
        """Returns a no-op UpDownCounter."""
        super().create_up_down_counter(name, unit=unit, description=description)
        if self._is_instrument_registered(name, NoOpUpDownCounter, unit, description)[
            0
        ]:
            _logger.warning(
                "An instrument with name %s, type %s, unit %s and "
                "description %s has been created already.",
                name,
                UpDownCounter.__name__,
                unit,
                description,
            )
        return NoOpUpDownCounter(name, unit=unit, description=description)

    def create_observable_counter(
        self,
        name: str,
        callbacks: Optional[Sequence[CallbackT]] = None,
        unit: str = "",
        description: str = "",
    ) -> ObservableCounter:
        """Returns a no-op ObservableCounter."""
        super().create_observable_counter(
            name, callbacks, unit=unit, description=description
        )
        if self._is_instrument_registered(
            name, NoOpObservableCounter, unit, description
        )[0]:
            _logger.warning(
                "An instrument with name %s, type %s, unit %s and "
                "description %s has been created already.",
                name,
                ObservableCounter.__name__,
                unit,
                description,
            )
        return NoOpObservableCounter(
            name,
            callbacks,
            unit=unit,
            description=description,
        )

    def create_histogram(
        self,
        name: str,
        unit: str = "",
        description: str = "",
    ) -> Histogram:
        """Returns a no-op Histogram."""
        super().create_histogram(name, unit=unit, description=description)
        if self._is_instrument_registered(name, NoOpHistogram, unit, description)[0]:
            _logger.warning(
                "An instrument with name %s, type %s, unit %s and "
                "description %s has been created already.",
                name,
                Histogram.__name__,
                unit,
                description,
            )
        return NoOpHistogram(name, unit=unit, description=description)

    def create_observable_gauge(
        self,
        name: str,
        callbacks: Optional[Sequence[CallbackT]] = None,
        unit: str = "",
        description: str = "",
    ) -> ObservableGauge:
        """Returns a no-op ObservableGauge."""
        super().create_observable_gauge(
            name, callbacks, unit=unit, description=description
        )
        if self._is_instrument_registered(name, NoOpObservableGauge, unit, description)[
            0
        ]:
            _logger.warning(
                "An instrument with name %s, type %s, unit %s and "
                "description %s has been created already.",
                name,
                ObservableGauge.__name__,
                unit,
                description,
            )
        return NoOpObservableGauge(
            name,
            callbacks,
            unit=unit,
            description=description,
        )

    def create_observable_up_down_counter(
        self,
        name: str,
        callbacks: Optional[Sequence[CallbackT]] = None,
        unit: str = "",
        description: str = "",
    ) -> ObservableUpDownCounter:
        """Returns a no-op ObservableUpDownCounter."""
        super().create_observable_up_down_counter(
            name, callbacks, unit=unit, description=description
        )
        if self._is_instrument_registered(
            name, NoOpObservableUpDownCounter, unit, description
        )[0]:
            _logger.warning(
                "An instrument with name %s, type %s, unit %s and "
                "description %s has been created already.",
                name,
                ObservableUpDownCounter.__name__,
                unit,
                description,
            )
        return NoOpObservableUpDownCounter(
            name,
            callbacks,
            unit=unit,
            description=description,
        )


_METER_PROVIDER_SET_ONCE = Once()
_METER_PROVIDER: Optional[MeterProvider] = None
_PROXY_METER_PROVIDER = _ProxyMeterProvider()


def get_meter(
    name: str,
    version: str = "",
    meter_provider: Optional[MeterProvider] = None,
) -> "Meter":
    """Returns a `Meter` for use by the given instrumentation library.

    This function is a convenience wrapper for
    `mysql.opentelemetry.metrics.MeterProvider.get_meter`.

    If meter_provider is omitted the current configured one is used.
    """
    if meter_provider is None:
        meter_provider = get_meter_provider()
    return meter_provider.get_meter(name, version)


def _set_meter_provider(meter_provider: MeterProvider, log: bool) -> None:
    def set_mp() -> None:
        global _METER_PROVIDER  # pylint: disable=global-statement
        _METER_PROVIDER = meter_provider

        # gives all proxies real instruments off the newly set meter provider
        _PROXY_METER_PROVIDER.on_set_meter_provider(meter_provider)

    did_set = _METER_PROVIDER_SET_ONCE.do_once(set_mp)

    if log and not did_set:
        _logger.warning("Overriding of current MeterProvider is not allowed")


def set_meter_provider(meter_provider: MeterProvider) -> None:
    """Sets the current global :class:`~.MeterProvider` object.

    This can only be done once, a warning will be logged if any further attempt
    is made.
    """
    _set_meter_provider(meter_provider, log=True)


def get_meter_provider() -> MeterProvider:
    """Gets the current global :class:`~.MeterProvider` object."""

    if _METER_PROVIDER is None:
        if OTEL_PYTHON_METER_PROVIDER not in environ.keys():
            return _PROXY_METER_PROVIDER

        meter_provider: MeterProvider = _load_provider(  # type: ignore
            OTEL_PYTHON_METER_PROVIDER, "meter_provider"
        )
        _set_meter_provider(meter_provider, log=False)

    # _METER_PROVIDER will have been set by one thread
    return cast("MeterProvider", _METER_PROVIDER)

virt/lib/python3.9/site-packages/mysql/opentelemetry/metrics/_internal/__init__.py
PrinceDisant/LostInNL
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# pylint: disable=too-many-ancestors


from abc import ABC, abstractmethod
from dataclasses import dataclass
from logging import getLogger
from re import compile as re_compile
from typing import (
    Callable,
    Dict,
    Generator,
    Generic,
    Iterable,
    Optional,
    Sequence,
    TypeVar,
    Union,
)

# pylint: disable=unused-import; needed for typing and sphinx
from mysql.opentelemetry import metrics
from mysql.opentelemetry.metrics._internal.observation import Observation
from mysql.opentelemetry.util.types import Attributes

_logger = getLogger(__name__)

_name_regex = re_compile(r"[a-zA-Z][-_.a-zA-Z0-9]{0,62}")
_unit_regex = re_compile(r"[\x00-\x7F]{0,63}")


@dataclass(frozen=True)
class CallbackOptions:
    """Options for the callback

    Args:
        timeout_millis: Timeout for the callback's execution. If the callback does asynchronous
            work (e.g. HTTP requests), it should respect this timeout.
    """

    timeout_millis: float = 10_000


InstrumentT = TypeVar("InstrumentT", bound="Instrument")
CallbackT = Union[
    Callable[[CallbackOptions], Iterable[Observation]],
    Generator[Iterable[Observation], CallbackOptions, None],
]


class Instrument(ABC):
    """Abstract class that serves as base for all instruments."""

    @abstractmethod
    def __init__(
        self,
        name: str,
        unit: str = "",
        description: str = "",
    ) -> None:
        pass

    @staticmethod
    def _check_name_unit_description(
        name: str, unit: str, description: str
    ) -> Dict[str, Optional[str]]:
        """
        Checks the following instrument name, unit and description for
        compliance with the spec.

        Returns a dict with keys "name", "unit" and "description", the
        corresponding values will be the checked strings or `None` if the value
        is invalid. If valid, the checked strings should be used instead of the
        original values.
        """

        result: Dict[str, Optional[str]] = {}

        if _name_regex.fullmatch(name) is not None:
            result["name"] = name
        else:
            result["name"] = None

        if unit is None:
            unit = ""
        if _unit_regex.fullmatch(unit) is not None:
            result["unit"] = unit
        else:
            result["unit"] = None

        if description is None:
            result["description"] = ""
        else:
            result["description"] = description

        return result


class _ProxyInstrument(ABC, Generic[InstrumentT]):
    def __init__(
        self,
        name: str,
        unit: str = "",
        description: str = "",
    ) -> None:
        self._name = name
        self._unit = unit
        self._description = description
        self._real_instrument: Optional[InstrumentT] = None

    def on_meter_set(self, meter: "metrics.Meter") -> None:
        """Called when a real meter is set on the creating _ProxyMeter"""

        # We don't need any locking on proxy instruments because it's OK if some
        # measurements get dropped while a real backing instrument is being
        # created.
        self._real_instrument = self._create_real_instrument(meter)

    @abstractmethod
    def _create_real_instrument(self, meter: "metrics.Meter") -> InstrumentT:
        """Create an instance of the real instrument. Implement this."""


class _ProxyAsynchronousInstrument(_ProxyInstrument[InstrumentT]):
    def __init__(
        self,
        name: str,
        callbacks: Optional[Sequence[CallbackT]] = None,
        unit: str = "",
        description: str = "",
    ) -> None:
        super().__init__(name, unit, description)
        self._callbacks = callbacks


class Synchronous(Instrument):
    """Base class for all synchronous instruments"""


class Asynchronous(Instrument):
    """Base class for all asynchronous instruments"""

    @abstractmethod
    def __init__(
        self,
        name: str,
        callbacks: Optional[Sequence[CallbackT]] = None,
        unit: str = "",
        description: str = "",
    ) -> None:
        super().__init__(name, unit=unit, description=description)


class Counter(Synchronous):
    """A Counter is a synchronous `Instrument` which supports non-negative increments."""

    @abstractmethod
    def add(
        self,
        amount: Union[int, float],
        attributes: Optional[Attributes] = None,
    ) -> None:
        pass


class NoOpCounter(Counter):
    """No-op implementation of `Counter`."""

    def __init__(
        self,
        name: str,
        unit: str = "",
        description: str = "",
    ) -> None:
        super().__init__(name, unit=unit, description=description)

    def add(
        self,
        amount: Union[int, float],
        attributes: Optional[Attributes] = None,
    ) -> None:
        return super().add(amount, attributes=attributes)


class _ProxyCounter(_ProxyInstrument[Counter], Counter):
    def add(
        self,
        amount: Union[int, float],
        attributes: Optional[Attributes] = None,
    ) -> None:
        if self._real_instrument:
            self._real_instrument.add(amount, attributes)

    def _create_real_instrument(self, meter: "metrics.Meter") -> Counter:
        return meter.create_counter(self._name, self._unit, self._description)


class UpDownCounter(Synchronous):
    """An UpDownCounter is a synchronous `Instrument` which supports increments and decrements."""

    @abstractmethod
    def add(
        self,
        amount: Union[int, float],
        attributes: Optional[Attributes] = None,
    ) -> None:
        pass


class NoOpUpDownCounter(UpDownCounter):
    """No-op implementation of `UpDownCounter`."""

    def __init__(
        self,
        name: str,
        unit: str = "",
        description: str = "",
    ) -> None:
        super().__init__(name, unit=unit, description=description)

    def add(
        self,
        amount: Union[int, float],
        attributes: Optional[Attributes] = None,
    ) -> None:
        return super().add(amount, attributes=attributes)


class _ProxyUpDownCounter(_ProxyInstrument[UpDownCounter], UpDownCounter):
    def add(
        self,
        amount: Union[int, float],
        attributes: Optional[Attributes] = None,
    ) -> None:
        if self._real_instrument:
            self._real_instrument.add(amount, attributes)

    def _create_real_instrument(self, meter: "metrics.Meter") -> UpDownCounter:
        return meter.create_up_down_counter(self._name, self._unit, self._description)


class ObservableCounter(Asynchronous):
    """An ObservableCounter is an asynchronous `Instrument` which reports monotonically
    increasing value(s) when the instrument is being observed.
    """


class NoOpObservableCounter(ObservableCounter):
    """No-op implementation of `ObservableCounter`."""

    def __init__(
        self,
        name: str,
        callbacks: Optional[Sequence[CallbackT]] = None,
        unit: str = "",
        description: str = "",
    ) -> None:
        super().__init__(name, callbacks, unit=unit, description=description)


class _ProxyObservableCounter(
    _ProxyAsynchronousInstrument[ObservableCounter], ObservableCounter
):
    def _create_real_instrument(self, meter: "metrics.Meter") -> ObservableCounter:
        return meter.create_observable_counter(
            self._name, self._callbacks, self._unit, self._description
        )


class ObservableUpDownCounter(Asynchronous):
    """An ObservableUpDownCounter is an asynchronous `Instrument` which reports additive value(s) (e.g.
    the process heap size - it makes sense to report the heap size from multiple processes and sum them
    up, so we get the total heap usage) when the instrument is being observed.
    """


class NoOpObservableUpDownCounter(ObservableUpDownCounter):
    """No-op implementation of `ObservableUpDownCounter`."""

    def __init__(
        self,
        name: str,
        callbacks: Optional[Sequence[CallbackT]] = None,
        unit: str = "",
        description: str = "",
    ) -> None:
        super().__init__(name, callbacks, unit=unit, description=description)


class _ProxyObservableUpDownCounter(
    _ProxyAsynchronousInstrument[ObservableUpDownCounter],
    ObservableUpDownCounter,
):
    def _create_real_instrument(
        self, meter: "metrics.Meter"
    ) -> ObservableUpDownCounter:
        return meter.create_observable_up_down_counter(
            self._name, self._callbacks, self._unit, self._description
        )


class Histogram(Synchronous):
    """Histogram is a synchronous `Instrument` which can be used to report arbitrary values
    that are likely to be statistically meaningful. It is intended for statistics such as
    histograms, summaries, and percentile.
    """

    @abstractmethod
    def record(
        self,
        amount: Union[int, float],
        attributes: Optional[Attributes] = None,
    ) -> None:
        pass


class NoOpHistogram(Histogram):
    """No-op implementation of `Histogram`."""

    def __init__(
        self,
        name: str,
        unit: str = "",
        description: str = "",
    ) -> None:
        super().__init__(name, unit=unit, description=description)

    def record(
        self,
        amount: Union[int, float],
        attributes: Optional[Attributes] = None,
    ) -> None:
        return super().record(amount, attributes=attributes)


class _ProxyHistogram(_ProxyInstrument[Histogram], Histogram):
    def record(
        self,
        amount: Union[int, float],
        attributes: Optional[Attributes] = None,
    ) -> None:
        if self._real_instrument:
            self._real_instrument.record(amount, attributes)

    def _create_real_instrument(self, meter: "metrics.Meter") -> Histogram:
        return meter.create_histogram(self._name, self._unit, self._description)


class ObservableGauge(Asynchronous):
    """Asynchronous Gauge is an asynchronous `Instrument` which reports non-additive value(s) (e.g.
    the room temperature - it makes no sense to report the temperature value from multiple rooms
    and sum them up) when the instrument is being observed.
    """


class NoOpObservableGauge(ObservableGauge):
    """No-op implementation of `ObservableGauge`."""

    def __init__(
        self,
        name: str,
        callbacks: Optional[Sequence[CallbackT]] = None,
        unit: str = "",
        description: str = "",
    ) -> None:
        super().__init__(name, callbacks, unit=unit, description=description)


class _ProxyObservableGauge(
    _ProxyAsynchronousInstrument[ObservableGauge],
    ObservableGauge,
):
    def _create_real_instrument(self, meter: "metrics.Meter") -> ObservableGauge:
        return meter.create_observable_gauge(
            self._name, self._callbacks, self._unit, self._description
        )

virt/lib/python3.9/site-packages/mysql/opentelemetry/metrics/_internal/instrument.py
PrinceDisant/LostInNL
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from typing import Union

from mysql.opentelemetry.util.types import Attributes


class Observation:
    """A measurement observed in an asynchronous instrument

    Return/yield instances of this class from asynchronous instrument callbacks.

    Args:
        value: The float or int measured value
        attributes: The measurement's attributes
    """

    def __init__(self, value: Union[int, float], attributes: Attributes = None) -> None:
        self._value = value
        self._attributes = attributes

    @property
    def value(self) -> Union[float, int]:
        return self._value

    @property
    def attributes(self) -> Attributes:
        return self._attributes

    def __eq__(self, other: object) -> bool:
        return (
            isinstance(other, Observation)
            and self.value == other.value
            and self.attributes == other.attributes
        )

    def __repr__(self) -> str:
        return f"Observation(value={self.value}, attributes={self.attributes})"

virt/lib/python3.9/site-packages/mysql/opentelemetry/metrics/_internal/observation.py
PrinceDisant/LostInNL
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""
API for propagation of context.

The propagators for the
``mysql.opentelemetry.propagators.composite.CompositePropagator`` can be defined
via configuration in the ``OTEL_PROPAGATORS`` environment variable. This
variable should be set to a comma-separated string of names of values for the
``opentelemetry_propagator`` entry point. For example, setting
``OTEL_PROPAGATORS`` to ``tracecontext,baggage`` (which is the default value)
would instantiate
``mysql.opentelemetry.propagators.composite.CompositePropagator`` with 2
propagators, one of type
``mysql.opentelemetry.trace.propagation.tracecontext.TraceContextTextMapPropagator``
and other of type ``mysql.opentelemetry.baggage.propagation.W3CBaggagePropagator``.
Notice that these propagator classes are defined as
``opentelemetry_propagator`` entry points in the ``pyproject.toml`` file of
``opentelemetry``.

Example::

    import flask
    import requests
    from opentelemetry import propagate


    PROPAGATOR = propagate.get_global_textmap()


    def get_header_from_flask_request(request, key):
        return request.headers.get_all(key)

    def set_header_into_requests_request(request: requests.Request,
                                            key: str, value: str):
        request.headers[key] = value

    def example_route():
        context = PROPAGATOR.extract(
            get_header_from_flask_request,
            flask.request
        )
        request_to_downstream = requests.Request(
            "GET", "http://httpbin.org/get"
        )
        PROPAGATOR.inject(
            set_header_into_requests_request,
            request_to_downstream,
            context=context
        )
        session = requests.Session()
        session.send(request_to_downstream.prepare())


.. _Propagation API Specification:
    https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/context/api-propagators.md
"""

from logging import getLogger
from os import environ
from typing import Optional

from mysql.opentelemetry.context.context import Context
from mysql.opentelemetry.environment_variables import OTEL_PROPAGATORS
from mysql.opentelemetry.propagators import composite, textmap
from mysql.opentelemetry.util._importlib_metadata import entry_points

logger = getLogger(__name__)


def extract(
    carrier: textmap.CarrierT,
    context: Optional[Context] = None,
    getter: textmap.Getter[textmap.CarrierT] = textmap.default_getter,
) -> Context:
    """Uses the configured propagator to extract a Context from the carrier.

    Args:
        getter: an object which contains a get function that can retrieve zero
            or more values from the carrier and a keys function that can get all the keys
            from carrier.
        carrier: and object which contains values that are
            used to construct a Context. This object
            must be paired with an appropriate getter
            which understands how to extract a value from it.
        context: an optional Context to use. Defaults to root
            context if not set.
    """
    return get_global_textmap().extract(carrier, context, getter=getter)


def inject(
    carrier: textmap.CarrierT,
    context: Optional[Context] = None,
    setter: textmap.Setter[textmap.CarrierT] = textmap.default_setter,
) -> None:
    """Uses the configured propagator to inject a Context into the carrier.

    Args:
        carrier: An object that contains a representation of HTTP
            headers. Should be paired with setter, which
            should know how to set header values on the carrier.
        context: An optional Context to use. Defaults to current
            context if not set.
        setter: An optional `Setter` object that can set values
            on the carrier.
    """
    get_global_textmap().inject(carrier, context=context, setter=setter)


propagators = []

# Single use variable here to hack black and make lint pass
environ_propagators = environ.get(
    OTEL_PROPAGATORS,
    "tracecontext,baggage",
)


for propagator in environ_propagators.split(","):
    propagator = propagator.strip()

    try:
        propagators.append(  # type: ignore
            next(  # type: ignore
                iter(  # type: ignore
                    entry_points(  # type: ignore
                        group="opentelemetry_propagator",
                        name=propagator,
                    )
                )
            ).load()()
        )

    except Exception:  # pylint: disable=broad-except
        logger.exception("Failed to load configured propagator: %s", propagator)
        raise


_HTTP_TEXT_FORMAT = composite.CompositePropagator(propagators)  # type: ignore


def get_global_textmap() -> textmap.TextMapPropagator:
    return _HTTP_TEXT_FORMAT


def set_global_textmap(
    http_text_format: textmap.TextMapPropagator,
) -> None:
    global _HTTP_TEXT_FORMAT  # pylint:disable=global-statement
    _HTTP_TEXT_FORMAT = http_text_format  # type: ignore

virt/lib/python3.9/site-packages/mysql/opentelemetry/propagate/__init__.py
PrinceDisant/LostInNL
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import logging
import typing

from deprecated import deprecated
from mysql.opentelemetry.context.context import Context
from mysql.opentelemetry.propagators import textmap

logger = logging.getLogger(__name__)


class CompositePropagator(textmap.TextMapPropagator):
    """CompositePropagator provides a mechanism for combining multiple
    propagators into a single one.

    Args:
        propagators: the list of propagators to use
    """

    def __init__(self, propagators: typing.Sequence[textmap.TextMapPropagator]) -> None:
        self._propagators = propagators

    def extract(
        self,
        carrier: textmap.CarrierT,
        context: typing.Optional[Context] = None,
        getter: textmap.Getter[textmap.CarrierT] = textmap.default_getter,
    ) -> Context:
        """Run each of the configured propagators with the given context and carrier.
        Propagators are run in the order they are configured, if multiple
        propagators write the same context key, the propagator later in the list
        will override previous propagators.

        See `mysql.opentelemetry.propagators.textmap.TextMapPropagator.extract`
        """
        for propagator in self._propagators:
            context = propagator.extract(carrier, context, getter=getter)
        return context  # type: ignore

    def inject(
        self,
        carrier: textmap.CarrierT,
        context: typing.Optional[Context] = None,
        setter: textmap.Setter[textmap.CarrierT] = textmap.default_setter,
    ) -> None:
        """Run each of the configured propagators with the given context and carrier.
        Propagators are run in the order they are configured, if multiple
        propagators write the same carrier key, the propagator later in the list
        will override previous propagators.

        See `mysql.opentelemetry.propagators.textmap.TextMapPropagator.inject`
        """
        for propagator in self._propagators:
            propagator.inject(carrier, context, setter=setter)

    @property
    def fields(self) -> typing.Set[str]:
        """Returns a set with the fields set in `inject`.

        See
        `mysql.opentelemetry.propagators.textmap.TextMapPropagator.fields`
        """
        composite_fields = set()

        for propagator in self._propagators:
            for field in propagator.fields:
                composite_fields.add(field)

        return composite_fields


@deprecated(version="1.2.0", reason="You should use CompositePropagator")  # type: ignore
class CompositeHTTPPropagator(CompositePropagator):
    """CompositeHTTPPropagator provides a mechanism for combining multiple
    propagators into a single one.
    """

virt/lib/python3.9/site-packages/mysql/opentelemetry/propagators/composite.py
PrinceDisant/LostInNL
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import abc
import typing

from mysql.opentelemetry.context.context import Context

CarrierT = typing.TypeVar("CarrierT")
CarrierValT = typing.Union[typing.List[str], str]


class Getter(abc.ABC, typing.Generic[CarrierT]):
    """This class implements a Getter that enables extracting propagated
    fields from a carrier.
    """

    @abc.abstractmethod
    def get(self, carrier: CarrierT, key: str) -> typing.Optional[typing.List[str]]:
        """Function that can retrieve zero
        or more values from the carrier. In the case that
        the value does not exist, returns None.

        Args:
            carrier: An object which contains values that are used to
                    construct a Context.
            key: key of a field in carrier.
        Returns: first value of the propagation key or None if the key doesn't
                exist.
        """

    @abc.abstractmethod
    def keys(self, carrier: CarrierT) -> typing.List[str]:
        """Function that can retrieve all the keys in a carrier object.

        Args:
            carrier: An object which contains values that are
                used to construct a Context.
        Returns:
            list of keys from the carrier.
        """


class Setter(abc.ABC, typing.Generic[CarrierT]):
    """This class implements a Setter that enables injecting propagated
    fields into a carrier.
    """

    @abc.abstractmethod
    def set(self, carrier: CarrierT, key: str, value: str) -> None:
        """Function that can set a value into a carrier""

        Args:
            carrier: An object which contains values that are used to
                    construct a Context.
            key: key of a field in carrier.
            value: value for a field in carrier.
        """


class DefaultGetter(Getter[typing.Mapping[str, CarrierValT]]):
    def get(
        self, carrier: typing.Mapping[str, CarrierValT], key: str
    ) -> typing.Optional[typing.List[str]]:
        """Getter implementation to retrieve a value from a dictionary.

        Args:
            carrier: dictionary in which to get value
            key: the key used to get the value
        Returns:
            A list with a single string with the value if it exists, else None.
        """
        val = carrier.get(key, None)
        if val is None:
            return None
        if isinstance(val, typing.Iterable) and not isinstance(val, str):
            return list(val)
        return [val]

    def keys(self, carrier: typing.Mapping[str, CarrierValT]) -> typing.List[str]:
        """Keys implementation that returns all keys from a dictionary."""
        return list(carrier.keys())


default_getter: Getter[CarrierT] = DefaultGetter()  # type: ignore


class DefaultSetter(Setter[typing.MutableMapping[str, CarrierValT]]):
    def set(
        self,
        carrier: typing.MutableMapping[str, CarrierValT],
        key: str,
        value: CarrierValT,
    ) -> None:
        """Setter implementation to set a value into a dictionary.

        Args:
            carrier: dictionary in which to set value
            key: the key used to set the value
            value: the value to set
        """
        carrier[key] = value


default_setter: Setter[CarrierT] = DefaultSetter()  # type: ignore


class TextMapPropagator(abc.ABC):
    """This class provides an interface that enables extracting and injecting
    context into headers of HTTP requests. HTTP frameworks and clients
    can integrate with TextMapPropagator by providing the object containing the
    headers, and a getter and setter function for the extraction and
    injection of values, respectively.

    """

    @abc.abstractmethod
    def extract(
        self,
        carrier: CarrierT,
        context: typing.Optional[Context] = None,
        getter: Getter[CarrierT] = default_getter,
    ) -> Context:
        """Create a Context from values in the carrier.

        The extract function should retrieve values from the carrier
        object using getter, and use values to populate a
        Context value and return it.

        Args:
            getter: a function that can retrieve zero
                or more values from the carrier. In the case that
                the value does not exist, return an empty list.
            carrier: and object which contains values that are
                used to construct a Context. This object
                must be paired with an appropriate getter
                which understands how to extract a value from it.
            context: an optional Context to use. Defaults to root
                context if not set.
        Returns:
            A Context with configuration found in the carrier.

        """

    @abc.abstractmethod
    def inject(
        self,
        carrier: CarrierT,
        context: typing.Optional[Context] = None,
        setter: Setter[CarrierT] = default_setter,
    ) -> None:
        """Inject values from a Context into a carrier.

        inject enables the propagation of values into HTTP clients or
        other objects which perform an HTTP request. Implementations
        should use the `Setter` 's set method to set values on the
        carrier.

        Args:
            carrier: An object that a place to define HTTP headers.
                Should be paired with setter, which should
                know how to set header values on the carrier.
            context: an optional Context to use. Defaults to current
                context if not set.
            setter: An optional `Setter` object that can set values
                on the carrier.

        """

    @property
    @abc.abstractmethod
    def fields(self) -> typing.Set[str]:
        """
        Gets the fields set in the carrier by the `inject` method.

        If the carrier is reused, its fields that correspond with the ones
        present in this attribute should be deleted before calling `inject`.

        Returns:
            A set with the fields set in `inject`.
        """

virt/lib/python3.9/site-packages/mysql/opentelemetry/propagators/textmap.py
PrinceDisant/LostInNL
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

"""
OpenTelemetry SDK Configurator for Easy Instrumentation with Distros
"""

import logging
import os

from abc import ABC, abstractmethod
from os import environ
from typing import Callable, Dict, List, Optional, Sequence, Tuple, Type

from mysql.opentelemetry._logs import set_logger_provider
from mysql.opentelemetry.environment_variables import (
    OTEL_LOGS_EXPORTER,
    OTEL_METRICS_EXPORTER,
    OTEL_PYTHON_ID_GENERATOR,
    OTEL_TRACES_EXPORTER,
)
from mysql.opentelemetry.metrics import set_meter_provider
from mysql.opentelemetry.sdk._logs import LoggerProvider, LoggingHandler
from mysql.opentelemetry.sdk._logs.export import BatchLogRecordProcessor, LogExporter
from mysql.opentelemetry.sdk.environment_variables import (
    _OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED,
    OTEL_EXPORTER_OTLP_LOGS_PROTOCOL,
    OTEL_EXPORTER_OTLP_METRICS_PROTOCOL,
    OTEL_EXPORTER_OTLP_PROTOCOL,
    OTEL_EXPORTER_OTLP_TRACES_PROTOCOL,
    OTEL_TRACES_SAMPLER,
    OTEL_TRACES_SAMPLER_ARG,
)
from mysql.opentelemetry.sdk.metrics import MeterProvider
from mysql.opentelemetry.sdk.metrics.export import (
    MetricExporter,
    PeriodicExportingMetricReader,
)
from mysql.opentelemetry.sdk.resources import Resource
from mysql.opentelemetry.sdk.trace import TracerProvider
from mysql.opentelemetry.sdk.trace.export import BatchSpanProcessor, SpanExporter
from mysql.opentelemetry.sdk.trace.id_generator import IdGenerator
from mysql.opentelemetry.sdk.trace.sampling import Sampler
from mysql.opentelemetry.semconv.resource import ResourceAttributes
from mysql.opentelemetry.trace import set_tracer_provider
from mysql.opentelemetry.util._importlib_metadata import entry_points
from typing_extensions import Literal

_EXPORTER_OTLP = "otlp"
_EXPORTER_OTLP_PROTO_GRPC = "otlp_proto_grpc"
_EXPORTER_OTLP_PROTO_HTTP = "otlp_proto_http"

_EXPORTER_BY_OTLP_PROTOCOL = {
    "grpc": _EXPORTER_OTLP_PROTO_GRPC,
    "http/protobuf": _EXPORTER_OTLP_PROTO_HTTP,
}

_EXPORTER_ENV_BY_SIGNAL_TYPE = {
    "traces": OTEL_TRACES_EXPORTER,
    "metrics": OTEL_METRICS_EXPORTER,
    "logs": OTEL_LOGS_EXPORTER,
}

_PROTOCOL_ENV_BY_SIGNAL_TYPE = {
    "traces": OTEL_EXPORTER_OTLP_TRACES_PROTOCOL,
    "metrics": OTEL_EXPORTER_OTLP_METRICS_PROTOCOL,
    "logs": OTEL_EXPORTER_OTLP_LOGS_PROTOCOL,
}

_RANDOM_ID_GENERATOR = "random"
_DEFAULT_ID_GENERATOR = _RANDOM_ID_GENERATOR

_OTEL_SAMPLER_ENTRY_POINT_GROUP = "opentelemetry_traces_sampler"

_logger = logging.getLogger(__name__)


def _import_config_components(
    selected_components: List[str], entry_point_name: str
) -> Sequence[Tuple[str, object]]:
    component_implementations = []

    for selected_component in selected_components:
        try:
            component_implementations.append(
                (
                    selected_component,
                    next(
                        iter(
                            entry_points(
                                group=entry_point_name, name=selected_component
                            )
                        )
                    ).load(),
                )
            )
        except KeyError:
            raise RuntimeError(f"Requested entry point '{entry_point_name}' not found")

        except StopIteration:
            raise RuntimeError(
                f"Requested component '{selected_component}' not found in "
                f"entry point '{entry_point_name}'"
            )

    return component_implementations


def _get_sampler() -> Optional[str]:
    return environ.get(OTEL_TRACES_SAMPLER, None)


def _get_id_generator() -> str:
    return environ.get(OTEL_PYTHON_ID_GENERATOR, _DEFAULT_ID_GENERATOR)


def _get_exporter_entry_point(
    exporter_name: str, signal_type: Literal["traces", "metrics", "logs"]
):
    if exporter_name not in (
        _EXPORTER_OTLP,
        _EXPORTER_OTLP_PROTO_GRPC,
        _EXPORTER_OTLP_PROTO_HTTP,
    ):
        return exporter_name

    # Checking env vars for OTLP protocol (grpc/http).
    otlp_protocol = environ.get(
        _PROTOCOL_ENV_BY_SIGNAL_TYPE[signal_type]
    ) or environ.get(OTEL_EXPORTER_OTLP_PROTOCOL)

    if not otlp_protocol:
        if exporter_name == _EXPORTER_OTLP:
            return _EXPORTER_OTLP_PROTO_GRPC
        return exporter_name

    otlp_protocol = otlp_protocol.strip()

    if exporter_name == _EXPORTER_OTLP:
        if otlp_protocol not in _EXPORTER_BY_OTLP_PROTOCOL:
            # Invalid value was set by the env var
            raise RuntimeError(
                f"Unsupported OTLP protocol '{otlp_protocol}' is configured"
            )

        return _EXPORTER_BY_OTLP_PROTOCOL[otlp_protocol]

    # grpc/http already specified by exporter_name, only add a warning in case
    # of a conflict.
    exporter_name_by_env = _EXPORTER_BY_OTLP_PROTOCOL.get(otlp_protocol)
    if exporter_name_by_env and exporter_name != exporter_name_by_env:
        _logger.warning(
            "Conflicting values for %s OTLP exporter protocol, using '%s'",
            signal_type,
            exporter_name,
        )

    return exporter_name


def _get_exporter_names(
    signal_type: Literal["traces", "metrics", "logs"]
) -> Sequence[str]:
    names = environ.get(_EXPORTER_ENV_BY_SIGNAL_TYPE.get(signal_type, ""))

    if not names or names.lower().strip() == "none":
        return []

    return [
        _get_exporter_entry_point(_exporter.strip(), signal_type)
        for _exporter in names.split(",")
    ]


def _init_tracing(
    exporters: Dict[str, Type[SpanExporter]],
    id_generator: IdGenerator = None,
    sampler: Sampler = None,
    resource: Resource = None,
):
    provider = TracerProvider(
        id_generator=id_generator,
        sampler=sampler,
        resource=resource,
    )
    set_tracer_provider(provider)

    for _, exporter_class in exporters.items():
        exporter_args = {}
        provider.add_span_processor(BatchSpanProcessor(exporter_class(**exporter_args)))


def _init_metrics(
    exporters: Dict[str, Type[MetricExporter]],
    resource: Resource = None,
):
    metric_readers = []

    for _, exporter_class in exporters.items():
        exporter_args = {}
        metric_readers.append(
            PeriodicExportingMetricReader(exporter_class(**exporter_args))
        )

    provider = MeterProvider(resource=resource, metric_readers=metric_readers)
    set_meter_provider(provider)


def _init_logging(
    exporters: Dict[str, Type[LogExporter]],
    resource: Resource = None,
):
    provider = LoggerProvider(resource=resource)
    set_logger_provider(provider)

    for _, exporter_class in exporters.items():
        exporter_args = {}
        provider.add_log_record_processor(
            BatchLogRecordProcessor(exporter_class(**exporter_args))
        )

    handler = LoggingHandler(level=logging.NOTSET, logger_provider=provider)

    logging.getLogger().addHandler(handler)


def _import_exporters(
    trace_exporter_names: Sequence[str],
    metric_exporter_names: Sequence[str],
    log_exporter_names: Sequence[str],
) -> Tuple[
    Dict[str, Type[SpanExporter]],
    Dict[str, Type[MetricExporter]],
    Dict[str, Type[LogExporter]],
]:
    trace_exporters = {}
    metric_exporters = {}
    log_exporters = {}

    for (
        exporter_name,
        exporter_impl,
    ) in _import_config_components(
        trace_exporter_names, "opentelemetry_traces_exporter"
    ):
        if issubclass(exporter_impl, SpanExporter):
            trace_exporters[exporter_name] = exporter_impl
        else:
            raise RuntimeError(f"{exporter_name} is not a trace exporter")

    for (
        exporter_name,
        exporter_impl,
    ) in _import_config_components(
        metric_exporter_names, "opentelemetry_metrics_exporter"
    ):
        if issubclass(exporter_impl, MetricExporter):
            metric_exporters[exporter_name] = exporter_impl
        else:
            raise RuntimeError(f"{exporter_name} is not a metric exporter")

    for (
        exporter_name,
        exporter_impl,
    ) in _import_config_components(log_exporter_names, "opentelemetry_logs_exporter"):
        if issubclass(exporter_impl, LogExporter):
            log_exporters[exporter_name] = exporter_impl
        else:
            raise RuntimeError(f"{exporter_name} is not a log exporter")

    return trace_exporters, metric_exporters, log_exporters


def _import_sampler_factory(sampler_name: str) -> Callable[[str], Sampler]:
    _, sampler_impl = _import_config_components(
        [sampler_name.strip()], _OTEL_SAMPLER_ENTRY_POINT_GROUP
    )[0]
    return sampler_impl


def _import_sampler(sampler_name: str) -> Optional[Sampler]:
    if not sampler_name:
        return None
    try:
        sampler_factory = _import_sampler_factory(sampler_name)
        arg = None
        if sampler_name in ("traceidratio", "parentbased_traceidratio"):
            try:
                rate = float(os.getenv(OTEL_TRACES_SAMPLER_ARG))
            except (ValueError, TypeError):
                _logger.warning(
                    "Could not convert TRACES_SAMPLER_ARG to float. Using default value 1.0."
                )
                rate = 1.0
            arg = rate
        else:
            arg = os.getenv(OTEL_TRACES_SAMPLER_ARG)

        sampler = sampler_factory(arg)
        if not isinstance(sampler, Sampler):
            message = f"Sampler factory, {sampler_factory}, produced output, {sampler}, which is not a Sampler."
            _logger.warning(message)
            raise ValueError(message)
        return sampler
    except Exception as exc:  # pylint: disable=broad-except
        _logger.warning(
            "Using default sampler. Failed to initialize sampler, %s: %s",
            sampler_name,
            exc,
        )
        return None


def _import_id_generator(id_generator_name: str) -> IdGenerator:
    id_generator_name, id_generator_impl = _import_config_components(
        [id_generator_name.strip()], "opentelemetry_id_generator"
    )[0]

    if issubclass(id_generator_impl, IdGenerator):
        return id_generator_impl()

    raise RuntimeError(f"{id_generator_name} is not an IdGenerator")


def _initialize_components(auto_instrumentation_version):
    trace_exporters, metric_exporters, log_exporters = _import_exporters(
        _get_exporter_names("traces"),
        _get_exporter_names("metrics"),
        _get_exporter_names("logs"),
    )
    sampler_name = _get_sampler()
    sampler = _import_sampler(sampler_name)
    id_generator_name = _get_id_generator()
    id_generator = _import_id_generator(id_generator_name)
    # if env var OTEL_RESOURCE_ATTRIBUTES is given, it will read the service_name
    # from the env variable else defaults to "unknown_service"
    auto_resource = {}
    # populate version if using auto-instrumentation
    if auto_instrumentation_version:
        auto_resource[
            ResourceAttributes.TELEMETRY_AUTO_VERSION
        ] = auto_instrumentation_version
    resource = Resource.create(auto_resource)

    _init_tracing(
        exporters=trace_exporters,
        id_generator=id_generator,
        sampler=sampler,
        resource=resource,
    )
    _init_metrics(metric_exporters, resource)
    logging_enabled = os.getenv(
        _OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED, "false"
    )
    if logging_enabled.strip().lower() == "true":
        _init_logging(log_exporters, resource)


class _BaseConfigurator(ABC):
    """An ABC for configurators

    Configurators are used to configure
    SDKs (i.e. TracerProvider, MeterProvider, Processors...)
    to reduce the amount of manual configuration required.
    """

    _instance = None
    _is_instrumented = False

    def __new__(cls, *args, **kwargs):
        if cls._instance is None:
            cls._instance = object.__new__(cls, *args, **kwargs)

        return cls._instance

    @abstractmethod
    def _configure(self, **kwargs):
        """Configure the SDK"""

    def configure(self, **kwargs):
        """Configure the SDK"""
        self._configure(**kwargs)


class _OTelSDKConfigurator(_BaseConfigurator):
    """A basic Configurator by OTel Python for initializing OTel SDK components

    Initializes several crucial OTel SDK components (i.e. TracerProvider,
    MeterProvider, Processors...) according to a default implementation. Other
    Configurators can subclass and slightly alter this initialization.

    NOTE: This class should not be instantiated nor should it become an entry
    point on the `opentelemetry-sdk` package. Instead, distros should subclass
    this Configurator and enchance it as needed.
    """

    def _configure(self, **kwargs):
        _initialize_components(kwargs.get("auto_instrumentation_version"))

virt/lib/python3.9/site-packages/mysql/opentelemetry/sdk/_configuration/__init__.py
PrinceDisant/LostInNL
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


from mysql.opentelemetry.sdk._logs._internal import (
    LogData,
    Logger,
    LoggerProvider,
    LoggingHandler,
    LogRecord,
    LogRecordProcessor,
)

__all__ = [
    "LogData",
    "Logger",
    "LoggerProvider",
    "LoggingHandler",
    "LogRecord",
    "LogRecordProcessor",
]

virt/lib/python3.9/site-packages/mysql/opentelemetry/sdk/_logs/__init__.py
PrinceDisant/LostInNL
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import abc
import atexit
import concurrent.futures
import json
import logging
import threading
import traceback

from time import time_ns
from typing import Any, Callable, Optional, Tuple, Union

from mysql.opentelemetry._logs import (
    Logger as APILogger,
    LoggerProvider as APILoggerProvider,
    LogRecord as APILogRecord,
    SeverityNumber,
    get_logger,
    get_logger_provider,
    std_to_otel,
)
from mysql.opentelemetry.sdk.resources import Resource
from mysql.opentelemetry.sdk.util import ns_to_iso_str
from mysql.opentelemetry.sdk.util.instrumentation import InstrumentationScope
from mysql.opentelemetry.semconv.trace import SpanAttributes
from mysql.opentelemetry.trace import format_span_id, format_trace_id, get_current_span
from mysql.opentelemetry.trace.span import TraceFlags
from mysql.opentelemetry.util.types import Attributes

_logger = logging.getLogger(__name__)


class LogRecord(APILogRecord):
    """A LogRecord instance represents an event being logged.

    LogRecord instances are created and emitted via `Logger`
    every time something is logged. They contain all the information
    pertinent to the event being logged.
    """

    def __init__(
        self,
        timestamp: Optional[int] = None,
        observed_timestamp: Optional[int] = None,
        trace_id: Optional[int] = None,
        span_id: Optional[int] = None,
        trace_flags: Optional[TraceFlags] = None,
        severity_text: Optional[str] = None,
        severity_number: Optional[SeverityNumber] = None,
        body: Optional[Any] = None,
        resource: Optional[Resource] = None,
        attributes: Optional[Attributes] = None,
    ):
        super().__init__(
            **{
                "timestamp": timestamp,
                "observed_timestamp": observed_timestamp,
                "trace_id": trace_id,
                "span_id": span_id,
                "trace_flags": trace_flags,
                "severity_text": severity_text,
                "severity_number": severity_number,
                "body": body,
                "attributes": attributes,
            }
        )
        self.resource = resource

    def __eq__(self, other: object) -> bool:
        if not isinstance(other, LogRecord):
            return NotImplemented
        return self.__dict__ == other.__dict__

    def to_json(self, indent=4) -> str:
        return json.dumps(
            {
                "body": self.body,
                "severity_number": repr(self.severity_number),
                "severity_text": self.severity_text,
                "attributes": self.attributes,
                "timestamp": ns_to_iso_str(self.timestamp),
                "trace_id": f"0x{format_trace_id(self.trace_id)}"
                if self.trace_id is not None
                else "",
                "span_id": f"0x{format_span_id(self.span_id)}"
                if self.span_id is not None
                else "",
                "trace_flags": self.trace_flags,
                "resource": repr(self.resource.attributes) if self.resource else "",
            },
            indent=indent,
        )


class LogData:
    """Readable LogRecord data plus associated InstrumentationLibrary."""

    def __init__(
        self,
        log_record: LogRecord,
        instrumentation_scope: InstrumentationScope,
    ):
        self.log_record = log_record
        self.instrumentation_scope = instrumentation_scope


class LogRecordProcessor(abc.ABC):
    """Interface to hook the log record emitting action.

    Log processors can be registered directly using
    :func:`LoggerProvider.add_log_record_processor` and they are invoked
    in the same order as they were registered.
    """

    @abc.abstractmethod
    def emit(self, log_data: LogData):
        """Emits the `LogData`"""

    @abc.abstractmethod
    def shutdown(self):
        """Called when a :class:`mysql.opentelemetry.sdk._logs.Logger` is shutdown"""

    @abc.abstractmethod
    def force_flush(self, timeout_millis: int = 30000):
        """Export all the received logs to the configured Exporter that have not yet
        been exported.

        Args:
            timeout_millis: The maximum amount of time to wait for logs to be
                exported.

        Returns:
            False if the timeout is exceeded, True otherwise.
        """


# Temporary fix until https://github.com/PyCQA/pylint/issues/4098 is resolved
# pylint:disable=no-member
class SynchronousMultiLogRecordProcessor(LogRecordProcessor):
    """Implementation of class:`LogRecordProcessor` that forwards all received
    events to a list of log processors sequentially.

    The underlying log processors are called in sequential order as they were
    added.
    """

    def __init__(self):
        # use a tuple to avoid race conditions when adding a new log and
        # iterating through it on "emit".
        self._log_record_processors = ()  # type: Tuple[LogRecordProcessor, ...]
        self._lock = threading.Lock()

    def add_log_record_processor(
        self, log_record_processor: LogRecordProcessor
    ) -> None:
        """Adds a Logprocessor to the list of log processors handled by this instance"""
        with self._lock:
            self._log_record_processors += (log_record_processor,)

    def emit(self, log_data: LogData) -> None:
        for lp in self._log_record_processors:
            lp.emit(log_data)

    def shutdown(self) -> None:
        """Shutdown the log processors one by one"""
        for lp in self._log_record_processors:
            lp.shutdown()

    def force_flush(self, timeout_millis: int = 30000) -> bool:
        """Force flush the log processors one by one

        Args:
            timeout_millis: The maximum amount of time to wait for logs to be
                exported. If the first n log processors exceeded the timeout
                then remaining log processors will not be flushed.

        Returns:
            True if all the log processors flushes the logs within timeout,
            False otherwise.
        """
        deadline_ns = time_ns() + timeout_millis * 1000000
        for lp in self._log_record_processors:
            current_ts = time_ns()
            if current_ts >= deadline_ns:
                return False

            if not lp.force_flush((deadline_ns - current_ts) // 1000000):
                return False

        return True


class ConcurrentMultiLogRecordProcessor(LogRecordProcessor):
    """Implementation of :class:`LogRecordProcessor` that forwards all received
    events to a list of log processors in parallel.

    Calls to the underlying log processors are forwarded in parallel by
    submitting them to a thread pool executor and waiting until each log
    processor finished its work.

    Args:
        max_workers: The number of threads managed by the thread pool executor
            and thus defining how many log processors can work in parallel.
    """

    def __init__(self, max_workers: int = 2):
        # use a tuple to avoid race conditions when adding a new log and
        # iterating through it on "emit".
        self._log_record_processors = ()  # type: Tuple[LogRecordProcessor, ...]
        self._lock = threading.Lock()
        self._executor = concurrent.futures.ThreadPoolExecutor(max_workers=max_workers)

    def add_log_record_processor(self, log_record_processor: LogRecordProcessor):
        with self._lock:
            self._log_record_processors += (log_record_processor,)

    def _submit_and_wait(
        self,
        func: Callable[[LogRecordProcessor], Callable[..., None]],
        *args: Any,
        **kwargs: Any,
    ):
        futures = []
        for lp in self._log_record_processors:
            future = self._executor.submit(func(lp), *args, **kwargs)
            futures.append(future)
        for future in futures:
            future.result()

    def emit(self, log_data: LogData):
        self._submit_and_wait(lambda lp: lp.emit, log_data)

    def shutdown(self):
        self._submit_and_wait(lambda lp: lp.shutdown)

    def force_flush(self, timeout_millis: int = 30000) -> bool:
        """Force flush the log processors in parallel.

        Args:
            timeout_millis: The maximum amount of time to wait for logs to be
                exported.

        Returns:
            True if all the log processors flushes the logs within timeout,
            False otherwise.
        """
        futures = []
        for lp in self._log_record_processors:
            future = self._executor.submit(lp.force_flush, timeout_millis)
            futures.append(future)

        done_futures, not_done_futures = concurrent.futures.wait(
            futures, timeout_millis / 1e3
        )

        if not_done_futures:
            return False

        for future in done_futures:
            if not future.result():
                return False

        return True


# skip natural LogRecord attributes
# http://docs.python.org/library/logging.html#logrecord-attributes
_RESERVED_ATTRS = frozenset(
    (
        "asctime",
        "args",
        "created",
        "exc_info",
        "exc_text",
        "filename",
        "funcName",
        "message",
        "levelname",
        "levelno",
        "lineno",
        "module",
        "msecs",
        "msg",
        "name",
        "pathname",
        "process",
        "processName",
        "relativeCreated",
        "stack_info",
        "thread",
        "threadName",
    )
)


class LoggingHandler(logging.Handler):
    """A handler class which writes logging records, in OTLP format, to
    a network destination or file. Supports signals from the `logging` module.
    https://docs.python.org/3/library/logging.html
    """

    def __init__(
        self,
        level=logging.NOTSET,
        logger_provider=None,
    ) -> None:
        super().__init__(level=level)
        self._logger_provider = logger_provider or get_logger_provider()
        self._logger = get_logger(__name__, logger_provider=self._logger_provider)

    @staticmethod
    def _get_attributes(record: logging.LogRecord) -> Attributes:
        attributes = {k: v for k, v in vars(record).items() if k not in _RESERVED_ATTRS}
        if record.exc_info:
            exc_type = ""
            message = ""
            stack_trace = ""
            exctype, value, tb = record.exc_info
            if exctype is not None:
                exc_type = exctype.__name__
            if value is not None and value.args:
                message = value.args[0]
            if tb is not None:
                # https://github.com/open-telemetry/opentelemetry-specification/blob/9fa7c656b26647b27e485a6af7e38dc716eba98a/specification/trace/semantic_conventions/exceptions.md#stacktrace-representation
                stack_trace = "".join(traceback.format_exception(*record.exc_info))
            attributes[SpanAttributes.EXCEPTION_TYPE] = exc_type
            attributes[SpanAttributes.EXCEPTION_MESSAGE] = message
            attributes[SpanAttributes.EXCEPTION_STACKTRACE] = stack_trace
        return attributes

    def _translate(self, record: logging.LogRecord) -> LogRecord:
        timestamp = int(record.created * 1e9)
        span_context = get_current_span().get_span_context()
        attributes = self._get_attributes(record)
        severity_number = std_to_otel(record.levelno)
        return LogRecord(
            timestamp=timestamp,
            trace_id=span_context.trace_id,
            span_id=span_context.span_id,
            trace_flags=span_context.trace_flags,
            severity_text=record.levelname,
            severity_number=severity_number,
            body=record.getMessage(),
            resource=self._logger.resource,
            attributes=attributes,
        )

    def emit(self, record: logging.LogRecord) -> None:
        """
        Emit a record.

        The record is translated to OTel format, and then sent across the pipeline.
        """
        self._logger.emit(self._translate(record))

    def flush(self) -> None:
        """
        Flushes the logging output.
        """
        self._logger_provider.force_flush()


class Logger(APILogger):
    def __init__(
        self,
        resource: Resource,
        multi_log_record_processor: Union[
            SynchronousMultiLogRecordProcessor,
            ConcurrentMultiLogRecordProcessor,
        ],
        instrumentation_scope: InstrumentationScope,
    ):
        super().__init__(
            instrumentation_scope.name,
            instrumentation_scope.version,
            instrumentation_scope.schema_url,
        )
        self._resource = resource
        self._multi_log_record_processor = multi_log_record_processor
        self._instrumentation_scope = instrumentation_scope

    @property
    def resource(self):
        return self._resource

    def emit(self, record: LogRecord):
        """Emits the :class:`LogData` by associating :class:`LogRecord`
        and instrumentation info.
        """
        log_data = LogData(record, self._instrumentation_scope)
        self._multi_log_record_processor.emit(log_data)


class LoggerProvider(APILoggerProvider):
    def __init__(
        self,
        resource: Resource = Resource.create(),
        shutdown_on_exit: bool = True,
        multi_log_record_processor: Union[
            SynchronousMultiLogRecordProcessor,
            ConcurrentMultiLogRecordProcessor,
        ] = None,
    ):
        self._resource = resource
        self._multi_log_record_processor = (
            multi_log_record_processor or SynchronousMultiLogRecordProcessor()
        )
        self._at_exit_handler = None
        if shutdown_on_exit:
            self._at_exit_handler = atexit.register(self.shutdown)

    @property
    def resource(self):
        return self._resource

    def get_logger(
        self,
        name: str,
        version: Optional[str] = None,
        schema_url: Optional[str] = None,
    ) -> Logger:
        return Logger(
            self._resource,
            self._multi_log_record_processor,
            InstrumentationScope(
                name,
                version,
                schema_url,
            ),
        )

    def add_log_record_processor(self, log_record_processor: LogRecordProcessor):
        """Registers a new :class:`LogRecordProcessor` for this `LoggerProvider` instance.

        The log processors are invoked in the same order they are registered.
        """
        self._multi_log_record_processor.add_log_record_processor(log_record_processor)

    def shutdown(self):
        """Shuts down the log processors."""
        self._multi_log_record_processor.shutdown()
        if self._at_exit_handler is not None:
            atexit.unregister(self._at_exit_handler)
            self._at_exit_handler = None

    def force_flush(self, timeout_millis: int = 30000) -> bool:
        """Force flush the log processors.

        Args:
            timeout_millis: The maximum amount of time to wait for logs to be
                exported.

        Returns:
            True if all the log processors flushes the logs within timeout,
            False otherwise.
        """
        return self._multi_log_record_processor.force_flush(timeout_millis)

virt/lib/python3.9/site-packages/mysql/opentelemetry/sdk/_logs/_internal/__init__ 2.py
PrinceDisant/LostInNL
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import abc
import atexit
import concurrent.futures
import json
import logging
import threading
import traceback

from time import time_ns
from typing import Any, Callable, Optional, Tuple, Union

from mysql.opentelemetry._logs import (
    Logger as APILogger,
    LoggerProvider as APILoggerProvider,
    LogRecord as APILogRecord,
    SeverityNumber,
    get_logger,
    get_logger_provider,
    std_to_otel,
)
from mysql.opentelemetry.sdk.resources import Resource
from mysql.opentelemetry.sdk.util import ns_to_iso_str
from mysql.opentelemetry.sdk.util.instrumentation import InstrumentationScope
from mysql.opentelemetry.semconv.trace import SpanAttributes
from mysql.opentelemetry.trace import format_span_id, format_trace_id, get_current_span
from mysql.opentelemetry.trace.span import TraceFlags
from mysql.opentelemetry.util.types import Attributes

_logger = logging.getLogger(__name__)


class LogRecord(APILogRecord):
    """A LogRecord instance represents an event being logged.

    LogRecord instances are created and emitted via `Logger`
    every time something is logged. They contain all the information
    pertinent to the event being logged.
    """

    def __init__(
        self,
        timestamp: Optional[int] = None,
        observed_timestamp: Optional[int] = None,
        trace_id: Optional[int] = None,
        span_id: Optional[int] = None,
        trace_flags: Optional[TraceFlags] = None,
        severity_text: Optional[str] = None,
        severity_number: Optional[SeverityNumber] = None,
        body: Optional[Any] = None,
        resource: Optional[Resource] = None,
        attributes: Optional[Attributes] = None,
    ):
        super().__init__(
            **{
                "timestamp": timestamp,
                "observed_timestamp": observed_timestamp,
                "trace_id": trace_id,
                "span_id": span_id,
                "trace_flags": trace_flags,
                "severity_text": severity_text,
                "severity_number": severity_number,
                "body": body,
                "attributes": attributes,
            }
        )
        self.resource = resource

    def __eq__(self, other: object) -> bool:
        if not isinstance(other, LogRecord):
            return NotImplemented
        return self.__dict__ == other.__dict__

    def to_json(self, indent=4) -> str:
        return json.dumps(
            {
                "body": self.body,
                "severity_number": repr(self.severity_number),
                "severity_text": self.severity_text,
                "attributes": self.attributes,
                "timestamp": ns_to_iso_str(self.timestamp),
                "trace_id": f"0x{format_trace_id(self.trace_id)}"
                if self.trace_id is not None
                else "",
                "span_id": f"0x{format_span_id(self.span_id)}"
                if self.span_id is not None
                else "",
                "trace_flags": self.trace_flags,
                "resource": repr(self.resource.attributes) if self.resource else "",
            },
            indent=indent,
        )


class LogData:
    """Readable LogRecord data plus associated InstrumentationLibrary."""

    def __init__(
        self,
        log_record: LogRecord,
        instrumentation_scope: InstrumentationScope,
    ):
        self.log_record = log_record
        self.instrumentation_scope = instrumentation_scope


class LogRecordProcessor(abc.ABC):
    """Interface to hook the log record emitting action.

    Log processors can be registered directly using
    :func:`LoggerProvider.add_log_record_processor` and they are invoked
    in the same order as they were registered.
    """

    @abc.abstractmethod
    def emit(self, log_data: LogData):
        """Emits the `LogData`"""

    @abc.abstractmethod
    def shutdown(self):
        """Called when a :class:`mysql.opentelemetry.sdk._logs.Logger` is shutdown"""

    @abc.abstractmethod
    def force_flush(self, timeout_millis: int = 30000):
        """Export all the received logs to the configured Exporter that have not yet
        been exported.

        Args:
            timeout_millis: The maximum amount of time to wait for logs to be
                exported.

        Returns:
            False if the timeout is exceeded, True otherwise.
        """


# Temporary fix until https://github.com/PyCQA/pylint/issues/4098 is resolved
# pylint:disable=no-member
class SynchronousMultiLogRecordProcessor(LogRecordProcessor):
    """Implementation of class:`LogRecordProcessor` that forwards all received
    events to a list of log processors sequentially.

    The underlying log processors are called in sequential order as they were
    added.
    """

    def __init__(self):
        # use a tuple to avoid race conditions when adding a new log and
        # iterating through it on "emit".
        self._log_record_processors = ()  # type: Tuple[LogRecordProcessor, ...]
        self._lock = threading.Lock()

    def add_log_record_processor(
        self, log_record_processor: LogRecordProcessor
    ) -> None:
        """Adds a Logprocessor to the list of log processors handled by this instance"""
        with self._lock:
            self._log_record_processors += (log_record_processor,)

    def emit(self, log_data: LogData) -> None:
        for lp in self._log_record_processors:
            lp.emit(log_data)

    def shutdown(self) -> None:
        """Shutdown the log processors one by one"""
        for lp in self._log_record_processors:
            lp.shutdown()

    def force_flush(self, timeout_millis: int = 30000) -> bool:
        """Force flush the log processors one by one

        Args:
            timeout_millis: The maximum amount of time to wait for logs to be
                exported. If the first n log processors exceeded the timeout
                then remaining log processors will not be flushed.

        Returns:
            True if all the log processors flushes the logs within timeout,
            False otherwise.
        """
        deadline_ns = time_ns() + timeout_millis * 1000000
        for lp in self._log_record_processors:
            current_ts = time_ns()
            if current_ts >= deadline_ns:
                return False

            if not lp.force_flush((deadline_ns - current_ts) // 1000000):
                return False

        return True


class ConcurrentMultiLogRecordProcessor(LogRecordProcessor):
    """Implementation of :class:`LogRecordProcessor` that forwards all received
    events to a list of log processors in parallel.

    Calls to the underlying log processors are forwarded in parallel by
    submitting them to a thread pool executor and waiting until each log
    processor finished its work.

    Args:
        max_workers: The number of threads managed by the thread pool executor
            and thus defining how many log processors can work in parallel.
    """

    def __init__(self, max_workers: int = 2):
        # use a tuple to avoid race conditions when adding a new log and
        # iterating through it on "emit".
        self._log_record_processors = ()  # type: Tuple[LogRecordProcessor, ...]
        self._lock = threading.Lock()
        self._executor = concurrent.futures.ThreadPoolExecutor(max_workers=max_workers)

    def add_log_record_processor(self, log_record_processor: LogRecordProcessor):
        with self._lock:
            self._log_record_processors += (log_record_processor,)

    def _submit_and_wait(
        self,
        func: Callable[[LogRecordProcessor], Callable[..., None]],
        *args: Any,
        **kwargs: Any,
    ):
        futures = []
        for lp in self._log_record_processors:
            future = self._executor.submit(func(lp), *args, **kwargs)
            futures.append(future)
        for future in futures:
            future.result()

    def emit(self, log_data: LogData):
        self._submit_and_wait(lambda lp: lp.emit, log_data)

    def shutdown(self):
        self._submit_and_wait(lambda lp: lp.shutdown)

    def force_flush(self, timeout_millis: int = 30000) -> bool:
        """Force flush the log processors in parallel.

        Args:
            timeout_millis: The maximum amount of time to wait for logs to be
                exported.

        Returns:
            True if all the log processors flushes the logs within timeout,
            False otherwise.
        """
        futures = []
        for lp in self._log_record_processors:
            future = self._executor.submit(lp.force_flush, timeout_millis)
            futures.append(future)

        done_futures, not_done_futures = concurrent.futures.wait(
            futures, timeout_millis / 1e3
        )

        if not_done_futures:
            return False

        for future in done_futures:
            if not future.result():
                return False

        return True


# skip natural LogRecord attributes
# http://docs.python.org/library/logging.html#logrecord-attributes
_RESERVED_ATTRS = frozenset(
    (
        "asctime",
        "args",
        "created",
        "exc_info",
        "exc_text",
        "filename",
        "funcName",
        "message",
        "levelname",
        "levelno",
        "lineno",
        "module",
        "msecs",
        "msg",
        "name",
        "pathname",
        "process",
        "processName",
        "relativeCreated",
        "stack_info",
        "thread",
        "threadName",
    )
)


class LoggingHandler(logging.Handler):
    """A handler class which writes logging records, in OTLP format, to
    a network destination or file. Supports signals from the `logging` module.
    https://docs.python.org/3/library/logging.html
    """

    def __init__(
        self,
        level=logging.NOTSET,
        logger_provider=None,
    ) -> None:
        super().__init__(level=level)
        self._logger_provider = logger_provider or get_logger_provider()
        self._logger = get_logger(__name__, logger_provider=self._logger_provider)

    @staticmethod
    def _get_attributes(record: logging.LogRecord) -> Attributes:
        attributes = {k: v for k, v in vars(record).items() if k not in _RESERVED_ATTRS}
        if record.exc_info:
            exc_type = ""
            message = ""
            stack_trace = ""
            exctype, value, tb = record.exc_info
            if exctype is not None:
                exc_type = exctype.__name__
            if value is not None and value.args:
                message = value.args[0]
            if tb is not None:
                # https://github.com/open-telemetry/opentelemetry-specification/blob/9fa7c656b26647b27e485a6af7e38dc716eba98a/specification/trace/semantic_conventions/exceptions.md#stacktrace-representation
                stack_trace = "".join(traceback.format_exception(*record.exc_info))
            attributes[SpanAttributes.EXCEPTION_TYPE] = exc_type
            attributes[SpanAttributes.EXCEPTION_MESSAGE] = message
            attributes[SpanAttributes.EXCEPTION_STACKTRACE] = stack_trace
        return attributes

    def _translate(self, record: logging.LogRecord) -> LogRecord:
        timestamp = int(record.created * 1e9)
        span_context = get_current_span().get_span_context()
        attributes = self._get_attributes(record)
        severity_number = std_to_otel(record.levelno)
        return LogRecord(
            timestamp=timestamp,
            trace_id=span_context.trace_id,
            span_id=span_context.span_id,
            trace_flags=span_context.trace_flags,
            severity_text=record.levelname,
            severity_number=severity_number,
            body=record.getMessage(),
            resource=self._logger.resource,
            attributes=attributes,
        )

    def emit(self, record: logging.LogRecord) -> None:
        """
        Emit a record.

        The record is translated to OTel format, and then sent across the pipeline.
        """
        self._logger.emit(self._translate(record))

    def flush(self) -> None:
        """
        Flushes the logging output.
        """
        self._logger_provider.force_flush()


class Logger(APILogger):
    def __init__(
        self,
        resource: Resource,
        multi_log_record_processor: Union[
            SynchronousMultiLogRecordProcessor,
            ConcurrentMultiLogRecordProcessor,
        ],
        instrumentation_scope: InstrumentationScope,
    ):
        super().__init__(
            instrumentation_scope.name,
            instrumentation_scope.version,
            instrumentation_scope.schema_url,
        )
        self._resource = resource
        self._multi_log_record_processor = multi_log_record_processor
        self._instrumentation_scope = instrumentation_scope

    @property
    def resource(self):
        return self._resource

    def emit(self, record: LogRecord):
        """Emits the :class:`LogData` by associating :class:`LogRecord`
        and instrumentation info.
        """
        log_data = LogData(record, self._instrumentation_scope)
        self._multi_log_record_processor.emit(log_data)


class LoggerProvider(APILoggerProvider):
    def __init__(
        self,
        resource: Resource = Resource.create(),
        shutdown_on_exit: bool = True,
        multi_log_record_processor: Union[
            SynchronousMultiLogRecordProcessor,
            ConcurrentMultiLogRecordProcessor,
        ] = None,
    ):
        self._resource = resource
        self._multi_log_record_processor = (
            multi_log_record_processor or SynchronousMultiLogRecordProcessor()
        )
        self._at_exit_handler = None
        if shutdown_on_exit:
            self._at_exit_handler = atexit.register(self.shutdown)

    @property
    def resource(self):
        return self._resource

    def get_logger(
        self,
        name: str,
        version: Optional[str] = None,
        schema_url: Optional[str] = None,
    ) -> Logger:
        return Logger(
            self._resource,
            self._multi_log_record_processor,
            InstrumentationScope(
                name,
                version,
                schema_url,
            ),
        )

    def add_log_record_processor(self, log_record_processor: LogRecordProcessor):
        """Registers a new :class:`LogRecordProcessor` for this `LoggerProvider` instance.

        The log processors are invoked in the same order they are registered.
        """
        self._multi_log_record_processor.add_log_record_processor(log_record_processor)

    def shutdown(self):
        """Shuts down the log processors."""
        self._multi_log_record_processor.shutdown()
        if self._at_exit_handler is not None:
            atexit.unregister(self._at_exit_handler)
            self._at_exit_handler = None

    def force_flush(self, timeout_millis: int = 30000) -> bool:
        """Force flush the log processors.

        Args:
            timeout_millis: The maximum amount of time to wait for logs to be
                exported.

        Returns:
            True if all the log processors flushes the logs within timeout,
            False otherwise.
        """
        return self._multi_log_record_processor.force_flush(timeout_millis)

virt/lib/python3.9/site-packages/mysql/opentelemetry/sdk/_logs/_internal/__init__ 3.py
PrinceDisant/LostInNL
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import abc
import atexit
import concurrent.futures
import json
import logging
import threading
import traceback

from time import time_ns
from typing import Any, Callable, Optional, Tuple, Union

from mysql.opentelemetry._logs import (
    Logger as APILogger,
    LoggerProvider as APILoggerProvider,
    LogRecord as APILogRecord,
    SeverityNumber,
    get_logger,
    get_logger_provider,
    std_to_otel,
)
from mysql.opentelemetry.sdk.resources import Resource
from mysql.opentelemetry.sdk.util import ns_to_iso_str
from mysql.opentelemetry.sdk.util.instrumentation import InstrumentationScope
from mysql.opentelemetry.semconv.trace import SpanAttributes
from mysql.opentelemetry.trace import format_span_id, format_trace_id, get_current_span
from mysql.opentelemetry.trace.span import TraceFlags
from mysql.opentelemetry.util.types import Attributes

_logger = logging.getLogger(__name__)


class LogRecord(APILogRecord):
    """A LogRecord instance represents an event being logged.

    LogRecord instances are created and emitted via `Logger`
    every time something is logged. They contain all the information
    pertinent to the event being logged.
    """

    def __init__(
        self,
        timestamp: Optional[int] = None,
        observed_timestamp: Optional[int] = None,
        trace_id: Optional[int] = None,
        span_id: Optional[int] = None,
        trace_flags: Optional[TraceFlags] = None,
        severity_text: Optional[str] = None,
        severity_number: Optional[SeverityNumber] = None,
        body: Optional[Any] = None,
        resource: Optional[Resource] = None,
        attributes: Optional[Attributes] = None,
    ):
        super().__init__(
            **{
                "timestamp": timestamp,
                "observed_timestamp": observed_timestamp,
                "trace_id": trace_id,
                "span_id": span_id,
                "trace_flags": trace_flags,
                "severity_text": severity_text,
                "severity_number": severity_number,
                "body": body,
                "attributes": attributes,
            }
        )
        self.resource = resource

    def __eq__(self, other: object) -> bool:
        if not isinstance(other, LogRecord):
            return NotImplemented
        return self.__dict__ == other.__dict__

    def to_json(self, indent=4) -> str:
        return json.dumps(
            {
                "body": self.body,
                "severity_number": repr(self.severity_number),
                "severity_text": self.severity_text,
                "attributes": self.attributes,
                "timestamp": ns_to_iso_str(self.timestamp),
                "trace_id": f"0x{format_trace_id(self.trace_id)}"
                if self.trace_id is not None
                else "",
                "span_id": f"0x{format_span_id(self.span_id)}"
                if self.span_id is not None
                else "",
                "trace_flags": self.trace_flags,
                "resource": repr(self.resource.attributes) if self.resource else "",
            },
            indent=indent,
        )


class LogData:
    """Readable LogRecord data plus associated InstrumentationLibrary."""

    def __init__(
        self,
        log_record: LogRecord,
        instrumentation_scope: InstrumentationScope,
    ):
        self.log_record = log_record
        self.instrumentation_scope = instrumentation_scope


class LogRecordProcessor(abc.ABC):
    """Interface to hook the log record emitting action.

    Log processors can be registered directly using
    :func:`LoggerProvider.add_log_record_processor` and they are invoked
    in the same order as they were registered.
    """

    @abc.abstractmethod
    def emit(self, log_data: LogData):
        """Emits the `LogData`"""

    @abc.abstractmethod
    def shutdown(self):
        """Called when a :class:`mysql.opentelemetry.sdk._logs.Logger` is shutdown"""

    @abc.abstractmethod
    def force_flush(self, timeout_millis: int = 30000):
        """Export all the received logs to the configured Exporter that have not yet
        been exported.

        Args:
            timeout_millis: The maximum amount of time to wait for logs to be
                exported.

        Returns:
            False if the timeout is exceeded, True otherwise.
        """


# Temporary fix until https://github.com/PyCQA/pylint/issues/4098 is resolved
# pylint:disable=no-member
class SynchronousMultiLogRecordProcessor(LogRecordProcessor):
    """Implementation of class:`LogRecordProcessor` that forwards all received
    events to a list of log processors sequentially.

    The underlying log processors are called in sequential order as they were
    added.
    """

    def __init__(self):
        # use a tuple to avoid race conditions when adding a new log and
        # iterating through it on "emit".
        self._log_record_processors = ()  # type: Tuple[LogRecordProcessor, ...]
        self._lock = threading.Lock()

    def add_log_record_processor(
        self, log_record_processor: LogRecordProcessor
    ) -> None:
        """Adds a Logprocessor to the list of log processors handled by this instance"""
        with self._lock:
            self._log_record_processors += (log_record_processor,)

    def emit(self, log_data: LogData) -> None:
        for lp in self._log_record_processors:
            lp.emit(log_data)

    def shutdown(self) -> None:
        """Shutdown the log processors one by one"""
        for lp in self._log_record_processors:
            lp.shutdown()

    def force_flush(self, timeout_millis: int = 30000) -> bool:
        """Force flush the log processors one by one

        Args:
            timeout_millis: The maximum amount of time to wait for logs to be
                exported. If the first n log processors exceeded the timeout
                then remaining log processors will not be flushed.

        Returns:
            True if all the log processors flushes the logs within timeout,
            False otherwise.
        """
        deadline_ns = time_ns() + timeout_millis * 1000000
        for lp in self._log_record_processors:
            current_ts = time_ns()
            if current_ts >= deadline_ns:
                return False

            if not lp.force_flush((deadline_ns - current_ts) // 1000000):
                return False

        return True


class ConcurrentMultiLogRecordProcessor(LogRecordProcessor):
    """Implementation of :class:`LogRecordProcessor` that forwards all received
    events to a list of log processors in parallel.

    Calls to the underlying log processors are forwarded in parallel by
    submitting them to a thread pool executor and waiting until each log
    processor finished its work.

    Args:
        max_workers: The number of threads managed by the thread pool executor
            and thus defining how many log processors can work in parallel.
    """

    def __init__(self, max_workers: int = 2):
        # use a tuple to avoid race conditions when adding a new log and
        # iterating through it on "emit".
        self._log_record_processors = ()  # type: Tuple[LogRecordProcessor, ...]
        self._lock = threading.Lock()
        self._executor = concurrent.futures.ThreadPoolExecutor(max_workers=max_workers)

    def add_log_record_processor(self, log_record_processor: LogRecordProcessor):
        with self._lock:
            self._log_record_processors += (log_record_processor,)

    def _submit_and_wait(
        self,
        func: Callable[[LogRecordProcessor], Callable[..., None]],
        *args: Any,
        **kwargs: Any,
    ):
        futures = []
        for lp in self._log_record_processors:
            future = self._executor.submit(func(lp), *args, **kwargs)
            futures.append(future)
        for future in futures:
            future.result()

    def emit(self, log_data: LogData):
        self._submit_and_wait(lambda lp: lp.emit, log_data)

    def shutdown(self):
        self._submit_and_wait(lambda lp: lp.shutdown)

    def force_flush(self, timeout_millis: int = 30000) -> bool:
        """Force flush the log processors in parallel.

        Args:
            timeout_millis: The maximum amount of time to wait for logs to be
                exported.

        Returns:
            True if all the log processors flushes the logs within timeout,
            False otherwise.
        """
        futures = []
        for lp in self._log_record_processors:
            future = self._executor.submit(lp.force_flush, timeout_millis)
            futures.append(future)

        done_futures, not_done_futures = concurrent.futures.wait(
            futures, timeout_millis / 1e3
        )

        if not_done_futures:
            return False

        for future in done_futures:
            if not future.result():
                return False

        return True


# skip natural LogRecord attributes
# http://docs.python.org/library/logging.html#logrecord-attributes
_RESERVED_ATTRS = frozenset(
    (
        "asctime",
        "args",
        "created",
        "exc_info",
        "exc_text",
        "filename",
        "funcName",
        "message",
        "levelname",
        "levelno",
        "lineno",
        "module",
        "msecs",
        "msg",
        "name",
        "pathname",
        "process",
        "processName",
        "relativeCreated",
        "stack_info",
        "thread",
        "threadName",
    )
)


class LoggingHandler(logging.Handler):
    """A handler class which writes logging records, in OTLP format, to
    a network destination or file. Supports signals from the `logging` module.
    https://docs.python.org/3/library/logging.html
    """

    def __init__(
        self,
        level=logging.NOTSET,
        logger_provider=None,
    ) -> None:
        super().__init__(level=level)
        self._logger_provider = logger_provider or get_logger_provider()
        self._logger = get_logger(__name__, logger_provider=self._logger_provider)

    @staticmethod
    def _get_attributes(record: logging.LogRecord) -> Attributes:
        attributes = {k: v for k, v in vars(record).items() if k not in _RESERVED_ATTRS}
        if record.exc_info:
            exc_type = ""
            message = ""
            stack_trace = ""
            exctype, value, tb = record.exc_info
            if exctype is not None:
                exc_type = exctype.__name__
            if value is not None and value.args:
                message = value.args[0]
            if tb is not None:
                # https://github.com/open-telemetry/opentelemetry-specification/blob/9fa7c656b26647b27e485a6af7e38dc716eba98a/specification/trace/semantic_conventions/exceptions.md#stacktrace-representation
                stack_trace = "".join(traceback.format_exception(*record.exc_info))
            attributes[SpanAttributes.EXCEPTION_TYPE] = exc_type
            attributes[SpanAttributes.EXCEPTION_MESSAGE] = message
            attributes[SpanAttributes.EXCEPTION_STACKTRACE] = stack_trace
        return attributes

    def _translate(self, record: logging.LogRecord) -> LogRecord:
        timestamp = int(record.created * 1e9)
        span_context = get_current_span().get_span_context()
        attributes = self._get_attributes(record)
        severity_number = std_to_otel(record.levelno)
        return LogRecord(
            timestamp=timestamp,
            trace_id=span_context.trace_id,
            span_id=span_context.span_id,
            trace_flags=span_context.trace_flags,
            severity_text=record.levelname,
            severity_number=severity_number,
            body=record.getMessage(),
            resource=self._logger.resource,
            attributes=attributes,
        )

    def emit(self, record: logging.LogRecord) -> None:
        """
        Emit a record.

        The record is translated to OTel format, and then sent across the pipeline.
        """
        self._logger.emit(self._translate(record))

    def flush(self) -> None:
        """
        Flushes the logging output.
        """
        self._logger_provider.force_flush()


class Logger(APILogger):
    def __init__(
        self,
        resource: Resource,
        multi_log_record_processor: Union[
            SynchronousMultiLogRecordProcessor,
            ConcurrentMultiLogRecordProcessor,
        ],
        instrumentation_scope: InstrumentationScope,
    ):
        super().__init__(
            instrumentation_scope.name,
            instrumentation_scope.version,
            instrumentation_scope.schema_url,
        )
        self._resource = resource
        self._multi_log_record_processor = multi_log_record_processor
        self._instrumentation_scope = instrumentation_scope

    @property
    def resource(self):
        return self._resource

    def emit(self, record: LogRecord):
        """Emits the :class:`LogData` by associating :class:`LogRecord`
        and instrumentation info.
        """
        log_data = LogData(record, self._instrumentation_scope)
        self._multi_log_record_processor.emit(log_data)


class LoggerProvider(APILoggerProvider):
    def __init__(
        self,
        resource: Resource = Resource.create(),
        shutdown_on_exit: bool = True,
        multi_log_record_processor: Union[
            SynchronousMultiLogRecordProcessor,
            ConcurrentMultiLogRecordProcessor,
        ] = None,
    ):
        self._resource = resource
        self._multi_log_record_processor = (
            multi_log_record_processor or SynchronousMultiLogRecordProcessor()
        )
        self._at_exit_handler = None
        if shutdown_on_exit:
            self._at_exit_handler = atexit.register(self.shutdown)

    @property
    def resource(self):
        return self._resource

    def get_logger(
        self,
        name: str,
        version: Optional[str] = None,
        schema_url: Optional[str] = None,
    ) -> Logger:
        return Logger(
            self._resource,
            self._multi_log_record_processor,
            InstrumentationScope(
                name,
                version,
                schema_url,
            ),
        )

    def add_log_record_processor(self, log_record_processor: LogRecordProcessor):
        """Registers a new :class:`LogRecordProcessor` for this `LoggerProvider` instance.

        The log processors are invoked in the same order they are registered.
        """
        self._multi_log_record_processor.add_log_record_processor(log_record_processor)

    def shutdown(self):
        """Shuts down the log processors."""
        self._multi_log_record_processor.shutdown()
        if self._at_exit_handler is not None:
            atexit.unregister(self._at_exit_handler)
            self._at_exit_handler = None

    def force_flush(self, timeout_millis: int = 30000) -> bool:
        """Force flush the log processors.

        Args:
            timeout_millis: The maximum amount of time to wait for logs to be
                exported.

        Returns:
            True if all the log processors flushes the logs within timeout,
            False otherwise.
        """
        return self._multi_log_record_processor.force_flush(timeout_millis)

virt/lib/python3.9/site-packages/mysql/opentelemetry/sdk/_logs/_internal/__init__.py
PrinceDisant/LostInNL
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import abc
import collections
import enum
import logging
import os
import sys
import threading

from os import environ, linesep
from time import time_ns
from typing import IO, Callable, Deque, List, Optional, Sequence

from mysql.opentelemetry.context import (
    _SUPPRESS_INSTRUMENTATION_KEY,
    attach,
    detach,
    set_value,
)
from mysql.opentelemetry.sdk._logs import LogData, LogRecord, LogRecordProcessor
from mysql.opentelemetry.sdk.environment_variables import (
    OTEL_BLRP_EXPORT_TIMEOUT,
    OTEL_BLRP_MAX_EXPORT_BATCH_SIZE,
    OTEL_BLRP_MAX_QUEUE_SIZE,
    OTEL_BLRP_SCHEDULE_DELAY,
)
from mysql.opentelemetry.util._once import Once

_DEFAULT_SCHEDULE_DELAY_MILLIS = 5000
_DEFAULT_MAX_EXPORT_BATCH_SIZE = 512
_DEFAULT_EXPORT_TIMEOUT_MILLIS = 30000
_DEFAULT_MAX_QUEUE_SIZE = 2048
_ENV_VAR_INT_VALUE_ERROR_MESSAGE = (
    "Unable to parse value for %s as integer. Defaulting to %s."
)

_logger = logging.getLogger(__name__)


class LogExportResult(enum.Enum):
    SUCCESS = 0
    FAILURE = 1


class LogExporter(abc.ABC):
    """Interface for exporting logs.

    Interface to be implemented by services that want to export logs received
    in their own format.

    To export data this MUST be registered to the :class`mysql.opentelemetry.sdk._logs.Logger` using a
    log processor.
    """

    @abc.abstractmethod
    def export(self, batch: Sequence[LogData]):
        """Exports a batch of logs.

        Args:
            batch: The list of `LogData` objects to be exported

        Returns:
            The result of the export
        """

    @abc.abstractmethod
    def shutdown(self):
        """Shuts down the exporter.

        Called when the SDK is shut down.
        """


class ConsoleLogExporter(LogExporter):
    """Implementation of :class:`LogExporter` that prints log records to the
    console.

    This class can be used for diagnostic purposes. It prints the exported
    log records to the console STDOUT.
    """

    def __init__(
        self,
        out: IO = sys.stdout,
        formatter: Callable[[LogRecord], str] = lambda record: record.to_json()
        + linesep,
    ):
        self.out = out
        self.formatter = formatter

    def export(self, batch: Sequence[LogData]):
        for data in batch:
            self.out.write(self.formatter(data.log_record))
        self.out.flush()
        return LogExportResult.SUCCESS

    def shutdown(self):
        pass


class SimpleLogRecordProcessor(LogRecordProcessor):
    """This is an implementation of LogRecordProcessor which passes
    received logs in the export-friendly LogData representation to the
    configured LogExporter, as soon as they are emitted.
    """

    def __init__(self, exporter: LogExporter):
        self._exporter = exporter
        self._shutdown = False

    def emit(self, log_data: LogData):
        if self._shutdown:
            _logger.warning("Processor is already shutdown, ignoring call")
            return
        token = attach(set_value(_SUPPRESS_INSTRUMENTATION_KEY, True))
        try:
            self._exporter.export((log_data,))
        except Exception:  # pylint: disable=broad-except
            _logger.exception("Exception while exporting logs.")
        detach(token)

    def shutdown(self):
        self._shutdown = True
        self._exporter.shutdown()

    def force_flush(
        self, timeout_millis: int = 30000
    ) -> bool:  # pylint: disable=no-self-use
        return True


class _FlushRequest:
    __slots__ = ["event", "num_log_records"]

    def __init__(self):
        self.event = threading.Event()
        self.num_log_records = 0


_BSP_RESET_ONCE = Once()


class BatchLogRecordProcessor(LogRecordProcessor):
    """This is an implementation of LogRecordProcessor which creates batches of
    received logs in the export-friendly LogData representation and
    send to the configured LogExporter, as soon as they are emitted.

    `BatchLogRecordProcessor` is configurable with the following environment
    variables which correspond to constructor parameters:

    - :envvar:`OTEL_BLRP_SCHEDULE_DELAY`
    - :envvar:`OTEL_BLRP_MAX_QUEUE_SIZE`
    - :envvar:`OTEL_BLRP_MAX_EXPORT_BATCH_SIZE`
    - :envvar:`OTEL_BLRP_EXPORT_TIMEOUT`
    """

    def __init__(
        self,
        exporter: LogExporter,
        schedule_delay_millis: float = None,
        max_export_batch_size: int = None,
        export_timeout_millis: float = None,
        max_queue_size: int = None,
    ):
        if max_queue_size is None:
            max_queue_size = BatchLogRecordProcessor._default_max_queue_size()

        if schedule_delay_millis is None:
            schedule_delay_millis = (
                BatchLogRecordProcessor._default_schedule_delay_millis()
            )

        if max_export_batch_size is None:
            max_export_batch_size = (
                BatchLogRecordProcessor._default_max_export_batch_size()
            )

        if export_timeout_millis is None:
            export_timeout_millis = (
                BatchLogRecordProcessor._default_export_timeout_millis()
            )

        BatchLogRecordProcessor._validate_arguments(
            max_queue_size, schedule_delay_millis, max_export_batch_size
        )

        self._exporter = exporter
        self._max_queue_size = max_queue_size
        self._schedule_delay_millis = schedule_delay_millis
        self._max_export_batch_size = max_export_batch_size
        self._export_timeout_millis = export_timeout_millis
        self._queue = collections.deque([], max_queue_size)  # type: Deque[LogData]
        self._worker_thread = threading.Thread(
            name="OtelBatchLogRecordProcessor",
            target=self.worker,
            daemon=True,
        )
        self._condition = threading.Condition(threading.Lock())
        self._shutdown = False
        self._flush_request = None  # type: Optional[_FlushRequest]
        self._log_records = [
            None
        ] * self._max_export_batch_size  # type: List[Optional[LogData]]
        self._worker_thread.start()
        # Only available in *nix since py37.
        if hasattr(os, "register_at_fork"):
            os.register_at_fork(
                after_in_child=self._at_fork_reinit
            )  # pylint: disable=protected-access
        self._pid = os.getpid()

    def _at_fork_reinit(self):
        self._condition = threading.Condition(threading.Lock())
        self._queue.clear()
        self._worker_thread = threading.Thread(
            name="OtelBatchLogRecordProcessor",
            target=self.worker,
            daemon=True,
        )
        self._worker_thread.start()
        self._pid = os.getpid()

    def worker(self):
        timeout = self._schedule_delay_millis / 1e3
        flush_request = None  # type: Optional[_FlushRequest]
        while not self._shutdown:
            with self._condition:
                if self._shutdown:
                    # shutdown may have been called, avoid further processing
                    break
                flush_request = self._get_and_unset_flush_request()
                if (
                    len(self._queue) < self._max_export_batch_size
                    and flush_request is None
                ):
                    self._condition.wait(timeout)

                    flush_request = self._get_and_unset_flush_request()
                    if not self._queue:
                        timeout = self._schedule_delay_millis / 1e3
                        self._notify_flush_request_finished(flush_request)
                        flush_request = None
                        continue
                    if self._shutdown:
                        break

            start_ns = time_ns()
            self._export(flush_request)
            end_ns = time_ns()
            # subtract the duration of this export call to the next timeout
            timeout = self._schedule_delay_millis / 1e3 - ((end_ns - start_ns) / 1e9)

            self._notify_flush_request_finished(flush_request)
            flush_request = None

        # there might have been a new flush request while export was running
        # and before the done flag switched to true
        with self._condition:
            shutdown_flush_request = self._get_and_unset_flush_request()

        # flush the remaining logs
        self._drain_queue()
        self._notify_flush_request_finished(flush_request)
        self._notify_flush_request_finished(shutdown_flush_request)

    def _export(self, flush_request: Optional[_FlushRequest] = None):
        """Exports logs considering the given flush_request.

        If flush_request is not None then logs are exported in batches
        until the number of exported logs reached or exceeded the num of logs in
        flush_request, otherwise exports at max max_export_batch_size logs.
        """
        if flush_request is None:
            self._export_batch()
            return

        num_log_records = flush_request.num_log_records
        while self._queue:
            exported = self._export_batch()
            num_log_records -= exported

            if num_log_records <= 0:
                break

    def _export_batch(self) -> int:
        """Exports at most max_export_batch_size logs and returns the number of
        exported logs.
        """
        idx = 0
        while idx < self._max_export_batch_size and self._queue:
            record = self._queue.pop()
            self._log_records[idx] = record
            idx += 1
        token = attach(set_value("suppress_instrumentation", True))
        try:
            self._exporter.export(self._log_records[:idx])  # type: ignore
        except Exception:  # pylint: disable=broad-except
            _logger.exception("Exception while exporting logs.")
        detach(token)

        for index in range(idx):
            self._log_records[index] = None
        return idx

    def _drain_queue(self):
        """Export all elements until queue is empty.

        Can only be called from the worker thread context because it invokes
        `export` that is not thread safe.
        """
        while self._queue:
            self._export_batch()

    def _get_and_unset_flush_request(self) -> Optional[_FlushRequest]:
        flush_request = self._flush_request
        self._flush_request = None
        if flush_request is not None:
            flush_request.num_log_records = len(self._queue)
        return flush_request

    @staticmethod
    def _notify_flush_request_finished(
        flush_request: Optional[_FlushRequest] = None,
    ):
        if flush_request is not None:
            flush_request.event.set()

    def _get_or_create_flush_request(self) -> _FlushRequest:
        if self._flush_request is None:
            self._flush_request = _FlushRequest()
        return self._flush_request

    def emit(self, log_data: LogData) -> None:
        """Adds the `LogData` to queue and notifies the waiting threads
        when size of queue reaches max_export_batch_size.
        """
        if self._shutdown:
            return
        if self._pid != os.getpid():
            _BSP_RESET_ONCE.do_once(self._at_fork_reinit)

        self._queue.appendleft(log_data)
        if len(self._queue) >= self._max_export_batch_size:
            with self._condition:
                self._condition.notify()

    def shutdown(self):
        self._shutdown = True
        with self._condition:
            self._condition.notify_all()
        self._worker_thread.join()
        self._exporter.shutdown()

    def force_flush(self, timeout_millis: Optional[int] = None) -> bool:
        if timeout_millis is None:
            timeout_millis = self._export_timeout_millis
        if self._shutdown:
            return True

        with self._condition:
            flush_request = self._get_or_create_flush_request()
            self._condition.notify_all()

        ret = flush_request.event.wait(timeout_millis / 1e3)
        if not ret:
            _logger.warning("Timeout was exceeded in force_flush().")
        return ret

    @staticmethod
    def _default_max_queue_size():
        try:
            return int(environ.get(OTEL_BLRP_MAX_QUEUE_SIZE, _DEFAULT_MAX_QUEUE_SIZE))
        except ValueError:
            _logger.exception(
                _ENV_VAR_INT_VALUE_ERROR_MESSAGE,
                OTEL_BLRP_MAX_QUEUE_SIZE,
                _DEFAULT_MAX_QUEUE_SIZE,
            )
            return _DEFAULT_MAX_QUEUE_SIZE

    @staticmethod
    def _default_schedule_delay_millis():
        try:
            return int(
                environ.get(OTEL_BLRP_SCHEDULE_DELAY, _DEFAULT_SCHEDULE_DELAY_MILLIS)
            )
        except ValueError:
            _logger.exception(
                _ENV_VAR_INT_VALUE_ERROR_MESSAGE,
                OTEL_BLRP_SCHEDULE_DELAY,
                _DEFAULT_SCHEDULE_DELAY_MILLIS,
            )
            return _DEFAULT_SCHEDULE_DELAY_MILLIS

    @staticmethod
    def _default_max_export_batch_size():
        try:
            return int(
                environ.get(
                    OTEL_BLRP_MAX_EXPORT_BATCH_SIZE,
                    _DEFAULT_MAX_EXPORT_BATCH_SIZE,
                )
            )
        except ValueError:
            _logger.exception(
                _ENV_VAR_INT_VALUE_ERROR_MESSAGE,
                OTEL_BLRP_MAX_EXPORT_BATCH_SIZE,
                _DEFAULT_MAX_EXPORT_BATCH_SIZE,
            )
            return _DEFAULT_MAX_EXPORT_BATCH_SIZE

    @staticmethod
    def _default_export_timeout_millis():
        try:
            return int(
                environ.get(OTEL_BLRP_EXPORT_TIMEOUT, _DEFAULT_EXPORT_TIMEOUT_MILLIS)
            )
        except ValueError:
            _logger.exception(
                _ENV_VAR_INT_VALUE_ERROR_MESSAGE,
                OTEL_BLRP_EXPORT_TIMEOUT,
                _DEFAULT_EXPORT_TIMEOUT_MILLIS,
            )
            return _DEFAULT_EXPORT_TIMEOUT_MILLIS

    @staticmethod
    def _validate_arguments(
        max_queue_size, schedule_delay_millis, max_export_batch_size
    ):
        if max_queue_size <= 0:
            raise ValueError("max_queue_size must be a positive integer.")

        if schedule_delay_millis <= 0:
            raise ValueError("schedule_delay_millis must be positive.")

        if max_export_batch_size <= 0:
            raise ValueError("max_export_batch_size must be a positive integer.")

        if max_export_batch_size > max_queue_size:
            raise ValueError(
                "max_export_batch_size must be less than or equal to max_queue_size."
            )

virt/lib/python3.9/site-packages/mysql/opentelemetry/sdk/_logs/_internal/export/__init__.py
PrinceDisant/LostInNL
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import threading
import typing

from mysql.opentelemetry.sdk._logs import LogData
from mysql.opentelemetry.sdk._logs.export import LogExporter, LogExportResult


class InMemoryLogExporter(LogExporter):
    """Implementation of :class:`.LogExporter` that stores logs in memory.

    This class can be used for testing purposes. It stores the exported logs
    in a list in memory that can be retrieved using the
    :func:`.get_finished_logs` method.
    """

    def __init__(self):
        self._logs = []
        self._lock = threading.Lock()
        self._stopped = False

    def clear(self) -> None:
        with self._lock:
            self._logs.clear()

    def get_finished_logs(self) -> typing.Tuple[LogData, ...]:
        with self._lock:
            return tuple(self._logs)

    def export(self, batch: typing.Sequence[LogData]) -> LogExportResult:
        if self._stopped:
            return LogExportResult.FAILURE
        with self._lock:
            self._logs.extend(batch)
        return LogExportResult.SUCCESS

    def shutdown(self) -> None:
        self._stopped = True

virt/lib/python3.9/site-packages/mysql/opentelemetry/sdk/_logs/_internal/export/in_memory_log_exporter.py
PrinceDisant/LostInNL
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from mysql.opentelemetry.sdk._logs._internal.export import (
    BatchLogRecordProcessor,
    ConsoleLogExporter,
    LogExporter,
    LogExportResult,
    SimpleLogRecordProcessor,
)

# The point module is not in the export directory to avoid a circular import.
from mysql.opentelemetry.sdk._logs._internal.export.in_memory_log_exporter import (
    InMemoryLogExporter,
)

__all__ = [
    "BatchLogRecordProcessor",
    "ConsoleLogExporter",
    "LogExporter",
    "LogExportResult",
    "SimpleLogRecordProcessor",
    "InMemoryLogExporter",
]

virt/lib/python3.9/site-packages/mysql/opentelemetry/sdk/_logs/export/__init__.py
PrinceDisant/LostInNL
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""
Global Error Handler

This module provides a global error handler and an interface that allows
error handlers to be registered with the global error handler via entry points.
A default error handler is also provided.

To use this feature, users can create an error handler that is registered
using the ``opentelemetry_error_handler`` entry point. A class is to be
registered in this entry point, this class must inherit from the
``opentelemetry.sdk.error_handler.ErrorHandler`` class and implement the
corresponding ``handle`` method. This method will receive the exception object
that is to be handled. The error handler class should also inherit from the
exception classes it wants to handle. For example, this would be an error
handler that handles ``ZeroDivisionError``:

.. code:: python

    from opentelemetry.sdk.error_handler import ErrorHandler
    from logging import getLogger

    logger = getLogger(__name__)


    class ErrorHandler0(ErrorHandler, ZeroDivisionError):

        def _handle(self, error: Exception, *args, **kwargs):

            logger.exception("ErrorHandler0 handling a ZeroDivisionError")

To use the global error handler, just instantiate it as a context manager where
you want exceptions to be handled:


.. code:: python

    from opentelemetry.sdk.error_handler import GlobalErrorHandler

    with GlobalErrorHandler():
        1 / 0

If the class of the exception raised in the scope of the ``GlobalErrorHandler``
object is not parent of any registered error handler, then the default error
handler will handle the exception. This default error handler will only log the
exception to standard logging, the exception won't be raised any further.
"""

from abc import ABC, abstractmethod
from logging import getLogger

from mysql.opentelemetry.util._importlib_metadata import entry_points

logger = getLogger(__name__)


class ErrorHandler(ABC):
    @abstractmethod
    def _handle(self, error: Exception, *args, **kwargs):
        """
        Handle an exception
        """


class _DefaultErrorHandler(ErrorHandler):
    """
    Default error handler

    This error handler just logs the exception using standard logging.
    """

    # pylint: disable=useless-return
    def _handle(self, error: Exception, *args, **kwargs):
        logger.exception("Error handled by default error handler: ")
        return None


class GlobalErrorHandler:
    """
    Global error handler

    This is a singleton class that can be instantiated anywhere to get the
    global error handler. This object provides a handle method that receives
    an exception object that will be handled by the registered error handlers.
    """

    _instance = None

    def __new__(cls) -> "GlobalErrorHandler":
        if cls._instance is None:
            cls._instance = super().__new__(cls)

        return cls._instance

    def __enter__(self):
        pass

    # pylint: disable=no-self-use
    def __exit__(self, exc_type, exc_value, traceback):
        if exc_value is None:
            return None

        plugin_handled = False

        error_handler_entry_points = entry_points(group="opentelemetry_error_handler")

        for error_handler_entry_point in error_handler_entry_points:
            error_handler_class = error_handler_entry_point.load()

            if issubclass(error_handler_class, exc_value.__class__):
                try:
                    error_handler_class()._handle(exc_value)
                    plugin_handled = True

                # pylint: disable=broad-except
                except Exception as error_handling_error:
                    logger.exception(
                        "%s error while handling error" " %s by error handler %s",
                        error_handling_error.__class__.__name__,
                        exc_value.__class__.__name__,
                        error_handler_class.__name__,
                    )

        if not plugin_handled:
            _DefaultErrorHandler()._handle(exc_value)

        return True

virt/lib/python3.9/site-packages/mysql/opentelemetry/sdk/error_handler/__init__.py
PrinceDisant/LostInNL
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


from mysql.opentelemetry.sdk.metrics._internal import Meter, MeterProvider
from mysql.opentelemetry.sdk.metrics._internal.exceptions import MetricsTimeoutError
from mysql.opentelemetry.sdk.metrics._internal.instrument import (
    Counter,
    Histogram,
    ObservableCounter,
    ObservableGauge,
    ObservableUpDownCounter,
    UpDownCounter,
)

__all__ = [
    "Meter",
    "MeterProvider",
    "MetricsTimeoutError",
    "Counter",
    "Histogram",
    "ObservableCounter",
    "ObservableGauge",
    "ObservableUpDownCounter",
    "UpDownCounter",
]

virt/lib/python3.9/site-packages/mysql/opentelemetry/sdk/metrics/__init__.py
PrinceDisant/LostInNL
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from atexit import register, unregister
from logging import getLogger
from threading import Lock
from time import time_ns
from typing import Optional, Sequence

# This kind of import is needed to avoid Sphinx errors.
import mysql.opentelemetry.sdk.metrics

from mysql.opentelemetry.metrics import (
    Counter as APICounter,
    Histogram as APIHistogram,
    Meter as APIMeter,
    MeterProvider as APIMeterProvider,
    NoOpMeter,
    ObservableCounter as APIObservableCounter,
    ObservableGauge as APIObservableGauge,
    ObservableUpDownCounter as APIObservableUpDownCounter,
    UpDownCounter as APIUpDownCounter,
)
from mysql.opentelemetry.sdk.metrics._internal.exceptions import MetricsTimeoutError
from mysql.opentelemetry.sdk.metrics._internal.instrument import (
    _Counter,
    _Histogram,
    _ObservableCounter,
    _ObservableGauge,
    _ObservableUpDownCounter,
    _UpDownCounter,
)
from mysql.opentelemetry.sdk.metrics._internal.measurement_consumer import (
    MeasurementConsumer,
    SynchronousMeasurementConsumer,
)
from mysql.opentelemetry.sdk.metrics._internal.sdk_configuration import SdkConfiguration
from mysql.opentelemetry.sdk.resources import Resource
from mysql.opentelemetry.sdk.util.instrumentation import InstrumentationScope
from mysql.opentelemetry.util._once import Once

_logger = getLogger(__name__)


class Meter(APIMeter):
    """See `mysql.opentelemetry.metrics.Meter`."""

    def __init__(
        self,
        instrumentation_scope: InstrumentationScope,
        measurement_consumer: MeasurementConsumer,
    ):
        super().__init__(
            name=instrumentation_scope.name,
            version=instrumentation_scope.version,
            schema_url=instrumentation_scope.schema_url,
        )
        self._instrumentation_scope = instrumentation_scope
        self._measurement_consumer = measurement_consumer
        self._instrument_id_instrument = {}
        self._instrument_id_instrument_lock = Lock()

    def create_counter(self, name, unit="", description="") -> APICounter:
        (
            is_instrument_registered,
            instrument_id,
        ) = self._is_instrument_registered(name, _Counter, unit, description)

        if is_instrument_registered:
            # FIXME #2558 go through all views here and check if this
            # instrument registration conflict can be fixed. If it can be, do
            # not log the following warning.
            _logger.warning(
                "An instrument with name %s, type %s, unit %s and "
                "description %s has been created already.",
                name,
                APICounter.__name__,
                unit,
                description,
            )
            with self._instrument_id_instrument_lock:
                return self._instrument_id_instrument[instrument_id]

        instrument = _Counter(
            name,
            self._instrumentation_scope,
            self._measurement_consumer,
            unit,
            description,
        )

        with self._instrument_id_instrument_lock:
            self._instrument_id_instrument[instrument_id] = instrument
            return instrument

    def create_up_down_counter(self, name, unit="", description="") -> APIUpDownCounter:
        (
            is_instrument_registered,
            instrument_id,
        ) = self._is_instrument_registered(name, _UpDownCounter, unit, description)

        if is_instrument_registered:
            # FIXME #2558 go through all views here and check if this
            # instrument registration conflict can be fixed. If it can be, do
            # not log the following warning.
            _logger.warning(
                "An instrument with name %s, type %s, unit %s and "
                "description %s has been created already.",
                name,
                APIUpDownCounter.__name__,
                unit,
                description,
            )
            with self._instrument_id_instrument_lock:
                return self._instrument_id_instrument[instrument_id]

        instrument = _UpDownCounter(
            name,
            self._instrumentation_scope,
            self._measurement_consumer,
            unit,
            description,
        )

        with self._instrument_id_instrument_lock:
            self._instrument_id_instrument[instrument_id] = instrument
            return instrument

    def create_observable_counter(
        self, name, callbacks=None, unit="", description=""
    ) -> APIObservableCounter:
        (
            is_instrument_registered,
            instrument_id,
        ) = self._is_instrument_registered(name, _ObservableCounter, unit, description)

        if is_instrument_registered:
            # FIXME #2558 go through all views here and check if this
            # instrument registration conflict can be fixed. If it can be, do
            # not log the following warning.
            _logger.warning(
                "An instrument with name %s, type %s, unit %s and "
                "description %s has been created already.",
                name,
                APIObservableCounter.__name__,
                unit,
                description,
            )
            with self._instrument_id_instrument_lock:
                return self._instrument_id_instrument[instrument_id]

        instrument = _ObservableCounter(
            name,
            self._instrumentation_scope,
            self._measurement_consumer,
            callbacks,
            unit,
            description,
        )

        self._measurement_consumer.register_asynchronous_instrument(instrument)

        with self._instrument_id_instrument_lock:
            self._instrument_id_instrument[instrument_id] = instrument
            return instrument

    def create_histogram(self, name, unit="", description="") -> APIHistogram:
        (
            is_instrument_registered,
            instrument_id,
        ) = self._is_instrument_registered(name, _Histogram, unit, description)

        if is_instrument_registered:
            # FIXME #2558 go through all views here and check if this
            # instrument registration conflict can be fixed. If it can be, do
            # not log the following warning.
            _logger.warning(
                "An instrument with name %s, type %s, unit %s and "
                "description %s has been created already.",
                name,
                APIHistogram.__name__,
                unit,
                description,
            )
            with self._instrument_id_instrument_lock:
                return self._instrument_id_instrument[instrument_id]

        instrument = _Histogram(
            name,
            self._instrumentation_scope,
            self._measurement_consumer,
            unit,
            description,
        )
        with self._instrument_id_instrument_lock:
            self._instrument_id_instrument[instrument_id] = instrument
            return instrument

    def create_observable_gauge(
        self, name, callbacks=None, unit="", description=""
    ) -> APIObservableGauge:
        (
            is_instrument_registered,
            instrument_id,
        ) = self._is_instrument_registered(name, _ObservableGauge, unit, description)

        if is_instrument_registered:
            # FIXME #2558 go through all views here and check if this
            # instrument registration conflict can be fixed. If it can be, do
            # not log the following warning.
            _logger.warning(
                "An instrument with name %s, type %s, unit %s and "
                "description %s has been created already.",
                name,
                APIObservableGauge.__name__,
                unit,
                description,
            )
            with self._instrument_id_instrument_lock:
                return self._instrument_id_instrument[instrument_id]

        instrument = _ObservableGauge(
            name,
            self._instrumentation_scope,
            self._measurement_consumer,
            callbacks,
            unit,
            description,
        )

        self._measurement_consumer.register_asynchronous_instrument(instrument)

        with self._instrument_id_instrument_lock:
            self._instrument_id_instrument[instrument_id] = instrument
            return instrument

    def create_observable_up_down_counter(
        self, name, callbacks=None, unit="", description=""
    ) -> APIObservableUpDownCounter:
        (
            is_instrument_registered,
            instrument_id,
        ) = self._is_instrument_registered(
            name, _ObservableUpDownCounter, unit, description
        )

        if is_instrument_registered:
            # FIXME #2558 go through all views here and check if this
            # instrument registration conflict can be fixed. If it can be, do
            # not log the following warning.
            _logger.warning(
                "An instrument with name %s, type %s, unit %s and "
                "description %s has been created already.",
                name,
                APIObservableUpDownCounter.__name__,
                unit,
                description,
            )
            with self._instrument_id_instrument_lock:
                return self._instrument_id_instrument[instrument_id]

        instrument = _ObservableUpDownCounter(
            name,
            self._instrumentation_scope,
            self._measurement_consumer,
            callbacks,
            unit,
            description,
        )

        self._measurement_consumer.register_asynchronous_instrument(instrument)

        with self._instrument_id_instrument_lock:
            self._instrument_id_instrument[instrument_id] = instrument
            return instrument


class MeterProvider(APIMeterProvider):
    r"""See `mysql.opentelemetry.metrics.MeterProvider`.

    Args:
        metric_readers: Register metric readers to collect metrics from the SDK
            on demand. Each :class:`mysql.opentelemetry.sdk.metrics.export.MetricReader` is
            completely independent and will collect separate streams of
            metrics. TODO: reference ``PeriodicExportingMetricReader`` usage with push
            exporters here.
        resource: The resource representing what the metrics emitted from the SDK pertain to.
        shutdown_on_exit: If true, registers an `atexit` handler to call
            `MeterProvider.shutdown`
        views: The views to configure the metric output the SDK

    By default, instruments which do not match any :class:`mysql.opentelemetry.sdk.metrics.view.View` (or if no :class:`mysql.opentelemetry.sdk.metrics.view.View`\ s
    are provided) will report metrics with the default aggregation for the
    instrument's kind. To disable instruments by default, configure a match-all
    :class:`mysql.opentelemetry.sdk.metrics.view.View` with `DropAggregation` and then create :class:`mysql.opentelemetry.sdk.metrics.view.View`\ s to re-enable
    individual instruments:

    .. code-block:: python
        :caption: Disable default views

        MeterProvider(
            views=[
                View(instrument_name="*", aggregation=DropAggregation()),
                View(instrument_name="mycounter"),
            ],
            # ...
        )
    """

    _all_metric_readers_lock = Lock()
    _all_metric_readers = set()

    def __init__(
        self,
        metric_readers: Sequence[
            "mysql.opentelemetry.sdk.metrics.export.MetricReader"
        ] = (),
        resource: Resource = Resource.create({}),
        shutdown_on_exit: bool = True,
        views: Sequence["mysql.opentelemetry.sdk.metrics.view.View"] = (),
    ):
        self._lock = Lock()
        self._meter_lock = Lock()
        self._atexit_handler = None
        self._sdk_config = SdkConfiguration(
            resource=resource,
            metric_readers=metric_readers,
            views=views,
        )
        self._measurement_consumer = SynchronousMeasurementConsumer(
            sdk_config=self._sdk_config
        )

        if shutdown_on_exit:
            self._atexit_handler = register(self.shutdown)

        self._meters = {}
        self._shutdown_once = Once()
        self._shutdown = False

        for metric_reader in self._sdk_config.metric_readers:
            with self._all_metric_readers_lock:
                if metric_reader in self._all_metric_readers:
                    raise Exception(
                        f"MetricReader {metric_reader} has been registered "
                        "already in other MeterProvider instance"
                    )

                self._all_metric_readers.add(metric_reader)

            metric_reader._set_collect_callback(self._measurement_consumer.collect)

    def force_flush(self, timeout_millis: float = 10_000) -> bool:
        deadline_ns = time_ns() + timeout_millis * 10**6

        metric_reader_error = {}

        for metric_reader in self._sdk_config.metric_readers:
            current_ts = time_ns()
            try:
                if current_ts >= deadline_ns:
                    raise MetricsTimeoutError("Timed out while flushing metric readers")
                metric_reader.force_flush(
                    timeout_millis=(deadline_ns - current_ts) / 10**6
                )

            # pylint: disable=broad-except
            except Exception as error:
                metric_reader_error[metric_reader] = error

        if metric_reader_error:
            metric_reader_error_string = "\n".join(
                [
                    f"{metric_reader.__class__.__name__}: {repr(error)}"
                    for metric_reader, error in metric_reader_error.items()
                ]
            )

            raise Exception(
                "MeterProvider.force_flush failed because the following "
                "metric readers failed during collect:\n"
                f"{metric_reader_error_string}"
            )
        return True

    def shutdown(self, timeout_millis: float = 30_000):
        deadline_ns = time_ns() + timeout_millis * 10**6

        def _shutdown():
            self._shutdown = True

        did_shutdown = self._shutdown_once.do_once(_shutdown)

        if not did_shutdown:
            _logger.warning("shutdown can only be called once")
            return

        metric_reader_error = {}

        for metric_reader in self._sdk_config.metric_readers:
            current_ts = time_ns()
            try:
                if current_ts >= deadline_ns:
                    raise Exception("Didn't get to execute, deadline already exceeded")
                metric_reader.shutdown(
                    timeout_millis=(deadline_ns - current_ts) / 10**6
                )

            # pylint: disable=broad-except
            except Exception as error:
                metric_reader_error[metric_reader] = error

        if self._atexit_handler is not None:
            unregister(self._atexit_handler)
            self._atexit_handler = None

        if metric_reader_error:
            metric_reader_error_string = "\n".join(
                [
                    f"{metric_reader.__class__.__name__}: {repr(error)}"
                    for metric_reader, error in metric_reader_error.items()
                ]
            )

            raise Exception(
                (
                    "MeterProvider.shutdown failed because the following "
                    "metric readers failed during shutdown:\n"
                    f"{metric_reader_error_string}"
                )
            )

    def get_meter(
        self,
        name: str,
        version: Optional[str] = None,
        schema_url: Optional[str] = None,
    ) -> Meter:
        if self._shutdown:
            _logger.warning("A shutdown `MeterProvider` can not provide a `Meter`")
            return NoOpMeter(name, version=version, schema_url=schema_url)

        if not name:
            _logger.warning("Meter name cannot be None or empty.")
            return NoOpMeter(name, version=version, schema_url=schema_url)

        info = InstrumentationScope(name, version, schema_url)
        with self._meter_lock:
            if not self._meters.get(info):
                # FIXME #2558 pass SDKConfig object to meter so that the meter
                # has access to views.
                self._meters[info] = Meter(
                    info,
                    self._measurement_consumer,
                )
            return self._meters[info]

virt/lib/python3.9/site-packages/mysql/opentelemetry/sdk/metrics/_internal/__init__ 2.py
PrinceDisant/LostInNL
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from atexit import register, unregister
from logging import getLogger
from threading import Lock
from time import time_ns
from typing import Optional, Sequence

# This kind of import is needed to avoid Sphinx errors.
import mysql.opentelemetry.sdk.metrics

from mysql.opentelemetry.metrics import (
    Counter as APICounter,
    Histogram as APIHistogram,
    Meter as APIMeter,
    MeterProvider as APIMeterProvider,
    NoOpMeter,
    ObservableCounter as APIObservableCounter,
    ObservableGauge as APIObservableGauge,
    ObservableUpDownCounter as APIObservableUpDownCounter,
    UpDownCounter as APIUpDownCounter,
)
from mysql.opentelemetry.sdk.metrics._internal.exceptions import MetricsTimeoutError
from mysql.opentelemetry.sdk.metrics._internal.instrument import (
    _Counter,
    _Histogram,
    _ObservableCounter,
    _ObservableGauge,
    _ObservableUpDownCounter,
    _UpDownCounter,
)
from mysql.opentelemetry.sdk.metrics._internal.measurement_consumer import (
    MeasurementConsumer,
    SynchronousMeasurementConsumer,
)
from mysql.opentelemetry.sdk.metrics._internal.sdk_configuration import SdkConfiguration
from mysql.opentelemetry.sdk.resources import Resource
from mysql.opentelemetry.sdk.util.instrumentation import InstrumentationScope
from mysql.opentelemetry.util._once import Once

_logger = getLogger(__name__)


class Meter(APIMeter):
    """See `mysql.opentelemetry.metrics.Meter`."""

    def __init__(
        self,
        instrumentation_scope: InstrumentationScope,
        measurement_consumer: MeasurementConsumer,
    ):
        super().__init__(
            name=instrumentation_scope.name,
            version=instrumentation_scope.version,
            schema_url=instrumentation_scope.schema_url,
        )
        self._instrumentation_scope = instrumentation_scope
        self._measurement_consumer = measurement_consumer
        self._instrument_id_instrument = {}
        self._instrument_id_instrument_lock = Lock()

    def create_counter(self, name, unit="", description="") -> APICounter:
        (
            is_instrument_registered,
            instrument_id,
        ) = self._is_instrument_registered(name, _Counter, unit, description)

        if is_instrument_registered:
            # FIXME #2558 go through all views here and check if this
            # instrument registration conflict can be fixed. If it can be, do
            # not log the following warning.
            _logger.warning(
                "An instrument with name %s, type %s, unit %s and "
                "description %s has been created already.",
                name,
                APICounter.__name__,
                unit,
                description,
            )
            with self._instrument_id_instrument_lock:
                return self._instrument_id_instrument[instrument_id]

        instrument = _Counter(
            name,
            self._instrumentation_scope,
            self._measurement_consumer,
            unit,
            description,
        )

        with self._instrument_id_instrument_lock:
            self._instrument_id_instrument[instrument_id] = instrument
            return instrument

    def create_up_down_counter(self, name, unit="", description="") -> APIUpDownCounter:
        (
            is_instrument_registered,
            instrument_id,
        ) = self._is_instrument_registered(name, _UpDownCounter, unit, description)

        if is_instrument_registered:
            # FIXME #2558 go through all views here and check if this
            # instrument registration conflict can be fixed. If it can be, do
            # not log the following warning.
            _logger.warning(
                "An instrument with name %s, type %s, unit %s and "
                "description %s has been created already.",
                name,
                APIUpDownCounter.__name__,
                unit,
                description,
            )
            with self._instrument_id_instrument_lock:
                return self._instrument_id_instrument[instrument_id]

        instrument = _UpDownCounter(
            name,
            self._instrumentation_scope,
            self._measurement_consumer,
            unit,
            description,
        )

        with self._instrument_id_instrument_lock:
            self._instrument_id_instrument[instrument_id] = instrument
            return instrument

    def create_observable_counter(
        self, name, callbacks=None, unit="", description=""
    ) -> APIObservableCounter:
        (
            is_instrument_registered,
            instrument_id,
        ) = self._is_instrument_registered(name, _ObservableCounter, unit, description)

        if is_instrument_registered:
            # FIXME #2558 go through all views here and check if this
            # instrument registration conflict can be fixed. If it can be, do
            # not log the following warning.
            _logger.warning(
                "An instrument with name %s, type %s, unit %s and "
                "description %s has been created already.",
                name,
                APIObservableCounter.__name__,
                unit,
                description,
            )
            with self._instrument_id_instrument_lock:
                return self._instrument_id_instrument[instrument_id]

        instrument = _ObservableCounter(
            name,
            self._instrumentation_scope,
            self._measurement_consumer,
            callbacks,
            unit,
            description,
        )

        self._measurement_consumer.register_asynchronous_instrument(instrument)

        with self._instrument_id_instrument_lock:
            self._instrument_id_instrument[instrument_id] = instrument
            return instrument

    def create_histogram(self, name, unit="", description="") -> APIHistogram:
        (
            is_instrument_registered,
            instrument_id,
        ) = self._is_instrument_registered(name, _Histogram, unit, description)

        if is_instrument_registered:
            # FIXME #2558 go through all views here and check if this
            # instrument registration conflict can be fixed. If it can be, do
            # not log the following warning.
            _logger.warning(
                "An instrument with name %s, type %s, unit %s and "
                "description %s has been created already.",
                name,
                APIHistogram.__name__,
                unit,
                description,
            )
            with self._instrument_id_instrument_lock:
                return self._instrument_id_instrument[instrument_id]

        instrument = _Histogram(
            name,
            self._instrumentation_scope,
            self._measurement_consumer,
            unit,
            description,
        )
        with self._instrument_id_instrument_lock:
            self._instrument_id_instrument[instrument_id] = instrument
            return instrument

    def create_observable_gauge(
        self, name, callbacks=None, unit="", description=""
    ) -> APIObservableGauge:
        (
            is_instrument_registered,
            instrument_id,
        ) = self._is_instrument_registered(name, _ObservableGauge, unit, description)

        if is_instrument_registered:
            # FIXME #2558 go through all views here and check if this
            # instrument registration conflict can be fixed. If it can be, do
            # not log the following warning.
            _logger.warning(
                "An instrument with name %s, type %s, unit %s and "
                "description %s has been created already.",
                name,
                APIObservableGauge.__name__,
                unit,
                description,
            )
            with self._instrument_id_instrument_lock:
                return self._instrument_id_instrument[instrument_id]

        instrument = _ObservableGauge(
            name,
            self._instrumentation_scope,
            self._measurement_consumer,
            callbacks,
            unit,
            description,
        )

        self._measurement_consumer.register_asynchronous_instrument(instrument)

        with self._instrument_id_instrument_lock:
            self._instrument_id_instrument[instrument_id] = instrument
            return instrument

    def create_observable_up_down_counter(
        self, name, callbacks=None, unit="", description=""
    ) -> APIObservableUpDownCounter:
        (
            is_instrument_registered,
            instrument_id,
        ) = self._is_instrument_registered(
            name, _ObservableUpDownCounter, unit, description
        )

        if is_instrument_registered:
            # FIXME #2558 go through all views here and check if this
            # instrument registration conflict can be fixed. If it can be, do
            # not log the following warning.
            _logger.warning(
                "An instrument with name %s, type %s, unit %s and "
                "description %s has been created already.",
                name,
                APIObservableUpDownCounter.__name__,
                unit,
                description,
            )
            with self._instrument_id_instrument_lock:
                return self._instrument_id_instrument[instrument_id]

        instrument = _ObservableUpDownCounter(
            name,
            self._instrumentation_scope,
            self._measurement_consumer,
            callbacks,
            unit,
            description,
        )

        self._measurement_consumer.register_asynchronous_instrument(instrument)

        with self._instrument_id_instrument_lock:
            self._instrument_id_instrument[instrument_id] = instrument
            return instrument


class MeterProvider(APIMeterProvider):
    r"""See `mysql.opentelemetry.metrics.MeterProvider`.

    Args:
        metric_readers: Register metric readers to collect metrics from the SDK
            on demand. Each :class:`mysql.opentelemetry.sdk.metrics.export.MetricReader` is
            completely independent and will collect separate streams of
            metrics. TODO: reference ``PeriodicExportingMetricReader`` usage with push
            exporters here.
        resource: The resource representing what the metrics emitted from the SDK pertain to.
        shutdown_on_exit: If true, registers an `atexit` handler to call
            `MeterProvider.shutdown`
        views: The views to configure the metric output the SDK

    By default, instruments which do not match any :class:`mysql.opentelemetry.sdk.metrics.view.View` (or if no :class:`mysql.opentelemetry.sdk.metrics.view.View`\ s
    are provided) will report metrics with the default aggregation for the
    instrument's kind. To disable instruments by default, configure a match-all
    :class:`mysql.opentelemetry.sdk.metrics.view.View` with `DropAggregation` and then create :class:`mysql.opentelemetry.sdk.metrics.view.View`\ s to re-enable
    individual instruments:

    .. code-block:: python
        :caption: Disable default views

        MeterProvider(
            views=[
                View(instrument_name="*", aggregation=DropAggregation()),
                View(instrument_name="mycounter"),
            ],
            # ...
        )
    """

    _all_metric_readers_lock = Lock()
    _all_metric_readers = set()

    def __init__(
        self,
        metric_readers: Sequence[
            "mysql.opentelemetry.sdk.metrics.export.MetricReader"
        ] = (),
        resource: Resource = Resource.create({}),
        shutdown_on_exit: bool = True,
        views: Sequence["mysql.opentelemetry.sdk.metrics.view.View"] = (),
    ):
        self._lock = Lock()
        self._meter_lock = Lock()
        self._atexit_handler = None
        self._sdk_config = SdkConfiguration(
            resource=resource,
            metric_readers=metric_readers,
            views=views,
        )
        self._measurement_consumer = SynchronousMeasurementConsumer(
            sdk_config=self._sdk_config
        )

        if shutdown_on_exit:
            self._atexit_handler = register(self.shutdown)

        self._meters = {}
        self._shutdown_once = Once()
        self._shutdown = False

        for metric_reader in self._sdk_config.metric_readers:
            with self._all_metric_readers_lock:
                if metric_reader in self._all_metric_readers:
                    raise Exception(
                        f"MetricReader {metric_reader} has been registered "
                        "already in other MeterProvider instance"
                    )

                self._all_metric_readers.add(metric_reader)

            metric_reader._set_collect_callback(self._measurement_consumer.collect)

    def force_flush(self, timeout_millis: float = 10_000) -> bool:
        deadline_ns = time_ns() + timeout_millis * 10**6

        metric_reader_error = {}

        for metric_reader in self._sdk_config.metric_readers:
            current_ts = time_ns()
            try:
                if current_ts >= deadline_ns:
                    raise MetricsTimeoutError("Timed out while flushing metric readers")
                metric_reader.force_flush(
                    timeout_millis=(deadline_ns - current_ts) / 10**6
                )

            # pylint: disable=broad-except
            except Exception as error:
                metric_reader_error[metric_reader] = error

        if metric_reader_error:
            metric_reader_error_string = "\n".join(
                [
                    f"{metric_reader.__class__.__name__}: {repr(error)}"
                    for metric_reader, error in metric_reader_error.items()
                ]
            )

            raise Exception(
                "MeterProvider.force_flush failed because the following "
                "metric readers failed during collect:\n"
                f"{metric_reader_error_string}"
            )
        return True

    def shutdown(self, timeout_millis: float = 30_000):
        deadline_ns = time_ns() + timeout_millis * 10**6

        def _shutdown():
            self._shutdown = True

        did_shutdown = self._shutdown_once.do_once(_shutdown)

        if not did_shutdown:
            _logger.warning("shutdown can only be called once")
            return

        metric_reader_error = {}

        for metric_reader in self._sdk_config.metric_readers:
            current_ts = time_ns()
            try:
                if current_ts >= deadline_ns:
                    raise Exception("Didn't get to execute, deadline already exceeded")
                metric_reader.shutdown(
                    timeout_millis=(deadline_ns - current_ts) / 10**6
                )

            # pylint: disable=broad-except
            except Exception as error:
                metric_reader_error[metric_reader] = error

        if self._atexit_handler is not None:
            unregister(self._atexit_handler)
            self._atexit_handler = None

        if metric_reader_error:
            metric_reader_error_string = "\n".join(
                [
                    f"{metric_reader.__class__.__name__}: {repr(error)}"
                    for metric_reader, error in metric_reader_error.items()
                ]
            )

            raise Exception(
                (
                    "MeterProvider.shutdown failed because the following "
                    "metric readers failed during shutdown:\n"
                    f"{metric_reader_error_string}"
                )
            )

    def get_meter(
        self,
        name: str,
        version: Optional[str] = None,
        schema_url: Optional[str] = None,
    ) -> Meter:
        if self._shutdown:
            _logger.warning("A shutdown `MeterProvider` can not provide a `Meter`")
            return NoOpMeter(name, version=version, schema_url=schema_url)

        if not name:
            _logger.warning("Meter name cannot be None or empty.")
            return NoOpMeter(name, version=version, schema_url=schema_url)

        info = InstrumentationScope(name, version, schema_url)
        with self._meter_lock:
            if not self._meters.get(info):
                # FIXME #2558 pass SDKConfig object to meter so that the meter
                # has access to views.
                self._meters[info] = Meter(
                    info,
                    self._measurement_consumer,
                )
            return self._meters[info]

virt/lib/python3.9/site-packages/mysql/opentelemetry/sdk/metrics/_internal/__init__ 3.py
PrinceDisant/LostInNL
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from atexit import register, unregister
from logging import getLogger
from threading import Lock
from time import time_ns
from typing import Optional, Sequence

# This kind of import is needed to avoid Sphinx errors.
import mysql.opentelemetry.sdk.metrics

from mysql.opentelemetry.metrics import (
    Counter as APICounter,
    Histogram as APIHistogram,
    Meter as APIMeter,
    MeterProvider as APIMeterProvider,
    NoOpMeter,
    ObservableCounter as APIObservableCounter,
    ObservableGauge as APIObservableGauge,
    ObservableUpDownCounter as APIObservableUpDownCounter,
    UpDownCounter as APIUpDownCounter,
)
from mysql.opentelemetry.sdk.metrics._internal.exceptions import MetricsTimeoutError
from mysql.opentelemetry.sdk.metrics._internal.instrument import (
    _Counter,
    _Histogram,
    _ObservableCounter,
    _ObservableGauge,
    _ObservableUpDownCounter,
    _UpDownCounter,
)
from mysql.opentelemetry.sdk.metrics._internal.measurement_consumer import (
    MeasurementConsumer,
    SynchronousMeasurementConsumer,
)
from mysql.opentelemetry.sdk.metrics._internal.sdk_configuration import SdkConfiguration
from mysql.opentelemetry.sdk.resources import Resource
from mysql.opentelemetry.sdk.util.instrumentation import InstrumentationScope
from mysql.opentelemetry.util._once import Once

_logger = getLogger(__name__)


class Meter(APIMeter):
    """See `mysql.opentelemetry.metrics.Meter`."""

    def __init__(
        self,
        instrumentation_scope: InstrumentationScope,
        measurement_consumer: MeasurementConsumer,
    ):
        super().__init__(
            name=instrumentation_scope.name,
            version=instrumentation_scope.version,
            schema_url=instrumentation_scope.schema_url,
        )
        self._instrumentation_scope = instrumentation_scope
        self._measurement_consumer = measurement_consumer
        self._instrument_id_instrument = {}
        self._instrument_id_instrument_lock = Lock()

    def create_counter(self, name, unit="", description="") -> APICounter:
        (
            is_instrument_registered,
            instrument_id,
        ) = self._is_instrument_registered(name, _Counter, unit, description)

        if is_instrument_registered:
            # FIXME #2558 go through all views here and check if this
            # instrument registration conflict can be fixed. If it can be, do
            # not log the following warning.
            _logger.warning(
                "An instrument with name %s, type %s, unit %s and "
                "description %s has been created already.",
                name,
                APICounter.__name__,
                unit,
                description,
            )
            with self._instrument_id_instrument_lock:
                return self._instrument_id_instrument[instrument_id]

        instrument = _Counter(
            name,
            self._instrumentation_scope,
            self._measurement_consumer,
            unit,
            description,
        )

        with self._instrument_id_instrument_lock:
            self._instrument_id_instrument[instrument_id] = instrument
            return instrument

    def create_up_down_counter(self, name, unit="", description="") -> APIUpDownCounter:
        (
            is_instrument_registered,
            instrument_id,
        ) = self._is_instrument_registered(name, _UpDownCounter, unit, description)

        if is_instrument_registered:
            # FIXME #2558 go through all views here and check if this
            # instrument registration conflict can be fixed. If it can be, do
            # not log the following warning.
            _logger.warning(
                "An instrument with name %s, type %s, unit %s and "
                "description %s has been created already.",
                name,
                APIUpDownCounter.__name__,
                unit,
                description,
            )
            with self._instrument_id_instrument_lock:
                return self._instrument_id_instrument[instrument_id]

        instrument = _UpDownCounter(
            name,
            self._instrumentation_scope,
            self._measurement_consumer,
            unit,
            description,
        )

        with self._instrument_id_instrument_lock:
            self._instrument_id_instrument[instrument_id] = instrument
            return instrument

    def create_observable_counter(
        self, name, callbacks=None, unit="", description=""
    ) -> APIObservableCounter:
        (
            is_instrument_registered,
            instrument_id,
        ) = self._is_instrument_registered(name, _ObservableCounter, unit, description)

        if is_instrument_registered:
            # FIXME #2558 go through all views here and check if this
            # instrument registration conflict can be fixed. If it can be, do
            # not log the following warning.
            _logger.warning(
                "An instrument with name %s, type %s, unit %s and "
                "description %s has been created already.",
                name,
                APIObservableCounter.__name__,
                unit,
                description,
            )
            with self._instrument_id_instrument_lock:
                return self._instrument_id_instrument[instrument_id]

        instrument = _ObservableCounter(
            name,
            self._instrumentation_scope,
            self._measurement_consumer,
            callbacks,
            unit,
            description,
        )

        self._measurement_consumer.register_asynchronous_instrument(instrument)

        with self._instrument_id_instrument_lock:
            self._instrument_id_instrument[instrument_id] = instrument
            return instrument

    def create_histogram(self, name, unit="", description="") -> APIHistogram:
        (
            is_instrument_registered,
            instrument_id,
        ) = self._is_instrument_registered(name, _Histogram, unit, description)

        if is_instrument_registered:
            # FIXME #2558 go through all views here and check if this
            # instrument registration conflict can be fixed. If it can be, do
            # not log the following warning.
            _logger.warning(
                "An instrument with name %s, type %s, unit %s and "
                "description %s has been created already.",
                name,
                APIHistogram.__name__,
                unit,
                description,
            )
            with self._instrument_id_instrument_lock:
                return self._instrument_id_instrument[instrument_id]

        instrument = _Histogram(
            name,
            self._instrumentation_scope,
            self._measurement_consumer,
            unit,
            description,
        )
        with self._instrument_id_instrument_lock:
            self._instrument_id_instrument[instrument_id] = instrument
            return instrument

    def create_observable_gauge(
        self, name, callbacks=None, unit="", description=""
    ) -> APIObservableGauge:
        (
            is_instrument_registered,
            instrument_id,
        ) = self._is_instrument_registered(name, _ObservableGauge, unit, description)

        if is_instrument_registered:
            # FIXME #2558 go through all views here and check if this
            # instrument registration conflict can be fixed. If it can be, do
            # not log the following warning.
            _logger.warning(
                "An instrument with name %s, type %s, unit %s and "
                "description %s has been created already.",
                name,
                APIObservableGauge.__name__,
                unit,
                description,
            )
            with self._instrument_id_instrument_lock:
                return self._instrument_id_instrument[instrument_id]

        instrument = _ObservableGauge(
            name,
            self._instrumentation_scope,
            self._measurement_consumer,
            callbacks,
            unit,
            description,
        )

        self._measurement_consumer.register_asynchronous_instrument(instrument)

        with self._instrument_id_instrument_lock:
            self._instrument_id_instrument[instrument_id] = instrument
            return instrument

    def create_observable_up_down_counter(
        self, name, callbacks=None, unit="", description=""
    ) -> APIObservableUpDownCounter:
        (
            is_instrument_registered,
            instrument_id,
        ) = self._is_instrument_registered(
            name, _ObservableUpDownCounter, unit, description
        )

        if is_instrument_registered:
            # FIXME #2558 go through all views here and check if this
            # instrument registration conflict can be fixed. If it can be, do
            # not log the following warning.
            _logger.warning(
                "An instrument with name %s, type %s, unit %s and "
                "description %s has been created already.",
                name,
                APIObservableUpDownCounter.__name__,
                unit,
                description,
            )
            with self._instrument_id_instrument_lock:
                return self._instrument_id_instrument[instrument_id]

        instrument = _ObservableUpDownCounter(
            name,
            self._instrumentation_scope,
            self._measurement_consumer,
            callbacks,
            unit,
            description,
        )

        self._measurement_consumer.register_asynchronous_instrument(instrument)

        with self._instrument_id_instrument_lock:
            self._instrument_id_instrument[instrument_id] = instrument
            return instrument


class MeterProvider(APIMeterProvider):
    r"""See `mysql.opentelemetry.metrics.MeterProvider`.

    Args:
        metric_readers: Register metric readers to collect metrics from the SDK
            on demand. Each :class:`mysql.opentelemetry.sdk.metrics.export.MetricReader` is
            completely independent and will collect separate streams of
            metrics. TODO: reference ``PeriodicExportingMetricReader`` usage with push
            exporters here.
        resource: The resource representing what the metrics emitted from the SDK pertain to.
        shutdown_on_exit: If true, registers an `atexit` handler to call
            `MeterProvider.shutdown`
        views: The views to configure the metric output the SDK

    By default, instruments which do not match any :class:`mysql.opentelemetry.sdk.metrics.view.View` (or if no :class:`mysql.opentelemetry.sdk.metrics.view.View`\ s
    are provided) will report metrics with the default aggregation for the
    instrument's kind. To disable instruments by default, configure a match-all
    :class:`mysql.opentelemetry.sdk.metrics.view.View` with `DropAggregation` and then create :class:`mysql.opentelemetry.sdk.metrics.view.View`\ s to re-enable
    individual instruments:

    .. code-block:: python
        :caption: Disable default views

        MeterProvider(
            views=[
                View(instrument_name="*", aggregation=DropAggregation()),
                View(instrument_name="mycounter"),
            ],
            # ...
        )
    """

    _all_metric_readers_lock = Lock()
    _all_metric_readers = set()

    def __init__(
        self,
        metric_readers: Sequence[
            "mysql.opentelemetry.sdk.metrics.export.MetricReader"
        ] = (),
        resource: Resource = Resource.create({}),
        shutdown_on_exit: bool = True,
        views: Sequence["mysql.opentelemetry.sdk.metrics.view.View"] = (),
    ):
        self._lock = Lock()
        self._meter_lock = Lock()
        self._atexit_handler = None
        self._sdk_config = SdkConfiguration(
            resource=resource,
            metric_readers=metric_readers,
            views=views,
        )
        self._measurement_consumer = SynchronousMeasurementConsumer(
            sdk_config=self._sdk_config
        )

        if shutdown_on_exit:
            self._atexit_handler = register(self.shutdown)

        self._meters = {}
        self._shutdown_once = Once()
        self._shutdown = False

        for metric_reader in self._sdk_config.metric_readers:
            with self._all_metric_readers_lock:
                if metric_reader in self._all_metric_readers:
                    raise Exception(
                        f"MetricReader {metric_reader} has been registered "
                        "already in other MeterProvider instance"
                    )

                self._all_metric_readers.add(metric_reader)

            metric_reader._set_collect_callback(self._measurement_consumer.collect)

    def force_flush(self, timeout_millis: float = 10_000) -> bool:
        deadline_ns = time_ns() + timeout_millis * 10**6

        metric_reader_error = {}

        for metric_reader in self._sdk_config.metric_readers:
            current_ts = time_ns()
            try:
                if current_ts >= deadline_ns:
                    raise MetricsTimeoutError("Timed out while flushing metric readers")
                metric_reader.force_flush(
                    timeout_millis=(deadline_ns - current_ts) / 10**6
                )

            # pylint: disable=broad-except
            except Exception as error:
                metric_reader_error[metric_reader] = error

        if metric_reader_error:
            metric_reader_error_string = "\n".join(
                [
                    f"{metric_reader.__class__.__name__}: {repr(error)}"
                    for metric_reader, error in metric_reader_error.items()
                ]
            )

            raise Exception(
                "MeterProvider.force_flush failed because the following "
                "metric readers failed during collect:\n"
                f"{metric_reader_error_string}"
            )
        return True

    def shutdown(self, timeout_millis: float = 30_000):
        deadline_ns = time_ns() + timeout_millis * 10**6

        def _shutdown():
            self._shutdown = True

        did_shutdown = self._shutdown_once.do_once(_shutdown)

        if not did_shutdown:
            _logger.warning("shutdown can only be called once")
            return

        metric_reader_error = {}

        for metric_reader in self._sdk_config.metric_readers:
            current_ts = time_ns()
            try:
                if current_ts >= deadline_ns:
                    raise Exception("Didn't get to execute, deadline already exceeded")
                metric_reader.shutdown(
                    timeout_millis=(deadline_ns - current_ts) / 10**6
                )

            # pylint: disable=broad-except
            except Exception as error:
                metric_reader_error[metric_reader] = error

        if self._atexit_handler is not None:
            unregister(self._atexit_handler)
            self._atexit_handler = None

        if metric_reader_error:
            metric_reader_error_string = "\n".join(
                [
                    f"{metric_reader.__class__.__name__}: {repr(error)}"
                    for metric_reader, error in metric_reader_error.items()
                ]
            )

            raise Exception(
                (
                    "MeterProvider.shutdown failed because the following "
                    "metric readers failed during shutdown:\n"
                    f"{metric_reader_error_string}"
                )
            )

    def get_meter(
        self,
        name: str,
        version: Optional[str] = None,
        schema_url: Optional[str] = None,
    ) -> Meter:
        if self._shutdown:
            _logger.warning("A shutdown `MeterProvider` can not provide a `Meter`")
            return NoOpMeter(name, version=version, schema_url=schema_url)

        if not name:
            _logger.warning("Meter name cannot be None or empty.")
            return NoOpMeter(name, version=version, schema_url=schema_url)

        info = InstrumentationScope(name, version, schema_url)
        with self._meter_lock:
            if not self._meters.get(info):
                # FIXME #2558 pass SDKConfig object to meter so that the meter
                # has access to views.
                self._meters[info] = Meter(
                    info,
                    self._measurement_consumer,
                )
            return self._meters[info]

virt/lib/python3.9/site-packages/mysql/opentelemetry/sdk/metrics/_internal/__init__.py
PrinceDisant/LostInNL
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


from logging import getLogger
from threading import Lock
from time import time_ns
from typing import Dict, List, Sequence

from mysql.opentelemetry.metrics import Instrument
from mysql.opentelemetry.sdk.metrics._internal.aggregation import (
    Aggregation,
    DefaultAggregation,
    _Aggregation,
    _SumAggregation,
)
from mysql.opentelemetry.sdk.metrics._internal.export import AggregationTemporality
from mysql.opentelemetry.sdk.metrics._internal.measurement import Measurement
from mysql.opentelemetry.sdk.metrics._internal.point import DataPointT
from mysql.opentelemetry.sdk.metrics._internal.view import View

_logger = getLogger(__name__)


class _ViewInstrumentMatch:
    def __init__(
        self,
        view: View,
        instrument: Instrument,
        instrument_class_aggregation: Dict[type, Aggregation],
    ):
        self._start_time_unix_nano = time_ns()
        self._view = view
        self._instrument = instrument
        self._attributes_aggregation: Dict[frozenset, _Aggregation] = {}
        self._lock = Lock()
        self._instrument_class_aggregation = instrument_class_aggregation
        self._name = self._view._name or self._instrument.name
        self._description = self._view._description or self._instrument.description
        if not isinstance(self._view._aggregation, DefaultAggregation):
            self._aggregation = self._view._aggregation._create_aggregation(
                self._instrument, None, 0
            )
        else:
            self._aggregation = self._instrument_class_aggregation[
                self._instrument.__class__
            ]._create_aggregation(self._instrument, None, 0)

    def conflicts(self, other: "_ViewInstrumentMatch") -> bool:
        # pylint: disable=protected-access

        result = (
            self._name == other._name
            and self._instrument.unit == other._instrument.unit
            # The aggregation class is being used here instead of data point
            # type since they are functionally equivalent.
            and self._aggregation.__class__ == other._aggregation.__class__
        )
        if isinstance(self._aggregation, _SumAggregation):
            result = (
                result
                and self._aggregation._instrument_is_monotonic
                == other._aggregation._instrument_is_monotonic
                and self._aggregation._instrument_temporality
                == other._aggregation._instrument_temporality
            )

        return result

    # pylint: disable=protected-access
    def consume_measurement(self, measurement: Measurement) -> None:
        if self._view._attribute_keys is not None:
            attributes = {}

            for key, value in (measurement.attributes or {}).items():
                if key in self._view._attribute_keys:
                    attributes[key] = value
        elif measurement.attributes is not None:
            attributes = measurement.attributes
        else:
            attributes = {}

        aggr_key = frozenset(attributes.items())

        if aggr_key not in self._attributes_aggregation:
            with self._lock:
                if aggr_key not in self._attributes_aggregation:
                    if not isinstance(self._view._aggregation, DefaultAggregation):
                        aggregation = self._view._aggregation._create_aggregation(
                            self._instrument,
                            attributes,
                            self._start_time_unix_nano,
                        )
                    else:
                        aggregation = self._instrument_class_aggregation[
                            self._instrument.__class__
                        ]._create_aggregation(
                            self._instrument,
                            attributes,
                            self._start_time_unix_nano,
                        )
                    self._attributes_aggregation[aggr_key] = aggregation

        self._attributes_aggregation[aggr_key].aggregate(measurement)

    def collect(
        self,
        aggregation_temporality: AggregationTemporality,
        collection_start_nanos: int,
    ) -> Sequence[DataPointT]:
        data_points: List[DataPointT] = []
        with self._lock:
            for aggregation in self._attributes_aggregation.values():
                data_point = aggregation.collect(
                    aggregation_temporality, collection_start_nanos
                )
                if data_point is not None:
                    data_points.append(data_point)
        return data_points

virt/lib/python3.9/site-packages/mysql/opentelemetry/sdk/metrics/_internal/_view_instrument_match.py
PrinceDisant/LostInNL
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# pylint: disable=too-many-lines

from abc import ABC, abstractmethod
from bisect import bisect_left
from enum import IntEnum
from logging import getLogger
from math import inf
from threading import Lock
from typing import Generic, List, Optional, Sequence, TypeVar

from mysql.opentelemetry.metrics import (
    Asynchronous,
    Counter,
    Histogram,
    Instrument,
    ObservableCounter,
    ObservableGauge,
    ObservableUpDownCounter,
    Synchronous,
    UpDownCounter,
)
from mysql.opentelemetry.sdk.metrics._internal.exponential_histogram.buckets import (
    Buckets,
)
from mysql.opentelemetry.sdk.metrics._internal.exponential_histogram.mapping.exponent_mapping import (
    ExponentMapping,
)
from mysql.opentelemetry.sdk.metrics._internal.exponential_histogram.mapping.logarithm_mapping import (
    LogarithmMapping,
)
from mysql.opentelemetry.sdk.metrics._internal.measurement import Measurement
from mysql.opentelemetry.sdk.metrics._internal.point import (
    Buckets as BucketsPoint,
    ExponentialHistogramDataPoint,
    Gauge,
    Histogram as HistogramPoint,
    HistogramDataPoint,
    NumberDataPoint,
    Sum,
)
from mysql.opentelemetry.util.types import Attributes

_DataPointVarT = TypeVar("_DataPointVarT", NumberDataPoint, HistogramDataPoint)

_logger = getLogger(__name__)


class AggregationTemporality(IntEnum):
    """
    The temporality to use when aggregating data.

    Can be one of the following values:
    """

    UNSPECIFIED = 0
    DELTA = 1
    CUMULATIVE = 2


class _Aggregation(ABC, Generic[_DataPointVarT]):
    def __init__(self, attributes: Attributes):
        self._lock = Lock()
        self._attributes = attributes
        self._previous_point = None

    @abstractmethod
    def aggregate(self, measurement: Measurement) -> None:
        pass

    @abstractmethod
    def collect(
        self,
        aggregation_temporality: AggregationTemporality,
        collection_start_nano: int,
    ) -> Optional[_DataPointVarT]:
        pass


class _DropAggregation(_Aggregation):
    def aggregate(self, measurement: Measurement) -> None:
        pass

    def collect(
        self,
        aggregation_temporality: AggregationTemporality,
        collection_start_nano: int,
    ) -> Optional[_DataPointVarT]:
        pass


class _SumAggregation(_Aggregation[Sum]):
    def __init__(
        self,
        attributes: Attributes,
        instrument_is_monotonic: bool,
        instrument_temporality: AggregationTemporality,
        start_time_unix_nano: int,
    ):
        super().__init__(attributes)

        self._start_time_unix_nano = start_time_unix_nano
        self._instrument_temporality = instrument_temporality
        self._instrument_is_monotonic = instrument_is_monotonic

        if self._instrument_temporality is AggregationTemporality.DELTA:
            self._value = 0
        else:
            self._value = None

    def aggregate(self, measurement: Measurement) -> None:
        with self._lock:
            if self._value is None:
                self._value = 0
            self._value = self._value + measurement.value

    def collect(
        self,
        aggregation_temporality: AggregationTemporality,
        collection_start_nano: int,
    ) -> Optional[NumberDataPoint]:
        """
        Atomically return a point for the current value of the metric and
        reset the aggregation value.
        """
        if self._instrument_temporality is AggregationTemporality.DELTA:
            with self._lock:
                value = self._value
                start_time_unix_nano = self._start_time_unix_nano

                self._value = 0
                self._start_time_unix_nano = collection_start_nano

        else:
            with self._lock:
                if self._value is None:
                    return None
                value = self._value
                self._value = None
                start_time_unix_nano = self._start_time_unix_nano

        current_point = NumberDataPoint(
            attributes=self._attributes,
            start_time_unix_nano=start_time_unix_nano,
            time_unix_nano=collection_start_nano,
            value=value,
        )

        if self._previous_point is None or (
            self._instrument_temporality is aggregation_temporality
        ):
            # Output DELTA for a synchronous instrument
            # Output CUMULATIVE for an asynchronous instrument
            self._previous_point = current_point
            return current_point

        if aggregation_temporality is AggregationTemporality.DELTA:
            # Output temporality DELTA for an asynchronous instrument
            value = current_point.value - self._previous_point.value
            output_start_time_unix_nano = self._previous_point.time_unix_nano

        else:
            # Output CUMULATIVE for a synchronous instrument
            value = current_point.value + self._previous_point.value
            output_start_time_unix_nano = self._previous_point.start_time_unix_nano

        current_point = NumberDataPoint(
            attributes=self._attributes,
            start_time_unix_nano=output_start_time_unix_nano,
            time_unix_nano=current_point.time_unix_nano,
            value=value,
        )

        self._previous_point = current_point
        return current_point


class _LastValueAggregation(_Aggregation[Gauge]):
    def __init__(self, attributes: Attributes):
        super().__init__(attributes)
        self._value = None

    def aggregate(self, measurement: Measurement):
        with self._lock:
            self._value = measurement.value

    def collect(
        self,
        aggregation_temporality: AggregationTemporality,
        collection_start_nano: int,
    ) -> Optional[_DataPointVarT]:
        """
        Atomically return a point for the current value of the metric.
        """
        with self._lock:
            if self._value is None:
                return None
            value = self._value
            self._value = None

        return NumberDataPoint(
            attributes=self._attributes,
            start_time_unix_nano=0,
            time_unix_nano=collection_start_nano,
            value=value,
        )


class _ExplicitBucketHistogramAggregation(_Aggregation[HistogramPoint]):
    def __init__(
        self,
        attributes: Attributes,
        start_time_unix_nano: int,
        boundaries: Sequence[float] = (
            0.0,
            5.0,
            10.0,
            25.0,
            50.0,
            75.0,
            100.0,
            250.0,
            500.0,
            750.0,
            1000.0,
            2500.0,
            5000.0,
            7500.0,
            10000.0,
        ),
        record_min_max: bool = True,
    ):
        super().__init__(attributes)
        self._boundaries = tuple(boundaries)
        self._bucket_counts = self._get_empty_bucket_counts()
        self._min = inf
        self._max = -inf
        self._sum = 0
        self._record_min_max = record_min_max
        self._start_time_unix_nano = start_time_unix_nano
        # It is assumed that the "natural" aggregation temporality for a
        # Histogram instrument is DELTA, like the "natural" aggregation
        # temporality for a Counter is DELTA and the "natural" aggregation
        # temporality for an ObservableCounter is CUMULATIVE.
        self._instrument_temporality = AggregationTemporality.DELTA

    def _get_empty_bucket_counts(self) -> List[int]:
        return [0] * (len(self._boundaries) + 1)

    def aggregate(self, measurement: Measurement) -> None:
        value = measurement.value

        if self._record_min_max:
            self._min = min(self._min, value)
            self._max = max(self._max, value)

        self._sum += value

        self._bucket_counts[bisect_left(self._boundaries, value)] += 1

    def collect(
        self,
        aggregation_temporality: AggregationTemporality,
        collection_start_nano: int,
    ) -> Optional[_DataPointVarT]:
        """
        Atomically return a point for the current value of the metric.
        """
        with self._lock:
            if not any(self._bucket_counts):
                return None

            bucket_counts = self._bucket_counts
            start_time_unix_nano = self._start_time_unix_nano
            sum_ = self._sum
            max_ = self._max
            min_ = self._min

            self._bucket_counts = self._get_empty_bucket_counts()
            self._start_time_unix_nano = collection_start_nano
            self._sum = 0
            self._min = inf
            self._max = -inf

        current_point = HistogramDataPoint(
            attributes=self._attributes,
            start_time_unix_nano=start_time_unix_nano,
            time_unix_nano=collection_start_nano,
            count=sum(bucket_counts),
            sum=sum_,
            bucket_counts=tuple(bucket_counts),
            explicit_bounds=self._boundaries,
            min=min_,
            max=max_,
        )

        if self._previous_point is None or (
            self._instrument_temporality is aggregation_temporality
        ):
            self._previous_point = current_point
            return current_point

        max_ = current_point.max
        min_ = current_point.min

        if aggregation_temporality is AggregationTemporality.CUMULATIVE:
            start_time_unix_nano = self._previous_point.start_time_unix_nano
            sum_ = current_point.sum + self._previous_point.sum
            # Only update min/max on delta -> cumulative
            max_ = max(current_point.max, self._previous_point.max)
            min_ = min(current_point.min, self._previous_point.min)
            bucket_counts = [
                curr_count + prev_count
                for curr_count, prev_count in zip(
                    current_point.bucket_counts,
                    self._previous_point.bucket_counts,
                )
            ]
        else:
            start_time_unix_nano = self._previous_point.time_unix_nano
            sum_ = current_point.sum - self._previous_point.sum
            bucket_counts = [
                curr_count - prev_count
                for curr_count, prev_count in zip(
                    current_point.bucket_counts,
                    self._previous_point.bucket_counts,
                )
            ]

        current_point = HistogramDataPoint(
            attributes=self._attributes,
            start_time_unix_nano=start_time_unix_nano,
            time_unix_nano=current_point.time_unix_nano,
            count=sum(bucket_counts),
            sum=sum_,
            bucket_counts=tuple(bucket_counts),
            explicit_bounds=current_point.explicit_bounds,
            min=min_,
            max=max_,
        )
        self._previous_point = current_point
        return current_point


# pylint: disable=protected-access
class _ExponentialBucketHistogramAggregation(_Aggregation[HistogramPoint]):
    # _min_max_size and _max_max_size are the smallest and largest values
    # the max_size parameter may have, respectively.

    # _min_max_size is is the smallest reasonable value which is small enough
    # to contain the entire normal floating point range at the minimum scale.
    _min_max_size = 2

    # _max_max_size is an arbitrary limit meant to limit accidental creation of
    # giant exponential bucket histograms.
    _max_max_size = 16384

    def __init__(
        self,
        attributes: Attributes,
        start_time_unix_nano: int,
        # This is the default maximum number of buckets per positive or
        # negative number range.  The value 160 is specified by mysql.OpenTelemetry.
        # See the derivation here:
        # https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/sdk.md#exponential-bucket-histogram-aggregation)
        max_size: int = 160,
    ):
        super().__init__(attributes)
        # max_size is the maximum capacity of the positive and negative
        # buckets.
        if max_size < self._min_max_size:
            raise ValueError(
                f"Buckets max size {max_size} is smaller than "
                "minimum max size {self._min_max_size}"
            )

        if max_size > self._max_max_size:
            raise ValueError(
                f"Buckets max size {max_size} is larger than "
                "maximum max size {self._max_max_size}"
            )

        self._max_size = max_size

        # _sum is the sum of all the values aggregated by this aggregator.
        self._sum = 0

        # _count is the count of all calls to aggregate.
        self._count = 0

        # _zero_count is the count of all the calls to aggregate when the value
        # to be aggregated is exactly 0.
        self._zero_count = 0

        # _min is the smallest value aggregated by this aggregator.
        self._min = inf

        # _max is the smallest value aggregated by this aggregator.
        self._max = -inf

        # _positive holds the positive values.
        self._positive = Buckets()

        # _negative holds the negative values by their absolute value.
        self._negative = Buckets()

        # _mapping corresponds to the current scale, is shared by both the
        # positive and negative buckets.
        self._mapping = LogarithmMapping(LogarithmMapping._max_scale)

        self._instrument_temporality = AggregationTemporality.DELTA
        self._start_time_unix_nano = start_time_unix_nano

        self._previous_scale = None
        self._previous_start_time_unix_nano = None
        self._previous_sum = None
        self._previous_max = None
        self._previous_min = None
        self._previous_positive = None
        self._previous_negative = None

    def aggregate(self, measurement: Measurement) -> None:
        # pylint: disable=too-many-branches,too-many-statements, too-many-locals

        with self._lock:
            value = measurement.value

            # 0. Set the following attributes:
            # _min
            # _max
            # _count
            # _zero_count
            # _sum
            if value < self._min:
                self._min = value

            if value > self._max:
                self._max = value

            self._count += 1

            if value == 0:
                self._zero_count += 1
                # No need to do anything else if value is zero, just increment the
                # zero count.
                return

            self._sum += value

            # 1. Use the positive buckets for positive values and the negative
            # buckets for negative values.
            if value > 0:
                buckets = self._positive

            else:
                # Both exponential and logarithm mappings use only positive values
                # so the absolute value is used here.
                value = -value
                buckets = self._negative

            # 2. Compute the index for the value at the current scale.
            index = self._mapping.map_to_index(value)

            # IncrementIndexBy starts here

            # 3. Determine if a change of scale is needed.
            is_rescaling_needed = False

            if len(buckets) == 0:
                buckets.index_start = index
                buckets.index_end = index
                buckets.index_base = index

            elif (
                index < buckets.index_start
                and (buckets.index_end - index) >= self._max_size
            ):
                is_rescaling_needed = True
                low = index
                high = buckets.index_end

            elif (
                index > buckets.index_end
                and (index - buckets.index_start) >= self._max_size
            ):
                is_rescaling_needed = True
                low = buckets.index_start
                high = index

            # 4. Rescale the mapping if needed.
            if is_rescaling_needed:
                self._downscale(
                    self._get_scale_change(low, high),
                    self._positive,
                    self._negative,
                )

                index = self._mapping.map_to_index(value)

            # 5. If the index is outside
            # [buckets.index_start, buckets.index_end] readjust the buckets
            # boundaries or add more buckets.
            if index < buckets.index_start:
                span = buckets.index_end - index

                if span >= len(buckets.counts):
                    buckets.grow(span + 1, self._max_size)

                buckets.index_start = index

            elif index > buckets.index_end:
                span = index - buckets.index_start

                if span >= len(buckets.counts):
                    buckets.grow(span + 1, self._max_size)

                buckets.index_end = index

            # 6. Compute the index of the bucket to be incremented.
            bucket_index = index - buckets.index_base

            if bucket_index < 0:
                bucket_index += len(buckets.counts)

            # 7. Increment the bucket.
            buckets.increment_bucket(bucket_index)

    def collect(
        self,
        aggregation_temporality: AggregationTemporality,
        collection_start_nano: int,
    ) -> Optional[_DataPointVarT]:
        """
        Atomically return a point for the current value of the metric.
        """
        # pylint: disable=too-many-statements, too-many-locals

        with self._lock:
            if self._count == 0:
                return None

            current_negative = self._negative
            current_positive = self._positive
            current_zero_count = self._zero_count
            current_count = self._count
            current_start_time_unix_nano = self._start_time_unix_nano
            current_sum = self._sum
            current_max = self._max
            if current_max == -inf:
                current_max = None
            current_min = self._min
            if current_min == inf:
                current_min = None

            if self._count == self._zero_count:
                current_scale = 0

            else:
                current_scale = self._mapping.scale

            self._negative = Buckets()
            self._positive = Buckets()
            self._start_time_unix_nano = collection_start_nano
            self._sum = 0
            self._count = 0
            self._zero_count = 0
            self._min = inf
            self._max = -inf

            current_point = ExponentialHistogramDataPoint(
                attributes=self._attributes,
                start_time_unix_nano=current_start_time_unix_nano,
                time_unix_nano=collection_start_nano,
                count=current_count,
                sum=current_sum,
                scale=current_scale,
                zero_count=current_zero_count,
                positive=BucketsPoint(
                    offset=current_positive.offset,
                    bucket_counts=current_positive.counts,
                ),
                negative=BucketsPoint(
                    offset=current_negative.offset,
                    bucket_counts=current_negative.counts,
                ),
                # FIXME: Find the right value for flags
                flags=0,
                min=current_min,
                max=current_max,
            )

            if self._previous_scale is None or (
                self._instrument_temporality is aggregation_temporality
            ):
                self._previous_scale = current_scale
                self._previous_start_time_unix_nano = current_start_time_unix_nano
                self._previous_max = current_max
                self._previous_min = current_min
                self._previous_sum = current_sum
                self._previous_positive = current_positive
                self._previous_negative = current_negative

                return current_point

            min_scale = min(self._previous_scale, current_scale)

            low_positive, high_positive = self._get_low_high_previous_current(
                self._previous_positive, current_positive, min_scale
            )
            low_negative, high_negative = self._get_low_high_previous_current(
                self._previous_negative, current_negative, min_scale
            )

            min_scale = min(
                min_scale - self._get_scale_change(low_positive, high_positive),
                min_scale - self._get_scale_change(low_negative, high_negative),
            )

            # FIXME Go implementation checks if the histogram (not the mapping
            # but the histogram) has a count larger than zero, if not, scale
            # (the histogram scale) would be zero. See exponential.go 191
            self._downscale(
                self._mapping.scale - min_scale,
                self._previous_positive,
                self._previous_negative,
            )

            if aggregation_temporality is AggregationTemporality.CUMULATIVE:
                start_time_unix_nano = self._previous_start_time_unix_nano
                sum_ = current_sum + self._previous_sum
                # Only update min/max on delta -> cumulative
                max_ = max(current_max, self._previous_max)
                min_ = min(current_min, self._previous_min)

                self._merge(
                    self._previous_positive,
                    current_positive,
                    current_scale,
                    min_scale,
                    aggregation_temporality,
                )
                self._merge(
                    self._previous_negative,
                    current_negative,
                    current_scale,
                    min_scale,
                    aggregation_temporality,
                )

            else:
                start_time_unix_nano = self._previous_start_time_unix_nano
                sum_ = current_sum - self._previous_sum
                max_ = current_max
                min_ = current_min

                self._merge(
                    self._previous_positive,
                    current_positive,
                    current_scale,
                    min_scale,
                    aggregation_temporality,
                )
                self._merge(
                    self._previous_negative,
                    current_negative,
                    current_scale,
                    min_scale,
                    aggregation_temporality,
                )

            current_point = ExponentialHistogramDataPoint(
                attributes=self._attributes,
                start_time_unix_nano=start_time_unix_nano,
                time_unix_nano=collection_start_nano,
                count=current_count,
                sum=sum_,
                scale=current_scale,
                zero_count=current_zero_count,
                positive=BucketsPoint(
                    offset=current_positive.offset,
                    bucket_counts=current_positive.counts,
                ),
                negative=BucketsPoint(
                    offset=current_negative.offset,
                    bucket_counts=current_negative.counts,
                ),
                # FIXME: Find the right value for flags
                flags=0,
                min=min_,
                max=max_,
            )

            self._previous_scale = current_scale
            self._previous_positive = current_positive
            self._previous_negative = current_negative
            self._previous_start_time_unix_nano = current_start_time_unix_nano
            self._previous_sum = current_sum

            return current_point

    def _get_low_high_previous_current(
        self, previous_point_buckets, current_point_buckets, min_scale
    ):
        (previous_point_low, previous_point_high) = self._get_low_high(
            previous_point_buckets, min_scale
        )
        (current_point_low, current_point_high) = self._get_low_high(
            current_point_buckets, min_scale
        )

        if current_point_low > current_point_high:
            low = previous_point_low
            high = previous_point_high

        elif previous_point_low > previous_point_high:
            low = current_point_low
            high = current_point_high

        else:
            low = min(previous_point_low, current_point_low)
            high = max(previous_point_high, current_point_high)

        return low, high

    def _get_low_high(self, buckets, min_scale):
        if buckets.counts == [0]:
            return 0, -1

        shift = self._mapping._scale - min_scale

        return buckets.index_start >> shift, buckets.index_end >> shift

    def _get_scale_change(self, low, high):
        change = 0

        while high - low >= self._max_size:
            high = high >> 1
            low = low >> 1

            change += 1

        return change

    def _downscale(self, change: int, positive, negative):
        if change == 0:
            return

        if change < 0:
            raise Exception("Invalid change of scale")

        new_scale = self._mapping.scale - change

        positive.downscale(change)
        negative.downscale(change)

        if new_scale <= 0:
            mapping = ExponentMapping(new_scale)
        else:
            mapping = LogarithmMapping(new_scale)

        self._mapping = mapping

    def _merge(
        self,
        previous_buckets,
        current_buckets,
        current_scale,
        min_scale,
        aggregation_temporality,
    ):
        current_change = current_scale - min_scale

        for current_bucket_index, current_bucket in enumerate(current_buckets.counts):
            if current_bucket == 0:
                continue

            # Not considering the case where len(previous_buckets) == 0. This
            # would not happen because self._previous_point is only assigned to
            # an ExponentialHistogramDataPoint object if self._count != 0.

            index = (current_buckets.offset + current_bucket_index) >> current_change

            if index < previous_buckets.index_start:
                span = previous_buckets.index_end - index

                if span >= self._max_size:
                    raise Exception("Incorrect merge scale")

                if span >= len(previous_buckets.counts):
                    previous_buckets.grow(span + 1, self._max_size)

                previous_buckets.index_start = index

            if index > previous_buckets.index_end:
                span = index - previous_buckets.index_end

                if span >= self._max_size:
                    raise Exception("Incorrect merge scale")

                if span >= len(previous_buckets.counts):
                    previous_buckets.grow(span + 1, self._max_size)

                previous_buckets.index_end = index

            bucket_index = index - previous_buckets.index_base

            if bucket_index < 0:
                bucket_index += len(previous_buckets.counts)

            if aggregation_temporality is AggregationTemporality.DELTA:
                current_bucket = -current_bucket

            previous_buckets.increment_bucket(bucket_index, increment=current_bucket)


class Aggregation(ABC):
    """
    Base class for all aggregation types.
    """

    @abstractmethod
    def _create_aggregation(
        self,
        instrument: Instrument,
        attributes: Attributes,
        start_time_unix_nano: int,
    ) -> _Aggregation:
        """Creates an aggregation"""


class DefaultAggregation(Aggregation):
    """
    The default aggregation to be used in a `View`.

    This aggregation will create an actual aggregation depending on the
    instrument type, as specified next:

    ==================================================== ====================================
    Instrument                                           Aggregation
    ==================================================== ====================================
    `mysql.opentelemetry.sdk.metrics.Counter`                  `SumAggregation`
    `mysql.opentelemetry.sdk.metrics.UpDownCounter`            `SumAggregation`
    `mysql.opentelemetry.sdk.metrics.ObservableCounter`        `SumAggregation`
    `mysql.opentelemetry.sdk.metrics.ObservableUpDownCounter`  `SumAggregation`
    `mysql.opentelemetry.sdk.metrics.Histogram`                `ExplicitBucketHistogramAggregation`
    `mysql.opentelemetry.sdk.metrics.ObservableGauge`          `LastValueAggregation`
    ==================================================== ====================================
    """

    def _create_aggregation(
        self,
        instrument: Instrument,
        attributes: Attributes,
        start_time_unix_nano: int,
    ) -> _Aggregation:
        # pylint: disable=too-many-return-statements
        if isinstance(instrument, Counter):
            return _SumAggregation(
                attributes,
                instrument_is_monotonic=True,
                instrument_temporality=AggregationTemporality.DELTA,
                start_time_unix_nano=start_time_unix_nano,
            )
        if isinstance(instrument, UpDownCounter):
            return _SumAggregation(
                attributes,
                instrument_is_monotonic=False,
                instrument_temporality=AggregationTemporality.DELTA,
                start_time_unix_nano=start_time_unix_nano,
            )

        if isinstance(instrument, ObservableCounter):
            return _SumAggregation(
                attributes,
                instrument_is_monotonic=True,
                instrument_temporality=AggregationTemporality.CUMULATIVE,
                start_time_unix_nano=start_time_unix_nano,
            )

        if isinstance(instrument, ObservableUpDownCounter):
            return _SumAggregation(
                attributes,
                instrument_is_monotonic=False,
                instrument_temporality=AggregationTemporality.CUMULATIVE,
                start_time_unix_nano=start_time_unix_nano,
            )

        if isinstance(instrument, Histogram):
            return _ExplicitBucketHistogramAggregation(attributes, start_time_unix_nano)

        if isinstance(instrument, ObservableGauge):
            return _LastValueAggregation(attributes)

        raise Exception(f"Invalid instrument type {type(instrument)} found")


class ExponentialBucketHistogramAggregation(Aggregation):
    def __init__(
        self,
        max_size: int = 160,
    ):
        self._max_size = max_size

    def _create_aggregation(
        self,
        instrument: Instrument,
        attributes: Attributes,
        start_time_unix_nano: int,
    ) -> _Aggregation:
        return _ExponentialBucketHistogramAggregation(
            attributes,
            start_time_unix_nano,
            max_size=self._max_size,
        )


class ExplicitBucketHistogramAggregation(Aggregation):
    """This aggregation informs the SDK to collect:

    - Count of Measurement values falling within explicit bucket boundaries.
    - Arithmetic sum of Measurement values in population. This SHOULD NOT be collected when used with instruments that record negative measurements, e.g. UpDownCounter or ObservableGauge.
    - Min (optional) Measurement value in population.
    - Max (optional) Measurement value in population.


    Args:
        boundaries: Array of increasing values representing explicit bucket boundary values.
        record_min_max: Whether to record min and max.
    """

    def __init__(
        self,
        boundaries: Sequence[float] = (
            0.0,
            5.0,
            10.0,
            25.0,
            50.0,
            75.0,
            100.0,
            250.0,
            500.0,
            750.0,
            1000.0,
            2500.0,
            5000.0,
            7500.0,
            10000.0,
        ),
        record_min_max: bool = True,
    ) -> None:
        self._boundaries = boundaries
        self._record_min_max = record_min_max

    def _create_aggregation(
        self,
        instrument: Instrument,
        attributes: Attributes,
        start_time_unix_nano: int,
    ) -> _Aggregation:
        return _ExplicitBucketHistogramAggregation(
            attributes,
            start_time_unix_nano,
            self._boundaries,
            self._record_min_max,
        )


class SumAggregation(Aggregation):
    """This aggregation informs the SDK to collect:

    - The arithmetic sum of Measurement values.
    """

    def _create_aggregation(
        self,
        instrument: Instrument,
        attributes: Attributes,
        start_time_unix_nano: int,
    ) -> _Aggregation:
        temporality = AggregationTemporality.UNSPECIFIED
        if isinstance(instrument, Synchronous):
            temporality = AggregationTemporality.DELTA
        elif isinstance(instrument, Asynchronous):
            temporality = AggregationTemporality.CUMULATIVE

        return _SumAggregation(
            attributes,
            isinstance(instrument, (Counter, ObservableCounter)),
            temporality,
            start_time_unix_nano,
        )


class LastValueAggregation(Aggregation):
    """
    This aggregation informs the SDK to collect:

    - The last Measurement.
    - The timestamp of the last Measurement.
    """

    def _create_aggregation(
        self,
        instrument: Instrument,
        attributes: Attributes,
        start_time_unix_nano: int,
    ) -> _Aggregation:
        return _LastValueAggregation(attributes)


class DropAggregation(Aggregation):
    """Using this aggregation will make all measurements be ignored."""

    def _create_aggregation(
        self,
        instrument: Instrument,
        attributes: Attributes,
        start_time_unix_nano: int,
    ) -> _Aggregation:
        return _DropAggregation(attributes)

virt/lib/python3.9/site-packages/mysql/opentelemetry/sdk/metrics/_internal/aggregation 2.py
PrinceDisant/LostInNL
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# pylint: disable=too-many-lines

from abc import ABC, abstractmethod
from bisect import bisect_left
from enum import IntEnum
from logging import getLogger
from math import inf
from threading import Lock
from typing import Generic, List, Optional, Sequence, TypeVar

from mysql.opentelemetry.metrics import (
    Asynchronous,
    Counter,
    Histogram,
    Instrument,
    ObservableCounter,
    ObservableGauge,
    ObservableUpDownCounter,
    Synchronous,
    UpDownCounter,
)
from mysql.opentelemetry.sdk.metrics._internal.exponential_histogram.buckets import (
    Buckets,
)
from mysql.opentelemetry.sdk.metrics._internal.exponential_histogram.mapping.exponent_mapping import (
    ExponentMapping,
)
from mysql.opentelemetry.sdk.metrics._internal.exponential_histogram.mapping.logarithm_mapping import (
    LogarithmMapping,
)
from mysql.opentelemetry.sdk.metrics._internal.measurement import Measurement
from mysql.opentelemetry.sdk.metrics._internal.point import (
    Buckets as BucketsPoint,
    ExponentialHistogramDataPoint,
    Gauge,
    Histogram as HistogramPoint,
    HistogramDataPoint,
    NumberDataPoint,
    Sum,
)
from mysql.opentelemetry.util.types import Attributes

_DataPointVarT = TypeVar("_DataPointVarT", NumberDataPoint, HistogramDataPoint)

_logger = getLogger(__name__)


class AggregationTemporality(IntEnum):
    """
    The temporality to use when aggregating data.

    Can be one of the following values:
    """

    UNSPECIFIED = 0
    DELTA = 1
    CUMULATIVE = 2


class _Aggregation(ABC, Generic[_DataPointVarT]):
    def __init__(self, attributes: Attributes):
        self._lock = Lock()
        self._attributes = attributes
        self._previous_point = None

    @abstractmethod
    def aggregate(self, measurement: Measurement) -> None:
        pass

    @abstractmethod
    def collect(
        self,
        aggregation_temporality: AggregationTemporality,
        collection_start_nano: int,
    ) -> Optional[_DataPointVarT]:
        pass


class _DropAggregation(_Aggregation):
    def aggregate(self, measurement: Measurement) -> None:
        pass

    def collect(
        self,
        aggregation_temporality: AggregationTemporality,
        collection_start_nano: int,
    ) -> Optional[_DataPointVarT]:
        pass


class _SumAggregation(_Aggregation[Sum]):
    def __init__(
        self,
        attributes: Attributes,
        instrument_is_monotonic: bool,
        instrument_temporality: AggregationTemporality,
        start_time_unix_nano: int,
    ):
        super().__init__(attributes)

        self._start_time_unix_nano = start_time_unix_nano
        self._instrument_temporality = instrument_temporality
        self._instrument_is_monotonic = instrument_is_monotonic

        if self._instrument_temporality is AggregationTemporality.DELTA:
            self._value = 0
        else:
            self._value = None

    def aggregate(self, measurement: Measurement) -> None:
        with self._lock:
            if self._value is None:
                self._value = 0
            self._value = self._value + measurement.value

    def collect(
        self,
        aggregation_temporality: AggregationTemporality,
        collection_start_nano: int,
    ) -> Optional[NumberDataPoint]:
        """
        Atomically return a point for the current value of the metric and
        reset the aggregation value.
        """
        if self._instrument_temporality is AggregationTemporality.DELTA:
            with self._lock:
                value = self._value
                start_time_unix_nano = self._start_time_unix_nano

                self._value = 0
                self._start_time_unix_nano = collection_start_nano

        else:
            with self._lock:
                if self._value is None:
                    return None
                value = self._value
                self._value = None
                start_time_unix_nano = self._start_time_unix_nano

        current_point = NumberDataPoint(
            attributes=self._attributes,
            start_time_unix_nano=start_time_unix_nano,
            time_unix_nano=collection_start_nano,
            value=value,
        )

        if self._previous_point is None or (
            self._instrument_temporality is aggregation_temporality
        ):
            # Output DELTA for a synchronous instrument
            # Output CUMULATIVE for an asynchronous instrument
            self._previous_point = current_point
            return current_point

        if aggregation_temporality is AggregationTemporality.DELTA:
            # Output temporality DELTA for an asynchronous instrument
            value = current_point.value - self._previous_point.value
            output_start_time_unix_nano = self._previous_point.time_unix_nano

        else:
            # Output CUMULATIVE for a synchronous instrument
            value = current_point.value + self._previous_point.value
            output_start_time_unix_nano = self._previous_point.start_time_unix_nano

        current_point = NumberDataPoint(
            attributes=self._attributes,
            start_time_unix_nano=output_start_time_unix_nano,
            time_unix_nano=current_point.time_unix_nano,
            value=value,
        )

        self._previous_point = current_point
        return current_point


class _LastValueAggregation(_Aggregation[Gauge]):
    def __init__(self, attributes: Attributes):
        super().__init__(attributes)
        self._value = None

    def aggregate(self, measurement: Measurement):
        with self._lock:
            self._value = measurement.value

    def collect(
        self,
        aggregation_temporality: AggregationTemporality,
        collection_start_nano: int,
    ) -> Optional[_DataPointVarT]:
        """
        Atomically return a point for the current value of the metric.
        """
        with self._lock:
            if self._value is None:
                return None
            value = self._value
            self._value = None

        return NumberDataPoint(
            attributes=self._attributes,
            start_time_unix_nano=0,
            time_unix_nano=collection_start_nano,
            value=value,
        )


class _ExplicitBucketHistogramAggregation(_Aggregation[HistogramPoint]):
    def __init__(
        self,
        attributes: Attributes,
        start_time_unix_nano: int,
        boundaries: Sequence[float] = (
            0.0,
            5.0,
            10.0,
            25.0,
            50.0,
            75.0,
            100.0,
            250.0,
            500.0,
            750.0,
            1000.0,
            2500.0,
            5000.0,
            7500.0,
            10000.0,
        ),
        record_min_max: bool = True,
    ):
        super().__init__(attributes)
        self._boundaries = tuple(boundaries)
        self._bucket_counts = self._get_empty_bucket_counts()
        self._min = inf
        self._max = -inf
        self._sum = 0
        self._record_min_max = record_min_max
        self._start_time_unix_nano = start_time_unix_nano
        # It is assumed that the "natural" aggregation temporality for a
        # Histogram instrument is DELTA, like the "natural" aggregation
        # temporality for a Counter is DELTA and the "natural" aggregation
        # temporality for an ObservableCounter is CUMULATIVE.
        self._instrument_temporality = AggregationTemporality.DELTA

    def _get_empty_bucket_counts(self) -> List[int]:
        return [0] * (len(self._boundaries) + 1)

    def aggregate(self, measurement: Measurement) -> None:
        value = measurement.value

        if self._record_min_max:
            self._min = min(self._min, value)
            self._max = max(self._max, value)

        self._sum += value

        self._bucket_counts[bisect_left(self._boundaries, value)] += 1

    def collect(
        self,
        aggregation_temporality: AggregationTemporality,
        collection_start_nano: int,
    ) -> Optional[_DataPointVarT]:
        """
        Atomically return a point for the current value of the metric.
        """
        with self._lock:
            if not any(self._bucket_counts):
                return None

            bucket_counts = self._bucket_counts
            start_time_unix_nano = self._start_time_unix_nano
            sum_ = self._sum
            max_ = self._max
            min_ = self._min

            self._bucket_counts = self._get_empty_bucket_counts()
            self._start_time_unix_nano = collection_start_nano
            self._sum = 0
            self._min = inf
            self._max = -inf

        current_point = HistogramDataPoint(
            attributes=self._attributes,
            start_time_unix_nano=start_time_unix_nano,
            time_unix_nano=collection_start_nano,
            count=sum(bucket_counts),
            sum=sum_,
            bucket_counts=tuple(bucket_counts),
            explicit_bounds=self._boundaries,
            min=min_,
            max=max_,
        )

        if self._previous_point is None or (
            self._instrument_temporality is aggregation_temporality
        ):
            self._previous_point = current_point
            return current_point

        max_ = current_point.max
        min_ = current_point.min

        if aggregation_temporality is AggregationTemporality.CUMULATIVE:
            start_time_unix_nano = self._previous_point.start_time_unix_nano
            sum_ = current_point.sum + self._previous_point.sum
            # Only update min/max on delta -> cumulative
            max_ = max(current_point.max, self._previous_point.max)
            min_ = min(current_point.min, self._previous_point.min)
            bucket_counts = [
                curr_count + prev_count
                for curr_count, prev_count in zip(
                    current_point.bucket_counts,
                    self._previous_point.bucket_counts,
                )
            ]
        else:
            start_time_unix_nano = self._previous_point.time_unix_nano
            sum_ = current_point.sum - self._previous_point.sum
            bucket_counts = [
                curr_count - prev_count
                for curr_count, prev_count in zip(
                    current_point.bucket_counts,
                    self._previous_point.bucket_counts,
                )
            ]

        current_point = HistogramDataPoint(
            attributes=self._attributes,
            start_time_unix_nano=start_time_unix_nano,
            time_unix_nano=current_point.time_unix_nano,
            count=sum(bucket_counts),
            sum=sum_,
            bucket_counts=tuple(bucket_counts),
            explicit_bounds=current_point.explicit_bounds,
            min=min_,
            max=max_,
        )
        self._previous_point = current_point
        return current_point


# pylint: disable=protected-access
class _ExponentialBucketHistogramAggregation(_Aggregation[HistogramPoint]):
    # _min_max_size and _max_max_size are the smallest and largest values
    # the max_size parameter may have, respectively.

    # _min_max_size is is the smallest reasonable value which is small enough
    # to contain the entire normal floating point range at the minimum scale.
    _min_max_size = 2

    # _max_max_size is an arbitrary limit meant to limit accidental creation of
    # giant exponential bucket histograms.
    _max_max_size = 16384

    def __init__(
        self,
        attributes: Attributes,
        start_time_unix_nano: int,
        # This is the default maximum number of buckets per positive or
        # negative number range.  The value 160 is specified by mysql.OpenTelemetry.
        # See the derivation here:
        # https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/sdk.md#exponential-bucket-histogram-aggregation)
        max_size: int = 160,
    ):
        super().__init__(attributes)
        # max_size is the maximum capacity of the positive and negative
        # buckets.
        if max_size < self._min_max_size:
            raise ValueError(
                f"Buckets max size {max_size} is smaller than "
                "minimum max size {self._min_max_size}"
            )

        if max_size > self._max_max_size:
            raise ValueError(
                f"Buckets max size {max_size} is larger than "
                "maximum max size {self._max_max_size}"
            )

        self._max_size = max_size

        # _sum is the sum of all the values aggregated by this aggregator.
        self._sum = 0

        # _count is the count of all calls to aggregate.
        self._count = 0

        # _zero_count is the count of all the calls to aggregate when the value
        # to be aggregated is exactly 0.
        self._zero_count = 0

        # _min is the smallest value aggregated by this aggregator.
        self._min = inf

        # _max is the smallest value aggregated by this aggregator.
        self._max = -inf

        # _positive holds the positive values.
        self._positive = Buckets()

        # _negative holds the negative values by their absolute value.
        self._negative = Buckets()

        # _mapping corresponds to the current scale, is shared by both the
        # positive and negative buckets.
        self._mapping = LogarithmMapping(LogarithmMapping._max_scale)

        self._instrument_temporality = AggregationTemporality.DELTA
        self._start_time_unix_nano = start_time_unix_nano

        self._previous_scale = None
        self._previous_start_time_unix_nano = None
        self._previous_sum = None
        self._previous_max = None
        self._previous_min = None
        self._previous_positive = None
        self._previous_negative = None

    def aggregate(self, measurement: Measurement) -> None:
        # pylint: disable=too-many-branches,too-many-statements, too-many-locals

        with self._lock:
            value = measurement.value

            # 0. Set the following attributes:
            # _min
            # _max
            # _count
            # _zero_count
            # _sum
            if value < self._min:
                self._min = value

            if value > self._max:
                self._max = value

            self._count += 1

            if value == 0:
                self._zero_count += 1
                # No need to do anything else if value is zero, just increment the
                # zero count.
                return

            self._sum += value

            # 1. Use the positive buckets for positive values and the negative
            # buckets for negative values.
            if value > 0:
                buckets = self._positive

            else:
                # Both exponential and logarithm mappings use only positive values
                # so the absolute value is used here.
                value = -value
                buckets = self._negative

            # 2. Compute the index for the value at the current scale.
            index = self._mapping.map_to_index(value)

            # IncrementIndexBy starts here

            # 3. Determine if a change of scale is needed.
            is_rescaling_needed = False

            if len(buckets) == 0:
                buckets.index_start = index
                buckets.index_end = index
                buckets.index_base = index

            elif (
                index < buckets.index_start
                and (buckets.index_end - index) >= self._max_size
            ):
                is_rescaling_needed = True
                low = index
                high = buckets.index_end

            elif (
                index > buckets.index_end
                and (index - buckets.index_start) >= self._max_size
            ):
                is_rescaling_needed = True
                low = buckets.index_start
                high = index

            # 4. Rescale the mapping if needed.
            if is_rescaling_needed:
                self._downscale(
                    self._get_scale_change(low, high),
                    self._positive,
                    self._negative,
                )

                index = self._mapping.map_to_index(value)

            # 5. If the index is outside
            # [buckets.index_start, buckets.index_end] readjust the buckets
            # boundaries or add more buckets.
            if index < buckets.index_start:
                span = buckets.index_end - index

                if span >= len(buckets.counts):
                    buckets.grow(span + 1, self._max_size)

                buckets.index_start = index

            elif index > buckets.index_end:
                span = index - buckets.index_start

                if span >= len(buckets.counts):
                    buckets.grow(span + 1, self._max_size)

                buckets.index_end = index

            # 6. Compute the index of the bucket to be incremented.
            bucket_index = index - buckets.index_base

            if bucket_index < 0:
                bucket_index += len(buckets.counts)

            # 7. Increment the bucket.
            buckets.increment_bucket(bucket_index)

    def collect(
        self,
        aggregation_temporality: AggregationTemporality,
        collection_start_nano: int,
    ) -> Optional[_DataPointVarT]:
        """
        Atomically return a point for the current value of the metric.
        """
        # pylint: disable=too-many-statements, too-many-locals

        with self._lock:
            if self._count == 0:
                return None

            current_negative = self._negative
            current_positive = self._positive
            current_zero_count = self._zero_count
            current_count = self._count
            current_start_time_unix_nano = self._start_time_unix_nano
            current_sum = self._sum
            current_max = self._max
            if current_max == -inf:
                current_max = None
            current_min = self._min
            if current_min == inf:
                current_min = None

            if self._count == self._zero_count:
                current_scale = 0

            else:
                current_scale = self._mapping.scale

            self._negative = Buckets()
            self._positive = Buckets()
            self._start_time_unix_nano = collection_start_nano
            self._sum = 0
            self._count = 0
            self._zero_count = 0
            self._min = inf
            self._max = -inf

            current_point = ExponentialHistogramDataPoint(
                attributes=self._attributes,
                start_time_unix_nano=current_start_time_unix_nano,
                time_unix_nano=collection_start_nano,
                count=current_count,
                sum=current_sum,
                scale=current_scale,
                zero_count=current_zero_count,
                positive=BucketsPoint(
                    offset=current_positive.offset,
                    bucket_counts=current_positive.counts,
                ),
                negative=BucketsPoint(
                    offset=current_negative.offset,
                    bucket_counts=current_negative.counts,
                ),
                # FIXME: Find the right value for flags
                flags=0,
                min=current_min,
                max=current_max,
            )

            if self._previous_scale is None or (
                self._instrument_temporality is aggregation_temporality
            ):
                self._previous_scale = current_scale
                self._previous_start_time_unix_nano = current_start_time_unix_nano
                self._previous_max = current_max
                self._previous_min = current_min
                self._previous_sum = current_sum
                self._previous_positive = current_positive
                self._previous_negative = current_negative

                return current_point

            min_scale = min(self._previous_scale, current_scale)

            low_positive, high_positive = self._get_low_high_previous_current(
                self._previous_positive, current_positive, min_scale
            )
            low_negative, high_negative = self._get_low_high_previous_current(
                self._previous_negative, current_negative, min_scale
            )

            min_scale = min(
                min_scale - self._get_scale_change(low_positive, high_positive),
                min_scale - self._get_scale_change(low_negative, high_negative),
            )

            # FIXME Go implementation checks if the histogram (not the mapping
            # but the histogram) has a count larger than zero, if not, scale
            # (the histogram scale) would be zero. See exponential.go 191
            self._downscale(
                self._mapping.scale - min_scale,
                self._previous_positive,
                self._previous_negative,
            )

            if aggregation_temporality is AggregationTemporality.CUMULATIVE:
                start_time_unix_nano = self._previous_start_time_unix_nano
                sum_ = current_sum + self._previous_sum
                # Only update min/max on delta -> cumulative
                max_ = max(current_max, self._previous_max)
                min_ = min(current_min, self._previous_min)

                self._merge(
                    self._previous_positive,
                    current_positive,
                    current_scale,
                    min_scale,
                    aggregation_temporality,
                )
                self._merge(
                    self._previous_negative,
                    current_negative,
                    current_scale,
                    min_scale,
                    aggregation_temporality,
                )

            else:
                start_time_unix_nano = self._previous_start_time_unix_nano
                sum_ = current_sum - self._previous_sum
                max_ = current_max
                min_ = current_min

                self._merge(
                    self._previous_positive,
                    current_positive,
                    current_scale,
                    min_scale,
                    aggregation_temporality,
                )
                self._merge(
                    self._previous_negative,
                    current_negative,
                    current_scale,
                    min_scale,
                    aggregation_temporality,
                )

            current_point = ExponentialHistogramDataPoint(
                attributes=self._attributes,
                start_time_unix_nano=start_time_unix_nano,
                time_unix_nano=collection_start_nano,
                count=current_count,
                sum=sum_,
                scale=current_scale,
                zero_count=current_zero_count,
                positive=BucketsPoint(
                    offset=current_positive.offset,
                    bucket_counts=current_positive.counts,
                ),
                negative=BucketsPoint(
                    offset=current_negative.offset,
                    bucket_counts=current_negative.counts,
                ),
                # FIXME: Find the right value for flags
                flags=0,
                min=min_,
                max=max_,
            )

            self._previous_scale = current_scale
            self._previous_positive = current_positive
            self._previous_negative = current_negative
            self._previous_start_time_unix_nano = current_start_time_unix_nano
            self._previous_sum = current_sum

            return current_point

    def _get_low_high_previous_current(
        self, previous_point_buckets, current_point_buckets, min_scale
    ):
        (previous_point_low, previous_point_high) = self._get_low_high(
            previous_point_buckets, min_scale
        )
        (current_point_low, current_point_high) = self._get_low_high(
            current_point_buckets, min_scale
        )

        if current_point_low > current_point_high:
            low = previous_point_low
            high = previous_point_high

        elif previous_point_low > previous_point_high:
            low = current_point_low
            high = current_point_high

        else:
            low = min(previous_point_low, current_point_low)
            high = max(previous_point_high, current_point_high)

        return low, high

    def _get_low_high(self, buckets, min_scale):
        if buckets.counts == [0]:
            return 0, -1

        shift = self._mapping._scale - min_scale

        return buckets.index_start >> shift, buckets.index_end >> shift

    def _get_scale_change(self, low, high):
        change = 0

        while high - low >= self._max_size:
            high = high >> 1
            low = low >> 1

            change += 1

        return change

    def _downscale(self, change: int, positive, negative):
        if change == 0:
            return

        if change < 0:
            raise Exception("Invalid change of scale")

        new_scale = self._mapping.scale - change

        positive.downscale(change)
        negative.downscale(change)

        if new_scale <= 0:
            mapping = ExponentMapping(new_scale)
        else:
            mapping = LogarithmMapping(new_scale)

        self._mapping = mapping

    def _merge(
        self,
        previous_buckets,
        current_buckets,
        current_scale,
        min_scale,
        aggregation_temporality,
    ):
        current_change = current_scale - min_scale

        for current_bucket_index, current_bucket in enumerate(current_buckets.counts):
            if current_bucket == 0:
                continue

            # Not considering the case where len(previous_buckets) == 0. This
            # would not happen because self._previous_point is only assigned to
            # an ExponentialHistogramDataPoint object if self._count != 0.

            index = (current_buckets.offset + current_bucket_index) >> current_change

            if index < previous_buckets.index_start:
                span = previous_buckets.index_end - index

                if span >= self._max_size:
                    raise Exception("Incorrect merge scale")

                if span >= len(previous_buckets.counts):
                    previous_buckets.grow(span + 1, self._max_size)

                previous_buckets.index_start = index

            if index > previous_buckets.index_end:
                span = index - previous_buckets.index_end

                if span >= self._max_size:
                    raise Exception("Incorrect merge scale")

                if span >= len(previous_buckets.counts):
                    previous_buckets.grow(span + 1, self._max_size)

                previous_buckets.index_end = index

            bucket_index = index - previous_buckets.index_base

            if bucket_index < 0:
                bucket_index += len(previous_buckets.counts)

            if aggregation_temporality is AggregationTemporality.DELTA:
                current_bucket = -current_bucket

            previous_buckets.increment_bucket(bucket_index, increment=current_bucket)


class Aggregation(ABC):
    """
    Base class for all aggregation types.
    """

    @abstractmethod
    def _create_aggregation(
        self,
        instrument: Instrument,
        attributes: Attributes,
        start_time_unix_nano: int,
    ) -> _Aggregation:
        """Creates an aggregation"""


class DefaultAggregation(Aggregation):
    """
    The default aggregation to be used in a `View`.

    This aggregation will create an actual aggregation depending on the
    instrument type, as specified next:

    ==================================================== ====================================
    Instrument                                           Aggregation
    ==================================================== ====================================
    `mysql.opentelemetry.sdk.metrics.Counter`                  `SumAggregation`
    `mysql.opentelemetry.sdk.metrics.UpDownCounter`            `SumAggregation`
    `mysql.opentelemetry.sdk.metrics.ObservableCounter`        `SumAggregation`
    `mysql.opentelemetry.sdk.metrics.ObservableUpDownCounter`  `SumAggregation`
    `mysql.opentelemetry.sdk.metrics.Histogram`                `ExplicitBucketHistogramAggregation`
    `mysql.opentelemetry.sdk.metrics.ObservableGauge`          `LastValueAggregation`
    ==================================================== ====================================
    """

    def _create_aggregation(
        self,
        instrument: Instrument,
        attributes: Attributes,
        start_time_unix_nano: int,
    ) -> _Aggregation:
        # pylint: disable=too-many-return-statements
        if isinstance(instrument, Counter):
            return _SumAggregation(
                attributes,
                instrument_is_monotonic=True,
                instrument_temporality=AggregationTemporality.DELTA,
                start_time_unix_nano=start_time_unix_nano,
            )
        if isinstance(instrument, UpDownCounter):
            return _SumAggregation(
                attributes,
                instrument_is_monotonic=False,
                instrument_temporality=AggregationTemporality.DELTA,
                start_time_unix_nano=start_time_unix_nano,
            )

        if isinstance(instrument, ObservableCounter):
            return _SumAggregation(
                attributes,
                instrument_is_monotonic=True,
                instrument_temporality=AggregationTemporality.CUMULATIVE,
                start_time_unix_nano=start_time_unix_nano,
            )

        if isinstance(instrument, ObservableUpDownCounter):
            return _SumAggregation(
                attributes,
                instrument_is_monotonic=False,
                instrument_temporality=AggregationTemporality.CUMULATIVE,
                start_time_unix_nano=start_time_unix_nano,
            )

        if isinstance(instrument, Histogram):
            return _ExplicitBucketHistogramAggregation(attributes, start_time_unix_nano)

        if isinstance(instrument, ObservableGauge):
            return _LastValueAggregation(attributes)

        raise Exception(f"Invalid instrument type {type(instrument)} found")


class ExponentialBucketHistogramAggregation(Aggregation):
    def __init__(
        self,
        max_size: int = 160,
    ):
        self._max_size = max_size

    def _create_aggregation(
        self,
        instrument: Instrument,
        attributes: Attributes,
        start_time_unix_nano: int,
    ) -> _Aggregation:
        return _ExponentialBucketHistogramAggregation(
            attributes,
            start_time_unix_nano,
            max_size=self._max_size,
        )


class ExplicitBucketHistogramAggregation(Aggregation):
    """This aggregation informs the SDK to collect:

    - Count of Measurement values falling within explicit bucket boundaries.
    - Arithmetic sum of Measurement values in population. This SHOULD NOT be collected when used with instruments that record negative measurements, e.g. UpDownCounter or ObservableGauge.
    - Min (optional) Measurement value in population.
    - Max (optional) Measurement value in population.


    Args:
        boundaries: Array of increasing values representing explicit bucket boundary values.
        record_min_max: Whether to record min and max.
    """

    def __init__(
        self,
        boundaries: Sequence[float] = (
            0.0,
            5.0,
            10.0,
            25.0,
            50.0,
            75.0,
            100.0,
            250.0,
            500.0,
            750.0,
            1000.0,
            2500.0,
            5000.0,
            7500.0,
            10000.0,
        ),
        record_min_max: bool = True,
    ) -> None:
        self._boundaries = boundaries
        self._record_min_max = record_min_max

    def _create_aggregation(
        self,
        instrument: Instrument,
        attributes: Attributes,
        start_time_unix_nano: int,
    ) -> _Aggregation:
        return _ExplicitBucketHistogramAggregation(
            attributes,
            start_time_unix_nano,
            self._boundaries,
            self._record_min_max,
        )


class SumAggregation(Aggregation):
    """This aggregation informs the SDK to collect:

    - The arithmetic sum of Measurement values.
    """

    def _create_aggregation(
        self,
        instrument: Instrument,
        attributes: Attributes,
        start_time_unix_nano: int,
    ) -> _Aggregation:
        temporality = AggregationTemporality.UNSPECIFIED
        if isinstance(instrument, Synchronous):
            temporality = AggregationTemporality.DELTA
        elif isinstance(instrument, Asynchronous):
            temporality = AggregationTemporality.CUMULATIVE

        return _SumAggregation(
            attributes,
            isinstance(instrument, (Counter, ObservableCounter)),
            temporality,
            start_time_unix_nano,
        )


class LastValueAggregation(Aggregation):
    """
    This aggregation informs the SDK to collect:

    - The last Measurement.
    - The timestamp of the last Measurement.
    """

    def _create_aggregation(
        self,
        instrument: Instrument,
        attributes: Attributes,
        start_time_unix_nano: int,
    ) -> _Aggregation:
        return _LastValueAggregation(attributes)


class DropAggregation(Aggregation):
    """Using this aggregation will make all measurements be ignored."""

    def _create_aggregation(
        self,
        instrument: Instrument,
        attributes: Attributes,
        start_time_unix_nano: int,
    ) -> _Aggregation:
        return _DropAggregation(attributes)

virt/lib/python3.9/site-packages/mysql/opentelemetry/sdk/metrics/_internal/aggregation 3.py
PrinceDisant/LostInNL
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# pylint: disable=too-many-lines

from abc import ABC, abstractmethod
from bisect import bisect_left
from enum import IntEnum
from logging import getLogger
from math import inf
from threading import Lock
from typing import Generic, List, Optional, Sequence, TypeVar

from mysql.opentelemetry.metrics import (
    Asynchronous,
    Counter,
    Histogram,
    Instrument,
    ObservableCounter,
    ObservableGauge,
    ObservableUpDownCounter,
    Synchronous,
    UpDownCounter,
)
from mysql.opentelemetry.sdk.metrics._internal.exponential_histogram.buckets import (
    Buckets,
)
from mysql.opentelemetry.sdk.metrics._internal.exponential_histogram.mapping.exponent_mapping import (
    ExponentMapping,
)
from mysql.opentelemetry.sdk.metrics._internal.exponential_histogram.mapping.logarithm_mapping import (
    LogarithmMapping,
)
from mysql.opentelemetry.sdk.metrics._internal.measurement import Measurement
from mysql.opentelemetry.sdk.metrics._internal.point import (
    Buckets as BucketsPoint,
    ExponentialHistogramDataPoint,
    Gauge,
    Histogram as HistogramPoint,
    HistogramDataPoint,
    NumberDataPoint,
    Sum,
)
from mysql.opentelemetry.util.types import Attributes

_DataPointVarT = TypeVar("_DataPointVarT", NumberDataPoint, HistogramDataPoint)

_logger = getLogger(__name__)


class AggregationTemporality(IntEnum):
    """
    The temporality to use when aggregating data.

    Can be one of the following values:
    """

    UNSPECIFIED = 0
    DELTA = 1
    CUMULATIVE = 2


class _Aggregation(ABC, Generic[_DataPointVarT]):
    def __init__(self, attributes: Attributes):
        self._lock = Lock()
        self._attributes = attributes
        self._previous_point = None

    @abstractmethod
    def aggregate(self, measurement: Measurement) -> None:
        pass

    @abstractmethod
    def collect(
        self,
        aggregation_temporality: AggregationTemporality,
        collection_start_nano: int,
    ) -> Optional[_DataPointVarT]:
        pass


class _DropAggregation(_Aggregation):
    def aggregate(self, measurement: Measurement) -> None:
        pass

    def collect(
        self,
        aggregation_temporality: AggregationTemporality,
        collection_start_nano: int,
    ) -> Optional[_DataPointVarT]:
        pass


class _SumAggregation(_Aggregation[Sum]):
    def __init__(
        self,
        attributes: Attributes,
        instrument_is_monotonic: bool,
        instrument_temporality: AggregationTemporality,
        start_time_unix_nano: int,
    ):
        super().__init__(attributes)

        self._start_time_unix_nano = start_time_unix_nano
        self._instrument_temporality = instrument_temporality
        self._instrument_is_monotonic = instrument_is_monotonic

        if self._instrument_temporality is AggregationTemporality.DELTA:
            self._value = 0
        else:
            self._value = None

    def aggregate(self, measurement: Measurement) -> None:
        with self._lock:
            if self._value is None:
                self._value = 0
            self._value = self._value + measurement.value

    def collect(
        self,
        aggregation_temporality: AggregationTemporality,
        collection_start_nano: int,
    ) -> Optional[NumberDataPoint]:
        """
        Atomically return a point for the current value of the metric and
        reset the aggregation value.
        """
        if self._instrument_temporality is AggregationTemporality.DELTA:
            with self._lock:
                value = self._value
                start_time_unix_nano = self._start_time_unix_nano

                self._value = 0
                self._start_time_unix_nano = collection_start_nano

        else:
            with self._lock:
                if self._value is None:
                    return None
                value = self._value
                self._value = None
                start_time_unix_nano = self._start_time_unix_nano

        current_point = NumberDataPoint(
            attributes=self._attributes,
            start_time_unix_nano=start_time_unix_nano,
            time_unix_nano=collection_start_nano,
            value=value,
        )

        if self._previous_point is None or (
            self._instrument_temporality is aggregation_temporality
        ):
            # Output DELTA for a synchronous instrument
            # Output CUMULATIVE for an asynchronous instrument
            self._previous_point = current_point
            return current_point

        if aggregation_temporality is AggregationTemporality.DELTA:
            # Output temporality DELTA for an asynchronous instrument
            value = current_point.value - self._previous_point.value
            output_start_time_unix_nano = self._previous_point.time_unix_nano

        else:
            # Output CUMULATIVE for a synchronous instrument
            value = current_point.value + self._previous_point.value
            output_start_time_unix_nano = self._previous_point.start_time_unix_nano

        current_point = NumberDataPoint(
            attributes=self._attributes,
            start_time_unix_nano=output_start_time_unix_nano,
            time_unix_nano=current_point.time_unix_nano,
            value=value,
        )

        self._previous_point = current_point
        return current_point


class _LastValueAggregation(_Aggregation[Gauge]):
    def __init__(self, attributes: Attributes):
        super().__init__(attributes)
        self._value = None

    def aggregate(self, measurement: Measurement):
        with self._lock:
            self._value = measurement.value

    def collect(
        self,
        aggregation_temporality: AggregationTemporality,
        collection_start_nano: int,
    ) -> Optional[_DataPointVarT]:
        """
        Atomically return a point for the current value of the metric.
        """
        with self._lock:
            if self._value is None:
                return None
            value = self._value
            self._value = None

        return NumberDataPoint(
            attributes=self._attributes,
            start_time_unix_nano=0,
            time_unix_nano=collection_start_nano,
            value=value,
        )


class _ExplicitBucketHistogramAggregation(_Aggregation[HistogramPoint]):
    def __init__(
        self,
        attributes: Attributes,
        start_time_unix_nano: int,
        boundaries: Sequence[float] = (
            0.0,
            5.0,
            10.0,
            25.0,
            50.0,
            75.0,
            100.0,
            250.0,
            500.0,
            750.0,
            1000.0,
            2500.0,
            5000.0,
            7500.0,
            10000.0,
        ),
        record_min_max: bool = True,
    ):
        super().__init__(attributes)
        self._boundaries = tuple(boundaries)
        self._bucket_counts = self._get_empty_bucket_counts()
        self._min = inf
        self._max = -inf
        self._sum = 0
        self._record_min_max = record_min_max
        self._start_time_unix_nano = start_time_unix_nano
        # It is assumed that the "natural" aggregation temporality for a
        # Histogram instrument is DELTA, like the "natural" aggregation
        # temporality for a Counter is DELTA and the "natural" aggregation
        # temporality for an ObservableCounter is CUMULATIVE.
        self._instrument_temporality = AggregationTemporality.DELTA

    def _get_empty_bucket_counts(self) -> List[int]:
        return [0] * (len(self._boundaries) + 1)

    def aggregate(self, measurement: Measurement) -> None:
        value = measurement.value

        if self._record_min_max:
            self._min = min(self._min, value)
            self._max = max(self._max, value)

        self._sum += value

        self._bucket_counts[bisect_left(self._boundaries, value)] += 1

    def collect(
        self,
        aggregation_temporality: AggregationTemporality,
        collection_start_nano: int,
    ) -> Optional[_DataPointVarT]:
        """
        Atomically return a point for the current value of the metric.
        """
        with self._lock:
            if not any(self._bucket_counts):
                return None

            bucket_counts = self._bucket_counts
            start_time_unix_nano = self._start_time_unix_nano
            sum_ = self._sum
            max_ = self._max
            min_ = self._min

            self._bucket_counts = self._get_empty_bucket_counts()
            self._start_time_unix_nano = collection_start_nano
            self._sum = 0
            self._min = inf
            self._max = -inf

        current_point = HistogramDataPoint(
            attributes=self._attributes,
            start_time_unix_nano=start_time_unix_nano,
            time_unix_nano=collection_start_nano,
            count=sum(bucket_counts),
            sum=sum_,
            bucket_counts=tuple(bucket_counts),
            explicit_bounds=self._boundaries,
            min=min_,
            max=max_,
        )

        if self._previous_point is None or (
            self._instrument_temporality is aggregation_temporality
        ):
            self._previous_point = current_point
            return current_point

        max_ = current_point.max
        min_ = current_point.min

        if aggregation_temporality is AggregationTemporality.CUMULATIVE:
            start_time_unix_nano = self._previous_point.start_time_unix_nano
            sum_ = current_point.sum + self._previous_point.sum
            # Only update min/max on delta -> cumulative
            max_ = max(current_point.max, self._previous_point.max)
            min_ = min(current_point.min, self._previous_point.min)
            bucket_counts = [
                curr_count + prev_count
                for curr_count, prev_count in zip(
                    current_point.bucket_counts,
                    self._previous_point.bucket_counts,
                )
            ]
        else:
            start_time_unix_nano = self._previous_point.time_unix_nano
            sum_ = current_point.sum - self._previous_point.sum
            bucket_counts = [
                curr_count - prev_count
                for curr_count, prev_count in zip(
                    current_point.bucket_counts,
                    self._previous_point.bucket_counts,
                )
            ]

        current_point = HistogramDataPoint(
            attributes=self._attributes,
            start_time_unix_nano=start_time_unix_nano,
            time_unix_nano=current_point.time_unix_nano,
            count=sum(bucket_counts),
            sum=sum_,
            bucket_counts=tuple(bucket_counts),
            explicit_bounds=current_point.explicit_bounds,
            min=min_,
            max=max_,
        )
        self._previous_point = current_point
        return current_point


# pylint: disable=protected-access
class _ExponentialBucketHistogramAggregation(_Aggregation[HistogramPoint]):
    # _min_max_size and _max_max_size are the smallest and largest values
    # the max_size parameter may have, respectively.

    # _min_max_size is is the smallest reasonable value which is small enough
    # to contain the entire normal floating point range at the minimum scale.
    _min_max_size = 2

    # _max_max_size is an arbitrary limit meant to limit accidental creation of
    # giant exponential bucket histograms.
    _max_max_size = 16384

    def __init__(
        self,
        attributes: Attributes,
        start_time_unix_nano: int,
        # This is the default maximum number of buckets per positive or
        # negative number range.  The value 160 is specified by mysql.OpenTelemetry.
        # See the derivation here:
        # https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/sdk.md#exponential-bucket-histogram-aggregation)
        max_size: int = 160,
    ):
        super().__init__(attributes)
        # max_size is the maximum capacity of the positive and negative
        # buckets.
        if max_size < self._min_max_size:
            raise ValueError(
                f"Buckets max size {max_size} is smaller than "
                "minimum max size {self._min_max_size}"
            )

        if max_size > self._max_max_size:
            raise ValueError(
                f"Buckets max size {max_size} is larger than "
                "maximum max size {self._max_max_size}"
            )

        self._max_size = max_size

        # _sum is the sum of all the values aggregated by this aggregator.
        self._sum = 0

        # _count is the count of all calls to aggregate.
        self._count = 0

        # _zero_count is the count of all the calls to aggregate when the value
        # to be aggregated is exactly 0.
        self._zero_count = 0

        # _min is the smallest value aggregated by this aggregator.
        self._min = inf

        # _max is the smallest value aggregated by this aggregator.
        self._max = -inf

        # _positive holds the positive values.
        self._positive = Buckets()

        # _negative holds the negative values by their absolute value.
        self._negative = Buckets()

        # _mapping corresponds to the current scale, is shared by both the
        # positive and negative buckets.
        self._mapping = LogarithmMapping(LogarithmMapping._max_scale)

        self._instrument_temporality = AggregationTemporality.DELTA
        self._start_time_unix_nano = start_time_unix_nano

        self._previous_scale = None
        self._previous_start_time_unix_nano = None
        self._previous_sum = None
        self._previous_max = None
        self._previous_min = None
        self._previous_positive = None
        self._previous_negative = None

    def aggregate(self, measurement: Measurement) -> None:
        # pylint: disable=too-many-branches,too-many-statements, too-many-locals

        with self._lock:
            value = measurement.value

            # 0. Set the following attributes:
            # _min
            # _max
            # _count
            # _zero_count
            # _sum
            if value < self._min:
                self._min = value

            if value > self._max:
                self._max = value

            self._count += 1

            if value == 0:
                self._zero_count += 1
                # No need to do anything else if value is zero, just increment the
                # zero count.
                return

            self._sum += value

            # 1. Use the positive buckets for positive values and the negative
            # buckets for negative values.
            if value > 0:
                buckets = self._positive

            else:
                # Both exponential and logarithm mappings use only positive values
                # so the absolute value is used here.
                value = -value
                buckets = self._negative

            # 2. Compute the index for the value at the current scale.
            index = self._mapping.map_to_index(value)

            # IncrementIndexBy starts here

            # 3. Determine if a change of scale is needed.
            is_rescaling_needed = False

            if len(buckets) == 0:
                buckets.index_start = index
                buckets.index_end = index
                buckets.index_base = index

            elif (
                index < buckets.index_start
                and (buckets.index_end - index) >= self._max_size
            ):
                is_rescaling_needed = True
                low = index
                high = buckets.index_end

            elif (
                index > buckets.index_end
                and (index - buckets.index_start) >= self._max_size
            ):
                is_rescaling_needed = True
                low = buckets.index_start
                high = index

            # 4. Rescale the mapping if needed.
            if is_rescaling_needed:
                self._downscale(
                    self._get_scale_change(low, high),
                    self._positive,
                    self._negative,
                )

                index = self._mapping.map_to_index(value)

            # 5. If the index is outside
            # [buckets.index_start, buckets.index_end] readjust the buckets
            # boundaries or add more buckets.
            if index < buckets.index_start:
                span = buckets.index_end - index

                if span >= len(buckets.counts):
                    buckets.grow(span + 1, self._max_size)

                buckets.index_start = index

            elif index > buckets.index_end:
                span = index - buckets.index_start

                if span >= len(buckets.counts):
                    buckets.grow(span + 1, self._max_size)

                buckets.index_end = index

            # 6. Compute the index of the bucket to be incremented.
            bucket_index = index - buckets.index_base

            if bucket_index < 0:
                bucket_index += len(buckets.counts)

            # 7. Increment the bucket.
            buckets.increment_bucket(bucket_index)

    def collect(
        self,
        aggregation_temporality: AggregationTemporality,
        collection_start_nano: int,
    ) -> Optional[_DataPointVarT]:
        """
        Atomically return a point for the current value of the metric.
        """
        # pylint: disable=too-many-statements, too-many-locals

        with self._lock:
            if self._count == 0:
                return None

            current_negative = self._negative
            current_positive = self._positive
            current_zero_count = self._zero_count
            current_count = self._count
            current_start_time_unix_nano = self._start_time_unix_nano
            current_sum = self._sum
            current_max = self._max
            if current_max == -inf:
                current_max = None
            current_min = self._min
            if current_min == inf:
                current_min = None

            if self._count == self._zero_count:
                current_scale = 0

            else:
                current_scale = self._mapping.scale

            self._negative = Buckets()
            self._positive = Buckets()
            self._start_time_unix_nano = collection_start_nano
            self._sum = 0
            self._count = 0
            self._zero_count = 0
            self._min = inf
            self._max = -inf

            current_point = ExponentialHistogramDataPoint(
                attributes=self._attributes,
                start_time_unix_nano=current_start_time_unix_nano,
                time_unix_nano=collection_start_nano,
                count=current_count,
                sum=current_sum,
                scale=current_scale,
                zero_count=current_zero_count,
                positive=BucketsPoint(
                    offset=current_positive.offset,
                    bucket_counts=current_positive.counts,
                ),
                negative=BucketsPoint(
                    offset=current_negative.offset,
                    bucket_counts=current_negative.counts,
                ),
                # FIXME: Find the right value for flags
                flags=0,
                min=current_min,
                max=current_max,
            )

            if self._previous_scale is None or (
                self._instrument_temporality is aggregation_temporality
            ):
                self._previous_scale = current_scale
                self._previous_start_time_unix_nano = current_start_time_unix_nano
                self._previous_max = current_max
                self._previous_min = current_min
                self._previous_sum = current_sum
                self._previous_positive = current_positive
                self._previous_negative = current_negative

                return current_point

            min_scale = min(self._previous_scale, current_scale)

            low_positive, high_positive = self._get_low_high_previous_current(
                self._previous_positive, current_positive, min_scale
            )
            low_negative, high_negative = self._get_low_high_previous_current(
                self._previous_negative, current_negative, min_scale
            )

            min_scale = min(
                min_scale - self._get_scale_change(low_positive, high_positive),
                min_scale - self._get_scale_change(low_negative, high_negative),
            )

            # FIXME Go implementation checks if the histogram (not the mapping
            # but the histogram) has a count larger than zero, if not, scale
            # (the histogram scale) would be zero. See exponential.go 191
            self._downscale(
                self._mapping.scale - min_scale,
                self._previous_positive,
                self._previous_negative,
            )

            if aggregation_temporality is AggregationTemporality.CUMULATIVE:
                start_time_unix_nano = self._previous_start_time_unix_nano
                sum_ = current_sum + self._previous_sum
                # Only update min/max on delta -> cumulative
                max_ = max(current_max, self._previous_max)
                min_ = min(current_min, self._previous_min)

                self._merge(
                    self._previous_positive,
                    current_positive,
                    current_scale,
                    min_scale,
                    aggregation_temporality,
                )
                self._merge(
                    self._previous_negative,
                    current_negative,
                    current_scale,
                    min_scale,
                    aggregation_temporality,
                )

            else:
                start_time_unix_nano = self._previous_start_time_unix_nano
                sum_ = current_sum - self._previous_sum
                max_ = current_max
                min_ = current_min

                self._merge(
                    self._previous_positive,
                    current_positive,
                    current_scale,
                    min_scale,
                    aggregation_temporality,
                )
                self._merge(
                    self._previous_negative,
                    current_negative,
                    current_scale,
                    min_scale,
                    aggregation_temporality,
                )

            current_point = ExponentialHistogramDataPoint(
                attributes=self._attributes,
                start_time_unix_nano=start_time_unix_nano,
                time_unix_nano=collection_start_nano,
                count=current_count,
                sum=sum_,
                scale=current_scale,
                zero_count=current_zero_count,
                positive=BucketsPoint(
                    offset=current_positive.offset,
                    bucket_counts=current_positive.counts,
                ),
                negative=BucketsPoint(
                    offset=current_negative.offset,
                    bucket_counts=current_negative.counts,
                ),
                # FIXME: Find the right value for flags
                flags=0,
                min=min_,
                max=max_,
            )

            self._previous_scale = current_scale
            self._previous_positive = current_positive
            self._previous_negative = current_negative
            self._previous_start_time_unix_nano = current_start_time_unix_nano
            self._previous_sum = current_sum

            return current_point

    def _get_low_high_previous_current(
        self, previous_point_buckets, current_point_buckets, min_scale
    ):
        (previous_point_low, previous_point_high) = self._get_low_high(
            previous_point_buckets, min_scale
        )
        (current_point_low, current_point_high) = self._get_low_high(
            current_point_buckets, min_scale
        )

        if current_point_low > current_point_high:
            low = previous_point_low
            high = previous_point_high

        elif previous_point_low > previous_point_high:
            low = current_point_low
            high = current_point_high

        else:
            low = min(previous_point_low, current_point_low)
            high = max(previous_point_high, current_point_high)

        return low, high

    def _get_low_high(self, buckets, min_scale):
        if buckets.counts == [0]:
            return 0, -1

        shift = self._mapping._scale - min_scale

        return buckets.index_start >> shift, buckets.index_end >> shift

    def _get_scale_change(self, low, high):
        change = 0

        while high - low >= self._max_size:
            high = high >> 1
            low = low >> 1

            change += 1

        return change

    def _downscale(self, change: int, positive, negative):
        if change == 0:
            return

        if change < 0:
            raise Exception("Invalid change of scale")

        new_scale = self._mapping.scale - change

        positive.downscale(change)
        negative.downscale(change)

        if new_scale <= 0:
            mapping = ExponentMapping(new_scale)
        else:
            mapping = LogarithmMapping(new_scale)

        self._mapping = mapping

    def _merge(
        self,
        previous_buckets,
        current_buckets,
        current_scale,
        min_scale,
        aggregation_temporality,
    ):
        current_change = current_scale - min_scale

        for current_bucket_index, current_bucket in enumerate(current_buckets.counts):
            if current_bucket == 0:
                continue

            # Not considering the case where len(previous_buckets) == 0. This
            # would not happen because self._previous_point is only assigned to
            # an ExponentialHistogramDataPoint object if self._count != 0.

            index = (current_buckets.offset + current_bucket_index) >> current_change

            if index < previous_buckets.index_start:
                span = previous_buckets.index_end - index

                if span >= self._max_size:
                    raise Exception("Incorrect merge scale")

                if span >= len(previous_buckets.counts):
                    previous_buckets.grow(span + 1, self._max_size)

                previous_buckets.index_start = index

            if index > previous_buckets.index_end:
                span = index - previous_buckets.index_end

                if span >= self._max_size:
                    raise Exception("Incorrect merge scale")

                if span >= len(previous_buckets.counts):
                    previous_buckets.grow(span + 1, self._max_size)

                previous_buckets.index_end = index

            bucket_index = index - previous_buckets.index_base

            if bucket_index < 0:
                bucket_index += len(previous_buckets.counts)

            if aggregation_temporality is AggregationTemporality.DELTA:
                current_bucket = -current_bucket

            previous_buckets.increment_bucket(bucket_index, increment=current_bucket)


class Aggregation(ABC):
    """
    Base class for all aggregation types.
    """

    @abstractmethod
    def _create_aggregation(
        self,
        instrument: Instrument,
        attributes: Attributes,
        start_time_unix_nano: int,
    ) -> _Aggregation:
        """Creates an aggregation"""


class DefaultAggregation(Aggregation):
    """
    The default aggregation to be used in a `View`.

    This aggregation will create an actual aggregation depending on the
    instrument type, as specified next:

    ==================================================== ====================================
    Instrument                                           Aggregation
    ==================================================== ====================================
    `mysql.opentelemetry.sdk.metrics.Counter`                  `SumAggregation`
    `mysql.opentelemetry.sdk.metrics.UpDownCounter`            `SumAggregation`
    `mysql.opentelemetry.sdk.metrics.ObservableCounter`        `SumAggregation`
    `mysql.opentelemetry.sdk.metrics.ObservableUpDownCounter`  `SumAggregation`
    `mysql.opentelemetry.sdk.metrics.Histogram`                `ExplicitBucketHistogramAggregation`
    `mysql.opentelemetry.sdk.metrics.ObservableGauge`          `LastValueAggregation`
    ==================================================== ====================================
    """

    def _create_aggregation(
        self,
        instrument: Instrument,
        attributes: Attributes,
        start_time_unix_nano: int,
    ) -> _Aggregation:
        # pylint: disable=too-many-return-statements
        if isinstance(instrument, Counter):
            return _SumAggregation(
                attributes,
                instrument_is_monotonic=True,
                instrument_temporality=AggregationTemporality.DELTA,
                start_time_unix_nano=start_time_unix_nano,
            )
        if isinstance(instrument, UpDownCounter):
            return _SumAggregation(
                attributes,
                instrument_is_monotonic=False,
                instrument_temporality=AggregationTemporality.DELTA,
                start_time_unix_nano=start_time_unix_nano,
            )

        if isinstance(instrument, ObservableCounter):
            return _SumAggregation(
                attributes,
                instrument_is_monotonic=True,
                instrument_temporality=AggregationTemporality.CUMULATIVE,
                start_time_unix_nano=start_time_unix_nano,
            )

        if isinstance(instrument, ObservableUpDownCounter):
            return _SumAggregation(
                attributes,
                instrument_is_monotonic=False,
                instrument_temporality=AggregationTemporality.CUMULATIVE,
                start_time_unix_nano=start_time_unix_nano,
            )

        if isinstance(instrument, Histogram):
            return _ExplicitBucketHistogramAggregation(attributes, start_time_unix_nano)

        if isinstance(instrument, ObservableGauge):
            return _LastValueAggregation(attributes)

        raise Exception(f"Invalid instrument type {type(instrument)} found")


class ExponentialBucketHistogramAggregation(Aggregation):
    def __init__(
        self,
        max_size: int = 160,
    ):
        self._max_size = max_size

    def _create_aggregation(
        self,
        instrument: Instrument,
        attributes: Attributes,
        start_time_unix_nano: int,
    ) -> _Aggregation:
        return _ExponentialBucketHistogramAggregation(
            attributes,
            start_time_unix_nano,
            max_size=self._max_size,
        )


class ExplicitBucketHistogramAggregation(Aggregation):
    """This aggregation informs the SDK to collect:

    - Count of Measurement values falling within explicit bucket boundaries.
    - Arithmetic sum of Measurement values in population. This SHOULD NOT be collected when used with instruments that record negative measurements, e.g. UpDownCounter or ObservableGauge.
    - Min (optional) Measurement value in population.
    - Max (optional) Measurement value in population.


    Args:
        boundaries: Array of increasing values representing explicit bucket boundary values.
        record_min_max: Whether to record min and max.
    """

    def __init__(
        self,
        boundaries: Sequence[float] = (
            0.0,
            5.0,
            10.0,
            25.0,
            50.0,
            75.0,
            100.0,
            250.0,
            500.0,
            750.0,
            1000.0,
            2500.0,
            5000.0,
            7500.0,
            10000.0,
        ),
        record_min_max: bool = True,
    ) -> None:
        self._boundaries = boundaries
        self._record_min_max = record_min_max

    def _create_aggregation(
        self,
        instrument: Instrument,
        attributes: Attributes,
        start_time_unix_nano: int,
    ) -> _Aggregation:
        return _ExplicitBucketHistogramAggregation(
            attributes,
            start_time_unix_nano,
            self._boundaries,
            self._record_min_max,
        )


class SumAggregation(Aggregation):
    """This aggregation informs the SDK to collect:

    - The arithmetic sum of Measurement values.
    """

    def _create_aggregation(
        self,
        instrument: Instrument,
        attributes: Attributes,
        start_time_unix_nano: int,
    ) -> _Aggregation:
        temporality = AggregationTemporality.UNSPECIFIED
        if isinstance(instrument, Synchronous):
            temporality = AggregationTemporality.DELTA
        elif isinstance(instrument, Asynchronous):
            temporality = AggregationTemporality.CUMULATIVE

        return _SumAggregation(
            attributes,
            isinstance(instrument, (Counter, ObservableCounter)),
            temporality,
            start_time_unix_nano,
        )


class LastValueAggregation(Aggregation):
    """
    This aggregation informs the SDK to collect:

    - The last Measurement.
    - The timestamp of the last Measurement.
    """

    def _create_aggregation(
        self,
        instrument: Instrument,
        attributes: Attributes,
        start_time_unix_nano: int,
    ) -> _Aggregation:
        return _LastValueAggregation(attributes)


class DropAggregation(Aggregation):
    """Using this aggregation will make all measurements be ignored."""

    def _create_aggregation(
        self,
        instrument: Instrument,
        attributes: Attributes,
        start_time_unix_nano: int,
    ) -> _Aggregation:
        return _DropAggregation(attributes)

virt/lib/python3.9/site-packages/mysql/opentelemetry/sdk/metrics/_internal/aggregation.py
PrinceDisant/LostInNL
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from math import ldexp
from threading import Lock

from mysql.opentelemetry.sdk.metrics._internal.exponential_histogram.mapping import (
    Mapping,
)
from mysql.opentelemetry.sdk.metrics._internal.exponential_histogram.mapping.errors import (
    MappingOverflowError,
    MappingUnderflowError,
)
from mysql.opentelemetry.sdk.metrics._internal.exponential_histogram.mapping.ieee_754 import (
    MANTISSA_WIDTH,
    MAX_NORMAL_EXPONENT,
    MIN_NORMAL_EXPONENT,
    MIN_NORMAL_VALUE,
    get_ieee_754_exponent,
    get_ieee_754_mantissa,
)


class ExponentMapping(Mapping):
    # Reference implementation here:
    # https://github.com/open-telemetry/opentelemetry-go/blob/0e6f9c29c10d6078e8131418e1d1d166c7195d61/sdk/metric/aggregator/exponential/mapping/exponent/exponent.go

    _mappings = {}
    _mappings_lock = Lock()

    _min_scale = -10
    _max_scale = 0

    def _get_min_scale(self):
        # _min_scale defines the point at which the exponential mapping
        # function becomes useless for 64-bit floats. With scale -10, ignoring
        # subnormal values, bucket indices range from -1 to 1.
        return -10

    def _get_max_scale(self):
        # _max_scale is the largest scale supported by exponential mapping. Use
        # a logarithm mapping for larger scales.
        return 0

    def _init(self, scale: int):
        # pylint: disable=attribute-defined-outside-init

        super()._init(scale)

        # self._min_normal_lower_boundary_index is the largest index such that
        # base ** index < MIN_NORMAL_VALUE and
        # base ** (index + 1) >= MIN_NORMAL_VALUE. An exponential histogram
        # bucket with this index covers the range
        # (base ** index, base (index + 1)], including MIN_NORMAL_VALUE. This
        # is the smallest valid index that contains at least one normal value.
        index = MIN_NORMAL_EXPONENT >> -self._scale

        if -self._scale < 2:
            # For scales -1 and 0, the maximum value 2 ** -1022 is a
            # power-of-two multiple, meaning base ** index == MIN_NORMAL_VALUE.
            # Subtracting 1 so that base ** (index + 1) == MIN_NORMAL_VALUE.
            index -= 1

        self._min_normal_lower_boundary_index = index

        # self._max_normal_lower_boundary_index is the index such that
        # base**index equals the greatest representable lower boundary. An
        # exponential histogram bucket with this index covers the range
        # ((2 ** 1024) / base, 2 ** 1024], which includes mysql.opentelemetry.sdk.
        # metrics._internal.exponential_histogram.ieee_754.MAX_NORMAL_VALUE.
        # This bucket is incomplete, since the upper boundary cannot be
        # represented. One greater than this index corresponds with the bucket
        # containing values > 2 ** 1024.
        self._max_normal_lower_boundary_index = MAX_NORMAL_EXPONENT >> -self._scale

    def map_to_index(self, value: float) -> int:
        if value < MIN_NORMAL_VALUE:
            return self._min_normal_lower_boundary_index

        exponent = get_ieee_754_exponent(value)

        # Positive integers are represented in binary as having an infinite
        # amount of leading zeroes, for example 2 is represented as ...00010.

        # A negative integer -x is represented in binary as the complement of
        # (x - 1). For example, -4 is represented as the complement of 4 - 1
        # == 3. 3 is represented as ...00011. Its compliment is ...11100, the
        # binary representation of -4.

        # get_ieee_754_mantissa(value) gets the positive integer made up
        # from the rightmost MANTISSA_WIDTH bits (the mantissa) of the IEEE
        # 754 representation of value. If value is an exact power of 2, all
        # these MANTISSA_WIDTH bits would be all zeroes, and when 1 is
        # subtracted the resulting value is -1. The binary representation of
        # -1 is ...111, so when these bits are right shifted MANTISSA_WIDTH
        # places, the resulting value for correction is -1. If value is not an
        # exact power of 2, at least one of the rightmost MANTISSA_WIDTH
        # bits would be 1 (even for values whose decimal part is 0, like 5.0
        # since the IEEE 754 of such number is too the product of a power of 2
        # (defined in the exponent part of the IEEE 754 representation) and the
        # value defined in the mantissa). Having at least one of the rightmost
        # MANTISSA_WIDTH bit being 1 means that get_ieee_754(value) will
        # always be greater or equal to 1, and when 1 is subtracted, the
        # result will be greater or equal to 0, whose representation in binary
        # will be of at most MANTISSA_WIDTH ones that have an infinite
        # amount of leading zeroes. When those MANTISSA_WIDTH bits are
        # shifted to the right MANTISSA_WIDTH places, the resulting value
        # will be 0.

        # In summary, correction will be -1 if value is a power of 2, 0 if not.

        # FIXME Document why we can assume value will not be 0, inf, or NaN.
        correction = (get_ieee_754_mantissa(value) - 1) >> MANTISSA_WIDTH

        return (exponent + correction) >> -self._scale

    def get_lower_boundary(self, index: int) -> float:
        if index < self._min_normal_lower_boundary_index:
            raise MappingUnderflowError()

        if index > self._max_normal_lower_boundary_index:
            raise MappingOverflowError()

        return ldexp(1, index << -self._scale)

    @property
    def scale(self) -> int:
        return self._scale

virt/lib/python3.9/site-packages/mysql/opentelemetry/sdk/metrics/_internal/exponential_histogram/mapping/exponent_mapping.py
PrinceDisant/LostInNL
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from math import exp, floor, ldexp, log
from threading import Lock

from mysql.opentelemetry.sdk.metrics._internal.exponential_histogram.mapping import (
    Mapping,
)
from mysql.opentelemetry.sdk.metrics._internal.exponential_histogram.mapping.errors import (
    MappingOverflowError,
    MappingUnderflowError,
)
from mysql.opentelemetry.sdk.metrics._internal.exponential_histogram.mapping.ieee_754 import (
    MAX_NORMAL_EXPONENT,
    MIN_NORMAL_EXPONENT,
    MIN_NORMAL_VALUE,
    get_ieee_754_exponent,
    get_ieee_754_mantissa,
)


class LogarithmMapping(Mapping):
    # Reference implementation here:
    # https://github.com/open-telemetry/opentelemetry-go/blob/0e6f9c29c10d6078e8131418e1d1d166c7195d61/sdk/metric/aggregator/exponential/mapping/logarithm/logarithm.go

    _mappings = {}
    _mappings_lock = Lock()

    _min_scale = 1
    _max_scale = 20

    def _get_min_scale(self):
        # _min_scale ensures that ExponentMapping is used for zero and negative
        # scale values.
        return self._min_scale

    def _get_max_scale(self):
        # FIXME The Go implementation uses a value of 20 here, find out the
        # right value for this implementation, more information here:
        # https://github.com/lightstep/otel-launcher-go/blob/c9ca8483be067a39ab306b09060446e7fda65f35/lightstep/sdk/metric/aggregator/histogram/structure/README.md#mapping-function
        # https://github.com/open-telemetry/opentelemetry-go/blob/0e6f9c29c10d6078e8131418e1d1d166c7195d61/sdk/metric/aggregator/exponential/mapping/logarithm/logarithm.go#L32-L45
        return self._max_scale

    def _init(self, scale: int):
        # pylint: disable=attribute-defined-outside-init

        super()._init(scale)

        # self._scale_factor is defined as a multiplier because multiplication
        # is faster than division. self._scale_factor is defined as:
        # index = log(value) * self._scale_factor
        # Where:
        # index = log(value) / log(base)
        # index = log(value) / log(2 ** (2 ** -scale))
        # index = log(value) / ((2 ** -scale) * log(2))
        # index = log(value) * ((1 / log(2)) * (2 ** scale))
        # self._scale_factor = ((1 / log(2)) * (2 ** scale))
        # self._scale_factor = (1 /log(2)) * (2 ** scale)
        # self._scale_factor = ldexp(1 / log(2), scale)
        # This implementation was copied from a Java prototype. See:
        # https://github.com/newrelic-experimental/newrelic-sketch-java/blob/1ce245713603d61ba3a4510f6df930a5479cd3f6/src/main/java/com/newrelic/nrsketch/indexer/LogIndexer.java
        # for the equations used here.
        self._scale_factor = ldexp(1 / log(2), scale)

        # self._min_normal_lower_boundary_index is the index such that
        # base ** index == MIN_NORMAL_VALUE. An exponential histogram bucket
        # with this index covers the range
        # (MIN_NORMAL_VALUE, MIN_NORMAL_VALUE * base]. One less than this index
        # corresponds with the bucket containing values <= MIN_NORMAL_VALUE.
        self._min_normal_lower_boundary_index = MIN_NORMAL_EXPONENT << self._scale

        # self._max_normal_lower_boundary_index is the index such that
        # base ** index equals the greatest representable lower boundary. An
        # exponential histogram bucket with this index covers the range
        # ((2 ** 1024) / base, 2 ** 1024], which includes mysql.opentelemetry.sdk.
        # metrics._internal.exponential_histogram.ieee_754.MAX_NORMAL_VALUE.
        # This bucket is incomplete, since the upper boundary cannot be
        # represented. One greater than this index corresponds with the bucket
        # containing values > 2 ** 1024.
        self._max_normal_lower_boundary_index = (
            (MAX_NORMAL_EXPONENT + 1) << self._scale
        ) - 1

    def map_to_index(self, value: float) -> int:
        """
        Maps positive floating point values to indexes corresponding to scale.
        """

        # value is subnormal
        if value <= MIN_NORMAL_VALUE:
            return self._min_normal_lower_boundary_index - 1

        # value is an exact power of two.
        if get_ieee_754_mantissa(value) == 0:
            exponent = get_ieee_754_exponent(value)
            return (exponent << self._scale) - 1

        return min(
            floor(log(value) * self._scale_factor),
            self._max_normal_lower_boundary_index,
        )

    def get_lower_boundary(self, index: int) -> float:
        if index >= self._max_normal_lower_boundary_index:
            if index == self._max_normal_lower_boundary_index:
                return 2 * exp((index - (1 << self._scale)) / self._scale_factor)
            raise MappingOverflowError()

        if index <= self._min_normal_lower_boundary_index:
            if index == self._min_normal_lower_boundary_index:
                return MIN_NORMAL_VALUE
            if index == self._min_normal_lower_boundary_index - 1:
                return exp((index + (1 << self._scale)) / self._scale_factor) / 2
            raise MappingUnderflowError()

        return exp(index / self._scale_factor)

    @property
    def scale(self) -> int:
        return self._scale

virt/lib/python3.9/site-packages/mysql/opentelemetry/sdk/metrics/_internal/exponential_histogram/mapping/logarithm_mapping.py
PrinceDisant/LostInNL
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import math
import os

from abc import ABC, abstractmethod
from enum import Enum
from logging import getLogger
from os import environ, linesep
from sys import stdout
from threading import Event, Lock, RLock, Thread
from time import time_ns
from typing import IO, Callable, Dict, Iterable, Optional

# This kind of import is needed to avoid Sphinx errors.
import mysql.opentelemetry.sdk.metrics._internal

from mysql.opentelemetry.context import (
    _SUPPRESS_INSTRUMENTATION_KEY,
    attach,
    detach,
    set_value,
)
from mysql.opentelemetry.sdk.environment_variables import (
    OTEL_METRIC_EXPORT_INTERVAL,
    OTEL_METRIC_EXPORT_TIMEOUT,
)
from mysql.opentelemetry.sdk.metrics._internal.aggregation import (
    AggregationTemporality,
    DefaultAggregation,
)
from mysql.opentelemetry.sdk.metrics._internal.exceptions import MetricsTimeoutError
from mysql.opentelemetry.sdk.metrics._internal.instrument import (
    Counter,
    Histogram,
    ObservableCounter,
    ObservableGauge,
    ObservableUpDownCounter,
    UpDownCounter,
    _Counter,
    _Histogram,
    _ObservableCounter,
    _ObservableGauge,
    _ObservableUpDownCounter,
    _UpDownCounter,
)
from mysql.opentelemetry.sdk.metrics._internal.point import MetricsData
from mysql.opentelemetry.util._once import Once
from typing_extensions import final

_logger = getLogger(__name__)


class MetricExportResult(Enum):
    """Result of exporting a metric

    Can be any of the following values:"""

    SUCCESS = 0
    FAILURE = 1


class MetricExporter(ABC):
    """Interface for exporting metrics.

    Interface to be implemented by services that want to export metrics received
    in their own format.

    Args:
        preferred_temporality: Used by `opentelemetry.sdk.metrics.export.PeriodicExportingMetricReader` to
            configure exporter level preferred temporality. See `opentelemetry.sdk.metrics.export.MetricReader` for
            more details on what preferred temporality is.
        preferred_aggregation: Used by `opentelemetry.sdk.metrics.export.PeriodicExportingMetricReader` to
            configure exporter level preferred aggregation. See `opentelemetry.sdk.metrics.export.MetricReader` for
            more details on what preferred aggregation is.
    """

    def __init__(
        self,
        preferred_temporality: Dict[type, AggregationTemporality] = None,
        preferred_aggregation: Dict[
            type, "mysql.opentelemetry.sdk.metrics.view.Aggregation"
        ] = None,
    ) -> None:
        self._preferred_temporality = preferred_temporality
        self._preferred_aggregation = preferred_aggregation

    @abstractmethod
    def export(
        self,
        metrics_data: MetricsData,
        timeout_millis: float = 10_000,
        **kwargs,
    ) -> MetricExportResult:
        """Exports a batch of telemetry data.

        Args:
            metrics: The list of `opentelemetry.sdk.metrics.export.Metric` objects to be exported

        Returns:
            The result of the export
        """

    @abstractmethod
    def force_flush(self, timeout_millis: float = 10_000) -> bool:
        """
        Ensure that export of any metrics currently received by the exporter
        are completed as soon as possible.
        """

    @abstractmethod
    def shutdown(self, timeout_millis: float = 30_000, **kwargs) -> None:
        """Shuts down the exporter.

        Called when the SDK is shut down.
        """


class ConsoleMetricExporter(MetricExporter):
    """Implementation of :class:`MetricExporter` that prints metrics to the
    console.

    This class can be used for diagnostic purposes. It prints the exported
    metrics to the console STDOUT.
    """

    def __init__(
        self,
        out: IO = stdout,
        formatter: Callable[
            ["mysql.opentelemetry.sdk.metrics.export.MetricsData"], str
        ] = lambda metrics_data: metrics_data.to_json()
        + linesep,
        preferred_temporality: Dict[type, AggregationTemporality] = None,
        preferred_aggregation: Dict[
            type, "mysql.opentelemetry.sdk.metrics.view.Aggregation"
        ] = None,
    ):
        super().__init__(
            preferred_temporality=preferred_temporality,
            preferred_aggregation=preferred_aggregation,
        )
        self.out = out
        self.formatter = formatter

    def export(
        self,
        metrics_data: MetricsData,
        timeout_millis: float = 10_000,
        **kwargs,
    ) -> MetricExportResult:
        self.out.write(self.formatter(metrics_data))
        self.out.flush()
        return MetricExportResult.SUCCESS

    def shutdown(self, timeout_millis: float = 30_000, **kwargs) -> None:
        pass

    def force_flush(self, timeout_millis: float = 10_000) -> bool:
        return True


class MetricReader(ABC):
    # pylint: disable=too-many-branches
    """
    Base class for all metric readers

    Args:
        preferred_temporality: A mapping between instrument classes and
            aggregation temporality. By default uses CUMULATIVE for all instrument
            classes. This mapping will be used to define the default aggregation
            temporality of every instrument class. If the user wants to make a
            change in the default aggregation temporality of an instrument class,
            it is enough to pass here a dictionary whose keys are the instrument
            classes and the values are the corresponding desired aggregation
            temporalities of the classes that the user wants to change, not all of
            them. The classes not included in the passed dictionary will retain
            their association to their default aggregation temporalities.
        preferred_aggregation: A mapping between instrument classes and
            aggregation instances. By default maps all instrument classes to an
            instance of `DefaultAggregation`. This mapping will be used to
            define the default aggregation of every instrument class. If the
            user wants to make a change in the default aggregation of an
            instrument class, it is enough to pass here a dictionary whose keys
            are the instrument classes and the values are the corresponding
            desired aggregation for the instrument classes that the user wants
            to change, not necessarily all of them. The classes not included in
            the passed dictionary will retain their association to their
            default aggregations. The aggregation defined here will be
            overridden by an aggregation defined by a view that is not
            `DefaultAggregation`.

    .. document protected _receive_metrics which is a intended to be overridden by subclass
    .. automethod:: _receive_metrics
    """

    def __init__(
        self,
        preferred_temporality: Dict[type, AggregationTemporality] = None,
        preferred_aggregation: Dict[
            type, "mysql.opentelemetry.sdk.metrics.view.Aggregation"
        ] = None,
    ) -> None:
        self._collect: Callable[
            [
                "mysql.opentelemetry.sdk.metrics.export.MetricReader",
                AggregationTemporality,
            ],
            Iterable["mysql.opentelemetry.sdk.metrics.export.Metric"],
        ] = None

        self._instrument_class_temporality = {
            _Counter: AggregationTemporality.CUMULATIVE,
            _UpDownCounter: AggregationTemporality.CUMULATIVE,
            _Histogram: AggregationTemporality.CUMULATIVE,
            _ObservableCounter: AggregationTemporality.CUMULATIVE,
            _ObservableUpDownCounter: AggregationTemporality.CUMULATIVE,
            _ObservableGauge: AggregationTemporality.CUMULATIVE,
        }

        if preferred_temporality is not None:
            for temporality in preferred_temporality.values():
                if temporality not in (
                    AggregationTemporality.CUMULATIVE,
                    AggregationTemporality.DELTA,
                ):
                    raise Exception(f"Invalid temporality value found {temporality}")

        if preferred_temporality is not None:
            for typ, temporality in preferred_temporality.items():
                if typ is Counter:
                    self._instrument_class_temporality[_Counter] = temporality
                elif typ is UpDownCounter:
                    self._instrument_class_temporality[_UpDownCounter] = temporality
                elif typ is Histogram:
                    self._instrument_class_temporality[_Histogram] = temporality
                elif typ is ObservableCounter:
                    self._instrument_class_temporality[_ObservableCounter] = temporality
                elif typ is ObservableUpDownCounter:
                    self._instrument_class_temporality[
                        _ObservableUpDownCounter
                    ] = temporality
                elif typ is ObservableGauge:
                    self._instrument_class_temporality[_ObservableGauge] = temporality
                else:
                    raise Exception(f"Invalid instrument class found {typ}")

        self._preferred_temporality = preferred_temporality
        self._instrument_class_aggregation = {
            _Counter: DefaultAggregation(),
            _UpDownCounter: DefaultAggregation(),
            _Histogram: DefaultAggregation(),
            _ObservableCounter: DefaultAggregation(),
            _ObservableUpDownCounter: DefaultAggregation(),
            _ObservableGauge: DefaultAggregation(),
        }

        if preferred_aggregation is not None:
            for typ, aggregation in preferred_aggregation.items():
                if typ is Counter:
                    self._instrument_class_aggregation[_Counter] = aggregation
                elif typ is UpDownCounter:
                    self._instrument_class_aggregation[_UpDownCounter] = aggregation
                elif typ is Histogram:
                    self._instrument_class_aggregation[_Histogram] = aggregation
                elif typ is ObservableCounter:
                    self._instrument_class_aggregation[_ObservableCounter] = aggregation
                elif typ is ObservableUpDownCounter:
                    self._instrument_class_aggregation[
                        _ObservableUpDownCounter
                    ] = aggregation
                elif typ is ObservableGauge:
                    self._instrument_class_aggregation[_ObservableGauge] = aggregation
                else:
                    raise Exception(f"Invalid instrument class found {typ}")

    @final
    def collect(self, timeout_millis: float = 10_000) -> None:
        """Collects the metrics from the internal SDK state and
        invokes the `_receive_metrics` with the collection.

        Args:
            timeout_millis: Amount of time in milliseconds before this function
              raises a timeout error.

        If any of the underlying ``collect`` methods called by this method
        fails by any reason (including timeout) an exception will be raised
        detailing the individual errors that caused this function to fail.
        """
        if self._collect is None:
            _logger.warning(
                "Cannot call collect on a MetricReader until it is registered on a MeterProvider"
            )
            return

        self._receive_metrics(
            self._collect(self, timeout_millis=timeout_millis),
            timeout_millis=timeout_millis,
        )

    @final
    def _set_collect_callback(
        self,
        func: Callable[
            [
                "mysql.opentelemetry.sdk.metrics.export.MetricReader",
                AggregationTemporality,
            ],
            Iterable["mysql.opentelemetry.sdk.metrics.export.Metric"],
        ],
    ) -> None:
        """This function is internal to the SDK. It should not be called or overridden by users"""
        self._collect = func

    @abstractmethod
    def _receive_metrics(
        self,
        metrics_data: "mysql.opentelemetry.sdk.metrics.export.MetricsData",
        timeout_millis: float = 10_000,
        **kwargs,
    ) -> None:
        """Called by `MetricReader.collect` when it receives a batch of metrics"""

    def force_flush(self, timeout_millis: float = 10_000) -> bool:
        self.collect(timeout_millis=timeout_millis)
        return True

    @abstractmethod
    def shutdown(self, timeout_millis: float = 30_000, **kwargs) -> None:
        """Shuts down the MetricReader. This method provides a way
        for the MetricReader to do any cleanup required. A metric reader can
        only be shutdown once, any subsequent calls are ignored and return
        failure status.

        When a `MetricReader` is registered on a
        :class:`~opentelemetry.sdk.metrics.MeterProvider`,
        :meth:`~opentelemetry.sdk.metrics.MeterProvider.shutdown` will invoke this
        automatically.
        """


class InMemoryMetricReader(MetricReader):
    """Implementation of `MetricReader` that returns its metrics from :func:`get_metrics_data`.

    This is useful for e.g. unit tests.
    """

    def __init__(
        self,
        preferred_temporality: Dict[type, AggregationTemporality] = None,
        preferred_aggregation: Dict[
            type, "mysql.opentelemetry.sdk.metrics.view.Aggregation"
        ] = None,
    ) -> None:
        super().__init__(
            preferred_temporality=preferred_temporality,
            preferred_aggregation=preferred_aggregation,
        )
        self._lock = RLock()
        self._metrics_data: (
            "mysql.opentelemetry.sdk.metrics.export.MetricsData"
        ) = None

    def get_metrics_data(
        self,
    ) -> "mysql.opentelemetry.sdk.metrics.export.MetricsData":
        """Reads and returns current metrics from the SDK"""
        with self._lock:
            self.collect()
            metrics_data = self._metrics_data
            self._metrics_data = None
        return metrics_data

    def _receive_metrics(
        self,
        metrics_data: "mysql.opentelemetry.sdk.metrics.export.MetricsData",
        timeout_millis: float = 10_000,
        **kwargs,
    ) -> None:
        with self._lock:
            self._metrics_data = metrics_data

    def shutdown(self, timeout_millis: float = 30_000, **kwargs) -> None:
        pass


class PeriodicExportingMetricReader(MetricReader):
    """`PeriodicExportingMetricReader` is an implementation of `MetricReader`
    that collects metrics based on a user-configurable time interval, and passes the
    metrics to the configured exporter. If the time interval is set to `math.inf`, the
    reader will not invoke periodic collection.

    The configured exporter's :py:meth:`~MetricExporter.export` method will not be called
    concurrently.
    """

    def __init__(
        self,
        exporter: MetricExporter,
        export_interval_millis: Optional[float] = None,
        export_timeout_millis: Optional[float] = None,
    ) -> None:
        # PeriodicExportingMetricReader defers to exporter for configuration
        super().__init__(
            preferred_temporality=exporter._preferred_temporality,
            preferred_aggregation=exporter._preferred_aggregation,
        )

        # This lock is held whenever calling self._exporter.export() to prevent concurrent
        # execution of MetricExporter.export()
        # https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/sdk.md#exportbatch
        self._export_lock = Lock()

        self._exporter = exporter
        if export_interval_millis is None:
            try:
                export_interval_millis = float(
                    environ.get(OTEL_METRIC_EXPORT_INTERVAL, 60000)
                )
            except ValueError:
                _logger.warning(
                    "Found invalid value for export interval, using default"
                )
                export_interval_millis = 60000
        if export_timeout_millis is None:
            try:
                export_timeout_millis = float(
                    environ.get(OTEL_METRIC_EXPORT_TIMEOUT, 30000)
                )
            except ValueError:
                _logger.warning("Found invalid value for export timeout, using default")
                export_timeout_millis = 30000
        self._export_interval_millis = export_interval_millis
        self._export_timeout_millis = export_timeout_millis
        self._shutdown = False
        self._shutdown_event = Event()
        self._shutdown_once = Once()
        self._daemon_thread = None
        if self._export_interval_millis > 0 and self._export_interval_millis < math.inf:
            self._daemon_thread = Thread(
                name="OtelPeriodicExportingMetricReader",
                target=self._ticker,
                daemon=True,
            )
            self._daemon_thread.start()
            if hasattr(os, "register_at_fork"):
                os.register_at_fork(
                    after_in_child=self._at_fork_reinit
                )  # pylint: disable=protected-access
        elif self._export_interval_millis <= 0:
            raise ValueError(
                f"interval value {self._export_interval_millis} is invalid \
                and needs to be larger than zero and lower than infinity."
            )

    def _at_fork_reinit(self):
        self._daemon_thread = Thread(
            name="OtelPeriodicExportingMetricReader",
            target=self._ticker,
            daemon=True,
        )
        self._daemon_thread.start()

    def _ticker(self) -> None:
        interval_secs = self._export_interval_millis / 1e3
        while not self._shutdown_event.wait(interval_secs):
            try:
                self.collect(timeout_millis=self._export_timeout_millis)
            except MetricsTimeoutError:
                _logger.warning(
                    "Metric collection timed out. Will try again after %s seconds",
                    interval_secs,
                    exc_info=True,
                )
        # one last collection below before shutting down completely
        self.collect(timeout_millis=self._export_interval_millis)

    def _receive_metrics(
        self,
        metrics_data: MetricsData,
        timeout_millis: float = 10_000,
        **kwargs,
    ) -> None:
        if metrics_data is None:
            return
        token = attach(set_value(_SUPPRESS_INSTRUMENTATION_KEY, True))
        try:
            with self._export_lock:
                self._exporter.export(metrics_data, timeout_millis=timeout_millis)
        except Exception as e:  # pylint: disable=broad-except,invalid-name
            _logger.exception("Exception while exporting metrics %s", str(e))
        detach(token)

    def shutdown(self, timeout_millis: float = 30_000, **kwargs) -> None:
        deadline_ns = time_ns() + timeout_millis * 10**6

        def _shutdown():
            self._shutdown = True

        did_set = self._shutdown_once.do_once(_shutdown)
        if not did_set:
            _logger.warning("Can't shutdown multiple times")
            return

        self._shutdown_event.set()
        if self._daemon_thread:
            self._daemon_thread.join(timeout=(deadline_ns - time_ns()) / 10**9)
        self._exporter.shutdown(timeout=(deadline_ns - time_ns()) / 10**6)

    def force_flush(self, timeout_millis: float = 10_000) -> bool:
        super().force_flush(timeout_millis=timeout_millis)
        self._exporter.force_flush(timeout_millis=timeout_millis)
        return True

virt/lib/python3.9/site-packages/mysql/opentelemetry/sdk/metrics/_internal/export/__init__ 2.py
PrinceDisant/LostInNL
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import math
import os

from abc import ABC, abstractmethod
from enum import Enum
from logging import getLogger
from os import environ, linesep
from sys import stdout
from threading import Event, Lock, RLock, Thread
from time import time_ns
from typing import IO, Callable, Dict, Iterable, Optional

# This kind of import is needed to avoid Sphinx errors.
import mysql.opentelemetry.sdk.metrics._internal

from mysql.opentelemetry.context import (
    _SUPPRESS_INSTRUMENTATION_KEY,
    attach,
    detach,
    set_value,
)
from mysql.opentelemetry.sdk.environment_variables import (
    OTEL_METRIC_EXPORT_INTERVAL,
    OTEL_METRIC_EXPORT_TIMEOUT,
)
from mysql.opentelemetry.sdk.metrics._internal.aggregation import (
    AggregationTemporality,
    DefaultAggregation,
)
from mysql.opentelemetry.sdk.metrics._internal.exceptions import MetricsTimeoutError
from mysql.opentelemetry.sdk.metrics._internal.instrument import (
    Counter,
    Histogram,
    ObservableCounter,
    ObservableGauge,
    ObservableUpDownCounter,
    UpDownCounter,
    _Counter,
    _Histogram,
    _ObservableCounter,
    _ObservableGauge,
    _ObservableUpDownCounter,
    _UpDownCounter,
)
from mysql.opentelemetry.sdk.metrics._internal.point import MetricsData
from mysql.opentelemetry.util._once import Once
from typing_extensions import final

_logger = getLogger(__name__)


class MetricExportResult(Enum):
    """Result of exporting a metric

    Can be any of the following values:"""

    SUCCESS = 0
    FAILURE = 1


class MetricExporter(ABC):
    """Interface for exporting metrics.

    Interface to be implemented by services that want to export metrics received
    in their own format.

    Args:
        preferred_temporality: Used by `opentelemetry.sdk.metrics.export.PeriodicExportingMetricReader` to
            configure exporter level preferred temporality. See `opentelemetry.sdk.metrics.export.MetricReader` for
            more details on what preferred temporality is.
        preferred_aggregation: Used by `opentelemetry.sdk.metrics.export.PeriodicExportingMetricReader` to
            configure exporter level preferred aggregation. See `opentelemetry.sdk.metrics.export.MetricReader` for
            more details on what preferred aggregation is.
    """

    def __init__(
        self,
        preferred_temporality: Dict[type, AggregationTemporality] = None,
        preferred_aggregation: Dict[
            type, "mysql.opentelemetry.sdk.metrics.view.Aggregation"
        ] = None,
    ) -> None:
        self._preferred_temporality = preferred_temporality
        self._preferred_aggregation = preferred_aggregation

    @abstractmethod
    def export(
        self,
        metrics_data: MetricsData,
        timeout_millis: float = 10_000,
        **kwargs,
    ) -> MetricExportResult:
        """Exports a batch of telemetry data.

        Args:
            metrics: The list of `opentelemetry.sdk.metrics.export.Metric` objects to be exported

        Returns:
            The result of the export
        """

    @abstractmethod
    def force_flush(self, timeout_millis: float = 10_000) -> bool:
        """
        Ensure that export of any metrics currently received by the exporter
        are completed as soon as possible.
        """

    @abstractmethod
    def shutdown(self, timeout_millis: float = 30_000, **kwargs) -> None:
        """Shuts down the exporter.

        Called when the SDK is shut down.
        """


class ConsoleMetricExporter(MetricExporter):
    """Implementation of :class:`MetricExporter` that prints metrics to the
    console.

    This class can be used for diagnostic purposes. It prints the exported
    metrics to the console STDOUT.
    """

    def __init__(
        self,
        out: IO = stdout,
        formatter: Callable[
            ["mysql.opentelemetry.sdk.metrics.export.MetricsData"], str
        ] = lambda metrics_data: metrics_data.to_json()
        + linesep,
        preferred_temporality: Dict[type, AggregationTemporality] = None,
        preferred_aggregation: Dict[
            type, "mysql.opentelemetry.sdk.metrics.view.Aggregation"
        ] = None,
    ):
        super().__init__(
            preferred_temporality=preferred_temporality,
            preferred_aggregation=preferred_aggregation,
        )
        self.out = out
        self.formatter = formatter

    def export(
        self,
        metrics_data: MetricsData,
        timeout_millis: float = 10_000,
        **kwargs,
    ) -> MetricExportResult:
        self.out.write(self.formatter(metrics_data))
        self.out.flush()
        return MetricExportResult.SUCCESS

    def shutdown(self, timeout_millis: float = 30_000, **kwargs) -> None:
        pass

    def force_flush(self, timeout_millis: float = 10_000) -> bool:
        return True


class MetricReader(ABC):
    # pylint: disable=too-many-branches
    """
    Base class for all metric readers

    Args:
        preferred_temporality: A mapping between instrument classes and
            aggregation temporality. By default uses CUMULATIVE for all instrument
            classes. This mapping will be used to define the default aggregation
            temporality of every instrument class. If the user wants to make a
            change in the default aggregation temporality of an instrument class,
            it is enough to pass here a dictionary whose keys are the instrument
            classes and the values are the corresponding desired aggregation
            temporalities of the classes that the user wants to change, not all of
            them. The classes not included in the passed dictionary will retain
            their association to their default aggregation temporalities.
        preferred_aggregation: A mapping between instrument classes and
            aggregation instances. By default maps all instrument classes to an
            instance of `DefaultAggregation`. This mapping will be used to
            define the default aggregation of every instrument class. If the
            user wants to make a change in the default aggregation of an
            instrument class, it is enough to pass here a dictionary whose keys
            are the instrument classes and the values are the corresponding
            desired aggregation for the instrument classes that the user wants
            to change, not necessarily all of them. The classes not included in
            the passed dictionary will retain their association to their
            default aggregations. The aggregation defined here will be
            overridden by an aggregation defined by a view that is not
            `DefaultAggregation`.

    .. document protected _receive_metrics which is a intended to be overridden by subclass
    .. automethod:: _receive_metrics
    """

    def __init__(
        self,
        preferred_temporality: Dict[type, AggregationTemporality] = None,
        preferred_aggregation: Dict[
            type, "mysql.opentelemetry.sdk.metrics.view.Aggregation"
        ] = None,
    ) -> None:
        self._collect: Callable[
            [
                "mysql.opentelemetry.sdk.metrics.export.MetricReader",
                AggregationTemporality,
            ],
            Iterable["mysql.opentelemetry.sdk.metrics.export.Metric"],
        ] = None

        self._instrument_class_temporality = {
            _Counter: AggregationTemporality.CUMULATIVE,
            _UpDownCounter: AggregationTemporality.CUMULATIVE,
            _Histogram: AggregationTemporality.CUMULATIVE,
            _ObservableCounter: AggregationTemporality.CUMULATIVE,
            _ObservableUpDownCounter: AggregationTemporality.CUMULATIVE,
            _ObservableGauge: AggregationTemporality.CUMULATIVE,
        }

        if preferred_temporality is not None:
            for temporality in preferred_temporality.values():
                if temporality not in (
                    AggregationTemporality.CUMULATIVE,
                    AggregationTemporality.DELTA,
                ):
                    raise Exception(f"Invalid temporality value found {temporality}")

        if preferred_temporality is not None:
            for typ, temporality in preferred_temporality.items():
                if typ is Counter:
                    self._instrument_class_temporality[_Counter] = temporality
                elif typ is UpDownCounter:
                    self._instrument_class_temporality[_UpDownCounter] = temporality
                elif typ is Histogram:
                    self._instrument_class_temporality[_Histogram] = temporality
                elif typ is ObservableCounter:
                    self._instrument_class_temporality[_ObservableCounter] = temporality
                elif typ is ObservableUpDownCounter:
                    self._instrument_class_temporality[
                        _ObservableUpDownCounter
                    ] = temporality
                elif typ is ObservableGauge:
                    self._instrument_class_temporality[_ObservableGauge] = temporality
                else:
                    raise Exception(f"Invalid instrument class found {typ}")

        self._preferred_temporality = preferred_temporality
        self._instrument_class_aggregation = {
            _Counter: DefaultAggregation(),
            _UpDownCounter: DefaultAggregation(),
            _Histogram: DefaultAggregation(),
            _ObservableCounter: DefaultAggregation(),
            _ObservableUpDownCounter: DefaultAggregation(),
            _ObservableGauge: DefaultAggregation(),
        }

        if preferred_aggregation is not None:
            for typ, aggregation in preferred_aggregation.items():
                if typ is Counter:
                    self._instrument_class_aggregation[_Counter] = aggregation
                elif typ is UpDownCounter:
                    self._instrument_class_aggregation[_UpDownCounter] = aggregation
                elif typ is Histogram:
                    self._instrument_class_aggregation[_Histogram] = aggregation
                elif typ is ObservableCounter:
                    self._instrument_class_aggregation[_ObservableCounter] = aggregation
                elif typ is ObservableUpDownCounter:
                    self._instrument_class_aggregation[
                        _ObservableUpDownCounter
                    ] = aggregation
                elif typ is ObservableGauge:
                    self._instrument_class_aggregation[_ObservableGauge] = aggregation
                else:
                    raise Exception(f"Invalid instrument class found {typ}")

    @final
    def collect(self, timeout_millis: float = 10_000) -> None:
        """Collects the metrics from the internal SDK state and
        invokes the `_receive_metrics` with the collection.

        Args:
            timeout_millis: Amount of time in milliseconds before this function
              raises a timeout error.

        If any of the underlying ``collect`` methods called by this method
        fails by any reason (including timeout) an exception will be raised
        detailing the individual errors that caused this function to fail.
        """
        if self._collect is None:
            _logger.warning(
                "Cannot call collect on a MetricReader until it is registered on a MeterProvider"
            )
            return

        self._receive_metrics(
            self._collect(self, timeout_millis=timeout_millis),
            timeout_millis=timeout_millis,
        )

    @final
    def _set_collect_callback(
        self,
        func: Callable[
            [
                "mysql.opentelemetry.sdk.metrics.export.MetricReader",
                AggregationTemporality,
            ],
            Iterable["mysql.opentelemetry.sdk.metrics.export.Metric"],
        ],
    ) -> None:
        """This function is internal to the SDK. It should not be called or overridden by users"""
        self._collect = func

    @abstractmethod
    def _receive_metrics(
        self,
        metrics_data: "mysql.opentelemetry.sdk.metrics.export.MetricsData",
        timeout_millis: float = 10_000,
        **kwargs,
    ) -> None:
        """Called by `MetricReader.collect` when it receives a batch of metrics"""

    def force_flush(self, timeout_millis: float = 10_000) -> bool:
        self.collect(timeout_millis=timeout_millis)
        return True

    @abstractmethod
    def shutdown(self, timeout_millis: float = 30_000, **kwargs) -> None:
        """Shuts down the MetricReader. This method provides a way
        for the MetricReader to do any cleanup required. A metric reader can
        only be shutdown once, any subsequent calls are ignored and return
        failure status.

        When a `MetricReader` is registered on a
        :class:`~opentelemetry.sdk.metrics.MeterProvider`,
        :meth:`~opentelemetry.sdk.metrics.MeterProvider.shutdown` will invoke this
        automatically.
        """


class InMemoryMetricReader(MetricReader):
    """Implementation of `MetricReader` that returns its metrics from :func:`get_metrics_data`.

    This is useful for e.g. unit tests.
    """

    def __init__(
        self,
        preferred_temporality: Dict[type, AggregationTemporality] = None,
        preferred_aggregation: Dict[
            type, "mysql.opentelemetry.sdk.metrics.view.Aggregation"
        ] = None,
    ) -> None:
        super().__init__(
            preferred_temporality=preferred_temporality,
            preferred_aggregation=preferred_aggregation,
        )
        self._lock = RLock()
        self._metrics_data: (
            "mysql.opentelemetry.sdk.metrics.export.MetricsData"
        ) = None

    def get_metrics_data(
        self,
    ) -> "mysql.opentelemetry.sdk.metrics.export.MetricsData":
        """Reads and returns current metrics from the SDK"""
        with self._lock:
            self.collect()
            metrics_data = self._metrics_data
            self._metrics_data = None
        return metrics_data

    def _receive_metrics(
        self,
        metrics_data: "mysql.opentelemetry.sdk.metrics.export.MetricsData",
        timeout_millis: float = 10_000,
        **kwargs,
    ) -> None:
        with self._lock:
            self._metrics_data = metrics_data

    def shutdown(self, timeout_millis: float = 30_000, **kwargs) -> None:
        pass


class PeriodicExportingMetricReader(MetricReader):
    """`PeriodicExportingMetricReader` is an implementation of `MetricReader`
    that collects metrics based on a user-configurable time interval, and passes the
    metrics to the configured exporter. If the time interval is set to `math.inf`, the
    reader will not invoke periodic collection.

    The configured exporter's :py:meth:`~MetricExporter.export` method will not be called
    concurrently.
    """

    def __init__(
        self,
        exporter: MetricExporter,
        export_interval_millis: Optional[float] = None,
        export_timeout_millis: Optional[float] = None,
    ) -> None:
        # PeriodicExportingMetricReader defers to exporter for configuration
        super().__init__(
            preferred_temporality=exporter._preferred_temporality,
            preferred_aggregation=exporter._preferred_aggregation,
        )

        # This lock is held whenever calling self._exporter.export() to prevent concurrent
        # execution of MetricExporter.export()
        # https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/sdk.md#exportbatch
        self._export_lock = Lock()

        self._exporter = exporter
        if export_interval_millis is None:
            try:
                export_interval_millis = float(
                    environ.get(OTEL_METRIC_EXPORT_INTERVAL, 60000)
                )
            except ValueError:
                _logger.warning(
                    "Found invalid value for export interval, using default"
                )
                export_interval_millis = 60000
        if export_timeout_millis is None:
            try:
                export_timeout_millis = float(
                    environ.get(OTEL_METRIC_EXPORT_TIMEOUT, 30000)
                )
            except ValueError:
                _logger.warning("Found invalid value for export timeout, using default")
                export_timeout_millis = 30000
        self._export_interval_millis = export_interval_millis
        self._export_timeout_millis = export_timeout_millis
        self._shutdown = False
        self._shutdown_event = Event()
        self._shutdown_once = Once()
        self._daemon_thread = None
        if self._export_interval_millis > 0 and self._export_interval_millis < math.inf:
            self._daemon_thread = Thread(
                name="OtelPeriodicExportingMetricReader",
                target=self._ticker,
                daemon=True,
            )
            self._daemon_thread.start()
            if hasattr(os, "register_at_fork"):
                os.register_at_fork(
                    after_in_child=self._at_fork_reinit
                )  # pylint: disable=protected-access
        elif self._export_interval_millis <= 0:
            raise ValueError(
                f"interval value {self._export_interval_millis} is invalid \
                and needs to be larger than zero and lower than infinity."
            )

    def _at_fork_reinit(self):
        self._daemon_thread = Thread(
            name="OtelPeriodicExportingMetricReader",
            target=self._ticker,
            daemon=True,
        )
        self._daemon_thread.start()

    def _ticker(self) -> None:
        interval_secs = self._export_interval_millis / 1e3
        while not self._shutdown_event.wait(interval_secs):
            try:
                self.collect(timeout_millis=self._export_timeout_millis)
            except MetricsTimeoutError:
                _logger.warning(
                    "Metric collection timed out. Will try again after %s seconds",
                    interval_secs,
                    exc_info=True,
                )
        # one last collection below before shutting down completely
        self.collect(timeout_millis=self._export_interval_millis)

    def _receive_metrics(
        self,
        metrics_data: MetricsData,
        timeout_millis: float = 10_000,
        **kwargs,
    ) -> None:
        if metrics_data is None:
            return
        token = attach(set_value(_SUPPRESS_INSTRUMENTATION_KEY, True))
        try:
            with self._export_lock:
                self._exporter.export(metrics_data, timeout_millis=timeout_millis)
        except Exception as e:  # pylint: disable=broad-except,invalid-name
            _logger.exception("Exception while exporting metrics %s", str(e))
        detach(token)

    def shutdown(self, timeout_millis: float = 30_000, **kwargs) -> None:
        deadline_ns = time_ns() + timeout_millis * 10**6

        def _shutdown():
            self._shutdown = True

        did_set = self._shutdown_once.do_once(_shutdown)
        if not did_set:
            _logger.warning("Can't shutdown multiple times")
            return

        self._shutdown_event.set()
        if self._daemon_thread:
            self._daemon_thread.join(timeout=(deadline_ns - time_ns()) / 10**9)
        self._exporter.shutdown(timeout=(deadline_ns - time_ns()) / 10**6)

    def force_flush(self, timeout_millis: float = 10_000) -> bool:
        super().force_flush(timeout_millis=timeout_millis)
        self._exporter.force_flush(timeout_millis=timeout_millis)
        return True

virt/lib/python3.9/site-packages/mysql/opentelemetry/sdk/metrics/_internal/export/__init__ 3.py
PrinceDisant/LostInNL
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import math
import os

from abc import ABC, abstractmethod
from enum import Enum
from logging import getLogger
from os import environ, linesep
from sys import stdout
from threading import Event, Lock, RLock, Thread
from time import time_ns
from typing import IO, Callable, Dict, Iterable, Optional

# This kind of import is needed to avoid Sphinx errors.
import mysql.opentelemetry.sdk.metrics._internal

from mysql.opentelemetry.context import (
    _SUPPRESS_INSTRUMENTATION_KEY,
    attach,
    detach,
    set_value,
)
from mysql.opentelemetry.sdk.environment_variables import (
    OTEL_METRIC_EXPORT_INTERVAL,
    OTEL_METRIC_EXPORT_TIMEOUT,
)
from mysql.opentelemetry.sdk.metrics._internal.aggregation import (
    AggregationTemporality,
    DefaultAggregation,
)
from mysql.opentelemetry.sdk.metrics._internal.exceptions import MetricsTimeoutError
from mysql.opentelemetry.sdk.metrics._internal.instrument import (
    Counter,
    Histogram,
    ObservableCounter,
    ObservableGauge,
    ObservableUpDownCounter,
    UpDownCounter,
    _Counter,
    _Histogram,
    _ObservableCounter,
    _ObservableGauge,
    _ObservableUpDownCounter,
    _UpDownCounter,
)
from mysql.opentelemetry.sdk.metrics._internal.point import MetricsData
from mysql.opentelemetry.util._once import Once
from typing_extensions import final

_logger = getLogger(__name__)


class MetricExportResult(Enum):
    """Result of exporting a metric

    Can be any of the following values:"""

    SUCCESS = 0
    FAILURE = 1


class MetricExporter(ABC):
    """Interface for exporting metrics.

    Interface to be implemented by services that want to export metrics received
    in their own format.

    Args:
        preferred_temporality: Used by `opentelemetry.sdk.metrics.export.PeriodicExportingMetricReader` to
            configure exporter level preferred temporality. See `opentelemetry.sdk.metrics.export.MetricReader` for
            more details on what preferred temporality is.
        preferred_aggregation: Used by `opentelemetry.sdk.metrics.export.PeriodicExportingMetricReader` to
            configure exporter level preferred aggregation. See `opentelemetry.sdk.metrics.export.MetricReader` for
            more details on what preferred aggregation is.
    """

    def __init__(
        self,
        preferred_temporality: Dict[type, AggregationTemporality] = None,
        preferred_aggregation: Dict[
            type, "mysql.opentelemetry.sdk.metrics.view.Aggregation"
        ] = None,
    ) -> None:
        self._preferred_temporality = preferred_temporality
        self._preferred_aggregation = preferred_aggregation

    @abstractmethod
    def export(
        self,
        metrics_data: MetricsData,
        timeout_millis: float = 10_000,
        **kwargs,
    ) -> MetricExportResult:
        """Exports a batch of telemetry data.

        Args:
            metrics: The list of `opentelemetry.sdk.metrics.export.Metric` objects to be exported

        Returns:
            The result of the export
        """

    @abstractmethod
    def force_flush(self, timeout_millis: float = 10_000) -> bool:
        """
        Ensure that export of any metrics currently received by the exporter
        are completed as soon as possible.
        """

    @abstractmethod
    def shutdown(self, timeout_millis: float = 30_000, **kwargs) -> None:
        """Shuts down the exporter.

        Called when the SDK is shut down.
        """


class ConsoleMetricExporter(MetricExporter):
    """Implementation of :class:`MetricExporter` that prints metrics to the
    console.

    This class can be used for diagnostic purposes. It prints the exported
    metrics to the console STDOUT.
    """

    def __init__(
        self,
        out: IO = stdout,
        formatter: Callable[
            ["mysql.opentelemetry.sdk.metrics.export.MetricsData"], str
        ] = lambda metrics_data: metrics_data.to_json()
        + linesep,
        preferred_temporality: Dict[type, AggregationTemporality] = None,
        preferred_aggregation: Dict[
            type, "mysql.opentelemetry.sdk.metrics.view.Aggregation"
        ] = None,
    ):
        super().__init__(
            preferred_temporality=preferred_temporality,
            preferred_aggregation=preferred_aggregation,
        )
        self.out = out
        self.formatter = formatter

    def export(
        self,
        metrics_data: MetricsData,
        timeout_millis: float = 10_000,
        **kwargs,
    ) -> MetricExportResult:
        self.out.write(self.formatter(metrics_data))
        self.out.flush()
        return MetricExportResult.SUCCESS

    def shutdown(self, timeout_millis: float = 30_000, **kwargs) -> None:
        pass

    def force_flush(self, timeout_millis: float = 10_000) -> bool:
        return True


class MetricReader(ABC):
    # pylint: disable=too-many-branches
    """
    Base class for all metric readers

    Args:
        preferred_temporality: A mapping between instrument classes and
            aggregation temporality. By default uses CUMULATIVE for all instrument
            classes. This mapping will be used to define the default aggregation
            temporality of every instrument class. If the user wants to make a
            change in the default aggregation temporality of an instrument class,
            it is enough to pass here a dictionary whose keys are the instrument
            classes and the values are the corresponding desired aggregation
            temporalities of the classes that the user wants to change, not all of
            them. The classes not included in the passed dictionary will retain
            their association to their default aggregation temporalities.
        preferred_aggregation: A mapping between instrument classes and
            aggregation instances. By default maps all instrument classes to an
            instance of `DefaultAggregation`. This mapping will be used to
            define the default aggregation of every instrument class. If the
            user wants to make a change in the default aggregation of an
            instrument class, it is enough to pass here a dictionary whose keys
            are the instrument classes and the values are the corresponding
            desired aggregation for the instrument classes that the user wants
            to change, not necessarily all of them. The classes not included in
            the passed dictionary will retain their association to their
            default aggregations. The aggregation defined here will be
            overridden by an aggregation defined by a view that is not
            `DefaultAggregation`.

    .. document protected _receive_metrics which is a intended to be overridden by subclass
    .. automethod:: _receive_metrics
    """

    def __init__(
        self,
        preferred_temporality: Dict[type, AggregationTemporality] = None,
        preferred_aggregation: Dict[
            type, "mysql.opentelemetry.sdk.metrics.view.Aggregation"
        ] = None,
    ) -> None:
        self._collect: Callable[
            [
                "mysql.opentelemetry.sdk.metrics.export.MetricReader",
                AggregationTemporality,
            ],
            Iterable["mysql.opentelemetry.sdk.metrics.export.Metric"],
        ] = None

        self._instrument_class_temporality = {
            _Counter: AggregationTemporality.CUMULATIVE,
            _UpDownCounter: AggregationTemporality.CUMULATIVE,
            _Histogram: AggregationTemporality.CUMULATIVE,
            _ObservableCounter: AggregationTemporality.CUMULATIVE,
            _ObservableUpDownCounter: AggregationTemporality.CUMULATIVE,
            _ObservableGauge: AggregationTemporality.CUMULATIVE,
        }

        if preferred_temporality is not None:
            for temporality in preferred_temporality.values():
                if temporality not in (
                    AggregationTemporality.CUMULATIVE,
                    AggregationTemporality.DELTA,
                ):
                    raise Exception(f"Invalid temporality value found {temporality}")

        if preferred_temporality is not None:
            for typ, temporality in preferred_temporality.items():
                if typ is Counter:
                    self._instrument_class_temporality[_Counter] = temporality
                elif typ is UpDownCounter:
                    self._instrument_class_temporality[_UpDownCounter] = temporality
                elif typ is Histogram:
                    self._instrument_class_temporality[_Histogram] = temporality
                elif typ is ObservableCounter:
                    self._instrument_class_temporality[_ObservableCounter] = temporality
                elif typ is ObservableUpDownCounter:
                    self._instrument_class_temporality[
                        _ObservableUpDownCounter
                    ] = temporality
                elif typ is ObservableGauge:
                    self._instrument_class_temporality[_ObservableGauge] = temporality
                else:
                    raise Exception(f"Invalid instrument class found {typ}")

        self._preferred_temporality = preferred_temporality
        self._instrument_class_aggregation = {
            _Counter: DefaultAggregation(),
            _UpDownCounter: DefaultAggregation(),
            _Histogram: DefaultAggregation(),
            _ObservableCounter: DefaultAggregation(),
            _ObservableUpDownCounter: DefaultAggregation(),
            _ObservableGauge: DefaultAggregation(),
        }

        if preferred_aggregation is not None:
            for typ, aggregation in preferred_aggregation.items():
                if typ is Counter:
                    self._instrument_class_aggregation[_Counter] = aggregation
                elif typ is UpDownCounter:
                    self._instrument_class_aggregation[_UpDownCounter] = aggregation
                elif typ is Histogram:
                    self._instrument_class_aggregation[_Histogram] = aggregation
                elif typ is ObservableCounter:
                    self._instrument_class_aggregation[_ObservableCounter] = aggregation
                elif typ is ObservableUpDownCounter:
                    self._instrument_class_aggregation[
                        _ObservableUpDownCounter
                    ] = aggregation
                elif typ is ObservableGauge:
                    self._instrument_class_aggregation[_ObservableGauge] = aggregation
                else:
                    raise Exception(f"Invalid instrument class found {typ}")

    @final
    def collect(self, timeout_millis: float = 10_000) -> None:
        """Collects the metrics from the internal SDK state and
        invokes the `_receive_metrics` with the collection.

        Args:
            timeout_millis: Amount of time in milliseconds before this function
              raises a timeout error.

        If any of the underlying ``collect`` methods called by this method
        fails by any reason (including timeout) an exception will be raised
        detailing the individual errors that caused this function to fail.
        """
        if self._collect is None:
            _logger.warning(
                "Cannot call collect on a MetricReader until it is registered on a MeterProvider"
            )
            return

        self._receive_metrics(
            self._collect(self, timeout_millis=timeout_millis),
            timeout_millis=timeout_millis,
        )

    @final
    def _set_collect_callback(
        self,
        func: Callable[
            [
                "mysql.opentelemetry.sdk.metrics.export.MetricReader",
                AggregationTemporality,
            ],
            Iterable["mysql.opentelemetry.sdk.metrics.export.Metric"],
        ],
    ) -> None:
        """This function is internal to the SDK. It should not be called or overridden by users"""
        self._collect = func

    @abstractmethod
    def _receive_metrics(
        self,
        metrics_data: "mysql.opentelemetry.sdk.metrics.export.MetricsData",
        timeout_millis: float = 10_000,
        **kwargs,
    ) -> None:
        """Called by `MetricReader.collect` when it receives a batch of metrics"""

    def force_flush(self, timeout_millis: float = 10_000) -> bool:
        self.collect(timeout_millis=timeout_millis)
        return True

    @abstractmethod
    def shutdown(self, timeout_millis: float = 30_000, **kwargs) -> None:
        """Shuts down the MetricReader. This method provides a way
        for the MetricReader to do any cleanup required. A metric reader can
        only be shutdown once, any subsequent calls are ignored and return
        failure status.

        When a `MetricReader` is registered on a
        :class:`~opentelemetry.sdk.metrics.MeterProvider`,
        :meth:`~opentelemetry.sdk.metrics.MeterProvider.shutdown` will invoke this
        automatically.
        """


class InMemoryMetricReader(MetricReader):
    """Implementation of `MetricReader` that returns its metrics from :func:`get_metrics_data`.

    This is useful for e.g. unit tests.
    """

    def __init__(
        self,
        preferred_temporality: Dict[type, AggregationTemporality] = None,
        preferred_aggregation: Dict[
            type, "mysql.opentelemetry.sdk.metrics.view.Aggregation"
        ] = None,
    ) -> None:
        super().__init__(
            preferred_temporality=preferred_temporality,
            preferred_aggregation=preferred_aggregation,
        )
        self._lock = RLock()
        self._metrics_data: (
            "mysql.opentelemetry.sdk.metrics.export.MetricsData"
        ) = None

    def get_metrics_data(
        self,
    ) -> "mysql.opentelemetry.sdk.metrics.export.MetricsData":
        """Reads and returns current metrics from the SDK"""
        with self._lock:
            self.collect()
            metrics_data = self._metrics_data
            self._metrics_data = None
        return metrics_data

    def _receive_metrics(
        self,
        metrics_data: "mysql.opentelemetry.sdk.metrics.export.MetricsData",
        timeout_millis: float = 10_000,
        **kwargs,
    ) -> None:
        with self._lock:
            self._metrics_data = metrics_data

    def shutdown(self, timeout_millis: float = 30_000, **kwargs) -> None:
        pass


class PeriodicExportingMetricReader(MetricReader):
    """`PeriodicExportingMetricReader` is an implementation of `MetricReader`
    that collects metrics based on a user-configurable time interval, and passes the
    metrics to the configured exporter. If the time interval is set to `math.inf`, the
    reader will not invoke periodic collection.

    The configured exporter's :py:meth:`~MetricExporter.export` method will not be called
    concurrently.
    """

    def __init__(
        self,
        exporter: MetricExporter,
        export_interval_millis: Optional[float] = None,
        export_timeout_millis: Optional[float] = None,
    ) -> None:
        # PeriodicExportingMetricReader defers to exporter for configuration
        super().__init__(
            preferred_temporality=exporter._preferred_temporality,
            preferred_aggregation=exporter._preferred_aggregation,
        )

        # This lock is held whenever calling self._exporter.export() to prevent concurrent
        # execution of MetricExporter.export()
        # https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/sdk.md#exportbatch
        self._export_lock = Lock()

        self._exporter = exporter
        if export_interval_millis is None:
            try:
                export_interval_millis = float(
                    environ.get(OTEL_METRIC_EXPORT_INTERVAL, 60000)
                )
            except ValueError:
                _logger.warning(
                    "Found invalid value for export interval, using default"
                )
                export_interval_millis = 60000
        if export_timeout_millis is None:
            try:
                export_timeout_millis = float(
                    environ.get(OTEL_METRIC_EXPORT_TIMEOUT, 30000)
                )
            except ValueError:
                _logger.warning("Found invalid value for export timeout, using default")
                export_timeout_millis = 30000
        self._export_interval_millis = export_interval_millis
        self._export_timeout_millis = export_timeout_millis
        self._shutdown = False
        self._shutdown_event = Event()
        self._shutdown_once = Once()
        self._daemon_thread = None
        if self._export_interval_millis > 0 and self._export_interval_millis < math.inf:
            self._daemon_thread = Thread(
                name="OtelPeriodicExportingMetricReader",
                target=self._ticker,
                daemon=True,
            )
            self._daemon_thread.start()
            if hasattr(os, "register_at_fork"):
                os.register_at_fork(
                    after_in_child=self._at_fork_reinit
                )  # pylint: disable=protected-access
        elif self._export_interval_millis <= 0:
            raise ValueError(
                f"interval value {self._export_interval_millis} is invalid \
                and needs to be larger than zero and lower than infinity."
            )

    def _at_fork_reinit(self):
        self._daemon_thread = Thread(
            name="OtelPeriodicExportingMetricReader",
            target=self._ticker,
            daemon=True,
        )
        self._daemon_thread.start()

    def _ticker(self) -> None:
        interval_secs = self._export_interval_millis / 1e3
        while not self._shutdown_event.wait(interval_secs):
            try:
                self.collect(timeout_millis=self._export_timeout_millis)
            except MetricsTimeoutError:
                _logger.warning(
                    "Metric collection timed out. Will try again after %s seconds",
                    interval_secs,
                    exc_info=True,
                )
        # one last collection below before shutting down completely
        self.collect(timeout_millis=self._export_interval_millis)

    def _receive_metrics(
        self,
        metrics_data: MetricsData,
        timeout_millis: float = 10_000,
        **kwargs,
    ) -> None:
        if metrics_data is None:
            return
        token = attach(set_value(_SUPPRESS_INSTRUMENTATION_KEY, True))
        try:
            with self._export_lock:
                self._exporter.export(metrics_data, timeout_millis=timeout_millis)
        except Exception as e:  # pylint: disable=broad-except,invalid-name
            _logger.exception("Exception while exporting metrics %s", str(e))
        detach(token)

    def shutdown(self, timeout_millis: float = 30_000, **kwargs) -> None:
        deadline_ns = time_ns() + timeout_millis * 10**6

        def _shutdown():
            self._shutdown = True

        did_set = self._shutdown_once.do_once(_shutdown)
        if not did_set:
            _logger.warning("Can't shutdown multiple times")
            return

        self._shutdown_event.set()
        if self._daemon_thread:
            self._daemon_thread.join(timeout=(deadline_ns - time_ns()) / 10**9)
        self._exporter.shutdown(timeout=(deadline_ns - time_ns()) / 10**6)

    def force_flush(self, timeout_millis: float = 10_000) -> bool:
        super().force_flush(timeout_millis=timeout_millis)
        self._exporter.force_flush(timeout_millis=timeout_millis)
        return True

virt/lib/python3.9/site-packages/mysql/opentelemetry/sdk/metrics/_internal/export/__init__.py
PrinceDisant/LostInNL
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# pylint: disable=too-many-ancestors, unused-import

from logging import getLogger
from typing import Dict, Generator, Iterable, List, Optional, Union

# This kind of import is needed to avoid Sphinx errors.
import mysql.opentelemetry.sdk.metrics

from mysql.opentelemetry.metrics import (
    CallbackT,
    Counter as APICounter,
    Histogram as APIHistogram,
    ObservableCounter as APIObservableCounter,
    ObservableGauge as APIObservableGauge,
    ObservableUpDownCounter as APIObservableUpDownCounter,
    UpDownCounter as APIUpDownCounter,
)
from mysql.opentelemetry.metrics._internal.instrument import CallbackOptions
from mysql.opentelemetry.sdk.metrics._internal.measurement import Measurement
from mysql.opentelemetry.sdk.util.instrumentation import InstrumentationScope

_logger = getLogger(__name__)


_ERROR_MESSAGE = "Expected ASCII string of maximum length 63 characters but got {}"


class _Synchronous:
    def __init__(
        self,
        name: str,
        instrumentation_scope: InstrumentationScope,
        measurement_consumer: "mysql.opentelemetry.sdk.metrics.MeasurementConsumer",
        unit: str = "",
        description: str = "",
    ):
        # pylint: disable=no-member
        result = self._check_name_unit_description(name, unit, description)

        if result["name"] is None:
            raise Exception(_ERROR_MESSAGE.format(name))

        if result["unit"] is None:
            raise Exception(_ERROR_MESSAGE.format(unit))

        name = result["name"]
        unit = result["unit"]
        description = result["description"]

        self.name = name.lower()
        self.unit = unit
        self.description = description
        self.instrumentation_scope = instrumentation_scope
        self._measurement_consumer = measurement_consumer
        super().__init__(name, unit=unit, description=description)


class _Asynchronous:
    def __init__(
        self,
        name: str,
        instrumentation_scope: InstrumentationScope,
        measurement_consumer: "mysql.opentelemetry.sdk.metrics.MeasurementConsumer",
        callbacks: Optional[Iterable[CallbackT]] = None,
        unit: str = "",
        description: str = "",
    ):
        # pylint: disable=no-member
        result = self._check_name_unit_description(name, unit, description)

        if result["name"] is None:
            raise Exception(_ERROR_MESSAGE.format(name))

        if result["unit"] is None:
            raise Exception(_ERROR_MESSAGE.format(unit))

        name = result["name"]
        unit = result["unit"]
        description = result["description"]

        self.name = name.lower()
        self.unit = unit
        self.description = description
        self.instrumentation_scope = instrumentation_scope
        self._measurement_consumer = measurement_consumer
        super().__init__(name, callbacks, unit=unit, description=description)

        self._callbacks: List[CallbackT] = []

        if callbacks is not None:
            for callback in callbacks:
                if isinstance(callback, Generator):
                    # advance generator to it's first yield
                    next(callback)

                    def inner(
                        options: CallbackOptions,
                        callback=callback,
                    ) -> Iterable[Measurement]:
                        try:
                            return callback.send(options)
                        except StopIteration:
                            return []

                    self._callbacks.append(inner)
                else:
                    self._callbacks.append(callback)

    def callback(self, callback_options: CallbackOptions) -> Iterable[Measurement]:
        for callback in self._callbacks:
            try:
                for api_measurement in callback(callback_options):
                    yield Measurement(
                        api_measurement.value,
                        instrument=self,
                        attributes=api_measurement.attributes,
                    )
            except Exception:  # pylint: disable=broad-except
                _logger.exception("Callback failed for instrument %s.", self.name)


class Counter(_Synchronous, APICounter):
    def __new__(cls, *args, **kwargs):
        if cls is Counter:
            raise TypeError("Counter must be instantiated via a meter.")
        return super().__new__(cls)

    def add(self, amount: Union[int, float], attributes: Dict[str, str] = None):
        if amount < 0:
            _logger.warning("Add amount must be non-negative on Counter %s.", self.name)
            return
        self._measurement_consumer.consume_measurement(
            Measurement(amount, self, attributes)
        )


class UpDownCounter(_Synchronous, APIUpDownCounter):
    def __new__(cls, *args, **kwargs):
        if cls is UpDownCounter:
            raise TypeError("UpDownCounter must be instantiated via a meter.")
        return super().__new__(cls)

    def add(self, amount: Union[int, float], attributes: Dict[str, str] = None):
        self._measurement_consumer.consume_measurement(
            Measurement(amount, self, attributes)
        )


class ObservableCounter(_Asynchronous, APIObservableCounter):
    def __new__(cls, *args, **kwargs):
        if cls is ObservableCounter:
            raise TypeError("ObservableCounter must be instantiated via a meter.")
        return super().__new__(cls)


class ObservableUpDownCounter(_Asynchronous, APIObservableUpDownCounter):
    def __new__(cls, *args, **kwargs):
        if cls is ObservableUpDownCounter:
            raise TypeError("ObservableUpDownCounter must be instantiated via a meter.")
        return super().__new__(cls)


class Histogram(_Synchronous, APIHistogram):
    def __new__(cls, *args, **kwargs):
        if cls is Histogram:
            raise TypeError("Histogram must be instantiated via a meter.")
        return super().__new__(cls)

    def record(self, amount: Union[int, float], attributes: Dict[str, str] = None):
        if amount < 0:
            _logger.warning(
                "Record amount must be non-negative on Histogram %s.",
                self.name,
            )
            return
        self._measurement_consumer.consume_measurement(
            Measurement(amount, self, attributes)
        )


class ObservableGauge(_Asynchronous, APIObservableGauge):
    def __new__(cls, *args, **kwargs):
        if cls is ObservableGauge:
            raise TypeError("ObservableGauge must be instantiated via a meter.")
        return super().__new__(cls)


# Below classes exist to prevent the direct instantiation
class _Counter(Counter):
    pass


class _UpDownCounter(UpDownCounter):
    pass


class _ObservableCounter(ObservableCounter):
    pass


class _ObservableUpDownCounter(ObservableUpDownCounter):
    pass


class _Histogram(Histogram):
    pass


class _ObservableGauge(ObservableGauge):
    pass

virt/lib/python3.9/site-packages/mysql/opentelemetry/sdk/metrics/_internal/instrument.py
PrinceDisant/LostInNL
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from dataclasses import dataclass
from typing import Union

from mysql.opentelemetry.metrics import Instrument
from mysql.opentelemetry.util.types import Attributes


@dataclass(frozen=True)
class Measurement:
    """
    Represents a data point reported via the metrics API to the SDK.
    """

    value: Union[int, float]
    instrument: Instrument
    attributes: Attributes = None

virt/lib/python3.9/site-packages/mysql/opentelemetry/sdk/metrics/_internal/measurement.py
PrinceDisant/LostInNL
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# pylint: disable=unused-import

from abc import ABC, abstractmethod
from threading import Lock
from time import time_ns
from typing import Iterable, List, Mapping

# This kind of import is needed to avoid Sphinx errors.
import mysql.opentelemetry.sdk.metrics
import mysql.opentelemetry.sdk.metrics._internal.instrument
import mysql.opentelemetry.sdk.metrics._internal.sdk_configuration

from mysql.opentelemetry.metrics._internal.instrument import CallbackOptions
from mysql.opentelemetry.sdk.metrics._internal.exceptions import MetricsTimeoutError
from mysql.opentelemetry.sdk.metrics._internal.measurement import Measurement
from mysql.opentelemetry.sdk.metrics._internal.metric_reader_storage import (
    MetricReaderStorage,
)
from mysql.opentelemetry.sdk.metrics._internal.point import Metric


class MeasurementConsumer(ABC):
    @abstractmethod
    def consume_measurement(self, measurement: Measurement) -> None:
        pass

    @abstractmethod
    def register_asynchronous_instrument(
        self,
        instrument: (
            "mysql.opentelemetry.sdk.metrics._internal.instrument_Asynchronous"
        ),
    ):
        pass

    @abstractmethod
    def collect(
        self,
        metric_reader: "mysql.opentelemetry.sdk.metrics.MetricReader",
        timeout_millis: float = 10_000,
    ) -> Iterable[Metric]:
        pass


class SynchronousMeasurementConsumer(MeasurementConsumer):
    def __init__(
        self,
        sdk_config: "mysql.opentelemetry.sdk.metrics._internal.SdkConfiguration",
    ) -> None:
        self._lock = Lock()
        self._sdk_config = sdk_config
        # should never be mutated
        self._reader_storages: Mapping[
            "mysql.opentelemetry.sdk.metrics.MetricReader", MetricReaderStorage
        ] = {
            reader: MetricReaderStorage(
                sdk_config,
                reader._instrument_class_temporality,
                reader._instrument_class_aggregation,
            )
            for reader in sdk_config.metric_readers
        }
        self._async_instruments: List[
            "mysql.opentelemetry.sdk.metrics._internal.instrument._Asynchronous"
        ] = []

    def consume_measurement(self, measurement: Measurement) -> None:
        for reader_storage in self._reader_storages.values():
            reader_storage.consume_measurement(measurement)

    def register_asynchronous_instrument(
        self,
        instrument: (
            "mysql.opentelemetry.sdk.metrics._internal.instrument._Asynchronous"
        ),
    ) -> None:
        with self._lock:
            self._async_instruments.append(instrument)

    def collect(
        self,
        metric_reader: "mysql.opentelemetry.sdk.metrics.MetricReader",
        timeout_millis: float = 10_000,
    ) -> Iterable[Metric]:
        with self._lock:
            metric_reader_storage = self._reader_storages[metric_reader]
            # for now, just use the defaults
            callback_options = CallbackOptions()
            deadline_ns = time_ns() + timeout_millis * 10**6

            default_timeout_millis = 10000 * 10**6

            for async_instrument in self._async_instruments:
                remaining_time = deadline_ns - time_ns()

                if remaining_time < default_timeout_millis:
                    callback_options = CallbackOptions(timeout_millis=remaining_time)

                measurements = async_instrument.callback(callback_options)
                if time_ns() >= deadline_ns:
                    raise MetricsTimeoutError("Timed out while executing callback")

                for measurement in measurements:
                    metric_reader_storage.consume_measurement(measurement)

        return self._reader_storages[metric_reader].collect()

virt/lib/python3.9/site-packages/mysql/opentelemetry/sdk/metrics/_internal/measurement_consumer.py
PrinceDisant/LostInNL
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from logging import getLogger
from threading import RLock
from time import time_ns
from typing import Dict, List

from mysql.opentelemetry.metrics import (
    Asynchronous,
    Counter,
    Instrument,
    ObservableCounter,
)
from mysql.opentelemetry.sdk.metrics._internal._view_instrument_match import (
    _ViewInstrumentMatch,
)
from mysql.opentelemetry.sdk.metrics._internal.aggregation import (
    Aggregation,
    ExplicitBucketHistogramAggregation,
    _DropAggregation,
    _ExplicitBucketHistogramAggregation,
    _ExponentialBucketHistogramAggregation,
    _LastValueAggregation,
    _SumAggregation,
)
from mysql.opentelemetry.sdk.metrics._internal.export import AggregationTemporality
from mysql.opentelemetry.sdk.metrics._internal.measurement import Measurement
from mysql.opentelemetry.sdk.metrics._internal.point import (
    ExponentialHistogram,
    Gauge,
    Histogram,
    Metric,
    MetricsData,
    ResourceMetrics,
    ScopeMetrics,
    Sum,
)
from mysql.opentelemetry.sdk.metrics._internal.sdk_configuration import SdkConfiguration
from mysql.opentelemetry.sdk.metrics._internal.view import View
from mysql.opentelemetry.sdk.util.instrumentation import InstrumentationScope

_logger = getLogger(__name__)

_DEFAULT_VIEW = View(instrument_name="")


class MetricReaderStorage:
    """The SDK's storage for a given reader"""

    def __init__(
        self,
        sdk_config: SdkConfiguration,
        instrument_class_temporality: Dict[type, AggregationTemporality],
        instrument_class_aggregation: Dict[type, Aggregation],
    ) -> None:
        self._lock = RLock()
        self._sdk_config = sdk_config
        self._instrument_view_instrument_matches: Dict[
            Instrument, List[_ViewInstrumentMatch]
        ] = {}
        self._instrument_class_temporality = instrument_class_temporality
        self._instrument_class_aggregation = instrument_class_aggregation

    def _get_or_init_view_instrument_match(
        self, instrument: Instrument
    ) -> List[_ViewInstrumentMatch]:
        # Optimistically get the relevant views for the given instrument. Once set for a given
        # instrument, the mapping will never change

        if instrument in self._instrument_view_instrument_matches:
            return self._instrument_view_instrument_matches[instrument]

        with self._lock:
            # double check if it was set before we held the lock
            if instrument in self._instrument_view_instrument_matches:
                return self._instrument_view_instrument_matches[instrument]

            # not present, hold the lock and add a new mapping
            view_instrument_matches = []

            self._handle_view_instrument_match(instrument, view_instrument_matches)

            # if no view targeted the instrument, use the default
            if not view_instrument_matches:
                view_instrument_matches.append(
                    _ViewInstrumentMatch(
                        view=_DEFAULT_VIEW,
                        instrument=instrument,
                        instrument_class_aggregation=(
                            self._instrument_class_aggregation
                        ),
                    )
                )
            self._instrument_view_instrument_matches[
                instrument
            ] = view_instrument_matches

            return view_instrument_matches

    def consume_measurement(self, measurement: Measurement) -> None:
        for view_instrument_match in self._get_or_init_view_instrument_match(
            measurement.instrument
        ):
            view_instrument_match.consume_measurement(measurement)

    def collect(self) -> MetricsData:
        # Use a list instead of yielding to prevent a slow reader from holding
        # SDK locks

        # While holding the lock, new _ViewInstrumentMatch can't be added from
        # another thread (so we are sure we collect all existing view).
        # However, instruments can still send measurements that will make it
        # into the individual aggregations; collection will acquire those locks
        # iteratively to keep locking as fine-grained as possible. One side
        # effect is that end times can be slightly skewed among the metric
        # streams produced by the SDK, but we still align the output timestamps
        # for a single instrument.

        collection_start_nanos = time_ns()

        with self._lock:
            instrumentation_scope_scope_metrics: (
                Dict[InstrumentationScope, ScopeMetrics]
            ) = {}

            for (
                instrument,
                view_instrument_matches,
            ) in self._instrument_view_instrument_matches.items():
                aggregation_temporality = self._instrument_class_temporality[
                    instrument.__class__
                ]

                metrics: List[Metric] = []

                for view_instrument_match in view_instrument_matches:
                    if isinstance(
                        # pylint: disable=protected-access
                        view_instrument_match._aggregation,
                        _SumAggregation,
                    ):
                        data = Sum(
                            aggregation_temporality=aggregation_temporality,
                            data_points=view_instrument_match.collect(
                                aggregation_temporality, collection_start_nanos
                            ),
                            is_monotonic=isinstance(
                                instrument, (Counter, ObservableCounter)
                            ),
                        )
                    elif isinstance(
                        # pylint: disable=protected-access
                        view_instrument_match._aggregation,
                        _LastValueAggregation,
                    ):
                        data = Gauge(
                            data_points=view_instrument_match.collect(
                                aggregation_temporality, collection_start_nanos
                            )
                        )
                    elif isinstance(
                        # pylint: disable=protected-access
                        view_instrument_match._aggregation,
                        _ExplicitBucketHistogramAggregation,
                    ):
                        data = Histogram(
                            data_points=view_instrument_match.collect(
                                aggregation_temporality, collection_start_nanos
                            ),
                            aggregation_temporality=aggregation_temporality,
                        )
                    elif isinstance(
                        # pylint: disable=protected-access
                        view_instrument_match._aggregation,
                        _DropAggregation,
                    ):
                        continue

                    elif isinstance(
                        # pylint: disable=protected-access
                        view_instrument_match._aggregation,
                        _ExponentialBucketHistogramAggregation,
                    ):
                        data = ExponentialHistogram(
                            data_points=view_instrument_match.collect(
                                aggregation_temporality, collection_start_nanos
                            ),
                            aggregation_temporality=aggregation_temporality,
                        )

                    metrics.append(
                        Metric(
                            # pylint: disable=protected-access
                            name=view_instrument_match._name,
                            description=view_instrument_match._description,
                            unit=view_instrument_match._instrument.unit,
                            data=data,
                        )
                    )

                if instrument.instrumentation_scope not in (
                    instrumentation_scope_scope_metrics
                ):
                    instrumentation_scope_scope_metrics[
                        instrument.instrumentation_scope
                    ] = ScopeMetrics(
                        scope=instrument.instrumentation_scope,
                        metrics=metrics,
                        schema_url=instrument.instrumentation_scope.schema_url,
                    )
                else:
                    instrumentation_scope_scope_metrics[
                        instrument.instrumentation_scope
                    ].metrics.extend(metrics)

        return MetricsData(
            resource_metrics=[
                ResourceMetrics(
                    resource=self._sdk_config.resource,
                    scope_metrics=list(instrumentation_scope_scope_metrics.values()),
                    schema_url=self._sdk_config.resource.schema_url,
                )
            ]
        )

    def _handle_view_instrument_match(
        self,
        instrument: Instrument,
        view_instrument_matches: List["_ViewInstrumentMatch"],
    ) -> None:
        for view in self._sdk_config.views:
            # pylint: disable=protected-access
            if not view._match(instrument):
                continue

            if not self._check_view_instrument_compatibility(view, instrument):
                continue

            new_view_instrument_match = _ViewInstrumentMatch(
                view=view,
                instrument=instrument,
                instrument_class_aggregation=(self._instrument_class_aggregation),
            )

            for (
                existing_view_instrument_matches
            ) in self._instrument_view_instrument_matches.values():
                for existing_view_instrument_match in existing_view_instrument_matches:
                    if existing_view_instrument_match.conflicts(
                        new_view_instrument_match
                    ):
                        _logger.warning(
                            "Views %s and %s will cause conflicting "
                            "metrics identities",
                            existing_view_instrument_match._view,
                            new_view_instrument_match._view,
                        )

            view_instrument_matches.append(new_view_instrument_match)

    @staticmethod
    def _check_view_instrument_compatibility(
        view: View, instrument: Instrument
    ) -> bool:
        """
        Checks if a view and an instrument are compatible.

        Returns `true` if they are compatible and a `_ViewInstrumentMatch`
        object should be created, `false` otherwise.
        """

        result = True

        # pylint: disable=protected-access
        if isinstance(instrument, Asynchronous) and isinstance(
            view._aggregation, ExplicitBucketHistogramAggregation
        ):
            _logger.warning(
                "View %s and instrument %s will produce "
                "semantic errors when matched, the view "
                "has not been applied.",
                view,
                instrument,
            )
            result = False

        return result

virt/lib/python3.9/site-packages/mysql/opentelemetry/sdk/metrics/_internal/metric_reader_storage.py
PrinceDisant/LostInNL
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# pylint: disable=unused-import

from dataclasses import asdict, dataclass
from json import dumps, loads
from typing import Optional, Sequence, Union

# This kind of import is needed to avoid Sphinx errors.
import mysql.opentelemetry.sdk.metrics._internal

from mysql.opentelemetry.sdk.resources import Resource
from mysql.opentelemetry.sdk.util.instrumentation import InstrumentationScope
from mysql.opentelemetry.util.types import Attributes


@dataclass(frozen=True)
class NumberDataPoint:
    """Single data point in a timeseries that describes the time-varying scalar
    value of a metric.
    """

    attributes: Attributes
    start_time_unix_nano: int
    time_unix_nano: int
    value: Union[int, float]

    def to_json(self, indent=4) -> str:
        return dumps(asdict(self), indent=indent)


@dataclass(frozen=True)
class HistogramDataPoint:
    """Single data point in a timeseries that describes the time-varying scalar
    value of a metric.
    """

    attributes: Attributes
    start_time_unix_nano: int
    time_unix_nano: int
    count: int
    sum: Union[int, float]
    bucket_counts: Sequence[int]
    explicit_bounds: Sequence[float]
    min: float
    max: float

    def to_json(self, indent=4) -> str:
        return dumps(asdict(self), indent=indent)


@dataclass(frozen=True)
class Buckets:
    offset: int
    bucket_counts: Sequence[int]


@dataclass(frozen=True)
class ExponentialHistogramDataPoint:
    """Single data point in a timeseries whose boundaries are defined by an
    exponential function. This timeseries describes the time-varying scalar
    value of a metric.
    """

    attributes: Attributes
    start_time_unix_nano: int
    time_unix_nano: int
    count: int
    sum: Union[int, float]
    scale: int
    zero_count: int
    positive: Buckets
    negative: Buckets
    flags: int
    min: float
    max: float

    def to_json(self, indent=4) -> str:
        return dumps(asdict(self), indent=indent)


@dataclass(frozen=True)
class ExponentialHistogram:
    """Represents the type of a metric that is calculated by aggregating as an
    ExponentialHistogram of all reported measurements over a time interval.
    """

    data_points: Sequence[ExponentialHistogramDataPoint]
    aggregation_temporality: (
        "mysql.opentelemetry.sdk.metrics.export.AggregationTemporality"
    )


@dataclass(frozen=True)
class Sum:
    """Represents the type of a scalar metric that is calculated as a sum of
    all reported measurements over a time interval."""

    data_points: Sequence[NumberDataPoint]
    aggregation_temporality: (
        "mysql.opentelemetry.sdk.metrics.export.AggregationTemporality"
    )
    is_monotonic: bool

    def to_json(self, indent=4) -> str:
        return dumps(
            {
                "data_points": [
                    loads(data_point.to_json(indent=indent))
                    for data_point in self.data_points
                ],
                "aggregation_temporality": self.aggregation_temporality,
                "is_monotonic": self.is_monotonic,
            },
            indent=indent,
        )


@dataclass(frozen=True)
class Gauge:
    """Represents the type of a scalar metric that always exports the current
    value for every data point. It should be used for an unknown
    aggregation."""

    data_points: Sequence[NumberDataPoint]

    def to_json(self, indent=4) -> str:
        return dumps(
            {
                "data_points": [
                    loads(data_point.to_json(indent=indent))
                    for data_point in self.data_points
                ],
            },
            indent=indent,
        )


@dataclass(frozen=True)
class Histogram:
    """Represents the type of a metric that is calculated by aggregating as a
    histogram of all reported measurements over a time interval."""

    data_points: Sequence[HistogramDataPoint]
    aggregation_temporality: (
        "mysql.opentelemetry.sdk.metrics.export.AggregationTemporality"
    )

    def to_json(self, indent=4) -> str:
        return dumps(
            {
                "data_points": [
                    loads(data_point.to_json(indent=indent))
                    for data_point in self.data_points
                ],
                "aggregation_temporality": self.aggregation_temporality,
            },
            indent=indent,
        )


DataT = Union[Sum, Gauge, Histogram]
DataPointT = Union[NumberDataPoint, HistogramDataPoint]


@dataclass(frozen=True)
class Metric:
    """Represents a metric point in the OpenTelemetry data model to be
    exported."""

    name: str
    description: Optional[str]
    unit: Optional[str]
    data: DataT

    def to_json(self, indent=4) -> str:
        return dumps(
            {
                "name": self.name,
                "description": self.description or "",
                "unit": self.unit or "",
                "data": loads(self.data.to_json(indent=indent)),
            },
            indent=indent,
        )


@dataclass(frozen=True)
class ScopeMetrics:
    """A collection of Metrics produced by a scope"""

    scope: InstrumentationScope
    metrics: Sequence[Metric]
    schema_url: str

    def to_json(self, indent=4) -> str:
        return dumps(
            {
                "scope": loads(self.scope.to_json(indent=indent)),
                "metrics": [
                    loads(metric.to_json(indent=indent)) for metric in self.metrics
                ],
                "schema_url": self.schema_url,
            },
            indent=indent,
        )


@dataclass(frozen=True)
class ResourceMetrics:
    """A collection of ScopeMetrics from a Resource"""

    resource: Resource
    scope_metrics: Sequence[ScopeMetrics]
    schema_url: str

    def to_json(self, indent=4) -> str:
        return dumps(
            {
                "resource": loads(self.resource.to_json(indent=indent)),
                "scope_metrics": [
                    loads(scope_metrics.to_json(indent=indent))
                    for scope_metrics in self.scope_metrics
                ],
                "schema_url": self.schema_url,
            },
            indent=indent,
        )


@dataclass(frozen=True)
class MetricsData:
    """An array of ResourceMetrics"""

    resource_metrics: Sequence[ResourceMetrics]

    def to_json(self, indent=4) -> str:
        return dumps(
            {
                "resource_metrics": [
                    loads(resource_metrics.to_json(indent=indent))
                    for resource_metrics in self.resource_metrics
                ]
            },
            indent=indent,
        )

virt/lib/python3.9/site-packages/mysql/opentelemetry/sdk/metrics/_internal/point.py
PrinceDisant/LostInNL
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# pylint: disable=unused-import

from dataclasses import dataclass
from typing import Sequence

# This kind of import is needed to avoid Sphinx errors.
import mysql.opentelemetry.sdk.metrics
import mysql.opentelemetry.sdk.resources


@dataclass
class SdkConfiguration:
    resource: "mysql.opentelemetry.sdk.resources.Resource"
    metric_readers: Sequence["mysql.opentelemetry.sdk.metrics.MetricReader"]
    views: Sequence["mysql.opentelemetry.sdk.metrics.View"]

virt/lib/python3.9/site-packages/mysql/opentelemetry/sdk/metrics/_internal/sdk_configuration.py
PrinceDisant/LostInNL
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


from fnmatch import fnmatch
from logging import getLogger
from typing import Optional, Set, Type

from mysql.opentelemetry.metrics import Instrument
from mysql.opentelemetry.sdk.metrics._internal.aggregation import (
    Aggregation,
    DefaultAggregation,
)

# FIXME import from typing when support for 3.7 is removed
from typing_extensions import final

_logger = getLogger(__name__)


class View:
    """
    A `View` configuration parameters can be used for the following
    purposes:

    1. Match instruments: When an instrument matches a view, measurements
       received by that instrument will be processed.
    2. Customize metric streams: A metric stream is identified by a match
       between a view and an instrument and a set of attributes. The metric
       stream can be customized by certain attributes of the corresponding view.

    The attributes documented next serve one of the previous two purposes.

    Args:
        instrument_type: This is an instrument matching attribute: the class the
            instrument must be to match the view.

        instrument_name: This is an instrument matching attribute: the name the
            instrument must have to match the view. Wild card characters are supported. Wild
            card characters should not be used with this attribute if the view has also a
            ``name`` defined.

        meter_name: This is an instrument matching attribute: the name the
            instrument meter must have to match the view.

        meter_version: This is an instrument matching attribute: the version
            the instrument meter must have to match the view.

        meter_schema_url: This is an instrument matching attribute: the schema
            URL the instrument meter must have to match the view.

        name: This is a metric stream customizing attribute: the name of the
            metric stream. If `None`, the name of the instrument will be used.

        description: This is a metric stream customizing attribute: the
            description of the metric stream. If `None`, the description of the instrument will
            be used.

        attribute_keys: This is a metric stream customizing attribute: this is
            a set of attribute keys. If not `None` then only the measurement attributes that
            are in ``attribute_keys`` will be used to identify the metric stream.

        aggregation: This is a metric stream customizing attribute: the
            aggregation instance to use when data is aggregated for the
            corresponding metrics stream. If `None` an instance of
            `DefaultAggregation` will be used.

    This class is not intended to be subclassed by the user.
    """

    _default_aggregation = DefaultAggregation()

    def __init__(
        self,
        instrument_type: Optional[Type[Instrument]] = None,
        instrument_name: Optional[str] = None,
        meter_name: Optional[str] = None,
        meter_version: Optional[str] = None,
        meter_schema_url: Optional[str] = None,
        name: Optional[str] = None,
        description: Optional[str] = None,
        attribute_keys: Optional[Set[str]] = None,
        aggregation: Optional[Aggregation] = None,
    ):
        if (
            instrument_type
            is instrument_name
            is meter_name
            is meter_version
            is meter_schema_url
            is None
        ):
            raise Exception(
                "Some instrument selection "
                f"criteria must be provided for View {name}"
            )

        if (
            name is not None
            and instrument_name is not None
            and ("*" in instrument_name or "?" in instrument_name)
        ):
            raise Exception(
                f"View {name} declared with wildcard " "characters in instrument_name"
            )

        # _name, _description, _aggregation and _attribute_keys will be
        # accessed when instantiating a _ViewInstrumentMatch.
        self._name = name
        self._instrument_type = instrument_type
        self._instrument_name = instrument_name
        self._meter_name = meter_name
        self._meter_version = meter_version
        self._meter_schema_url = meter_schema_url

        self._description = description
        self._attribute_keys = attribute_keys
        self._aggregation = aggregation or self._default_aggregation

    # pylint: disable=too-many-return-statements
    # pylint: disable=too-many-branches
    @final
    def _match(self, instrument: Instrument) -> bool:
        if self._instrument_type is not None:
            if not isinstance(instrument, self._instrument_type):
                return False

        if self._instrument_name is not None:
            if not fnmatch(instrument.name, self._instrument_name):
                return False

        if self._meter_name is not None:
            if instrument.instrumentation_scope.name != self._meter_name:
                return False

        if self._meter_version is not None:
            if instrument.instrumentation_scope.version != self._meter_version:
                return False

        if self._meter_schema_url is not None:
            if instrument.instrumentation_scope.schema_url != self._meter_schema_url:
                return False

        return True

virt/lib/python3.9/site-packages/mysql/opentelemetry/sdk/metrics/_internal/view.py
PrinceDisant/LostInNL
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


from mysql.opentelemetry.sdk.metrics._internal.export import (
    AggregationTemporality,
    ConsoleMetricExporter,
    InMemoryMetricReader,
    MetricExporter,
    MetricExportResult,
    MetricReader,
    PeriodicExportingMetricReader,
)

# The point module is not in the export directory to avoid a circular import.
from mysql.opentelemetry.sdk.metrics._internal.point import (  # noqa: F401
    Buckets,
    DataPointT,
    DataT,
    ExponentialHistogram,
    ExponentialHistogramDataPoint,
    Gauge,
    Histogram,
    HistogramDataPoint,
    Metric,
    MetricsData,
    NumberDataPoint,
    ResourceMetrics,
    ScopeMetrics,
    Sum,
)

__all__ = [
    "AggregationTemporality",
    "ConsoleMetricExporter",
    "InMemoryMetricReader",
    "MetricExporter",
    "MetricExportResult",
    "MetricReader",
    "PeriodicExportingMetricReader",
    "DataPointT",
    "DataT",
    "Gauge",
    "Histogram",
    "HistogramDataPoint",
    "Metric",
    "MetricsData",
    "NumberDataPoint",
    "ResourceMetrics",
    "ScopeMetrics",
    "Sum",
]

virt/lib/python3.9/site-packages/mysql/opentelemetry/sdk/metrics/export/__init__.py
PrinceDisant/LostInNL
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from mysql.opentelemetry.sdk.metrics._internal.aggregation import (
    Aggregation,
    DefaultAggregation,
    DropAggregation,
    ExplicitBucketHistogramAggregation,
    ExponentialBucketHistogramAggregation,
    LastValueAggregation,
    SumAggregation,
)
from mysql.opentelemetry.sdk.metrics._internal.view import View

__all__ = [
    "Aggregation",
    "DefaultAggregation",
    "DropAggregation",
    "ExplicitBucketHistogramAggregation",
    "ExponentialBucketHistogramAggregation",
    "LastValueAggregation",
    "SumAggregation",
    "View",
]

virt/lib/python3.9/site-packages/mysql/opentelemetry/sdk/metrics/view/__init__.py
PrinceDisant/LostInNL
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""
This package implements `OpenTelemetry Resources
<https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/resource/sdk.md#resource-sdk>`_:

    *A Resource is an immutable representation of the entity producing
    telemetry. For example, a process producing telemetry that is running in
    a container on Kubernetes has a Pod name, it is in a namespace and
    possibly is part of a Deployment which also has a name. All three of
    these attributes can be included in the Resource.*

Resource objects are created with `Resource.create`, which accepts attributes
(key-values). Resources should NOT be created via constructor, and working with
`Resource` objects should only be done via the Resource API methods. Resource
attributes can also be passed at process invocation in the
:envvar:`OTEL_RESOURCE_ATTRIBUTES` environment variable. You should register
your resource with the  `mysql.opentelemetry.sdk.trace.TracerProvider` by passing
them into their constructors. The `Resource` passed to a provider is available
to the exporter, which can send on this information as it sees fit.

.. code-block:: python

    trace.set_tracer_provider(
        TracerProvider(
            resource=Resource.create({
                "service.name": "shoppingcart",
                "service.instance.id": "instance-12",
            }),
        ),
    )
    print(trace.get_tracer_provider().resource.attributes)

    {'telemetry.sdk.language': 'python',
    'telemetry.sdk.name': 'opentelemetry',
    'telemetry.sdk.version': '0.13.dev0',
    'service.name': 'shoppingcart',
    'service.instance.id': 'instance-12'}

Note that the OpenTelemetry project documents certain `"standard attributes"
<https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/resource/semantic_conventions/README.md>`_
that have prescribed semantic meanings, for example ``service.name`` in the
above example.
 """

import abc
import concurrent.futures
import logging
import sys
import typing

from json import dumps
from os import environ
from urllib import parse

from mysql.opentelemetry.attributes import BoundedAttributes
from mysql.opentelemetry.sdk.environment_variables import (
    OTEL_EXPERIMENTAL_RESOURCE_DETECTORS,
    OTEL_RESOURCE_ATTRIBUTES,
    OTEL_SERVICE_NAME,
)
from mysql.opentelemetry.semconv.resource import ResourceAttributes
from mysql.opentelemetry.util._importlib_metadata import entry_points, version
from mysql.opentelemetry.util.types import AttributeValue

LabelValue = AttributeValue
Attributes = typing.Dict[str, LabelValue]
logger = logging.getLogger(__name__)


CLOUD_PROVIDER = ResourceAttributes.CLOUD_PROVIDER
CLOUD_ACCOUNT_ID = ResourceAttributes.CLOUD_ACCOUNT_ID
CLOUD_REGION = ResourceAttributes.CLOUD_REGION
CLOUD_AVAILABILITY_ZONE = ResourceAttributes.CLOUD_AVAILABILITY_ZONE
CONTAINER_NAME = ResourceAttributes.CONTAINER_NAME
CONTAINER_ID = ResourceAttributes.CONTAINER_ID
CONTAINER_IMAGE_NAME = ResourceAttributes.CONTAINER_IMAGE_NAME
CONTAINER_IMAGE_TAG = ResourceAttributes.CONTAINER_IMAGE_TAG
DEPLOYMENT_ENVIRONMENT = ResourceAttributes.DEPLOYMENT_ENVIRONMENT
FAAS_NAME = ResourceAttributes.FAAS_NAME
FAAS_ID = ResourceAttributes.FAAS_ID
FAAS_VERSION = ResourceAttributes.FAAS_VERSION
FAAS_INSTANCE = ResourceAttributes.FAAS_INSTANCE
HOST_NAME = ResourceAttributes.HOST_NAME
HOST_TYPE = ResourceAttributes.HOST_TYPE
HOST_IMAGE_NAME = ResourceAttributes.HOST_IMAGE_NAME
HOST_IMAGE_ID = ResourceAttributes.HOST_IMAGE_ID
HOST_IMAGE_VERSION = ResourceAttributes.HOST_IMAGE_VERSION
KUBERNETES_CLUSTER_NAME = ResourceAttributes.K8S_CLUSTER_NAME
KUBERNETES_NAMESPACE_NAME = ResourceAttributes.K8S_NAMESPACE_NAME
KUBERNETES_POD_UID = ResourceAttributes.K8S_POD_UID
KUBERNETES_POD_NAME = ResourceAttributes.K8S_POD_NAME
KUBERNETES_CONTAINER_NAME = ResourceAttributes.K8S_CONTAINER_NAME
KUBERNETES_REPLICA_SET_UID = ResourceAttributes.K8S_REPLICASET_UID
KUBERNETES_REPLICA_SET_NAME = ResourceAttributes.K8S_REPLICASET_NAME
KUBERNETES_DEPLOYMENT_UID = ResourceAttributes.K8S_DEPLOYMENT_UID
KUBERNETES_DEPLOYMENT_NAME = ResourceAttributes.K8S_DEPLOYMENT_NAME
KUBERNETES_STATEFUL_SET_UID = ResourceAttributes.K8S_STATEFULSET_UID
KUBERNETES_STATEFUL_SET_NAME = ResourceAttributes.K8S_STATEFULSET_NAME
KUBERNETES_DAEMON_SET_UID = ResourceAttributes.K8S_DAEMONSET_UID
KUBERNETES_DAEMON_SET_NAME = ResourceAttributes.K8S_DAEMONSET_NAME
KUBERNETES_JOB_UID = ResourceAttributes.K8S_JOB_UID
KUBERNETES_JOB_NAME = ResourceAttributes.K8S_JOB_NAME
KUBERNETES_CRON_JOB_UID = ResourceAttributes.K8S_CRONJOB_UID
KUBERNETES_CRON_JOB_NAME = ResourceAttributes.K8S_CRONJOB_NAME
OS_TYPE = ResourceAttributes.OS_TYPE
OS_DESCRIPTION = ResourceAttributes.OS_DESCRIPTION
PROCESS_PID = ResourceAttributes.PROCESS_PID
PROCESS_EXECUTABLE_NAME = ResourceAttributes.PROCESS_EXECUTABLE_NAME
PROCESS_EXECUTABLE_PATH = ResourceAttributes.PROCESS_EXECUTABLE_PATH
PROCESS_COMMAND = ResourceAttributes.PROCESS_COMMAND
PROCESS_COMMAND_LINE = ResourceAttributes.PROCESS_COMMAND_LINE
PROCESS_COMMAND_ARGS = ResourceAttributes.PROCESS_COMMAND_ARGS
PROCESS_OWNER = ResourceAttributes.PROCESS_OWNER
PROCESS_RUNTIME_NAME = ResourceAttributes.PROCESS_RUNTIME_NAME
PROCESS_RUNTIME_VERSION = ResourceAttributes.PROCESS_RUNTIME_VERSION
PROCESS_RUNTIME_DESCRIPTION = ResourceAttributes.PROCESS_RUNTIME_DESCRIPTION
SERVICE_NAME = ResourceAttributes.SERVICE_NAME
SERVICE_NAMESPACE = ResourceAttributes.SERVICE_NAMESPACE
SERVICE_INSTANCE_ID = ResourceAttributes.SERVICE_INSTANCE_ID
SERVICE_VERSION = ResourceAttributes.SERVICE_VERSION
TELEMETRY_SDK_NAME = ResourceAttributes.TELEMETRY_SDK_NAME
TELEMETRY_SDK_VERSION = ResourceAttributes.TELEMETRY_SDK_VERSION
TELEMETRY_AUTO_VERSION = ResourceAttributes.TELEMETRY_AUTO_VERSION
TELEMETRY_SDK_LANGUAGE = ResourceAttributes.TELEMETRY_SDK_LANGUAGE


_OPENTELEMETRY_SDK_VERSION = version("opentelemetry-sdk")


class Resource:
    """A Resource is an immutable representation of the entity producing telemetry as Attributes."""

    def __init__(self, attributes: Attributes, schema_url: typing.Optional[str] = None):
        self._attributes = BoundedAttributes(attributes=attributes)
        if schema_url is None:
            schema_url = ""
        self._schema_url = schema_url

    @staticmethod
    def create(
        attributes: typing.Optional[Attributes] = None,
        schema_url: typing.Optional[str] = None,
    ) -> "Resource":
        """Creates a new `Resource` from attributes.

        Args:
            attributes: Optional zero or more key-value pairs.
            schema_url: Optional URL pointing to the schema

        Returns:
            The newly-created Resource.
        """

        if not attributes:
            attributes = {}

        resource_detectors = []

        resource = _DEFAULT_RESOURCE

        otel_experimental_resource_detectors = environ.get(
            OTEL_EXPERIMENTAL_RESOURCE_DETECTORS, "otel"
        ).split(",")

        if "otel" not in otel_experimental_resource_detectors:
            otel_experimental_resource_detectors.append("otel")

        for resource_detector in otel_experimental_resource_detectors:
            resource_detectors.append(
                next(
                    iter(
                        entry_points(
                            group="opentelemetry_resource_detector",
                            name=resource_detector.strip(),
                        )
                    )
                ).load()()
            )

        resource = get_aggregated_resources(
            resource_detectors, _DEFAULT_RESOURCE
        ).merge(Resource(attributes, schema_url))

        if not resource.attributes.get(SERVICE_NAME, None):
            default_service_name = "unknown_service"
            process_executable_name = resource.attributes.get(
                PROCESS_EXECUTABLE_NAME, None
            )
            if process_executable_name:
                default_service_name += ":" + process_executable_name
            resource = resource.merge(
                Resource({SERVICE_NAME: default_service_name}, schema_url)
            )
        return resource

    @staticmethod
    def get_empty() -> "Resource":
        return _EMPTY_RESOURCE

    @property
    def attributes(self) -> Attributes:
        return self._attributes

    @property
    def schema_url(self) -> str:
        return self._schema_url

    def merge(self, other: "Resource") -> "Resource":
        """Merges this resource and an updating resource into a new `Resource`.

        If a key exists on both the old and updating resource, the value of the
        updating resource will override the old resource value.

        The updating resource's `schema_url` will be used only if the old
        `schema_url` is empty. Attempting to merge two resources with
        different, non-empty values for `schema_url` will result in an error
        and return the old resource.

        Args:
            other: The other resource to be merged.

        Returns:
            The newly-created Resource.
        """
        merged_attributes = self.attributes.copy()
        merged_attributes.update(other.attributes)

        if self.schema_url == "":
            schema_url = other.schema_url
        elif other.schema_url == "":
            schema_url = self.schema_url
        elif self.schema_url == other.schema_url:
            schema_url = other.schema_url
        else:
            logger.error(
                "Failed to merge resources: The two schemas %s and %s are incompatible",
                self.schema_url,
                other.schema_url,
            )
            return self

        return Resource(merged_attributes, schema_url)

    def __eq__(self, other: object) -> bool:
        if not isinstance(other, Resource):
            return False
        return (
            self._attributes == other._attributes
            and self._schema_url == other._schema_url
        )

    def __hash__(self):
        return hash(
            f"{dumps(self._attributes.copy(), sort_keys=True)}|{self._schema_url}"
        )

    def to_json(self, indent=4) -> str:
        return dumps(
            {
                "attributes": dict(self._attributes),
                "schema_url": self._schema_url,
            },
            indent=indent,
        )


_EMPTY_RESOURCE = Resource({})
_DEFAULT_RESOURCE = Resource(
    {
        TELEMETRY_SDK_LANGUAGE: "python",
        TELEMETRY_SDK_NAME: "opentelemetry",
        TELEMETRY_SDK_VERSION: _OPENTELEMETRY_SDK_VERSION,
    }
)


class ResourceDetector(abc.ABC):
    def __init__(self, raise_on_error=False):
        self.raise_on_error = raise_on_error

    @abc.abstractmethod
    def detect(self) -> "Resource":
        raise NotImplementedError()


class OTELResourceDetector(ResourceDetector):
    # pylint: disable=no-self-use
    def detect(self) -> "Resource":
        env_resources_items = environ.get(OTEL_RESOURCE_ATTRIBUTES)
        env_resource_map = {}

        if env_resources_items:
            for item in env_resources_items.split(","):
                try:
                    key, value = item.split("=", maxsplit=1)
                except ValueError as exc:
                    logger.warning(
                        "Invalid key value resource attribute pair %s: %s",
                        item,
                        exc,
                    )
                    continue
                value_url_decoded = parse.unquote(value.strip())
                env_resource_map[key.strip()] = value_url_decoded

        service_name = environ.get(OTEL_SERVICE_NAME)
        if service_name:
            env_resource_map[SERVICE_NAME] = service_name
        return Resource(env_resource_map)


class ProcessResourceDetector(ResourceDetector):
    # pylint: disable=no-self-use
    def detect(self) -> "Resource":
        _runtime_version = ".".join(
            map(
                str,
                sys.version_info[:3]
                if sys.version_info.releaselevel == "final"
                and not sys.version_info.serial
                else sys.version_info,
            )
        )

        return Resource(
            {
                PROCESS_RUNTIME_DESCRIPTION: sys.version,
                PROCESS_RUNTIME_NAME: sys.implementation.name,
                PROCESS_RUNTIME_VERSION: _runtime_version,
            }
        )


def get_aggregated_resources(
    detectors: typing.List["ResourceDetector"],
    initial_resource: typing.Optional[Resource] = None,
    timeout=5,
) -> "Resource":
    """Retrieves resources from detectors in the order that they were passed

    :param detectors: List of resources in order of priority
    :param initial_resource: Static resource. This has highest priority
    :param timeout: Number of seconds to wait for each detector to return
    :return:
    """
    detectors_merged_resource = initial_resource or Resource.create()

    with concurrent.futures.ThreadPoolExecutor(max_workers=4) as executor:
        futures = [executor.submit(detector.detect) for detector in detectors]
        for detector_ind, future in enumerate(futures):
            detector = detectors[detector_ind]
            try:
                detected_resource = future.result(timeout=timeout)
            # pylint: disable=broad-except
            except Exception as ex:
                detected_resource = _EMPTY_RESOURCE
                if detector.raise_on_error:
                    raise ex
                logger.warning("Exception %s in detector %s, ignoring", ex, detector)
            finally:
                detectors_merged_resource = detectors_merged_resource.merge(
                    detected_resource
                )

    return detectors_merged_resource

virt/lib/python3.9/site-packages/mysql/opentelemetry/sdk/resources/__init__ 2.py
PrinceDisant/LostInNL
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""
This package implements `OpenTelemetry Resources
<https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/resource/sdk.md#resource-sdk>`_:

    *A Resource is an immutable representation of the entity producing
    telemetry. For example, a process producing telemetry that is running in
    a container on Kubernetes has a Pod name, it is in a namespace and
    possibly is part of a Deployment which also has a name. All three of
    these attributes can be included in the Resource.*

Resource objects are created with `Resource.create`, which accepts attributes
(key-values). Resources should NOT be created via constructor, and working with
`Resource` objects should only be done via the Resource API methods. Resource
attributes can also be passed at process invocation in the
:envvar:`OTEL_RESOURCE_ATTRIBUTES` environment variable. You should register
your resource with the  `mysql.opentelemetry.sdk.trace.TracerProvider` by passing
them into their constructors. The `Resource` passed to a provider is available
to the exporter, which can send on this information as it sees fit.

.. code-block:: python

    trace.set_tracer_provider(
        TracerProvider(
            resource=Resource.create({
                "service.name": "shoppingcart",
                "service.instance.id": "instance-12",
            }),
        ),
    )
    print(trace.get_tracer_provider().resource.attributes)

    {'telemetry.sdk.language': 'python',
    'telemetry.sdk.name': 'opentelemetry',
    'telemetry.sdk.version': '0.13.dev0',
    'service.name': 'shoppingcart',
    'service.instance.id': 'instance-12'}

Note that the OpenTelemetry project documents certain `"standard attributes"
<https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/resource/semantic_conventions/README.md>`_
that have prescribed semantic meanings, for example ``service.name`` in the
above example.
 """

import abc
import concurrent.futures
import logging
import sys
import typing

from json import dumps
from os import environ
from urllib import parse

from mysql.opentelemetry.attributes import BoundedAttributes
from mysql.opentelemetry.sdk.environment_variables import (
    OTEL_EXPERIMENTAL_RESOURCE_DETECTORS,
    OTEL_RESOURCE_ATTRIBUTES,
    OTEL_SERVICE_NAME,
)
from mysql.opentelemetry.semconv.resource import ResourceAttributes
from mysql.opentelemetry.util._importlib_metadata import entry_points, version
from mysql.opentelemetry.util.types import AttributeValue

LabelValue = AttributeValue
Attributes = typing.Dict[str, LabelValue]
logger = logging.getLogger(__name__)


CLOUD_PROVIDER = ResourceAttributes.CLOUD_PROVIDER
CLOUD_ACCOUNT_ID = ResourceAttributes.CLOUD_ACCOUNT_ID
CLOUD_REGION = ResourceAttributes.CLOUD_REGION
CLOUD_AVAILABILITY_ZONE = ResourceAttributes.CLOUD_AVAILABILITY_ZONE
CONTAINER_NAME = ResourceAttributes.CONTAINER_NAME
CONTAINER_ID = ResourceAttributes.CONTAINER_ID
CONTAINER_IMAGE_NAME = ResourceAttributes.CONTAINER_IMAGE_NAME
CONTAINER_IMAGE_TAG = ResourceAttributes.CONTAINER_IMAGE_TAG
DEPLOYMENT_ENVIRONMENT = ResourceAttributes.DEPLOYMENT_ENVIRONMENT
FAAS_NAME = ResourceAttributes.FAAS_NAME
FAAS_ID = ResourceAttributes.FAAS_ID
FAAS_VERSION = ResourceAttributes.FAAS_VERSION
FAAS_INSTANCE = ResourceAttributes.FAAS_INSTANCE
HOST_NAME = ResourceAttributes.HOST_NAME
HOST_TYPE = ResourceAttributes.HOST_TYPE
HOST_IMAGE_NAME = ResourceAttributes.HOST_IMAGE_NAME
HOST_IMAGE_ID = ResourceAttributes.HOST_IMAGE_ID
HOST_IMAGE_VERSION = ResourceAttributes.HOST_IMAGE_VERSION
KUBERNETES_CLUSTER_NAME = ResourceAttributes.K8S_CLUSTER_NAME
KUBERNETES_NAMESPACE_NAME = ResourceAttributes.K8S_NAMESPACE_NAME
KUBERNETES_POD_UID = ResourceAttributes.K8S_POD_UID
KUBERNETES_POD_NAME = ResourceAttributes.K8S_POD_NAME
KUBERNETES_CONTAINER_NAME = ResourceAttributes.K8S_CONTAINER_NAME
KUBERNETES_REPLICA_SET_UID = ResourceAttributes.K8S_REPLICASET_UID
KUBERNETES_REPLICA_SET_NAME = ResourceAttributes.K8S_REPLICASET_NAME
KUBERNETES_DEPLOYMENT_UID = ResourceAttributes.K8S_DEPLOYMENT_UID
KUBERNETES_DEPLOYMENT_NAME = ResourceAttributes.K8S_DEPLOYMENT_NAME
KUBERNETES_STATEFUL_SET_UID = ResourceAttributes.K8S_STATEFULSET_UID
KUBERNETES_STATEFUL_SET_NAME = ResourceAttributes.K8S_STATEFULSET_NAME
KUBERNETES_DAEMON_SET_UID = ResourceAttributes.K8S_DAEMONSET_UID
KUBERNETES_DAEMON_SET_NAME = ResourceAttributes.K8S_DAEMONSET_NAME
KUBERNETES_JOB_UID = ResourceAttributes.K8S_JOB_UID
KUBERNETES_JOB_NAME = ResourceAttributes.K8S_JOB_NAME
KUBERNETES_CRON_JOB_UID = ResourceAttributes.K8S_CRONJOB_UID
KUBERNETES_CRON_JOB_NAME = ResourceAttributes.K8S_CRONJOB_NAME
OS_TYPE = ResourceAttributes.OS_TYPE
OS_DESCRIPTION = ResourceAttributes.OS_DESCRIPTION
PROCESS_PID = ResourceAttributes.PROCESS_PID
PROCESS_EXECUTABLE_NAME = ResourceAttributes.PROCESS_EXECUTABLE_NAME
PROCESS_EXECUTABLE_PATH = ResourceAttributes.PROCESS_EXECUTABLE_PATH
PROCESS_COMMAND = ResourceAttributes.PROCESS_COMMAND
PROCESS_COMMAND_LINE = ResourceAttributes.PROCESS_COMMAND_LINE
PROCESS_COMMAND_ARGS = ResourceAttributes.PROCESS_COMMAND_ARGS
PROCESS_OWNER = ResourceAttributes.PROCESS_OWNER
PROCESS_RUNTIME_NAME = ResourceAttributes.PROCESS_RUNTIME_NAME
PROCESS_RUNTIME_VERSION = ResourceAttributes.PROCESS_RUNTIME_VERSION
PROCESS_RUNTIME_DESCRIPTION = ResourceAttributes.PROCESS_RUNTIME_DESCRIPTION
SERVICE_NAME = ResourceAttributes.SERVICE_NAME
SERVICE_NAMESPACE = ResourceAttributes.SERVICE_NAMESPACE
SERVICE_INSTANCE_ID = ResourceAttributes.SERVICE_INSTANCE_ID
SERVICE_VERSION = ResourceAttributes.SERVICE_VERSION
TELEMETRY_SDK_NAME = ResourceAttributes.TELEMETRY_SDK_NAME
TELEMETRY_SDK_VERSION = ResourceAttributes.TELEMETRY_SDK_VERSION
TELEMETRY_AUTO_VERSION = ResourceAttributes.TELEMETRY_AUTO_VERSION
TELEMETRY_SDK_LANGUAGE = ResourceAttributes.TELEMETRY_SDK_LANGUAGE


_OPENTELEMETRY_SDK_VERSION = version("opentelemetry-sdk")


class Resource:
    """A Resource is an immutable representation of the entity producing telemetry as Attributes."""

    def __init__(self, attributes: Attributes, schema_url: typing.Optional[str] = None):
        self._attributes = BoundedAttributes(attributes=attributes)
        if schema_url is None:
            schema_url = ""
        self._schema_url = schema_url

    @staticmethod
    def create(
        attributes: typing.Optional[Attributes] = None,
        schema_url: typing.Optional[str] = None,
    ) -> "Resource":
        """Creates a new `Resource` from attributes.

        Args:
            attributes: Optional zero or more key-value pairs.
            schema_url: Optional URL pointing to the schema

        Returns:
            The newly-created Resource.
        """

        if not attributes:
            attributes = {}

        resource_detectors = []

        resource = _DEFAULT_RESOURCE

        otel_experimental_resource_detectors = environ.get(
            OTEL_EXPERIMENTAL_RESOURCE_DETECTORS, "otel"
        ).split(",")

        if "otel" not in otel_experimental_resource_detectors:
            otel_experimental_resource_detectors.append("otel")

        for resource_detector in otel_experimental_resource_detectors:
            resource_detectors.append(
                next(
                    iter(
                        entry_points(
                            group="opentelemetry_resource_detector",
                            name=resource_detector.strip(),
                        )
                    )
                ).load()()
            )

        resource = get_aggregated_resources(
            resource_detectors, _DEFAULT_RESOURCE
        ).merge(Resource(attributes, schema_url))

        if not resource.attributes.get(SERVICE_NAME, None):
            default_service_name = "unknown_service"
            process_executable_name = resource.attributes.get(
                PROCESS_EXECUTABLE_NAME, None
            )
            if process_executable_name:
                default_service_name += ":" + process_executable_name
            resource = resource.merge(
                Resource({SERVICE_NAME: default_service_name}, schema_url)
            )
        return resource

    @staticmethod
    def get_empty() -> "Resource":
        return _EMPTY_RESOURCE

    @property
    def attributes(self) -> Attributes:
        return self._attributes

    @property
    def schema_url(self) -> str:
        return self._schema_url

    def merge(self, other: "Resource") -> "Resource":
        """Merges this resource and an updating resource into a new `Resource`.

        If a key exists on both the old and updating resource, the value of the
        updating resource will override the old resource value.

        The updating resource's `schema_url` will be used only if the old
        `schema_url` is empty. Attempting to merge two resources with
        different, non-empty values for `schema_url` will result in an error
        and return the old resource.

        Args:
            other: The other resource to be merged.

        Returns:
            The newly-created Resource.
        """
        merged_attributes = self.attributes.copy()
        merged_attributes.update(other.attributes)

        if self.schema_url == "":
            schema_url = other.schema_url
        elif other.schema_url == "":
            schema_url = self.schema_url
        elif self.schema_url == other.schema_url:
            schema_url = other.schema_url
        else:
            logger.error(
                "Failed to merge resources: The two schemas %s and %s are incompatible",
                self.schema_url,
                other.schema_url,
            )
            return self

        return Resource(merged_attributes, schema_url)

    def __eq__(self, other: object) -> bool:
        if not isinstance(other, Resource):
            return False
        return (
            self._attributes == other._attributes
            and self._schema_url == other._schema_url
        )

    def __hash__(self):
        return hash(
            f"{dumps(self._attributes.copy(), sort_keys=True)}|{self._schema_url}"
        )

    def to_json(self, indent=4) -> str:
        return dumps(
            {
                "attributes": dict(self._attributes),
                "schema_url": self._schema_url,
            },
            indent=indent,
        )


_EMPTY_RESOURCE = Resource({})
_DEFAULT_RESOURCE = Resource(
    {
        TELEMETRY_SDK_LANGUAGE: "python",
        TELEMETRY_SDK_NAME: "opentelemetry",
        TELEMETRY_SDK_VERSION: _OPENTELEMETRY_SDK_VERSION,
    }
)


class ResourceDetector(abc.ABC):
    def __init__(self, raise_on_error=False):
        self.raise_on_error = raise_on_error

    @abc.abstractmethod
    def detect(self) -> "Resource":
        raise NotImplementedError()


class OTELResourceDetector(ResourceDetector):
    # pylint: disable=no-self-use
    def detect(self) -> "Resource":
        env_resources_items = environ.get(OTEL_RESOURCE_ATTRIBUTES)
        env_resource_map = {}

        if env_resources_items:
            for item in env_resources_items.split(","):
                try:
                    key, value = item.split("=", maxsplit=1)
                except ValueError as exc:
                    logger.warning(
                        "Invalid key value resource attribute pair %s: %s",
                        item,
                        exc,
                    )
                    continue
                value_url_decoded = parse.unquote(value.strip())
                env_resource_map[key.strip()] = value_url_decoded

        service_name = environ.get(OTEL_SERVICE_NAME)
        if service_name:
            env_resource_map[SERVICE_NAME] = service_name
        return Resource(env_resource_map)


class ProcessResourceDetector(ResourceDetector):
    # pylint: disable=no-self-use
    def detect(self) -> "Resource":
        _runtime_version = ".".join(
            map(
                str,
                sys.version_info[:3]
                if sys.version_info.releaselevel == "final"
                and not sys.version_info.serial
                else sys.version_info,
            )
        )

        return Resource(
            {
                PROCESS_RUNTIME_DESCRIPTION: sys.version,
                PROCESS_RUNTIME_NAME: sys.implementation.name,
                PROCESS_RUNTIME_VERSION: _runtime_version,
            }
        )


def get_aggregated_resources(
    detectors: typing.List["ResourceDetector"],
    initial_resource: typing.Optional[Resource] = None,
    timeout=5,
) -> "Resource":
    """Retrieves resources from detectors in the order that they were passed

    :param detectors: List of resources in order of priority
    :param initial_resource: Static resource. This has highest priority
    :param timeout: Number of seconds to wait for each detector to return
    :return:
    """
    detectors_merged_resource = initial_resource or Resource.create()

    with concurrent.futures.ThreadPoolExecutor(max_workers=4) as executor:
        futures = [executor.submit(detector.detect) for detector in detectors]
        for detector_ind, future in enumerate(futures):
            detector = detectors[detector_ind]
            try:
                detected_resource = future.result(timeout=timeout)
            # pylint: disable=broad-except
            except Exception as ex:
                detected_resource = _EMPTY_RESOURCE
                if detector.raise_on_error:
                    raise ex
                logger.warning("Exception %s in detector %s, ignoring", ex, detector)
            finally:
                detectors_merged_resource = detectors_merged_resource.merge(
                    detected_resource
                )

    return detectors_merged_resource

virt/lib/python3.9/site-packages/mysql/opentelemetry/sdk/resources/__init__ 3.py
PrinceDisant/LostInNL
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""
This package implements `OpenTelemetry Resources
<https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/resource/sdk.md#resource-sdk>`_:

    *A Resource is an immutable representation of the entity producing
    telemetry. For example, a process producing telemetry that is running in
    a container on Kubernetes has a Pod name, it is in a namespace and
    possibly is part of a Deployment which also has a name. All three of
    these attributes can be included in the Resource.*

Resource objects are created with `Resource.create`, which accepts attributes
(key-values). Resources should NOT be created via constructor, and working with
`Resource` objects should only be done via the Resource API methods. Resource
attributes can also be passed at process invocation in the
:envvar:`OTEL_RESOURCE_ATTRIBUTES` environment variable. You should register
your resource with the  `mysql.opentelemetry.sdk.trace.TracerProvider` by passing
them into their constructors. The `Resource` passed to a provider is available
to the exporter, which can send on this information as it sees fit.

.. code-block:: python

    trace.set_tracer_provider(
        TracerProvider(
            resource=Resource.create({
                "service.name": "shoppingcart",
                "service.instance.id": "instance-12",
            }),
        ),
    )
    print(trace.get_tracer_provider().resource.attributes)

    {'telemetry.sdk.language': 'python',
    'telemetry.sdk.name': 'opentelemetry',
    'telemetry.sdk.version': '0.13.dev0',
    'service.name': 'shoppingcart',
    'service.instance.id': 'instance-12'}

Note that the OpenTelemetry project documents certain `"standard attributes"
<https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/resource/semantic_conventions/README.md>`_
that have prescribed semantic meanings, for example ``service.name`` in the
above example.
 """

import abc
import concurrent.futures
import logging
import sys
import typing

from json import dumps
from os import environ
from urllib import parse

from mysql.opentelemetry.attributes import BoundedAttributes
from mysql.opentelemetry.sdk.environment_variables import (
    OTEL_EXPERIMENTAL_RESOURCE_DETECTORS,
    OTEL_RESOURCE_ATTRIBUTES,
    OTEL_SERVICE_NAME,
)
from mysql.opentelemetry.semconv.resource import ResourceAttributes
from mysql.opentelemetry.util._importlib_metadata import entry_points, version
from mysql.opentelemetry.util.types import AttributeValue

LabelValue = AttributeValue
Attributes = typing.Dict[str, LabelValue]
logger = logging.getLogger(__name__)


CLOUD_PROVIDER = ResourceAttributes.CLOUD_PROVIDER
CLOUD_ACCOUNT_ID = ResourceAttributes.CLOUD_ACCOUNT_ID
CLOUD_REGION = ResourceAttributes.CLOUD_REGION
CLOUD_AVAILABILITY_ZONE = ResourceAttributes.CLOUD_AVAILABILITY_ZONE
CONTAINER_NAME = ResourceAttributes.CONTAINER_NAME
CONTAINER_ID = ResourceAttributes.CONTAINER_ID
CONTAINER_IMAGE_NAME = ResourceAttributes.CONTAINER_IMAGE_NAME
CONTAINER_IMAGE_TAG = ResourceAttributes.CONTAINER_IMAGE_TAG
DEPLOYMENT_ENVIRONMENT = ResourceAttributes.DEPLOYMENT_ENVIRONMENT
FAAS_NAME = ResourceAttributes.FAAS_NAME
FAAS_ID = ResourceAttributes.FAAS_ID
FAAS_VERSION = ResourceAttributes.FAAS_VERSION
FAAS_INSTANCE = ResourceAttributes.FAAS_INSTANCE
HOST_NAME = ResourceAttributes.HOST_NAME
HOST_TYPE = ResourceAttributes.HOST_TYPE
HOST_IMAGE_NAME = ResourceAttributes.HOST_IMAGE_NAME
HOST_IMAGE_ID = ResourceAttributes.HOST_IMAGE_ID
HOST_IMAGE_VERSION = ResourceAttributes.HOST_IMAGE_VERSION
KUBERNETES_CLUSTER_NAME = ResourceAttributes.K8S_CLUSTER_NAME
KUBERNETES_NAMESPACE_NAME = ResourceAttributes.K8S_NAMESPACE_NAME
KUBERNETES_POD_UID = ResourceAttributes.K8S_POD_UID
KUBERNETES_POD_NAME = ResourceAttributes.K8S_POD_NAME
KUBERNETES_CONTAINER_NAME = ResourceAttributes.K8S_CONTAINER_NAME
KUBERNETES_REPLICA_SET_UID = ResourceAttributes.K8S_REPLICASET_UID
KUBERNETES_REPLICA_SET_NAME = ResourceAttributes.K8S_REPLICASET_NAME
KUBERNETES_DEPLOYMENT_UID = ResourceAttributes.K8S_DEPLOYMENT_UID
KUBERNETES_DEPLOYMENT_NAME = ResourceAttributes.K8S_DEPLOYMENT_NAME
KUBERNETES_STATEFUL_SET_UID = ResourceAttributes.K8S_STATEFULSET_UID
KUBERNETES_STATEFUL_SET_NAME = ResourceAttributes.K8S_STATEFULSET_NAME
KUBERNETES_DAEMON_SET_UID = ResourceAttributes.K8S_DAEMONSET_UID
KUBERNETES_DAEMON_SET_NAME = ResourceAttributes.K8S_DAEMONSET_NAME
KUBERNETES_JOB_UID = ResourceAttributes.K8S_JOB_UID
KUBERNETES_JOB_NAME = ResourceAttributes.K8S_JOB_NAME
KUBERNETES_CRON_JOB_UID = ResourceAttributes.K8S_CRONJOB_UID
KUBERNETES_CRON_JOB_NAME = ResourceAttributes.K8S_CRONJOB_NAME
OS_TYPE = ResourceAttributes.OS_TYPE
OS_DESCRIPTION = ResourceAttributes.OS_DESCRIPTION
PROCESS_PID = ResourceAttributes.PROCESS_PID
PROCESS_EXECUTABLE_NAME = ResourceAttributes.PROCESS_EXECUTABLE_NAME
PROCESS_EXECUTABLE_PATH = ResourceAttributes.PROCESS_EXECUTABLE_PATH
PROCESS_COMMAND = ResourceAttributes.PROCESS_COMMAND
PROCESS_COMMAND_LINE = ResourceAttributes.PROCESS_COMMAND_LINE
PROCESS_COMMAND_ARGS = ResourceAttributes.PROCESS_COMMAND_ARGS
PROCESS_OWNER = ResourceAttributes.PROCESS_OWNER
PROCESS_RUNTIME_NAME = ResourceAttributes.PROCESS_RUNTIME_NAME
PROCESS_RUNTIME_VERSION = ResourceAttributes.PROCESS_RUNTIME_VERSION
PROCESS_RUNTIME_DESCRIPTION = ResourceAttributes.PROCESS_RUNTIME_DESCRIPTION
SERVICE_NAME = ResourceAttributes.SERVICE_NAME
SERVICE_NAMESPACE = ResourceAttributes.SERVICE_NAMESPACE
SERVICE_INSTANCE_ID = ResourceAttributes.SERVICE_INSTANCE_ID
SERVICE_VERSION = ResourceAttributes.SERVICE_VERSION
TELEMETRY_SDK_NAME = ResourceAttributes.TELEMETRY_SDK_NAME
TELEMETRY_SDK_VERSION = ResourceAttributes.TELEMETRY_SDK_VERSION
TELEMETRY_AUTO_VERSION = ResourceAttributes.TELEMETRY_AUTO_VERSION
TELEMETRY_SDK_LANGUAGE = ResourceAttributes.TELEMETRY_SDK_LANGUAGE


_OPENTELEMETRY_SDK_VERSION = version("opentelemetry-sdk")


class Resource:
    """A Resource is an immutable representation of the entity producing telemetry as Attributes."""

    def __init__(self, attributes: Attributes, schema_url: typing.Optional[str] = None):
        self._attributes = BoundedAttributes(attributes=attributes)
        if schema_url is None:
            schema_url = ""
        self._schema_url = schema_url

    @staticmethod
    def create(
        attributes: typing.Optional[Attributes] = None,
        schema_url: typing.Optional[str] = None,
    ) -> "Resource":
        """Creates a new `Resource` from attributes.

        Args:
            attributes: Optional zero or more key-value pairs.
            schema_url: Optional URL pointing to the schema

        Returns:
            The newly-created Resource.
        """

        if not attributes:
            attributes = {}

        resource_detectors = []

        resource = _DEFAULT_RESOURCE

        otel_experimental_resource_detectors = environ.get(
            OTEL_EXPERIMENTAL_RESOURCE_DETECTORS, "otel"
        ).split(",")

        if "otel" not in otel_experimental_resource_detectors:
            otel_experimental_resource_detectors.append("otel")

        for resource_detector in otel_experimental_resource_detectors:
            resource_detectors.append(
                next(
                    iter(
                        entry_points(
                            group="opentelemetry_resource_detector",
                            name=resource_detector.strip(),
                        )
                    )
                ).load()()
            )

        resource = get_aggregated_resources(
            resource_detectors, _DEFAULT_RESOURCE
        ).merge(Resource(attributes, schema_url))

        if not resource.attributes.get(SERVICE_NAME, None):
            default_service_name = "unknown_service"
            process_executable_name = resource.attributes.get(
                PROCESS_EXECUTABLE_NAME, None
            )
            if process_executable_name:
                default_service_name += ":" + process_executable_name
            resource = resource.merge(
                Resource({SERVICE_NAME: default_service_name}, schema_url)
            )
        return resource

    @staticmethod
    def get_empty() -> "Resource":
        return _EMPTY_RESOURCE

    @property
    def attributes(self) -> Attributes:
        return self._attributes

    @property
    def schema_url(self) -> str:
        return self._schema_url

    def merge(self, other: "Resource") -> "Resource":
        """Merges this resource and an updating resource into a new `Resource`.

        If a key exists on both the old and updating resource, the value of the
        updating resource will override the old resource value.

        The updating resource's `schema_url` will be used only if the old
        `schema_url` is empty. Attempting to merge two resources with
        different, non-empty values for `schema_url` will result in an error
        and return the old resource.

        Args:
            other: The other resource to be merged.

        Returns:
            The newly-created Resource.
        """
        merged_attributes = self.attributes.copy()
        merged_attributes.update(other.attributes)

        if self.schema_url == "":
            schema_url = other.schema_url
        elif other.schema_url == "":
            schema_url = self.schema_url
        elif self.schema_url == other.schema_url:
            schema_url = other.schema_url
        else:
            logger.error(
                "Failed to merge resources: The two schemas %s and %s are incompatible",
                self.schema_url,
                other.schema_url,
            )
            return self

        return Resource(merged_attributes, schema_url)

    def __eq__(self, other: object) -> bool:
        if not isinstance(other, Resource):
            return False
        return (
            self._attributes == other._attributes
            and self._schema_url == other._schema_url
        )

    def __hash__(self):
        return hash(
            f"{dumps(self._attributes.copy(), sort_keys=True)}|{self._schema_url}"
        )

    def to_json(self, indent=4) -> str:
        return dumps(
            {
                "attributes": dict(self._attributes),
                "schema_url": self._schema_url,
            },
            indent=indent,
        )


_EMPTY_RESOURCE = Resource({})
_DEFAULT_RESOURCE = Resource(
    {
        TELEMETRY_SDK_LANGUAGE: "python",
        TELEMETRY_SDK_NAME: "opentelemetry",
        TELEMETRY_SDK_VERSION: _OPENTELEMETRY_SDK_VERSION,
    }
)


class ResourceDetector(abc.ABC):
    def __init__(self, raise_on_error=False):
        self.raise_on_error = raise_on_error

    @abc.abstractmethod
    def detect(self) -> "Resource":
        raise NotImplementedError()


class OTELResourceDetector(ResourceDetector):
    # pylint: disable=no-self-use
    def detect(self) -> "Resource":
        env_resources_items = environ.get(OTEL_RESOURCE_ATTRIBUTES)
        env_resource_map = {}

        if env_resources_items:
            for item in env_resources_items.split(","):
                try:
                    key, value = item.split("=", maxsplit=1)
                except ValueError as exc:
                    logger.warning(
                        "Invalid key value resource attribute pair %s: %s",
                        item,
                        exc,
                    )
                    continue
                value_url_decoded = parse.unquote(value.strip())
                env_resource_map[key.strip()] = value_url_decoded

        service_name = environ.get(OTEL_SERVICE_NAME)
        if service_name:
            env_resource_map[SERVICE_NAME] = service_name
        return Resource(env_resource_map)


class ProcessResourceDetector(ResourceDetector):
    # pylint: disable=no-self-use
    def detect(self) -> "Resource":
        _runtime_version = ".".join(
            map(
                str,
                sys.version_info[:3]
                if sys.version_info.releaselevel == "final"
                and not sys.version_info.serial
                else sys.version_info,
            )
        )

        return Resource(
            {
                PROCESS_RUNTIME_DESCRIPTION: sys.version,
                PROCESS_RUNTIME_NAME: sys.implementation.name,
                PROCESS_RUNTIME_VERSION: _runtime_version,
            }
        )


def get_aggregated_resources(
    detectors: typing.List["ResourceDetector"],
    initial_resource: typing.Optional[Resource] = None,
    timeout=5,
) -> "Resource":
    """Retrieves resources from detectors in the order that they were passed

    :param detectors: List of resources in order of priority
    :param initial_resource: Static resource. This has highest priority
    :param timeout: Number of seconds to wait for each detector to return
    :return:
    """
    detectors_merged_resource = initial_resource or Resource.create()

    with concurrent.futures.ThreadPoolExecutor(max_workers=4) as executor:
        futures = [executor.submit(detector.detect) for detector in detectors]
        for detector_ind, future in enumerate(futures):
            detector = detectors[detector_ind]
            try:
                detected_resource = future.result(timeout=timeout)
            # pylint: disable=broad-except
            except Exception as ex:
                detected_resource = _EMPTY_RESOURCE
                if detector.raise_on_error:
                    raise ex
                logger.warning("Exception %s in detector %s, ignoring", ex, detector)
            finally:
                detectors_merged_resource = detectors_merged_resource.merge(
                    detected_resource
                )

    return detectors_merged_resource

virt/lib/python3.9/site-packages/mysql/opentelemetry/sdk/resources/__init__.py
PrinceDisant/LostInNL
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# pylint: disable=too-many-lines
import abc
import atexit
import concurrent.futures
import json
import logging
import threading
import traceback
import typing

from collections import OrderedDict
from contextlib import contextmanager
from os import environ
from time import time_ns
from types import MappingProxyType, TracebackType
from typing import Any, Callable, Dict, Iterator, Optional, Sequence, Tuple, Type, Union
from warnings import filterwarnings

from deprecated import deprecated
from mysql.opentelemetry import context as context_api, trace as trace_api
from mysql.opentelemetry.attributes import BoundedAttributes
from mysql.opentelemetry.sdk import util
from mysql.opentelemetry.sdk.environment_variables import (
    OTEL_ATTRIBUTE_COUNT_LIMIT,
    OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT,
    OTEL_EVENT_ATTRIBUTE_COUNT_LIMIT,
    OTEL_LINK_ATTRIBUTE_COUNT_LIMIT,
    OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT,
    OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT,
    OTEL_SPAN_EVENT_COUNT_LIMIT,
    OTEL_SPAN_LINK_COUNT_LIMIT,
)
from mysql.opentelemetry.sdk.resources import Resource
from mysql.opentelemetry.sdk.trace import sampling
from mysql.opentelemetry.sdk.trace.id_generator import IdGenerator, RandomIdGenerator
from mysql.opentelemetry.sdk.util import BoundedList
from mysql.opentelemetry.sdk.util.instrumentation import (
    InstrumentationInfo,
    InstrumentationScope,
)
from mysql.opentelemetry.trace import SpanContext
from mysql.opentelemetry.trace.status import Status, StatusCode
from mysql.opentelemetry.util import types

logger = logging.getLogger(__name__)

_DEFAULT_OTEL_ATTRIBUTE_COUNT_LIMIT = 128
_DEFAULT_OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT = 128
_DEFAULT_OTEL_EVENT_ATTRIBUTE_COUNT_LIMIT = 128
_DEFAULT_OTEL_LINK_ATTRIBUTE_COUNT_LIMIT = 128
_DEFAULT_OTEL_SPAN_EVENT_COUNT_LIMIT = 128
_DEFAULT_OTEL_SPAN_LINK_COUNT_LIMIT = 128


_ENV_VALUE_UNSET = ""


class SpanProcessor:
    """Interface which allows hooks for SDK's `Span` start and end method
    invocations.

    Span processors can be registered directly using
    :func:`TracerProvider.add_span_processor` and they are invoked
    in the same order as they were registered.
    """

    def on_start(
        self,
        span: "Span",
        parent_context: Optional[context_api.Context] = None,
    ) -> None:
        """Called when a :class:`mysql.opentelemetry.trace.Span` is started.

        This method is called synchronously on the thread that starts the
        span, therefore it should not block or throw an exception.

        Args:
            span: The :class:`mysql.opentelemetry.trace.Span` that just started.
            parent_context: The parent context of the span that just started.
        """

    def on_end(self, span: "ReadableSpan") -> None:
        """Called when a :class:`mysql.opentelemetry.trace.Span` is ended.

        This method is called synchronously on the thread that ends the
        span, therefore it should not block or throw an exception.

        Args:
            span: The :class:`mysql.opentelemetry.trace.Span` that just ended.
        """

    def shutdown(self) -> None:
        """Called when a :class:`mysql.opentelemetry.sdk.trace.TracerProvider` is shutdown."""

    def force_flush(self, timeout_millis: int = 30000) -> bool:
        """Export all ended spans to the configured Exporter that have not yet
        been exported.

        Args:
            timeout_millis: The maximum amount of time to wait for spans to be
                exported.

        Returns:
            False if the timeout is exceeded, True otherwise.
        """


# Temporary fix until https://github.com/PyCQA/pylint/issues/4098 is resolved
# pylint:disable=no-member
class SynchronousMultiSpanProcessor(SpanProcessor):
    """Implementation of class:`SpanProcessor` that forwards all received
    events to a list of span processors sequentially.

    The underlying span processors are called in sequential order as they were
    added.
    """

    def __init__(self):
        # use a tuple to avoid race conditions when adding a new span and
        # iterating through it on "on_start" and "on_end".
        self._span_processors = ()  # type: Tuple[SpanProcessor, ...]
        self._lock = threading.Lock()

    def add_span_processor(self, span_processor: SpanProcessor) -> None:
        """Adds a SpanProcessor to the list handled by this instance."""
        with self._lock:
            self._span_processors += (span_processor,)

    def on_start(
        self,
        span: "Span",
        parent_context: Optional[context_api.Context] = None,
    ) -> None:
        for sp in self._span_processors:
            sp.on_start(span, parent_context=parent_context)

    def on_end(self, span: "ReadableSpan") -> None:
        for sp in self._span_processors:
            sp.on_end(span)

    def shutdown(self) -> None:
        """Sequentially shuts down all underlying span processors."""
        for sp in self._span_processors:
            sp.shutdown()

    def force_flush(self, timeout_millis: int = 30000) -> bool:
        """Sequentially calls force_flush on all underlying
        :class:`SpanProcessor`

        Args:
            timeout_millis: The maximum amount of time over all span processors
                to wait for spans to be exported. In case the first n span
                processors exceeded the timeout followup span processors will be
                skipped.

        Returns:
            True if all span processors flushed their spans within the
            given timeout, False otherwise.
        """
        deadline_ns = time_ns() + timeout_millis * 1000000
        for sp in self._span_processors:
            current_time_ns = time_ns()
            if current_time_ns >= deadline_ns:
                return False

            if not sp.force_flush((deadline_ns - current_time_ns) // 1000000):
                return False

        return True


class ConcurrentMultiSpanProcessor(SpanProcessor):
    """Implementation of :class:`SpanProcessor` that forwards all received
    events to a list of span processors in parallel.

    Calls to the underlying span processors are forwarded in parallel by
    submitting them to a thread pool executor and waiting until each span
    processor finished its work.

    Args:
        num_threads: The number of threads managed by the thread pool executor
            and thus defining how many span processors can work in parallel.
    """

    def __init__(self, num_threads: int = 2):
        # use a tuple to avoid race conditions when adding a new span and
        # iterating through it on "on_start" and "on_end".
        self._span_processors = ()  # type: Tuple[SpanProcessor, ...]
        self._lock = threading.Lock()
        self._executor = concurrent.futures.ThreadPoolExecutor(max_workers=num_threads)

    def add_span_processor(self, span_processor: SpanProcessor) -> None:
        """Adds a SpanProcessor to the list handled by this instance."""
        with self._lock:
            self._span_processors += (span_processor,)

    def _submit_and_await(
        self,
        func: Callable[[SpanProcessor], Callable[..., None]],
        *args: Any,
        **kwargs: Any,
    ):
        futures = []
        for sp in self._span_processors:
            future = self._executor.submit(func(sp), *args, **kwargs)
            futures.append(future)
        for future in futures:
            future.result()

    def on_start(
        self,
        span: "Span",
        parent_context: Optional[context_api.Context] = None,
    ) -> None:
        self._submit_and_await(
            lambda sp: sp.on_start, span, parent_context=parent_context
        )

    def on_end(self, span: "ReadableSpan") -> None:
        self._submit_and_await(lambda sp: sp.on_end, span)

    def shutdown(self) -> None:
        """Shuts down all underlying span processors in parallel."""
        self._submit_and_await(lambda sp: sp.shutdown)

    def force_flush(self, timeout_millis: int = 30000) -> bool:
        """Calls force_flush on all underlying span processors in parallel.

        Args:
            timeout_millis: The maximum amount of time to wait for spans to be
                exported.

        Returns:
            True if all span processors flushed their spans within the given
            timeout, False otherwise.
        """
        futures = []
        for sp in self._span_processors:  # type: SpanProcessor
            future = self._executor.submit(sp.force_flush, timeout_millis)
            futures.append(future)

        timeout_sec = timeout_millis / 1e3
        done_futures, not_done_futures = concurrent.futures.wait(futures, timeout_sec)
        if not_done_futures:
            return False

        for future in done_futures:
            if not future.result():
                return False

        return True


class EventBase(abc.ABC):
    def __init__(self, name: str, timestamp: Optional[int] = None) -> None:
        self._name = name
        if timestamp is None:
            self._timestamp = time_ns()
        else:
            self._timestamp = timestamp

    @property
    def name(self) -> str:
        return self._name

    @property
    def timestamp(self) -> int:
        return self._timestamp

    @property
    @abc.abstractmethod
    def attributes(self) -> types.Attributes:
        pass


class Event(EventBase):
    """A text annotation with a set of attributes. The attributes of an event
    are immutable.

    Args:
        name: Name of the event.
        attributes: Attributes of the event.
        timestamp: Timestamp of the event. If `None` it will filled
            automatically.
    """

    def __init__(
        self,
        name: str,
        attributes: types.Attributes = None,
        timestamp: Optional[int] = None,
        limit: Optional[int] = _DEFAULT_OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT,
    ) -> None:
        super().__init__(name, timestamp)
        self._attributes = attributes

    @property
    def attributes(self) -> types.Attributes:
        return self._attributes


def _check_span_ended(func):
    def wrapper(self, *args, **kwargs):
        already_ended = False
        with self._lock:  # pylint: disable=protected-access
            if self._end_time is None:  # pylint: disable=protected-access
                func(self, *args, **kwargs)
            else:
                already_ended = True

        if already_ended:
            logger.warning("Tried calling %s on an ended span.", func.__name__)

    return wrapper


class ReadableSpan:
    """Provides read-only access to span attributes"""

    def __init__(
        self,
        name: str = None,
        context: trace_api.SpanContext = None,
        parent: Optional[trace_api.SpanContext] = None,
        resource: Resource = Resource.create({}),
        attributes: types.Attributes = None,
        events: Sequence[Event] = (),
        links: Sequence[trace_api.Link] = (),
        kind: trace_api.SpanKind = trace_api.SpanKind.INTERNAL,
        instrumentation_info: InstrumentationInfo = None,
        status: Status = Status(StatusCode.UNSET),
        start_time: Optional[int] = None,
        end_time: Optional[int] = None,
        instrumentation_scope: InstrumentationScope = None,
    ) -> None:
        self._name = name
        self._context = context
        self._kind = kind
        self._instrumentation_info = instrumentation_info
        self._instrumentation_scope = instrumentation_scope
        self._parent = parent
        self._start_time = start_time
        self._end_time = end_time
        self._attributes = attributes
        self._events = events
        self._links = links
        self._resource = resource
        self._status = status

    @property
    def dropped_attributes(self) -> int:
        if self._attributes:
            return self._attributes.dropped
        return 0

    @property
    def dropped_events(self) -> int:
        if self._events:
            return self._events.dropped
        return 0

    @property
    def dropped_links(self) -> int:
        if self._links:
            return self._links.dropped
        return 0

    @property
    def name(self) -> str:
        return self._name

    def get_span_context(self):
        return self._context

    @property
    def context(self):
        return self._context

    @property
    def kind(self) -> trace_api.SpanKind:
        return self._kind

    @property
    def parent(self) -> Optional[trace_api.SpanContext]:
        return self._parent

    @property
    def start_time(self) -> Optional[int]:
        return self._start_time

    @property
    def end_time(self) -> Optional[int]:
        return self._end_time

    @property
    def status(self) -> trace_api.Status:
        return self._status

    @property
    def attributes(self) -> types.Attributes:
        return MappingProxyType(self._attributes)

    @property
    def events(self) -> Sequence[Event]:
        return tuple(event for event in self._events)

    @property
    def links(self) -> Sequence[trace_api.Link]:
        return tuple(link for link in self._links)

    @property
    def resource(self) -> Resource:
        return self._resource

    @property
    @deprecated(version="1.11.1", reason="You should use instrumentation_scope")
    def instrumentation_info(self) -> InstrumentationInfo:
        return self._instrumentation_info

    @property
    def instrumentation_scope(self) -> InstrumentationScope:
        return self._instrumentation_scope

    def to_json(self, indent=4):
        parent_id = None
        if self.parent is not None:
            if isinstance(self.parent, Span):
                ctx = self.parent.context
                parent_id = f"0x{trace_api.format_span_id(ctx.span_id)}"
            elif isinstance(self.parent, SpanContext):
                parent_id = f"0x{trace_api.format_span_id(self.parent.span_id)}"

        start_time = None
        if self._start_time:
            start_time = util.ns_to_iso_str(self._start_time)

        end_time = None
        if self._end_time:
            end_time = util.ns_to_iso_str(self._end_time)

        if self._status is not None:
            status = OrderedDict()
            status["status_code"] = str(self._status.status_code.name)
            if self._status.description:
                status["description"] = self._status.description

        f_span = OrderedDict()

        f_span["name"] = self._name
        f_span["context"] = self._format_context(self._context)
        f_span["kind"] = str(self.kind)
        f_span["parent_id"] = parent_id
        f_span["start_time"] = start_time
        f_span["end_time"] = end_time
        if self._status is not None:
            f_span["status"] = status
        f_span["attributes"] = self._format_attributes(self._attributes)
        f_span["events"] = self._format_events(self._events)
        f_span["links"] = self._format_links(self._links)
        f_span["resource"] = json.loads(self.resource.to_json())

        return json.dumps(f_span, indent=indent)

    @staticmethod
    def _format_context(context):
        x_ctx = OrderedDict()
        x_ctx["trace_id"] = f"0x{trace_api.format_trace_id(context.trace_id)}"
        x_ctx["span_id"] = f"0x{trace_api.format_span_id(context.span_id)}"
        x_ctx["trace_state"] = repr(context.trace_state)
        return x_ctx

    @staticmethod
    def _format_attributes(attributes):
        if isinstance(attributes, BoundedAttributes):
            return attributes._dict  # pylint: disable=protected-access
        if isinstance(attributes, MappingProxyType):
            return attributes.copy()
        return attributes

    @staticmethod
    def _format_events(events):
        f_events = []
        for event in events:
            f_event = OrderedDict()
            f_event["name"] = event.name
            f_event["timestamp"] = util.ns_to_iso_str(event.timestamp)
            f_event[
                "attributes"
            ] = Span._format_attributes(  # pylint: disable=protected-access
                event.attributes
            )
            f_events.append(f_event)
        return f_events

    @staticmethod
    def _format_links(links):
        f_links = []
        for link in links:
            f_link = OrderedDict()
            f_link[
                "context"
            ] = Span._format_context(  # pylint: disable=protected-access
                link.context
            )
            f_link[
                "attributes"
            ] = Span._format_attributes(  # pylint: disable=protected-access
                link.attributes
            )
            f_links.append(f_link)
        return f_links


class SpanLimits:
    """The limits that should be enforce on recorded data such as events, links, attributes etc.

    This class does not enforce any limits itself. It only provides an a way read limits from env,
    default values and from user provided arguments.

    All limit arguments must be either a non-negative integer, ``None`` or ``SpanLimits.UNSET``.

    - All limit arguments are optional.
    - If a limit argument is not set, the class will try to read its value from the corresponding
      environment variable.
    - If the environment variable is not set, the default value, if any, will be used.

    Limit precedence:

    - If a model specific limit is set, it will be used.
    - Else if the corresponding global limit is set, it will be used.
    - Else if the model specific limit has a default value, the default value will be used.
    - Else if the global limit has a default value, the default value will be used.

    Args:
        max_attributes: Maximum number of attributes that can be added to a span, event, and link.
            Environment variable: OTEL_ATTRIBUTE_COUNT_LIMIT
            Default: {_DEFAULT_ATTRIBUTE_COUNT_LIMIT}
        max_events: Maximum number of events that can be added to a Span.
            Environment variable: OTEL_SPAN_EVENT_COUNT_LIMIT
            Default: {_DEFAULT_SPAN_EVENT_COUNT_LIMIT}
        max_links: Maximum number of links that can be added to a Span.
            Environment variable: OTEL_SPAN_LINK_COUNT_LIMIT
            Default: {_DEFAULT_SPAN_LINK_COUNT_LIMIT}
        max_span_attributes: Maximum number of attributes that can be added to a Span.
            Environment variable: OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT
            Default: {_DEFAULT_OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT}
        max_event_attributes: Maximum number of attributes that can be added to an Event.
            Default: {_DEFAULT_OTEL_EVENT_ATTRIBUTE_COUNT_LIMIT}
        max_link_attributes: Maximum number of attributes that can be added to a Link.
            Default: {_DEFAULT_OTEL_LINK_ATTRIBUTE_COUNT_LIMIT}
        max_attribute_length: Maximum length an attribute value can have. Values longer than
            the specified length will be truncated.
        max_span_attribute_length: Maximum length a span attribute value can have. Values longer than
            the specified length will be truncated.
    """

    UNSET = -1

    def __init__(
        self,
        max_attributes: Optional[int] = None,
        max_events: Optional[int] = None,
        max_links: Optional[int] = None,
        max_span_attributes: Optional[int] = None,
        max_event_attributes: Optional[int] = None,
        max_link_attributes: Optional[int] = None,
        max_attribute_length: Optional[int] = None,
        max_span_attribute_length: Optional[int] = None,
    ):
        # span events and links count
        self.max_events = self._from_env_if_absent(
            max_events,
            OTEL_SPAN_EVENT_COUNT_LIMIT,
            _DEFAULT_OTEL_SPAN_EVENT_COUNT_LIMIT,
        )
        self.max_links = self._from_env_if_absent(
            max_links,
            OTEL_SPAN_LINK_COUNT_LIMIT,
            _DEFAULT_OTEL_SPAN_LINK_COUNT_LIMIT,
        )

        # attribute count
        global_max_attributes = self._from_env_if_absent(
            max_attributes, OTEL_ATTRIBUTE_COUNT_LIMIT
        )
        self.max_attributes = (
            global_max_attributes
            if global_max_attributes is not None
            else _DEFAULT_OTEL_ATTRIBUTE_COUNT_LIMIT
        )

        self.max_span_attributes = self._from_env_if_absent(
            max_span_attributes,
            OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT,
            global_max_attributes
            if global_max_attributes is not None
            else _DEFAULT_OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT,
        )
        self.max_event_attributes = self._from_env_if_absent(
            max_event_attributes,
            OTEL_EVENT_ATTRIBUTE_COUNT_LIMIT,
            global_max_attributes
            if global_max_attributes is not None
            else _DEFAULT_OTEL_EVENT_ATTRIBUTE_COUNT_LIMIT,
        )
        self.max_link_attributes = self._from_env_if_absent(
            max_link_attributes,
            OTEL_LINK_ATTRIBUTE_COUNT_LIMIT,
            global_max_attributes
            if global_max_attributes is not None
            else _DEFAULT_OTEL_LINK_ATTRIBUTE_COUNT_LIMIT,
        )

        # attribute length
        self.max_attribute_length = self._from_env_if_absent(
            max_attribute_length,
            OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT,
        )
        self.max_span_attribute_length = self._from_env_if_absent(
            max_span_attribute_length,
            OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT,
            # use global attribute length limit as default
            self.max_attribute_length,
        )

    def __repr__(self):
        return f"{type(self).__name__}(max_span_attributes={self.max_span_attributes}, max_events_attributes={self.max_event_attributes}, max_link_attributes={self.max_link_attributes}, max_attributes={self.max_attributes}, max_events={self.max_events}, max_links={self.max_links}, max_attribute_length={self.max_attribute_length})"

    @classmethod
    def _from_env_if_absent(
        cls, value: Optional[int], env_var: str, default: Optional[int] = None
    ) -> Optional[int]:
        if value == cls.UNSET:
            return None

        err_msg = "{0} must be a non-negative integer but got {}"

        # if no value is provided for the limit, try to load it from env
        if value is None:
            # return default value if env var is not set
            if env_var not in environ:
                return default

            str_value = environ.get(env_var, "").strip().lower()
            if str_value == _ENV_VALUE_UNSET:
                return None

            try:
                value = int(str_value)
            except ValueError:
                raise ValueError(err_msg.format(env_var, str_value))

        if value < 0:
            raise ValueError(err_msg.format(env_var, value))
        return value


_UnsetLimits = SpanLimits(
    max_attributes=SpanLimits.UNSET,
    max_events=SpanLimits.UNSET,
    max_links=SpanLimits.UNSET,
    max_span_attributes=SpanLimits.UNSET,
    max_event_attributes=SpanLimits.UNSET,
    max_link_attributes=SpanLimits.UNSET,
    max_attribute_length=SpanLimits.UNSET,
    max_span_attribute_length=SpanLimits.UNSET,
)

# not removed for backward compat. please use SpanLimits instead.
SPAN_ATTRIBUTE_COUNT_LIMIT = (
    SpanLimits._from_env_if_absent(  # pylint: disable=protected-access
        None,
        OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT,
        _DEFAULT_OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT,
    )
)


class Span(trace_api.Span, ReadableSpan):
    """See `mysql.opentelemetry.trace.Span`.

    Users should create `Span` objects via the `Tracer` instead of this
    constructor.

    Args:
        name: The name of the operation this span represents
        context: The immutable span context
        parent: This span's parent's `mysql.opentelemetry.trace.SpanContext`, or
            None if this is a root span
        sampler: The sampler used to create this span
        trace_config: TODO
        resource: Entity producing telemetry
        attributes: The span's attributes to be exported
        events: Timestamped events to be exported
        links: Links to other spans to be exported
        span_processor: `SpanProcessor` to invoke when starting and ending
            this `Span`.
        limits: `SpanLimits` instance that was passed to the `TracerProvider`
    """

    def __new__(cls, *args, **kwargs):
        if cls is Span:
            raise TypeError("Span must be instantiated via a tracer.")
        return super().__new__(cls)

    # pylint: disable=too-many-locals
    def __init__(
        self,
        name: str,
        context: trace_api.SpanContext,
        parent: Optional[trace_api.SpanContext] = None,
        sampler: Optional[sampling.Sampler] = None,
        trace_config: None = None,  # TODO
        resource: Resource = Resource.create({}),
        attributes: types.Attributes = None,
        events: Sequence[Event] = None,
        links: Sequence[trace_api.Link] = (),
        kind: trace_api.SpanKind = trace_api.SpanKind.INTERNAL,
        span_processor: SpanProcessor = SpanProcessor(),
        instrumentation_info: InstrumentationInfo = None,
        record_exception: bool = True,
        set_status_on_exception: bool = True,
        limits=_UnsetLimits,
        instrumentation_scope: InstrumentationScope = None,
    ) -> None:
        super().__init__(
            name=name,
            context=context,
            parent=parent,
            kind=kind,
            resource=resource,
            instrumentation_info=instrumentation_info,
            instrumentation_scope=instrumentation_scope,
        )
        self._sampler = sampler
        self._trace_config = trace_config
        self._record_exception = record_exception
        self._set_status_on_exception = set_status_on_exception
        self._span_processor = span_processor
        self._limits = limits
        self._lock = threading.Lock()
        self._attributes = BoundedAttributes(
            self._limits.max_span_attributes,
            attributes,
            immutable=False,
            max_value_len=self._limits.max_span_attribute_length,
        )
        self._events = self._new_events()
        if events:
            for event in events:
                event._attributes = BoundedAttributes(
                    self._limits.max_event_attributes,
                    event.attributes,
                    max_value_len=self._limits.max_attribute_length,
                )
                self._events.append(event)

        if links is None:
            self._links = self._new_links()
        else:
            for link in links:
                link._attributes = BoundedAttributes(
                    self._limits.max_link_attributes,
                    link.attributes,
                    max_value_len=self._limits.max_attribute_length,
                )
            self._links = BoundedList.from_seq(self._limits.max_links, links)

    def __repr__(self):
        return f'{type(self).__name__}(name="{self._name}", context={self._context})'

    def _new_events(self):
        return BoundedList(self._limits.max_events)

    def _new_links(self):
        return BoundedList(self._limits.max_links)

    def get_span_context(self):
        return self._context

    def set_attributes(self, attributes: Dict[str, types.AttributeValue]) -> None:
        with self._lock:
            if self._end_time is not None:
                logger.warning("Setting attribute on ended span.")
                return

            for key, value in attributes.items():
                self._attributes[key] = value

    def set_attribute(self, key: str, value: types.AttributeValue) -> None:
        return self.set_attributes({key: value})

    @_check_span_ended
    def _add_event(self, event: EventBase) -> None:
        self._events.append(event)

    def add_event(
        self,
        name: str,
        attributes: types.Attributes = None,
        timestamp: Optional[int] = None,
    ) -> None:
        attributes = BoundedAttributes(
            self._limits.max_event_attributes,
            attributes,
            max_value_len=self._limits.max_attribute_length,
        )
        self._add_event(
            Event(
                name=name,
                attributes=attributes,
                timestamp=timestamp,
            )
        )

    def _readable_span(self) -> ReadableSpan:
        return ReadableSpan(
            name=self._name,
            context=self._context,
            parent=self._parent,
            resource=self._resource,
            attributes=self._attributes,
            events=self._events,
            links=self._links,
            kind=self.kind,
            status=self._status,
            start_time=self._start_time,
            end_time=self._end_time,
            instrumentation_info=self._instrumentation_info,
            instrumentation_scope=self._instrumentation_scope,
        )

    def start(
        self,
        start_time: Optional[int] = None,
        parent_context: Optional[context_api.Context] = None,
    ) -> None:
        with self._lock:
            if self._start_time is not None:
                logger.warning("Calling start() on a started span.")
                return
            self._start_time = start_time if start_time is not None else time_ns()

        self._span_processor.on_start(self, parent_context=parent_context)

    def end(self, end_time: Optional[int] = None) -> None:
        with self._lock:
            if self._start_time is None:
                raise RuntimeError("Calling end() on a not started span.")
            if self._end_time is not None:
                logger.warning("Calling end() on an ended span.")
                return

            self._end_time = end_time if end_time is not None else time_ns()

        self._span_processor.on_end(self._readable_span())

    @_check_span_ended
    def update_name(self, name: str) -> None:
        self._name = name

    def is_recording(self) -> bool:
        return self._end_time is None

    @_check_span_ended
    def set_status(
        self,
        status: typing.Union[Status, StatusCode],
        description: typing.Optional[str] = None,
    ) -> None:
        # Ignore future calls if status is already set to OK
        # Ignore calls to set to StatusCode.UNSET
        if isinstance(status, Status):
            if (
                self._status
                and self._status.status_code is StatusCode.OK
                or status.status_code is StatusCode.UNSET
            ):
                return
            if description is not None:
                logger.warning(
                    "Description %s ignored. Use either `Status` or `(StatusCode, Description)`",
                    description,
                )
            self._status = status
        elif isinstance(status, StatusCode):
            if (
                self._status
                and self._status.status_code is StatusCode.OK
                or status is StatusCode.UNSET
            ):
                return
            self._status = Status(status, description)

    def __exit__(
        self,
        exc_type: Optional[Type[BaseException]],
        exc_val: Optional[BaseException],
        exc_tb: Optional[TracebackType],
    ) -> None:
        """Ends context manager and calls `end` on the `Span`."""
        if exc_val is not None and self.is_recording():
            # Record the exception as an event
            # pylint:disable=protected-access
            if self._record_exception:
                self.record_exception(exception=exc_val, escaped=True)
            # Records status if span is used as context manager
            # i.e. with tracer.start_span() as span:
            if self._set_status_on_exception:
                self.set_status(
                    Status(
                        status_code=StatusCode.ERROR,
                        description=f"{exc_type.__name__}: {exc_val}",
                    )
                )

        super().__exit__(exc_type, exc_val, exc_tb)

    def record_exception(
        self,
        exception: Exception,
        attributes: types.Attributes = None,
        timestamp: Optional[int] = None,
        escaped: bool = False,
    ) -> None:
        """Records an exception as a span event."""
        try:
            stacktrace = traceback.format_exc()
        except Exception:  # pylint: disable=broad-except
            # workaround for python 3.4, format_exc can raise
            # an AttributeError if the __context__ on
            # an exception is None
            stacktrace = "Exception occurred on stacktrace formatting"
        _attributes = {
            "exception.type": exception.__class__.__name__,
            "exception.message": str(exception),
            "exception.stacktrace": stacktrace,
            "exception.escaped": str(escaped),
        }
        if attributes:
            _attributes.update(attributes)
        self.add_event(name="exception", attributes=_attributes, timestamp=timestamp)


class _Span(Span):
    """Protected implementation of `mysql.opentelemetry.trace.Span`.

    This constructor exists to prevent the instantiation of the `Span` class
    by other mechanisms than through the `Tracer`.
    """


class Tracer(trace_api.Tracer):
    """See `mysql.opentelemetry.trace.Tracer`."""

    def __init__(
        self,
        sampler: sampling.Sampler,
        resource: Resource,
        span_processor: Union[
            SynchronousMultiSpanProcessor, ConcurrentMultiSpanProcessor
        ],
        id_generator: IdGenerator,
        instrumentation_info: InstrumentationInfo,
        span_limits: SpanLimits,
        instrumentation_scope: InstrumentationScope,
    ) -> None:
        self.sampler = sampler
        self.resource = resource
        self.span_processor = span_processor
        self.id_generator = id_generator
        self.instrumentation_info = instrumentation_info
        self._span_limits = span_limits
        self._instrumentation_scope = instrumentation_scope

    @contextmanager
    def start_as_current_span(
        self,
        name: str,
        context: Optional[context_api.Context] = None,
        kind: trace_api.SpanKind = trace_api.SpanKind.INTERNAL,
        attributes: types.Attributes = None,
        links: Sequence[trace_api.Link] = (),
        start_time: Optional[int] = None,
        record_exception: bool = True,
        set_status_on_exception: bool = True,
        end_on_exit: bool = True,
    ) -> Iterator[trace_api.Span]:
        span = self.start_span(
            name=name,
            context=context,
            kind=kind,
            attributes=attributes,
            links=links,
            start_time=start_time,
            record_exception=record_exception,
            set_status_on_exception=set_status_on_exception,
        )
        with trace_api.use_span(
            span,
            end_on_exit=end_on_exit,
            record_exception=record_exception,
            set_status_on_exception=set_status_on_exception,
        ) as span_context:
            yield span_context

    def start_span(  # pylint: disable=too-many-locals
        self,
        name: str,
        context: Optional[context_api.Context] = None,
        kind: trace_api.SpanKind = trace_api.SpanKind.INTERNAL,
        attributes: types.Attributes = None,
        links: Sequence[trace_api.Link] = (),
        start_time: Optional[int] = None,
        record_exception: bool = True,
        set_status_on_exception: bool = True,
    ) -> trace_api.Span:
        parent_span_context = trace_api.get_current_span(context).get_span_context()

        if parent_span_context is not None and not isinstance(
            parent_span_context, trace_api.SpanContext
        ):
            raise TypeError("parent_span_context must be a SpanContext or None.")

        # is_valid determines root span
        if parent_span_context is None or not parent_span_context.is_valid:
            parent_span_context = None
            trace_id = self.id_generator.generate_trace_id()
        else:
            trace_id = parent_span_context.trace_id

        # The sampler decides whether to create a real or no-op span at the
        # time of span creation. No-op spans do not record events, and are not
        # exported.
        # The sampler may also add attributes to the newly-created span, e.g.
        # to include information about the sampling result.
        # The sampler may also modify the parent span context's tracestate
        sampling_result = self.sampler.should_sample(
            context, trace_id, name, kind, attributes, links
        )

        trace_flags = (
            trace_api.TraceFlags(trace_api.TraceFlags.SAMPLED)
            if sampling_result.decision.is_sampled()
            else trace_api.TraceFlags(trace_api.TraceFlags.DEFAULT)
        )
        span_context = trace_api.SpanContext(
            trace_id,
            self.id_generator.generate_span_id(),
            is_remote=False,
            trace_flags=trace_flags,
            trace_state=sampling_result.trace_state,
        )

        # Only record if is_recording() is true
        if sampling_result.decision.is_recording():
            # pylint:disable=protected-access
            span = _Span(
                name=name,
                context=span_context,
                parent=parent_span_context,
                sampler=self.sampler,
                resource=self.resource,
                attributes=sampling_result.attributes.copy(),
                span_processor=self.span_processor,
                kind=kind,
                links=links,
                instrumentation_info=self.instrumentation_info,
                record_exception=record_exception,
                set_status_on_exception=set_status_on_exception,
                limits=self._span_limits,
                instrumentation_scope=self._instrumentation_scope,
            )
            span.start(start_time=start_time, parent_context=context)
        else:
            span = trace_api.NonRecordingSpan(context=span_context)
        return span


class TracerProvider(trace_api.TracerProvider):
    """See `mysql.opentelemetry.trace.TracerProvider`."""

    def __init__(
        self,
        sampler: sampling.Sampler = None,
        resource: Resource = Resource.create({}),
        shutdown_on_exit: bool = True,
        active_span_processor: Union[
            SynchronousMultiSpanProcessor, ConcurrentMultiSpanProcessor
        ] = None,
        id_generator: IdGenerator = None,
        span_limits: SpanLimits = None,
    ):
        self._active_span_processor = (
            active_span_processor or SynchronousMultiSpanProcessor()
        )
        if id_generator is None:
            self.id_generator = RandomIdGenerator()
        else:
            self.id_generator = id_generator
        self._resource = resource
        if not sampler:
            sampler = sampling._get_from_env_or_default()
        self.sampler = sampler
        self._span_limits = span_limits or SpanLimits()
        self._atexit_handler = None

        if shutdown_on_exit:
            self._atexit_handler = atexit.register(self.shutdown)

    @property
    def resource(self) -> Resource:
        return self._resource

    def get_tracer(
        self,
        instrumenting_module_name: str,
        instrumenting_library_version: typing.Optional[str] = None,
        schema_url: typing.Optional[str] = None,
    ) -> "trace_api.Tracer":
        if not instrumenting_module_name:  # Reject empty strings too.
            instrumenting_module_name = ""
            logger.error("get_tracer called with missing module name.")
        if instrumenting_library_version is None:
            instrumenting_library_version = ""

        filterwarnings(
            "ignore",
            message=(
                r"Call to deprecated method __init__. \(You should use "
                r"InstrumentationScope\) -- Deprecated since version 1.11.1."
            ),
            category=DeprecationWarning,
            module="mysql.opentelemetry.sdk.trace",
        )

        instrumentation_info = InstrumentationInfo(
            instrumenting_module_name,
            instrumenting_library_version,
            schema_url,
        )

        return Tracer(
            self.sampler,
            self.resource,
            self._active_span_processor,
            self.id_generator,
            instrumentation_info,
            self._span_limits,
            InstrumentationScope(
                instrumenting_module_name,
                instrumenting_library_version,
                schema_url,
            ),
        )

    def add_span_processor(self, span_processor: SpanProcessor) -> None:
        """Registers a new :class:`SpanProcessor` for this `TracerProvider`.

        The span processors are invoked in the same order they are registered.
        """

        # no lock here because add_span_processor is thread safe for both
        # SynchronousMultiSpanProcessor and ConcurrentMultiSpanProcessor.
        self._active_span_processor.add_span_processor(span_processor)

    def shutdown(self):
        """Shut down the span processors added to the tracer provider."""
        self._active_span_processor.shutdown()
        if self._atexit_handler is not None:
            atexit.unregister(self._atexit_handler)
            self._atexit_handler = None

    def force_flush(self, timeout_millis: int = 30000) -> bool:
        """Requests the active span processor to process all spans that have not
        yet been processed.

        By default force flush is called sequentially on all added span
        processors. This means that span processors further back in the list
        have less time to flush their spans.
        To have span processors flush their spans in parallel it is possible to
        initialize the tracer provider with an instance of
        `ConcurrentMultiSpanProcessor` at the cost of using multiple threads.

        Args:
            timeout_millis: The maximum amount of time to wait for spans to be
                processed.

        Returns:
            False if the timeout is exceeded, True otherwise.
        """
        return self._active_span_processor.force_flush(timeout_millis)

virt/lib/python3.9/site-packages/mysql/opentelemetry/sdk/trace/__init__ 2.py
PrinceDisant/LostInNL
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# pylint: disable=too-many-lines
import abc
import atexit
import concurrent.futures
import json
import logging
import threading
import traceback
import typing

from collections import OrderedDict
from contextlib import contextmanager
from os import environ
from time import time_ns
from types import MappingProxyType, TracebackType
from typing import Any, Callable, Dict, Iterator, Optional, Sequence, Tuple, Type, Union
from warnings import filterwarnings

from deprecated import deprecated
from mysql.opentelemetry import context as context_api, trace as trace_api
from mysql.opentelemetry.attributes import BoundedAttributes
from mysql.opentelemetry.sdk import util
from mysql.opentelemetry.sdk.environment_variables import (
    OTEL_ATTRIBUTE_COUNT_LIMIT,
    OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT,
    OTEL_EVENT_ATTRIBUTE_COUNT_LIMIT,
    OTEL_LINK_ATTRIBUTE_COUNT_LIMIT,
    OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT,
    OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT,
    OTEL_SPAN_EVENT_COUNT_LIMIT,
    OTEL_SPAN_LINK_COUNT_LIMIT,
)
from mysql.opentelemetry.sdk.resources import Resource
from mysql.opentelemetry.sdk.trace import sampling
from mysql.opentelemetry.sdk.trace.id_generator import IdGenerator, RandomIdGenerator
from mysql.opentelemetry.sdk.util import BoundedList
from mysql.opentelemetry.sdk.util.instrumentation import (
    InstrumentationInfo,
    InstrumentationScope,
)
from mysql.opentelemetry.trace import SpanContext
from mysql.opentelemetry.trace.status import Status, StatusCode
from mysql.opentelemetry.util import types

logger = logging.getLogger(__name__)

_DEFAULT_OTEL_ATTRIBUTE_COUNT_LIMIT = 128
_DEFAULT_OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT = 128
_DEFAULT_OTEL_EVENT_ATTRIBUTE_COUNT_LIMIT = 128
_DEFAULT_OTEL_LINK_ATTRIBUTE_COUNT_LIMIT = 128
_DEFAULT_OTEL_SPAN_EVENT_COUNT_LIMIT = 128
_DEFAULT_OTEL_SPAN_LINK_COUNT_LIMIT = 128


_ENV_VALUE_UNSET = ""


class SpanProcessor:
    """Interface which allows hooks for SDK's `Span` start and end method
    invocations.

    Span processors can be registered directly using
    :func:`TracerProvider.add_span_processor` and they are invoked
    in the same order as they were registered.
    """

    def on_start(
        self,
        span: "Span",
        parent_context: Optional[context_api.Context] = None,
    ) -> None:
        """Called when a :class:`mysql.opentelemetry.trace.Span` is started.

        This method is called synchronously on the thread that starts the
        span, therefore it should not block or throw an exception.

        Args:
            span: The :class:`mysql.opentelemetry.trace.Span` that just started.
            parent_context: The parent context of the span that just started.
        """

    def on_end(self, span: "ReadableSpan") -> None:
        """Called when a :class:`mysql.opentelemetry.trace.Span` is ended.

        This method is called synchronously on the thread that ends the
        span, therefore it should not block or throw an exception.

        Args:
            span: The :class:`mysql.opentelemetry.trace.Span` that just ended.
        """

    def shutdown(self) -> None:
        """Called when a :class:`mysql.opentelemetry.sdk.trace.TracerProvider` is shutdown."""

    def force_flush(self, timeout_millis: int = 30000) -> bool:
        """Export all ended spans to the configured Exporter that have not yet
        been exported.

        Args:
            timeout_millis: The maximum amount of time to wait for spans to be
                exported.

        Returns:
            False if the timeout is exceeded, True otherwise.
        """


# Temporary fix until https://github.com/PyCQA/pylint/issues/4098 is resolved
# pylint:disable=no-member
class SynchronousMultiSpanProcessor(SpanProcessor):
    """Implementation of class:`SpanProcessor` that forwards all received
    events to a list of span processors sequentially.

    The underlying span processors are called in sequential order as they were
    added.
    """

    def __init__(self):
        # use a tuple to avoid race conditions when adding a new span and
        # iterating through it on "on_start" and "on_end".
        self._span_processors = ()  # type: Tuple[SpanProcessor, ...]
        self._lock = threading.Lock()

    def add_span_processor(self, span_processor: SpanProcessor) -> None:
        """Adds a SpanProcessor to the list handled by this instance."""
        with self._lock:
            self._span_processors += (span_processor,)

    def on_start(
        self,
        span: "Span",
        parent_context: Optional[context_api.Context] = None,
    ) -> None:
        for sp in self._span_processors:
            sp.on_start(span, parent_context=parent_context)

    def on_end(self, span: "ReadableSpan") -> None:
        for sp in self._span_processors:
            sp.on_end(span)

    def shutdown(self) -> None:
        """Sequentially shuts down all underlying span processors."""
        for sp in self._span_processors:
            sp.shutdown()

    def force_flush(self, timeout_millis: int = 30000) -> bool:
        """Sequentially calls force_flush on all underlying
        :class:`SpanProcessor`

        Args:
            timeout_millis: The maximum amount of time over all span processors
                to wait for spans to be exported. In case the first n span
                processors exceeded the timeout followup span processors will be
                skipped.

        Returns:
            True if all span processors flushed their spans within the
            given timeout, False otherwise.
        """
        deadline_ns = time_ns() + timeout_millis * 1000000
        for sp in self._span_processors:
            current_time_ns = time_ns()
            if current_time_ns >= deadline_ns:
                return False

            if not sp.force_flush((deadline_ns - current_time_ns) // 1000000):
                return False

        return True


class ConcurrentMultiSpanProcessor(SpanProcessor):
    """Implementation of :class:`SpanProcessor` that forwards all received
    events to a list of span processors in parallel.

    Calls to the underlying span processors are forwarded in parallel by
    submitting them to a thread pool executor and waiting until each span
    processor finished its work.

    Args:
        num_threads: The number of threads managed by the thread pool executor
            and thus defining how many span processors can work in parallel.
    """

    def __init__(self, num_threads: int = 2):
        # use a tuple to avoid race conditions when adding a new span and
        # iterating through it on "on_start" and "on_end".
        self._span_processors = ()  # type: Tuple[SpanProcessor, ...]
        self._lock = threading.Lock()
        self._executor = concurrent.futures.ThreadPoolExecutor(max_workers=num_threads)

    def add_span_processor(self, span_processor: SpanProcessor) -> None:
        """Adds a SpanProcessor to the list handled by this instance."""
        with self._lock:
            self._span_processors += (span_processor,)

    def _submit_and_await(
        self,
        func: Callable[[SpanProcessor], Callable[..., None]],
        *args: Any,
        **kwargs: Any,
    ):
        futures = []
        for sp in self._span_processors:
            future = self._executor.submit(func(sp), *args, **kwargs)
            futures.append(future)
        for future in futures:
            future.result()

    def on_start(
        self,
        span: "Span",
        parent_context: Optional[context_api.Context] = None,
    ) -> None:
        self._submit_and_await(
            lambda sp: sp.on_start, span, parent_context=parent_context
        )

    def on_end(self, span: "ReadableSpan") -> None:
        self._submit_and_await(lambda sp: sp.on_end, span)

    def shutdown(self) -> None:
        """Shuts down all underlying span processors in parallel."""
        self._submit_and_await(lambda sp: sp.shutdown)

    def force_flush(self, timeout_millis: int = 30000) -> bool:
        """Calls force_flush on all underlying span processors in parallel.

        Args:
            timeout_millis: The maximum amount of time to wait for spans to be
                exported.

        Returns:
            True if all span processors flushed their spans within the given
            timeout, False otherwise.
        """
        futures = []
        for sp in self._span_processors:  # type: SpanProcessor
            future = self._executor.submit(sp.force_flush, timeout_millis)
            futures.append(future)

        timeout_sec = timeout_millis / 1e3
        done_futures, not_done_futures = concurrent.futures.wait(futures, timeout_sec)
        if not_done_futures:
            return False

        for future in done_futures:
            if not future.result():
                return False

        return True


class EventBase(abc.ABC):
    def __init__(self, name: str, timestamp: Optional[int] = None) -> None:
        self._name = name
        if timestamp is None:
            self._timestamp = time_ns()
        else:
            self._timestamp = timestamp

    @property
    def name(self) -> str:
        return self._name

    @property
    def timestamp(self) -> int:
        return self._timestamp

    @property
    @abc.abstractmethod
    def attributes(self) -> types.Attributes:
        pass


class Event(EventBase):
    """A text annotation with a set of attributes. The attributes of an event
    are immutable.

    Args:
        name: Name of the event.
        attributes: Attributes of the event.
        timestamp: Timestamp of the event. If `None` it will filled
            automatically.
    """

    def __init__(
        self,
        name: str,
        attributes: types.Attributes = None,
        timestamp: Optional[int] = None,
        limit: Optional[int] = _DEFAULT_OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT,
    ) -> None:
        super().__init__(name, timestamp)
        self._attributes = attributes

    @property
    def attributes(self) -> types.Attributes:
        return self._attributes


def _check_span_ended(func):
    def wrapper(self, *args, **kwargs):
        already_ended = False
        with self._lock:  # pylint: disable=protected-access
            if self._end_time is None:  # pylint: disable=protected-access
                func(self, *args, **kwargs)
            else:
                already_ended = True

        if already_ended:
            logger.warning("Tried calling %s on an ended span.", func.__name__)

    return wrapper


class ReadableSpan:
    """Provides read-only access to span attributes"""

    def __init__(
        self,
        name: str = None,
        context: trace_api.SpanContext = None,
        parent: Optional[trace_api.SpanContext] = None,
        resource: Resource = Resource.create({}),
        attributes: types.Attributes = None,
        events: Sequence[Event] = (),
        links: Sequence[trace_api.Link] = (),
        kind: trace_api.SpanKind = trace_api.SpanKind.INTERNAL,
        instrumentation_info: InstrumentationInfo = None,
        status: Status = Status(StatusCode.UNSET),
        start_time: Optional[int] = None,
        end_time: Optional[int] = None,
        instrumentation_scope: InstrumentationScope = None,
    ) -> None:
        self._name = name
        self._context = context
        self._kind = kind
        self._instrumentation_info = instrumentation_info
        self._instrumentation_scope = instrumentation_scope
        self._parent = parent
        self._start_time = start_time
        self._end_time = end_time
        self._attributes = attributes
        self._events = events
        self._links = links
        self._resource = resource
        self._status = status

    @property
    def dropped_attributes(self) -> int:
        if self._attributes:
            return self._attributes.dropped
        return 0

    @property
    def dropped_events(self) -> int:
        if self._events:
            return self._events.dropped
        return 0

    @property
    def dropped_links(self) -> int:
        if self._links:
            return self._links.dropped
        return 0

    @property
    def name(self) -> str:
        return self._name

    def get_span_context(self):
        return self._context

    @property
    def context(self):
        return self._context

    @property
    def kind(self) -> trace_api.SpanKind:
        return self._kind

    @property
    def parent(self) -> Optional[trace_api.SpanContext]:
        return self._parent

    @property
    def start_time(self) -> Optional[int]:
        return self._start_time

    @property
    def end_time(self) -> Optional[int]:
        return self._end_time

    @property
    def status(self) -> trace_api.Status:
        return self._status

    @property
    def attributes(self) -> types.Attributes:
        return MappingProxyType(self._attributes)

    @property
    def events(self) -> Sequence[Event]:
        return tuple(event for event in self._events)

    @property
    def links(self) -> Sequence[trace_api.Link]:
        return tuple(link for link in self._links)

    @property
    def resource(self) -> Resource:
        return self._resource

    @property
    @deprecated(version="1.11.1", reason="You should use instrumentation_scope")
    def instrumentation_info(self) -> InstrumentationInfo:
        return self._instrumentation_info

    @property
    def instrumentation_scope(self) -> InstrumentationScope:
        return self._instrumentation_scope

    def to_json(self, indent=4):
        parent_id = None
        if self.parent is not None:
            if isinstance(self.parent, Span):
                ctx = self.parent.context
                parent_id = f"0x{trace_api.format_span_id(ctx.span_id)}"
            elif isinstance(self.parent, SpanContext):
                parent_id = f"0x{trace_api.format_span_id(self.parent.span_id)}"

        start_time = None
        if self._start_time:
            start_time = util.ns_to_iso_str(self._start_time)

        end_time = None
        if self._end_time:
            end_time = util.ns_to_iso_str(self._end_time)

        if self._status is not None:
            status = OrderedDict()
            status["status_code"] = str(self._status.status_code.name)
            if self._status.description:
                status["description"] = self._status.description

        f_span = OrderedDict()

        f_span["name"] = self._name
        f_span["context"] = self._format_context(self._context)
        f_span["kind"] = str(self.kind)
        f_span["parent_id"] = parent_id
        f_span["start_time"] = start_time
        f_span["end_time"] = end_time
        if self._status is not None:
            f_span["status"] = status
        f_span["attributes"] = self._format_attributes(self._attributes)
        f_span["events"] = self._format_events(self._events)
        f_span["links"] = self._format_links(self._links)
        f_span["resource"] = json.loads(self.resource.to_json())

        return json.dumps(f_span, indent=indent)

    @staticmethod
    def _format_context(context):
        x_ctx = OrderedDict()
        x_ctx["trace_id"] = f"0x{trace_api.format_trace_id(context.trace_id)}"
        x_ctx["span_id"] = f"0x{trace_api.format_span_id(context.span_id)}"
        x_ctx["trace_state"] = repr(context.trace_state)
        return x_ctx

    @staticmethod
    def _format_attributes(attributes):
        if isinstance(attributes, BoundedAttributes):
            return attributes._dict  # pylint: disable=protected-access
        if isinstance(attributes, MappingProxyType):
            return attributes.copy()
        return attributes

    @staticmethod
    def _format_events(events):
        f_events = []
        for event in events:
            f_event = OrderedDict()
            f_event["name"] = event.name
            f_event["timestamp"] = util.ns_to_iso_str(event.timestamp)
            f_event[
                "attributes"
            ] = Span._format_attributes(  # pylint: disable=protected-access
                event.attributes
            )
            f_events.append(f_event)
        return f_events

    @staticmethod
    def _format_links(links):
        f_links = []
        for link in links:
            f_link = OrderedDict()
            f_link[
                "context"
            ] = Span._format_context(  # pylint: disable=protected-access
                link.context
            )
            f_link[
                "attributes"
            ] = Span._format_attributes(  # pylint: disable=protected-access
                link.attributes
            )
            f_links.append(f_link)
        return f_links


class SpanLimits:
    """The limits that should be enforce on recorded data such as events, links, attributes etc.

    This class does not enforce any limits itself. It only provides an a way read limits from env,
    default values and from user provided arguments.

    All limit arguments must be either a non-negative integer, ``None`` or ``SpanLimits.UNSET``.

    - All limit arguments are optional.
    - If a limit argument is not set, the class will try to read its value from the corresponding
      environment variable.
    - If the environment variable is not set, the default value, if any, will be used.

    Limit precedence:

    - If a model specific limit is set, it will be used.
    - Else if the corresponding global limit is set, it will be used.
    - Else if the model specific limit has a default value, the default value will be used.
    - Else if the global limit has a default value, the default value will be used.

    Args:
        max_attributes: Maximum number of attributes that can be added to a span, event, and link.
            Environment variable: OTEL_ATTRIBUTE_COUNT_LIMIT
            Default: {_DEFAULT_ATTRIBUTE_COUNT_LIMIT}
        max_events: Maximum number of events that can be added to a Span.
            Environment variable: OTEL_SPAN_EVENT_COUNT_LIMIT
            Default: {_DEFAULT_SPAN_EVENT_COUNT_LIMIT}
        max_links: Maximum number of links that can be added to a Span.
            Environment variable: OTEL_SPAN_LINK_COUNT_LIMIT
            Default: {_DEFAULT_SPAN_LINK_COUNT_LIMIT}
        max_span_attributes: Maximum number of attributes that can be added to a Span.
            Environment variable: OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT
            Default: {_DEFAULT_OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT}
        max_event_attributes: Maximum number of attributes that can be added to an Event.
            Default: {_DEFAULT_OTEL_EVENT_ATTRIBUTE_COUNT_LIMIT}
        max_link_attributes: Maximum number of attributes that can be added to a Link.
            Default: {_DEFAULT_OTEL_LINK_ATTRIBUTE_COUNT_LIMIT}
        max_attribute_length: Maximum length an attribute value can have. Values longer than
            the specified length will be truncated.
        max_span_attribute_length: Maximum length a span attribute value can have. Values longer than
            the specified length will be truncated.
    """

    UNSET = -1

    def __init__(
        self,
        max_attributes: Optional[int] = None,
        max_events: Optional[int] = None,
        max_links: Optional[int] = None,
        max_span_attributes: Optional[int] = None,
        max_event_attributes: Optional[int] = None,
        max_link_attributes: Optional[int] = None,
        max_attribute_length: Optional[int] = None,
        max_span_attribute_length: Optional[int] = None,
    ):
        # span events and links count
        self.max_events = self._from_env_if_absent(
            max_events,
            OTEL_SPAN_EVENT_COUNT_LIMIT,
            _DEFAULT_OTEL_SPAN_EVENT_COUNT_LIMIT,
        )
        self.max_links = self._from_env_if_absent(
            max_links,
            OTEL_SPAN_LINK_COUNT_LIMIT,
            _DEFAULT_OTEL_SPAN_LINK_COUNT_LIMIT,
        )

        # attribute count
        global_max_attributes = self._from_env_if_absent(
            max_attributes, OTEL_ATTRIBUTE_COUNT_LIMIT
        )
        self.max_attributes = (
            global_max_attributes
            if global_max_attributes is not None
            else _DEFAULT_OTEL_ATTRIBUTE_COUNT_LIMIT
        )

        self.max_span_attributes = self._from_env_if_absent(
            max_span_attributes,
            OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT,
            global_max_attributes
            if global_max_attributes is not None
            else _DEFAULT_OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT,
        )
        self.max_event_attributes = self._from_env_if_absent(
            max_event_attributes,
            OTEL_EVENT_ATTRIBUTE_COUNT_LIMIT,
            global_max_attributes
            if global_max_attributes is not None
            else _DEFAULT_OTEL_EVENT_ATTRIBUTE_COUNT_LIMIT,
        )
        self.max_link_attributes = self._from_env_if_absent(
            max_link_attributes,
            OTEL_LINK_ATTRIBUTE_COUNT_LIMIT,
            global_max_attributes
            if global_max_attributes is not None
            else _DEFAULT_OTEL_LINK_ATTRIBUTE_COUNT_LIMIT,
        )

        # attribute length
        self.max_attribute_length = self._from_env_if_absent(
            max_attribute_length,
            OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT,
        )
        self.max_span_attribute_length = self._from_env_if_absent(
            max_span_attribute_length,
            OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT,
            # use global attribute length limit as default
            self.max_attribute_length,
        )

    def __repr__(self):
        return f"{type(self).__name__}(max_span_attributes={self.max_span_attributes}, max_events_attributes={self.max_event_attributes}, max_link_attributes={self.max_link_attributes}, max_attributes={self.max_attributes}, max_events={self.max_events}, max_links={self.max_links}, max_attribute_length={self.max_attribute_length})"

    @classmethod
    def _from_env_if_absent(
        cls, value: Optional[int], env_var: str, default: Optional[int] = None
    ) -> Optional[int]:
        if value == cls.UNSET:
            return None

        err_msg = "{0} must be a non-negative integer but got {}"

        # if no value is provided for the limit, try to load it from env
        if value is None:
            # return default value if env var is not set
            if env_var not in environ:
                return default

            str_value = environ.get(env_var, "").strip().lower()
            if str_value == _ENV_VALUE_UNSET:
                return None

            try:
                value = int(str_value)
            except ValueError:
                raise ValueError(err_msg.format(env_var, str_value))

        if value < 0:
            raise ValueError(err_msg.format(env_var, value))
        return value


_UnsetLimits = SpanLimits(
    max_attributes=SpanLimits.UNSET,
    max_events=SpanLimits.UNSET,
    max_links=SpanLimits.UNSET,
    max_span_attributes=SpanLimits.UNSET,
    max_event_attributes=SpanLimits.UNSET,
    max_link_attributes=SpanLimits.UNSET,
    max_attribute_length=SpanLimits.UNSET,
    max_span_attribute_length=SpanLimits.UNSET,
)

# not removed for backward compat. please use SpanLimits instead.
SPAN_ATTRIBUTE_COUNT_LIMIT = (
    SpanLimits._from_env_if_absent(  # pylint: disable=protected-access
        None,
        OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT,
        _DEFAULT_OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT,
    )
)


class Span(trace_api.Span, ReadableSpan):
    """See `mysql.opentelemetry.trace.Span`.

    Users should create `Span` objects via the `Tracer` instead of this
    constructor.

    Args:
        name: The name of the operation this span represents
        context: The immutable span context
        parent: This span's parent's `mysql.opentelemetry.trace.SpanContext`, or
            None if this is a root span
        sampler: The sampler used to create this span
        trace_config: TODO
        resource: Entity producing telemetry
        attributes: The span's attributes to be exported
        events: Timestamped events to be exported
        links: Links to other spans to be exported
        span_processor: `SpanProcessor` to invoke when starting and ending
            this `Span`.
        limits: `SpanLimits` instance that was passed to the `TracerProvider`
    """

    def __new__(cls, *args, **kwargs):
        if cls is Span:
            raise TypeError("Span must be instantiated via a tracer.")
        return super().__new__(cls)

    # pylint: disable=too-many-locals
    def __init__(
        self,
        name: str,
        context: trace_api.SpanContext,
        parent: Optional[trace_api.SpanContext] = None,
        sampler: Optional[sampling.Sampler] = None,
        trace_config: None = None,  # TODO
        resource: Resource = Resource.create({}),
        attributes: types.Attributes = None,
        events: Sequence[Event] = None,
        links: Sequence[trace_api.Link] = (),
        kind: trace_api.SpanKind = trace_api.SpanKind.INTERNAL,
        span_processor: SpanProcessor = SpanProcessor(),
        instrumentation_info: InstrumentationInfo = None,
        record_exception: bool = True,
        set_status_on_exception: bool = True,
        limits=_UnsetLimits,
        instrumentation_scope: InstrumentationScope = None,
    ) -> None:
        super().__init__(
            name=name,
            context=context,
            parent=parent,
            kind=kind,
            resource=resource,
            instrumentation_info=instrumentation_info,
            instrumentation_scope=instrumentation_scope,
        )
        self._sampler = sampler
        self._trace_config = trace_config
        self._record_exception = record_exception
        self._set_status_on_exception = set_status_on_exception
        self._span_processor = span_processor
        self._limits = limits
        self._lock = threading.Lock()
        self._attributes = BoundedAttributes(
            self._limits.max_span_attributes,
            attributes,
            immutable=False,
            max_value_len=self._limits.max_span_attribute_length,
        )
        self._events = self._new_events()
        if events:
            for event in events:
                event._attributes = BoundedAttributes(
                    self._limits.max_event_attributes,
                    event.attributes,
                    max_value_len=self._limits.max_attribute_length,
                )
                self._events.append(event)

        if links is None:
            self._links = self._new_links()
        else:
            for link in links:
                link._attributes = BoundedAttributes(
                    self._limits.max_link_attributes,
                    link.attributes,
                    max_value_len=self._limits.max_attribute_length,
                )
            self._links = BoundedList.from_seq(self._limits.max_links, links)

    def __repr__(self):
        return f'{type(self).__name__}(name="{self._name}", context={self._context})'

    def _new_events(self):
        return BoundedList(self._limits.max_events)

    def _new_links(self):
        return BoundedList(self._limits.max_links)

    def get_span_context(self):
        return self._context

    def set_attributes(self, attributes: Dict[str, types.AttributeValue]) -> None:
        with self._lock:
            if self._end_time is not None:
                logger.warning("Setting attribute on ended span.")
                return

            for key, value in attributes.items():
                self._attributes[key] = value

    def set_attribute(self, key: str, value: types.AttributeValue) -> None:
        return self.set_attributes({key: value})

    @_check_span_ended
    def _add_event(self, event: EventBase) -> None:
        self._events.append(event)

    def add_event(
        self,
        name: str,
        attributes: types.Attributes = None,
        timestamp: Optional[int] = None,
    ) -> None:
        attributes = BoundedAttributes(
            self._limits.max_event_attributes,
            attributes,
            max_value_len=self._limits.max_attribute_length,
        )
        self._add_event(
            Event(
                name=name,
                attributes=attributes,
                timestamp=timestamp,
            )
        )

    def _readable_span(self) -> ReadableSpan:
        return ReadableSpan(
            name=self._name,
            context=self._context,
            parent=self._parent,
            resource=self._resource,
            attributes=self._attributes,
            events=self._events,
            links=self._links,
            kind=self.kind,
            status=self._status,
            start_time=self._start_time,
            end_time=self._end_time,
            instrumentation_info=self._instrumentation_info,
            instrumentation_scope=self._instrumentation_scope,
        )

    def start(
        self,
        start_time: Optional[int] = None,
        parent_context: Optional[context_api.Context] = None,
    ) -> None:
        with self._lock:
            if self._start_time is not None:
                logger.warning("Calling start() on a started span.")
                return
            self._start_time = start_time if start_time is not None else time_ns()

        self._span_processor.on_start(self, parent_context=parent_context)

    def end(self, end_time: Optional[int] = None) -> None:
        with self._lock:
            if self._start_time is None:
                raise RuntimeError("Calling end() on a not started span.")
            if self._end_time is not None:
                logger.warning("Calling end() on an ended span.")
                return

            self._end_time = end_time if end_time is not None else time_ns()

        self._span_processor.on_end(self._readable_span())

    @_check_span_ended
    def update_name(self, name: str) -> None:
        self._name = name

    def is_recording(self) -> bool:
        return self._end_time is None

    @_check_span_ended
    def set_status(
        self,
        status: typing.Union[Status, StatusCode],
        description: typing.Optional[str] = None,
    ) -> None:
        # Ignore future calls if status is already set to OK
        # Ignore calls to set to StatusCode.UNSET
        if isinstance(status, Status):
            if (
                self._status
                and self._status.status_code is StatusCode.OK
                or status.status_code is StatusCode.UNSET
            ):
                return
            if description is not None:
                logger.warning(
                    "Description %s ignored. Use either `Status` or `(StatusCode, Description)`",
                    description,
                )
            self._status = status
        elif isinstance(status, StatusCode):
            if (
                self._status
                and self._status.status_code is StatusCode.OK
                or status is StatusCode.UNSET
            ):
                return
            self._status = Status(status, description)

    def __exit__(
        self,
        exc_type: Optional[Type[BaseException]],
        exc_val: Optional[BaseException],
        exc_tb: Optional[TracebackType],
    ) -> None:
        """Ends context manager and calls `end` on the `Span`."""
        if exc_val is not None and self.is_recording():
            # Record the exception as an event
            # pylint:disable=protected-access
            if self._record_exception:
                self.record_exception(exception=exc_val, escaped=True)
            # Records status if span is used as context manager
            # i.e. with tracer.start_span() as span:
            if self._set_status_on_exception:
                self.set_status(
                    Status(
                        status_code=StatusCode.ERROR,
                        description=f"{exc_type.__name__}: {exc_val}",
                    )
                )

        super().__exit__(exc_type, exc_val, exc_tb)

    def record_exception(
        self,
        exception: Exception,
        attributes: types.Attributes = None,
        timestamp: Optional[int] = None,
        escaped: bool = False,
    ) -> None:
        """Records an exception as a span event."""
        try:
            stacktrace = traceback.format_exc()
        except Exception:  # pylint: disable=broad-except
            # workaround for python 3.4, format_exc can raise
            # an AttributeError if the __context__ on
            # an exception is None
            stacktrace = "Exception occurred on stacktrace formatting"
        _attributes = {
            "exception.type": exception.__class__.__name__,
            "exception.message": str(exception),
            "exception.stacktrace": stacktrace,
            "exception.escaped": str(escaped),
        }
        if attributes:
            _attributes.update(attributes)
        self.add_event(name="exception", attributes=_attributes, timestamp=timestamp)


class _Span(Span):
    """Protected implementation of `mysql.opentelemetry.trace.Span`.

    This constructor exists to prevent the instantiation of the `Span` class
    by other mechanisms than through the `Tracer`.
    """


class Tracer(trace_api.Tracer):
    """See `mysql.opentelemetry.trace.Tracer`."""

    def __init__(
        self,
        sampler: sampling.Sampler,
        resource: Resource,
        span_processor: Union[
            SynchronousMultiSpanProcessor, ConcurrentMultiSpanProcessor
        ],
        id_generator: IdGenerator,
        instrumentation_info: InstrumentationInfo,
        span_limits: SpanLimits,
        instrumentation_scope: InstrumentationScope,
    ) -> None:
        self.sampler = sampler
        self.resource = resource
        self.span_processor = span_processor
        self.id_generator = id_generator
        self.instrumentation_info = instrumentation_info
        self._span_limits = span_limits
        self._instrumentation_scope = instrumentation_scope

    @contextmanager
    def start_as_current_span(
        self,
        name: str,
        context: Optional[context_api.Context] = None,
        kind: trace_api.SpanKind = trace_api.SpanKind.INTERNAL,
        attributes: types.Attributes = None,
        links: Sequence[trace_api.Link] = (),
        start_time: Optional[int] = None,
        record_exception: bool = True,
        set_status_on_exception: bool = True,
        end_on_exit: bool = True,
    ) -> Iterator[trace_api.Span]:
        span = self.start_span(
            name=name,
            context=context,
            kind=kind,
            attributes=attributes,
            links=links,
            start_time=start_time,
            record_exception=record_exception,
            set_status_on_exception=set_status_on_exception,
        )
        with trace_api.use_span(
            span,
            end_on_exit=end_on_exit,
            record_exception=record_exception,
            set_status_on_exception=set_status_on_exception,
        ) as span_context:
            yield span_context

    def start_span(  # pylint: disable=too-many-locals
        self,
        name: str,
        context: Optional[context_api.Context] = None,
        kind: trace_api.SpanKind = trace_api.SpanKind.INTERNAL,
        attributes: types.Attributes = None,
        links: Sequence[trace_api.Link] = (),
        start_time: Optional[int] = None,
        record_exception: bool = True,
        set_status_on_exception: bool = True,
    ) -> trace_api.Span:
        parent_span_context = trace_api.get_current_span(context).get_span_context()

        if parent_span_context is not None and not isinstance(
            parent_span_context, trace_api.SpanContext
        ):
            raise TypeError("parent_span_context must be a SpanContext or None.")

        # is_valid determines root span
        if parent_span_context is None or not parent_span_context.is_valid:
            parent_span_context = None
            trace_id = self.id_generator.generate_trace_id()
        else:
            trace_id = parent_span_context.trace_id

        # The sampler decides whether to create a real or no-op span at the
        # time of span creation. No-op spans do not record events, and are not
        # exported.
        # The sampler may also add attributes to the newly-created span, e.g.
        # to include information about the sampling result.
        # The sampler may also modify the parent span context's tracestate
        sampling_result = self.sampler.should_sample(
            context, trace_id, name, kind, attributes, links
        )

        trace_flags = (
            trace_api.TraceFlags(trace_api.TraceFlags.SAMPLED)
            if sampling_result.decision.is_sampled()
            else trace_api.TraceFlags(trace_api.TraceFlags.DEFAULT)
        )
        span_context = trace_api.SpanContext(
            trace_id,
            self.id_generator.generate_span_id(),
            is_remote=False,
            trace_flags=trace_flags,
            trace_state=sampling_result.trace_state,
        )

        # Only record if is_recording() is true
        if sampling_result.decision.is_recording():
            # pylint:disable=protected-access
            span = _Span(
                name=name,
                context=span_context,
                parent=parent_span_context,
                sampler=self.sampler,
                resource=self.resource,
                attributes=sampling_result.attributes.copy(),
                span_processor=self.span_processor,
                kind=kind,
                links=links,
                instrumentation_info=self.instrumentation_info,
                record_exception=record_exception,
                set_status_on_exception=set_status_on_exception,
                limits=self._span_limits,
                instrumentation_scope=self._instrumentation_scope,
            )
            span.start(start_time=start_time, parent_context=context)
        else:
            span = trace_api.NonRecordingSpan(context=span_context)
        return span


class TracerProvider(trace_api.TracerProvider):
    """See `mysql.opentelemetry.trace.TracerProvider`."""

    def __init__(
        self,
        sampler: sampling.Sampler = None,
        resource: Resource = Resource.create({}),
        shutdown_on_exit: bool = True,
        active_span_processor: Union[
            SynchronousMultiSpanProcessor, ConcurrentMultiSpanProcessor
        ] = None,
        id_generator: IdGenerator = None,
        span_limits: SpanLimits = None,
    ):
        self._active_span_processor = (
            active_span_processor or SynchronousMultiSpanProcessor()
        )
        if id_generator is None:
            self.id_generator = RandomIdGenerator()
        else:
            self.id_generator = id_generator
        self._resource = resource
        if not sampler:
            sampler = sampling._get_from_env_or_default()
        self.sampler = sampler
        self._span_limits = span_limits or SpanLimits()
        self._atexit_handler = None

        if shutdown_on_exit:
            self._atexit_handler = atexit.register(self.shutdown)

    @property
    def resource(self) -> Resource:
        return self._resource

    def get_tracer(
        self,
        instrumenting_module_name: str,
        instrumenting_library_version: typing.Optional[str] = None,
        schema_url: typing.Optional[str] = None,
    ) -> "trace_api.Tracer":
        if not instrumenting_module_name:  # Reject empty strings too.
            instrumenting_module_name = ""
            logger.error("get_tracer called with missing module name.")
        if instrumenting_library_version is None:
            instrumenting_library_version = ""

        filterwarnings(
            "ignore",
            message=(
                r"Call to deprecated method __init__. \(You should use "
                r"InstrumentationScope\) -- Deprecated since version 1.11.1."
            ),
            category=DeprecationWarning,
            module="mysql.opentelemetry.sdk.trace",
        )

        instrumentation_info = InstrumentationInfo(
            instrumenting_module_name,
            instrumenting_library_version,
            schema_url,
        )

        return Tracer(
            self.sampler,
            self.resource,
            self._active_span_processor,
            self.id_generator,
            instrumentation_info,
            self._span_limits,
            InstrumentationScope(
                instrumenting_module_name,
                instrumenting_library_version,
                schema_url,
            ),
        )

    def add_span_processor(self, span_processor: SpanProcessor) -> None:
        """Registers a new :class:`SpanProcessor` for this `TracerProvider`.

        The span processors are invoked in the same order they are registered.
        """

        # no lock here because add_span_processor is thread safe for both
        # SynchronousMultiSpanProcessor and ConcurrentMultiSpanProcessor.
        self._active_span_processor.add_span_processor(span_processor)

    def shutdown(self):
        """Shut down the span processors added to the tracer provider."""
        self._active_span_processor.shutdown()
        if self._atexit_handler is not None:
            atexit.unregister(self._atexit_handler)
            self._atexit_handler = None

    def force_flush(self, timeout_millis: int = 30000) -> bool:
        """Requests the active span processor to process all spans that have not
        yet been processed.

        By default force flush is called sequentially on all added span
        processors. This means that span processors further back in the list
        have less time to flush their spans.
        To have span processors flush their spans in parallel it is possible to
        initialize the tracer provider with an instance of
        `ConcurrentMultiSpanProcessor` at the cost of using multiple threads.

        Args:
            timeout_millis: The maximum amount of time to wait for spans to be
                processed.

        Returns:
            False if the timeout is exceeded, True otherwise.
        """
        return self._active_span_processor.force_flush(timeout_millis)

virt/lib/python3.9/site-packages/mysql/opentelemetry/sdk/trace/__init__ 3.py
PrinceDisant/LostInNL
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# pylint: disable=too-many-lines
import abc
import atexit
import concurrent.futures
import json
import logging
import threading
import traceback
import typing

from collections import OrderedDict
from contextlib import contextmanager
from os import environ
from time import time_ns
from types import MappingProxyType, TracebackType
from typing import Any, Callable, Dict, Iterator, Optional, Sequence, Tuple, Type, Union
from warnings import filterwarnings

from deprecated import deprecated
from mysql.opentelemetry import context as context_api, trace as trace_api
from mysql.opentelemetry.attributes import BoundedAttributes
from mysql.opentelemetry.sdk import util
from mysql.opentelemetry.sdk.environment_variables import (
    OTEL_ATTRIBUTE_COUNT_LIMIT,
    OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT,
    OTEL_EVENT_ATTRIBUTE_COUNT_LIMIT,
    OTEL_LINK_ATTRIBUTE_COUNT_LIMIT,
    OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT,
    OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT,
    OTEL_SPAN_EVENT_COUNT_LIMIT,
    OTEL_SPAN_LINK_COUNT_LIMIT,
)
from mysql.opentelemetry.sdk.resources import Resource
from mysql.opentelemetry.sdk.trace import sampling
from mysql.opentelemetry.sdk.trace.id_generator import IdGenerator, RandomIdGenerator
from mysql.opentelemetry.sdk.util import BoundedList
from mysql.opentelemetry.sdk.util.instrumentation import (
    InstrumentationInfo,
    InstrumentationScope,
)
from mysql.opentelemetry.trace import SpanContext
from mysql.opentelemetry.trace.status import Status, StatusCode
from mysql.opentelemetry.util import types

logger = logging.getLogger(__name__)

_DEFAULT_OTEL_ATTRIBUTE_COUNT_LIMIT = 128
_DEFAULT_OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT = 128
_DEFAULT_OTEL_EVENT_ATTRIBUTE_COUNT_LIMIT = 128
_DEFAULT_OTEL_LINK_ATTRIBUTE_COUNT_LIMIT = 128
_DEFAULT_OTEL_SPAN_EVENT_COUNT_LIMIT = 128
_DEFAULT_OTEL_SPAN_LINK_COUNT_LIMIT = 128


_ENV_VALUE_UNSET = ""


class SpanProcessor:
    """Interface which allows hooks for SDK's `Span` start and end method
    invocations.

    Span processors can be registered directly using
    :func:`TracerProvider.add_span_processor` and they are invoked
    in the same order as they were registered.
    """

    def on_start(
        self,
        span: "Span",
        parent_context: Optional[context_api.Context] = None,
    ) -> None:
        """Called when a :class:`mysql.opentelemetry.trace.Span` is started.

        This method is called synchronously on the thread that starts the
        span, therefore it should not block or throw an exception.

        Args:
            span: The :class:`mysql.opentelemetry.trace.Span` that just started.
            parent_context: The parent context of the span that just started.
        """

    def on_end(self, span: "ReadableSpan") -> None:
        """Called when a :class:`mysql.opentelemetry.trace.Span` is ended.

        This method is called synchronously on the thread that ends the
        span, therefore it should not block or throw an exception.

        Args:
            span: The :class:`mysql.opentelemetry.trace.Span` that just ended.
        """

    def shutdown(self) -> None:
        """Called when a :class:`mysql.opentelemetry.sdk.trace.TracerProvider` is shutdown."""

    def force_flush(self, timeout_millis: int = 30000) -> bool:
        """Export all ended spans to the configured Exporter that have not yet
        been exported.

        Args:
            timeout_millis: The maximum amount of time to wait for spans to be
                exported.

        Returns:
            False if the timeout is exceeded, True otherwise.
        """


# Temporary fix until https://github.com/PyCQA/pylint/issues/4098 is resolved
# pylint:disable=no-member
class SynchronousMultiSpanProcessor(SpanProcessor):
    """Implementation of class:`SpanProcessor` that forwards all received
    events to a list of span processors sequentially.

    The underlying span processors are called in sequential order as they were
    added.
    """

    def __init__(self):
        # use a tuple to avoid race conditions when adding a new span and
        # iterating through it on "on_start" and "on_end".
        self._span_processors = ()  # type: Tuple[SpanProcessor, ...]
        self._lock = threading.Lock()

    def add_span_processor(self, span_processor: SpanProcessor) -> None:
        """Adds a SpanProcessor to the list handled by this instance."""
        with self._lock:
            self._span_processors += (span_processor,)

    def on_start(
        self,
        span: "Span",
        parent_context: Optional[context_api.Context] = None,
    ) -> None:
        for sp in self._span_processors:
            sp.on_start(span, parent_context=parent_context)

    def on_end(self, span: "ReadableSpan") -> None:
        for sp in self._span_processors:
            sp.on_end(span)

    def shutdown(self) -> None:
        """Sequentially shuts down all underlying span processors."""
        for sp in self._span_processors:
            sp.shutdown()

    def force_flush(self, timeout_millis: int = 30000) -> bool:
        """Sequentially calls force_flush on all underlying
        :class:`SpanProcessor`

        Args:
            timeout_millis: The maximum amount of time over all span processors
                to wait for spans to be exported. In case the first n span
                processors exceeded the timeout followup span processors will be
                skipped.

        Returns:
            True if all span processors flushed their spans within the
            given timeout, False otherwise.
        """
        deadline_ns = time_ns() + timeout_millis * 1000000
        for sp in self._span_processors:
            current_time_ns = time_ns()
            if current_time_ns >= deadline_ns:
                return False

            if not sp.force_flush((deadline_ns - current_time_ns) // 1000000):
                return False

        return True


class ConcurrentMultiSpanProcessor(SpanProcessor):
    """Implementation of :class:`SpanProcessor` that forwards all received
    events to a list of span processors in parallel.

    Calls to the underlying span processors are forwarded in parallel by
    submitting them to a thread pool executor and waiting until each span
    processor finished its work.

    Args:
        num_threads: The number of threads managed by the thread pool executor
            and thus defining how many span processors can work in parallel.
    """

    def __init__(self, num_threads: int = 2):
        # use a tuple to avoid race conditions when adding a new span and
        # iterating through it on "on_start" and "on_end".
        self._span_processors = ()  # type: Tuple[SpanProcessor, ...]
        self._lock = threading.Lock()
        self._executor = concurrent.futures.ThreadPoolExecutor(max_workers=num_threads)

    def add_span_processor(self, span_processor: SpanProcessor) -> None:
        """Adds a SpanProcessor to the list handled by this instance."""
        with self._lock:
            self._span_processors += (span_processor,)

    def _submit_and_await(
        self,
        func: Callable[[SpanProcessor], Callable[..., None]],
        *args: Any,
        **kwargs: Any,
    ):
        futures = []
        for sp in self._span_processors:
            future = self._executor.submit(func(sp), *args, **kwargs)
            futures.append(future)
        for future in futures:
            future.result()

    def on_start(
        self,
        span: "Span",
        parent_context: Optional[context_api.Context] = None,
    ) -> None:
        self._submit_and_await(
            lambda sp: sp.on_start, span, parent_context=parent_context
        )

    def on_end(self, span: "ReadableSpan") -> None:
        self._submit_and_await(lambda sp: sp.on_end, span)

    def shutdown(self) -> None:
        """Shuts down all underlying span processors in parallel."""
        self._submit_and_await(lambda sp: sp.shutdown)

    def force_flush(self, timeout_millis: int = 30000) -> bool:
        """Calls force_flush on all underlying span processors in parallel.

        Args:
            timeout_millis: The maximum amount of time to wait for spans to be
                exported.

        Returns:
            True if all span processors flushed their spans within the given
            timeout, False otherwise.
        """
        futures = []
        for sp in self._span_processors:  # type: SpanProcessor
            future = self._executor.submit(sp.force_flush, timeout_millis)
            futures.append(future)

        timeout_sec = timeout_millis / 1e3
        done_futures, not_done_futures = concurrent.futures.wait(futures, timeout_sec)
        if not_done_futures:
            return False

        for future in done_futures:
            if not future.result():
                return False

        return True


class EventBase(abc.ABC):
    def __init__(self, name: str, timestamp: Optional[int] = None) -> None:
        self._name = name
        if timestamp is None:
            self._timestamp = time_ns()
        else:
            self._timestamp = timestamp

    @property
    def name(self) -> str:
        return self._name

    @property
    def timestamp(self) -> int:
        return self._timestamp

    @property
    @abc.abstractmethod
    def attributes(self) -> types.Attributes:
        pass


class Event(EventBase):
    """A text annotation with a set of attributes. The attributes of an event
    are immutable.

    Args:
        name: Name of the event.
        attributes: Attributes of the event.
        timestamp: Timestamp of the event. If `None` it will filled
            automatically.
    """

    def __init__(
        self,
        name: str,
        attributes: types.Attributes = None,
        timestamp: Optional[int] = None,
        limit: Optional[int] = _DEFAULT_OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT,
    ) -> None:
        super().__init__(name, timestamp)
        self._attributes = attributes

    @property
    def attributes(self) -> types.Attributes:
        return self._attributes


def _check_span_ended(func):
    def wrapper(self, *args, **kwargs):
        already_ended = False
        with self._lock:  # pylint: disable=protected-access
            if self._end_time is None:  # pylint: disable=protected-access
                func(self, *args, **kwargs)
            else:
                already_ended = True

        if already_ended:
            logger.warning("Tried calling %s on an ended span.", func.__name__)

    return wrapper


class ReadableSpan:
    """Provides read-only access to span attributes"""

    def __init__(
        self,
        name: str = None,
        context: trace_api.SpanContext = None,
        parent: Optional[trace_api.SpanContext] = None,
        resource: Resource = Resource.create({}),
        attributes: types.Attributes = None,
        events: Sequence[Event] = (),
        links: Sequence[trace_api.Link] = (),
        kind: trace_api.SpanKind = trace_api.SpanKind.INTERNAL,
        instrumentation_info: InstrumentationInfo = None,
        status: Status = Status(StatusCode.UNSET),
        start_time: Optional[int] = None,
        end_time: Optional[int] = None,
        instrumentation_scope: InstrumentationScope = None,
    ) -> None:
        self._name = name
        self._context = context
        self._kind = kind
        self._instrumentation_info = instrumentation_info
        self._instrumentation_scope = instrumentation_scope
        self._parent = parent
        self._start_time = start_time
        self._end_time = end_time
        self._attributes = attributes
        self._events = events
        self._links = links
        self._resource = resource
        self._status = status

    @property
    def dropped_attributes(self) -> int:
        if self._attributes:
            return self._attributes.dropped
        return 0

    @property
    def dropped_events(self) -> int:
        if self._events:
            return self._events.dropped
        return 0

    @property
    def dropped_links(self) -> int:
        if self._links:
            return self._links.dropped
        return 0

    @property
    def name(self) -> str:
        return self._name

    def get_span_context(self):
        return self._context

    @property
    def context(self):
        return self._context

    @property
    def kind(self) -> trace_api.SpanKind:
        return self._kind

    @property
    def parent(self) -> Optional[trace_api.SpanContext]:
        return self._parent

    @property
    def start_time(self) -> Optional[int]:
        return self._start_time

    @property
    def end_time(self) -> Optional[int]:
        return self._end_time

    @property
    def status(self) -> trace_api.Status:
        return self._status

    @property
    def attributes(self) -> types.Attributes:
        return MappingProxyType(self._attributes)

    @property
    def events(self) -> Sequence[Event]:
        return tuple(event for event in self._events)

    @property
    def links(self) -> Sequence[trace_api.Link]:
        return tuple(link for link in self._links)

    @property
    def resource(self) -> Resource:
        return self._resource

    @property
    @deprecated(version="1.11.1", reason="You should use instrumentation_scope")
    def instrumentation_info(self) -> InstrumentationInfo:
        return self._instrumentation_info

    @property
    def instrumentation_scope(self) -> InstrumentationScope:
        return self._instrumentation_scope

    def to_json(self, indent=4):
        parent_id = None
        if self.parent is not None:
            if isinstance(self.parent, Span):
                ctx = self.parent.context
                parent_id = f"0x{trace_api.format_span_id(ctx.span_id)}"
            elif isinstance(self.parent, SpanContext):
                parent_id = f"0x{trace_api.format_span_id(self.parent.span_id)}"

        start_time = None
        if self._start_time:
            start_time = util.ns_to_iso_str(self._start_time)

        end_time = None
        if self._end_time:
            end_time = util.ns_to_iso_str(self._end_time)

        if self._status is not None:
            status = OrderedDict()
            status["status_code"] = str(self._status.status_code.name)
            if self._status.description:
                status["description"] = self._status.description

        f_span = OrderedDict()

        f_span["name"] = self._name
        f_span["context"] = self._format_context(self._context)
        f_span["kind"] = str(self.kind)
        f_span["parent_id"] = parent_id
        f_span["start_time"] = start_time
        f_span["end_time"] = end_time
        if self._status is not None:
            f_span["status"] = status
        f_span["attributes"] = self._format_attributes(self._attributes)
        f_span["events"] = self._format_events(self._events)
        f_span["links"] = self._format_links(self._links)
        f_span["resource"] = json.loads(self.resource.to_json())

        return json.dumps(f_span, indent=indent)

    @staticmethod
    def _format_context(context):
        x_ctx = OrderedDict()
        x_ctx["trace_id"] = f"0x{trace_api.format_trace_id(context.trace_id)}"
        x_ctx["span_id"] = f"0x{trace_api.format_span_id(context.span_id)}"
        x_ctx["trace_state"] = repr(context.trace_state)
        return x_ctx

    @staticmethod
    def _format_attributes(attributes):
        if isinstance(attributes, BoundedAttributes):
            return attributes._dict  # pylint: disable=protected-access
        if isinstance(attributes, MappingProxyType):
            return attributes.copy()
        return attributes

    @staticmethod
    def _format_events(events):
        f_events = []
        for event in events:
            f_event = OrderedDict()
            f_event["name"] = event.name
            f_event["timestamp"] = util.ns_to_iso_str(event.timestamp)
            f_event[
                "attributes"
            ] = Span._format_attributes(  # pylint: disable=protected-access
                event.attributes
            )
            f_events.append(f_event)
        return f_events

    @staticmethod
    def _format_links(links):
        f_links = []
        for link in links:
            f_link = OrderedDict()
            f_link[
                "context"
            ] = Span._format_context(  # pylint: disable=protected-access
                link.context
            )
            f_link[
                "attributes"
            ] = Span._format_attributes(  # pylint: disable=protected-access
                link.attributes
            )
            f_links.append(f_link)
        return f_links


class SpanLimits:
    """The limits that should be enforce on recorded data such as events, links, attributes etc.

    This class does not enforce any limits itself. It only provides an a way read limits from env,
    default values and from user provided arguments.

    All limit arguments must be either a non-negative integer, ``None`` or ``SpanLimits.UNSET``.

    - All limit arguments are optional.
    - If a limit argument is not set, the class will try to read its value from the corresponding
      environment variable.
    - If the environment variable is not set, the default value, if any, will be used.

    Limit precedence:

    - If a model specific limit is set, it will be used.
    - Else if the corresponding global limit is set, it will be used.
    - Else if the model specific limit has a default value, the default value will be used.
    - Else if the global limit has a default value, the default value will be used.

    Args:
        max_attributes: Maximum number of attributes that can be added to a span, event, and link.
            Environment variable: OTEL_ATTRIBUTE_COUNT_LIMIT
            Default: {_DEFAULT_ATTRIBUTE_COUNT_LIMIT}
        max_events: Maximum number of events that can be added to a Span.
            Environment variable: OTEL_SPAN_EVENT_COUNT_LIMIT
            Default: {_DEFAULT_SPAN_EVENT_COUNT_LIMIT}
        max_links: Maximum number of links that can be added to a Span.
            Environment variable: OTEL_SPAN_LINK_COUNT_LIMIT
            Default: {_DEFAULT_SPAN_LINK_COUNT_LIMIT}
        max_span_attributes: Maximum number of attributes that can be added to a Span.
            Environment variable: OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT
            Default: {_DEFAULT_OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT}
        max_event_attributes: Maximum number of attributes that can be added to an Event.
            Default: {_DEFAULT_OTEL_EVENT_ATTRIBUTE_COUNT_LIMIT}
        max_link_attributes: Maximum number of attributes that can be added to a Link.
            Default: {_DEFAULT_OTEL_LINK_ATTRIBUTE_COUNT_LIMIT}
        max_attribute_length: Maximum length an attribute value can have. Values longer than
            the specified length will be truncated.
        max_span_attribute_length: Maximum length a span attribute value can have. Values longer than
            the specified length will be truncated.
    """

    UNSET = -1

    def __init__(
        self,
        max_attributes: Optional[int] = None,
        max_events: Optional[int] = None,
        max_links: Optional[int] = None,
        max_span_attributes: Optional[int] = None,
        max_event_attributes: Optional[int] = None,
        max_link_attributes: Optional[int] = None,
        max_attribute_length: Optional[int] = None,
        max_span_attribute_length: Optional[int] = None,
    ):
        # span events and links count
        self.max_events = self._from_env_if_absent(
            max_events,
            OTEL_SPAN_EVENT_COUNT_LIMIT,
            _DEFAULT_OTEL_SPAN_EVENT_COUNT_LIMIT,
        )
        self.max_links = self._from_env_if_absent(
            max_links,
            OTEL_SPAN_LINK_COUNT_LIMIT,
            _DEFAULT_OTEL_SPAN_LINK_COUNT_LIMIT,
        )

        # attribute count
        global_max_attributes = self._from_env_if_absent(
            max_attributes, OTEL_ATTRIBUTE_COUNT_LIMIT
        )
        self.max_attributes = (
            global_max_attributes
            if global_max_attributes is not None
            else _DEFAULT_OTEL_ATTRIBUTE_COUNT_LIMIT
        )

        self.max_span_attributes = self._from_env_if_absent(
            max_span_attributes,
            OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT,
            global_max_attributes
            if global_max_attributes is not None
            else _DEFAULT_OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT,
        )
        self.max_event_attributes = self._from_env_if_absent(
            max_event_attributes,
            OTEL_EVENT_ATTRIBUTE_COUNT_LIMIT,
            global_max_attributes
            if global_max_attributes is not None
            else _DEFAULT_OTEL_EVENT_ATTRIBUTE_COUNT_LIMIT,
        )
        self.max_link_attributes = self._from_env_if_absent(
            max_link_attributes,
            OTEL_LINK_ATTRIBUTE_COUNT_LIMIT,
            global_max_attributes
            if global_max_attributes is not None
            else _DEFAULT_OTEL_LINK_ATTRIBUTE_COUNT_LIMIT,
        )

        # attribute length
        self.max_attribute_length = self._from_env_if_absent(
            max_attribute_length,
            OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT,
        )
        self.max_span_attribute_length = self._from_env_if_absent(
            max_span_attribute_length,
            OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT,
            # use global attribute length limit as default
            self.max_attribute_length,
        )

    def __repr__(self):
        return f"{type(self).__name__}(max_span_attributes={self.max_span_attributes}, max_events_attributes={self.max_event_attributes}, max_link_attributes={self.max_link_attributes}, max_attributes={self.max_attributes}, max_events={self.max_events}, max_links={self.max_links}, max_attribute_length={self.max_attribute_length})"

    @classmethod
    def _from_env_if_absent(
        cls, value: Optional[int], env_var: str, default: Optional[int] = None
    ) -> Optional[int]:
        if value == cls.UNSET:
            return None

        err_msg = "{0} must be a non-negative integer but got {}"

        # if no value is provided for the limit, try to load it from env
        if value is None:
            # return default value if env var is not set
            if env_var not in environ:
                return default

            str_value = environ.get(env_var, "").strip().lower()
            if str_value == _ENV_VALUE_UNSET:
                return None

            try:
                value = int(str_value)
            except ValueError:
                raise ValueError(err_msg.format(env_var, str_value))

        if value < 0:
            raise ValueError(err_msg.format(env_var, value))
        return value


_UnsetLimits = SpanLimits(
    max_attributes=SpanLimits.UNSET,
    max_events=SpanLimits.UNSET,
    max_links=SpanLimits.UNSET,
    max_span_attributes=SpanLimits.UNSET,
    max_event_attributes=SpanLimits.UNSET,
    max_link_attributes=SpanLimits.UNSET,
    max_attribute_length=SpanLimits.UNSET,
    max_span_attribute_length=SpanLimits.UNSET,
)

# not removed for backward compat. please use SpanLimits instead.
SPAN_ATTRIBUTE_COUNT_LIMIT = (
    SpanLimits._from_env_if_absent(  # pylint: disable=protected-access
        None,
        OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT,
        _DEFAULT_OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT,
    )
)


class Span(trace_api.Span, ReadableSpan):
    """See `mysql.opentelemetry.trace.Span`.

    Users should create `Span` objects via the `Tracer` instead of this
    constructor.

    Args:
        name: The name of the operation this span represents
        context: The immutable span context
        parent: This span's parent's `mysql.opentelemetry.trace.SpanContext`, or
            None if this is a root span
        sampler: The sampler used to create this span
        trace_config: TODO
        resource: Entity producing telemetry
        attributes: The span's attributes to be exported
        events: Timestamped events to be exported
        links: Links to other spans to be exported
        span_processor: `SpanProcessor` to invoke when starting and ending
            this `Span`.
        limits: `SpanLimits` instance that was passed to the `TracerProvider`
    """

    def __new__(cls, *args, **kwargs):
        if cls is Span:
            raise TypeError("Span must be instantiated via a tracer.")
        return super().__new__(cls)

    # pylint: disable=too-many-locals
    def __init__(
        self,
        name: str,
        context: trace_api.SpanContext,
        parent: Optional[trace_api.SpanContext] = None,
        sampler: Optional[sampling.Sampler] = None,
        trace_config: None = None,  # TODO
        resource: Resource = Resource.create({}),
        attributes: types.Attributes = None,
        events: Sequence[Event] = None,
        links: Sequence[trace_api.Link] = (),
        kind: trace_api.SpanKind = trace_api.SpanKind.INTERNAL,
        span_processor: SpanProcessor = SpanProcessor(),
        instrumentation_info: InstrumentationInfo = None,
        record_exception: bool = True,
        set_status_on_exception: bool = True,
        limits=_UnsetLimits,
        instrumentation_scope: InstrumentationScope = None,
    ) -> None:
        super().__init__(
            name=name,
            context=context,
            parent=parent,
            kind=kind,
            resource=resource,
            instrumentation_info=instrumentation_info,
            instrumentation_scope=instrumentation_scope,
        )
        self._sampler = sampler
        self._trace_config = trace_config
        self._record_exception = record_exception
        self._set_status_on_exception = set_status_on_exception
        self._span_processor = span_processor
        self._limits = limits
        self._lock = threading.Lock()
        self._attributes = BoundedAttributes(
            self._limits.max_span_attributes,
            attributes,
            immutable=False,
            max_value_len=self._limits.max_span_attribute_length,
        )
        self._events = self._new_events()
        if events:
            for event in events:
                event._attributes = BoundedAttributes(
                    self._limits.max_event_attributes,
                    event.attributes,
                    max_value_len=self._limits.max_attribute_length,
                )
                self._events.append(event)

        if links is None:
            self._links = self._new_links()
        else:
            for link in links:
                link._attributes = BoundedAttributes(
                    self._limits.max_link_attributes,
                    link.attributes,
                    max_value_len=self._limits.max_attribute_length,
                )
            self._links = BoundedList.from_seq(self._limits.max_links, links)

    def __repr__(self):
        return f'{type(self).__name__}(name="{self._name}", context={self._context})'

    def _new_events(self):
        return BoundedList(self._limits.max_events)

    def _new_links(self):
        return BoundedList(self._limits.max_links)

    def get_span_context(self):
        return self._context

    def set_attributes(self, attributes: Dict[str, types.AttributeValue]) -> None:
        with self._lock:
            if self._end_time is not None:
                logger.warning("Setting attribute on ended span.")
                return

            for key, value in attributes.items():
                self._attributes[key] = value

    def set_attribute(self, key: str, value: types.AttributeValue) -> None:
        return self.set_attributes({key: value})

    @_check_span_ended
    def _add_event(self, event: EventBase) -> None:
        self._events.append(event)

    def add_event(
        self,
        name: str,
        attributes: types.Attributes = None,
        timestamp: Optional[int] = None,
    ) -> None:
        attributes = BoundedAttributes(
            self._limits.max_event_attributes,
            attributes,
            max_value_len=self._limits.max_attribute_length,
        )
        self._add_event(
            Event(
                name=name,
                attributes=attributes,
                timestamp=timestamp,
            )
        )

    def _readable_span(self) -> ReadableSpan:
        return ReadableSpan(
            name=self._name,
            context=self._context,
            parent=self._parent,
            resource=self._resource,
            attributes=self._attributes,
            events=self._events,
            links=self._links,
            kind=self.kind,
            status=self._status,
            start_time=self._start_time,
            end_time=self._end_time,
            instrumentation_info=self._instrumentation_info,
            instrumentation_scope=self._instrumentation_scope,
        )

    def start(
        self,
        start_time: Optional[int] = None,
        parent_context: Optional[context_api.Context] = None,
    ) -> None:
        with self._lock:
            if self._start_time is not None:
                logger.warning("Calling start() on a started span.")
                return
            self._start_time = start_time if start_time is not None else time_ns()

        self._span_processor.on_start(self, parent_context=parent_context)

    def end(self, end_time: Optional[int] = None) -> None:
        with self._lock:
            if self._start_time is None:
                raise RuntimeError("Calling end() on a not started span.")
            if self._end_time is not None:
                logger.warning("Calling end() on an ended span.")
                return

            self._end_time = end_time if end_time is not None else time_ns()

        self._span_processor.on_end(self._readable_span())

    @_check_span_ended
    def update_name(self, name: str) -> None:
        self._name = name

    def is_recording(self) -> bool:
        return self._end_time is None

    @_check_span_ended
    def set_status(
        self,
        status: typing.Union[Status, StatusCode],
        description: typing.Optional[str] = None,
    ) -> None:
        # Ignore future calls if status is already set to OK
        # Ignore calls to set to StatusCode.UNSET
        if isinstance(status, Status):
            if (
                self._status
                and self._status.status_code is StatusCode.OK
                or status.status_code is StatusCode.UNSET
            ):
                return
            if description is not None:
                logger.warning(
                    "Description %s ignored. Use either `Status` or `(StatusCode, Description)`",
                    description,
                )
            self._status = status
        elif isinstance(status, StatusCode):
            if (
                self._status
                and self._status.status_code is StatusCode.OK
                or status is StatusCode.UNSET
            ):
                return
            self._status = Status(status, description)

    def __exit__(
        self,
        exc_type: Optional[Type[BaseException]],
        exc_val: Optional[BaseException],
        exc_tb: Optional[TracebackType],
    ) -> None:
        """Ends context manager and calls `end` on the `Span`."""
        if exc_val is not None and self.is_recording():
            # Record the exception as an event
            # pylint:disable=protected-access
            if self._record_exception:
                self.record_exception(exception=exc_val, escaped=True)
            # Records status if span is used as context manager
            # i.e. with tracer.start_span() as span:
            if self._set_status_on_exception:
                self.set_status(
                    Status(
                        status_code=StatusCode.ERROR,
                        description=f"{exc_type.__name__}: {exc_val}",
                    )
                )

        super().__exit__(exc_type, exc_val, exc_tb)

    def record_exception(
        self,
        exception: Exception,
        attributes: types.Attributes = None,
        timestamp: Optional[int] = None,
        escaped: bool = False,
    ) -> None:
        """Records an exception as a span event."""
        try:
            stacktrace = traceback.format_exc()
        except Exception:  # pylint: disable=broad-except
            # workaround for python 3.4, format_exc can raise
            # an AttributeError if the __context__ on
            # an exception is None
            stacktrace = "Exception occurred on stacktrace formatting"
        _attributes = {
            "exception.type": exception.__class__.__name__,
            "exception.message": str(exception),
            "exception.stacktrace": stacktrace,
            "exception.escaped": str(escaped),
        }
        if attributes:
            _attributes.update(attributes)
        self.add_event(name="exception", attributes=_attributes, timestamp=timestamp)


class _Span(Span):
    """Protected implementation of `mysql.opentelemetry.trace.Span`.

    This constructor exists to prevent the instantiation of the `Span` class
    by other mechanisms than through the `Tracer`.
    """


class Tracer(trace_api.Tracer):
    """See `mysql.opentelemetry.trace.Tracer`."""

    def __init__(
        self,
        sampler: sampling.Sampler,
        resource: Resource,
        span_processor: Union[
            SynchronousMultiSpanProcessor, ConcurrentMultiSpanProcessor
        ],
        id_generator: IdGenerator,
        instrumentation_info: InstrumentationInfo,
        span_limits: SpanLimits,
        instrumentation_scope: InstrumentationScope,
    ) -> None:
        self.sampler = sampler
        self.resource = resource
        self.span_processor = span_processor
        self.id_generator = id_generator
        self.instrumentation_info = instrumentation_info
        self._span_limits = span_limits
        self._instrumentation_scope = instrumentation_scope

    @contextmanager
    def start_as_current_span(
        self,
        name: str,
        context: Optional[context_api.Context] = None,
        kind: trace_api.SpanKind = trace_api.SpanKind.INTERNAL,
        attributes: types.Attributes = None,
        links: Sequence[trace_api.Link] = (),
        start_time: Optional[int] = None,
        record_exception: bool = True,
        set_status_on_exception: bool = True,
        end_on_exit: bool = True,
    ) -> Iterator[trace_api.Span]:
        span = self.start_span(
            name=name,
            context=context,
            kind=kind,
            attributes=attributes,
            links=links,
            start_time=start_time,
            record_exception=record_exception,
            set_status_on_exception=set_status_on_exception,
        )
        with trace_api.use_span(
            span,
            end_on_exit=end_on_exit,
            record_exception=record_exception,
            set_status_on_exception=set_status_on_exception,
        ) as span_context:
            yield span_context

    def start_span(  # pylint: disable=too-many-locals
        self,
        name: str,
        context: Optional[context_api.Context] = None,
        kind: trace_api.SpanKind = trace_api.SpanKind.INTERNAL,
        attributes: types.Attributes = None,
        links: Sequence[trace_api.Link] = (),
        start_time: Optional[int] = None,
        record_exception: bool = True,
        set_status_on_exception: bool = True,
    ) -> trace_api.Span:
        parent_span_context = trace_api.get_current_span(context).get_span_context()

        if parent_span_context is not None and not isinstance(
            parent_span_context, trace_api.SpanContext
        ):
            raise TypeError("parent_span_context must be a SpanContext or None.")

        # is_valid determines root span
        if parent_span_context is None or not parent_span_context.is_valid:
            parent_span_context = None
            trace_id = self.id_generator.generate_trace_id()
        else:
            trace_id = parent_span_context.trace_id

        # The sampler decides whether to create a real or no-op span at the
        # time of span creation. No-op spans do not record events, and are not
        # exported.
        # The sampler may also add attributes to the newly-created span, e.g.
        # to include information about the sampling result.
        # The sampler may also modify the parent span context's tracestate
        sampling_result = self.sampler.should_sample(
            context, trace_id, name, kind, attributes, links
        )

        trace_flags = (
            trace_api.TraceFlags(trace_api.TraceFlags.SAMPLED)
            if sampling_result.decision.is_sampled()
            else trace_api.TraceFlags(trace_api.TraceFlags.DEFAULT)
        )
        span_context = trace_api.SpanContext(
            trace_id,
            self.id_generator.generate_span_id(),
            is_remote=False,
            trace_flags=trace_flags,
            trace_state=sampling_result.trace_state,
        )

        # Only record if is_recording() is true
        if sampling_result.decision.is_recording():
            # pylint:disable=protected-access
            span = _Span(
                name=name,
                context=span_context,
                parent=parent_span_context,
                sampler=self.sampler,
                resource=self.resource,
                attributes=sampling_result.attributes.copy(),
                span_processor=self.span_processor,
                kind=kind,
                links=links,
                instrumentation_info=self.instrumentation_info,
                record_exception=record_exception,
                set_status_on_exception=set_status_on_exception,
                limits=self._span_limits,
                instrumentation_scope=self._instrumentation_scope,
            )
            span.start(start_time=start_time, parent_context=context)
        else:
            span = trace_api.NonRecordingSpan(context=span_context)
        return span


class TracerProvider(trace_api.TracerProvider):
    """See `mysql.opentelemetry.trace.TracerProvider`."""

    def __init__(
        self,
        sampler: sampling.Sampler = None,
        resource: Resource = Resource.create({}),
        shutdown_on_exit: bool = True,
        active_span_processor: Union[
            SynchronousMultiSpanProcessor, ConcurrentMultiSpanProcessor
        ] = None,
        id_generator: IdGenerator = None,
        span_limits: SpanLimits = None,
    ):
        self._active_span_processor = (
            active_span_processor or SynchronousMultiSpanProcessor()
        )
        if id_generator is None:
            self.id_generator = RandomIdGenerator()
        else:
            self.id_generator = id_generator
        self._resource = resource
        if not sampler:
            sampler = sampling._get_from_env_or_default()
        self.sampler = sampler
        self._span_limits = span_limits or SpanLimits()
        self._atexit_handler = None

        if shutdown_on_exit:
            self._atexit_handler = atexit.register(self.shutdown)

    @property
    def resource(self) -> Resource:
        return self._resource

    def get_tracer(
        self,
        instrumenting_module_name: str,
        instrumenting_library_version: typing.Optional[str] = None,
        schema_url: typing.Optional[str] = None,
    ) -> "trace_api.Tracer":
        if not instrumenting_module_name:  # Reject empty strings too.
            instrumenting_module_name = ""
            logger.error("get_tracer called with missing module name.")
        if instrumenting_library_version is None:
            instrumenting_library_version = ""

        filterwarnings(
            "ignore",
            message=(
                r"Call to deprecated method __init__. \(You should use "
                r"InstrumentationScope\) -- Deprecated since version 1.11.1."
            ),
            category=DeprecationWarning,
            module="mysql.opentelemetry.sdk.trace",
        )

        instrumentation_info = InstrumentationInfo(
            instrumenting_module_name,
            instrumenting_library_version,
            schema_url,
        )

        return Tracer(
            self.sampler,
            self.resource,
            self._active_span_processor,
            self.id_generator,
            instrumentation_info,
            self._span_limits,
            InstrumentationScope(
                instrumenting_module_name,
                instrumenting_library_version,
                schema_url,
            ),
        )

    def add_span_processor(self, span_processor: SpanProcessor) -> None:
        """Registers a new :class:`SpanProcessor` for this `TracerProvider`.

        The span processors are invoked in the same order they are registered.
        """

        # no lock here because add_span_processor is thread safe for both
        # SynchronousMultiSpanProcessor and ConcurrentMultiSpanProcessor.
        self._active_span_processor.add_span_processor(span_processor)

    def shutdown(self):
        """Shut down the span processors added to the tracer provider."""
        self._active_span_processor.shutdown()
        if self._atexit_handler is not None:
            atexit.unregister(self._atexit_handler)
            self._atexit_handler = None

    def force_flush(self, timeout_millis: int = 30000) -> bool:
        """Requests the active span processor to process all spans that have not
        yet been processed.

        By default force flush is called sequentially on all added span
        processors. This means that span processors further back in the list
        have less time to flush their spans.
        To have span processors flush their spans in parallel it is possible to
        initialize the tracer provider with an instance of
        `ConcurrentMultiSpanProcessor` at the cost of using multiple threads.

        Args:
            timeout_millis: The maximum amount of time to wait for spans to be
                processed.

        Returns:
            False if the timeout is exceeded, True otherwise.
        """
        return self._active_span_processor.force_flush(timeout_millis)

virt/lib/python3.9/site-packages/mysql/opentelemetry/sdk/trace/__init__.py
PrinceDisant/LostInNL
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import collections
import logging
import os
import sys
import threading
import typing

from enum import Enum
from os import environ, linesep
from time import time_ns
from typing import Optional

from mysql.opentelemetry.context import (
    _SUPPRESS_INSTRUMENTATION_KEY,
    Context,
    attach,
    detach,
    set_value,
)
from mysql.opentelemetry.sdk.environment_variables import (
    OTEL_BSP_EXPORT_TIMEOUT,
    OTEL_BSP_MAX_EXPORT_BATCH_SIZE,
    OTEL_BSP_MAX_QUEUE_SIZE,
    OTEL_BSP_SCHEDULE_DELAY,
)
from mysql.opentelemetry.sdk.trace import ReadableSpan, Span, SpanProcessor
from mysql.opentelemetry.util._once import Once

_DEFAULT_SCHEDULE_DELAY_MILLIS = 5000
_DEFAULT_MAX_EXPORT_BATCH_SIZE = 512
_DEFAULT_EXPORT_TIMEOUT_MILLIS = 30000
_DEFAULT_MAX_QUEUE_SIZE = 2048
_ENV_VAR_INT_VALUE_ERROR_MESSAGE = (
    "Unable to parse value for %s as integer. Defaulting to %s."
)

logger = logging.getLogger(__name__)


class SpanExportResult(Enum):
    SUCCESS = 0
    FAILURE = 1


class SpanExporter:
    """Interface for exporting spans.

    Interface to be implemented by services that want to export spans recorded
    in their own format.

    To export data this MUST be registered to the :class`mysql.opentelemetry.sdk.trace.Tracer` using a
    `SimpleSpanProcessor` or a `BatchSpanProcessor`.
    """

    def export(self, spans: typing.Sequence[ReadableSpan]) -> "SpanExportResult":
        """Exports a batch of telemetry data.

        Args:
            spans: The list of `mysql.opentelemetry.trace.Span` objects to be exported

        Returns:
            The result of the export
        """

    def shutdown(self) -> None:
        """Shuts down the exporter.

        Called when the SDK is shut down.
        """

    def force_flush(self, timeout_millis: int = 30000) -> bool:
        """Hint to ensure that the export of any spans the exporter has received
        prior to the call to ForceFlush SHOULD be completed as soon as possible, preferably
        before returning from this method.
        """


class SimpleSpanProcessor(SpanProcessor):
    """Simple SpanProcessor implementation.

    SimpleSpanProcessor is an implementation of `SpanProcessor` that
    passes ended spans directly to the configured `SpanExporter`.
    """

    def __init__(self, span_exporter: SpanExporter):
        self.span_exporter = span_exporter

    def on_start(
        self, span: Span, parent_context: typing.Optional[Context] = None
    ) -> None:
        pass

    def on_end(self, span: ReadableSpan) -> None:
        if not span.context.trace_flags.sampled:
            return
        token = attach(set_value(_SUPPRESS_INSTRUMENTATION_KEY, True))
        try:
            self.span_exporter.export((span,))
        # pylint: disable=broad-except
        except Exception:
            logger.exception("Exception while exporting Span.")
        detach(token)

    def shutdown(self) -> None:
        self.span_exporter.shutdown()

    def force_flush(self, timeout_millis: int = 30000) -> bool:
        # pylint: disable=unused-argument
        return True


class _FlushRequest:
    """Represents a request for the BatchSpanProcessor to flush spans."""

    __slots__ = ["event", "num_spans"]

    def __init__(self):
        self.event = threading.Event()
        self.num_spans = 0


_BSP_RESET_ONCE = Once()


class BatchSpanProcessor(SpanProcessor):
    """Batch span processor implementation.

    `BatchSpanProcessor` is an implementation of `SpanProcessor` that
    batches ended spans and pushes them to the configured `SpanExporter`.

    `BatchSpanProcessor` is configurable with the following environment
    variables which correspond to constructor parameters:

    - :envvar:`OTEL_BSP_SCHEDULE_DELAY`
    - :envvar:`OTEL_BSP_MAX_QUEUE_SIZE`
    - :envvar:`OTEL_BSP_MAX_EXPORT_BATCH_SIZE`
    - :envvar:`OTEL_BSP_EXPORT_TIMEOUT`
    """

    def __init__(
        self,
        span_exporter: SpanExporter,
        max_queue_size: int = None,
        schedule_delay_millis: float = None,
        max_export_batch_size: int = None,
        export_timeout_millis: float = None,
    ):
        if max_queue_size is None:
            max_queue_size = BatchSpanProcessor._default_max_queue_size()

        if schedule_delay_millis is None:
            schedule_delay_millis = BatchSpanProcessor._default_schedule_delay_millis()

        if max_export_batch_size is None:
            max_export_batch_size = BatchSpanProcessor._default_max_export_batch_size()

        if export_timeout_millis is None:
            export_timeout_millis = BatchSpanProcessor._default_export_timeout_millis()

        BatchSpanProcessor._validate_arguments(
            max_queue_size, schedule_delay_millis, max_export_batch_size
        )

        self.span_exporter = span_exporter
        self.queue = collections.deque([], max_queue_size)  # type: typing.Deque[Span]
        self.worker_thread = threading.Thread(
            name="OtelBatchSpanProcessor", target=self.worker, daemon=True
        )
        self.condition = threading.Condition(threading.Lock())
        self._flush_request = None  # type: typing.Optional[_FlushRequest]
        self.schedule_delay_millis = schedule_delay_millis
        self.max_export_batch_size = max_export_batch_size
        self.max_queue_size = max_queue_size
        self.export_timeout_millis = export_timeout_millis
        self.done = False
        # flag that indicates that spans are being dropped
        self._spans_dropped = False
        # precallocated list to send spans to exporter
        self.spans_list = [
            None
        ] * self.max_export_batch_size  # type: typing.List[typing.Optional[Span]]
        self.worker_thread.start()
        # Only available in *nix since py37.
        if hasattr(os, "register_at_fork"):
            os.register_at_fork(
                after_in_child=self._at_fork_reinit
            )  # pylint: disable=protected-access
        self._pid = os.getpid()

    def on_start(
        self, span: Span, parent_context: typing.Optional[Context] = None
    ) -> None:
        pass

    def on_end(self, span: ReadableSpan) -> None:
        if self.done:
            logger.warning("Already shutdown, dropping span.")
            return
        if not span.context.trace_flags.sampled:
            return
        if self._pid != os.getpid():
            _BSP_RESET_ONCE.do_once(self._at_fork_reinit)

        if len(self.queue) == self.max_queue_size:
            if not self._spans_dropped:
                logger.warning("Queue is full, likely spans will be dropped.")
                self._spans_dropped = True

        self.queue.appendleft(span)

        if len(self.queue) >= self.max_export_batch_size:
            with self.condition:
                self.condition.notify()

    def _at_fork_reinit(self):
        self.condition = threading.Condition(threading.Lock())
        self.queue.clear()

        # worker_thread is local to a process, only the thread that issued fork continues
        # to exist. A new worker thread must be started in child process.
        self.worker_thread = threading.Thread(
            name="OtelBatchSpanProcessor", target=self.worker, daemon=True
        )
        self.worker_thread.start()
        self._pid = os.getpid()

    def worker(self):
        timeout = self.schedule_delay_millis / 1e3
        flush_request = None  # type: typing.Optional[_FlushRequest]
        while not self.done:
            with self.condition:
                if self.done:
                    # done flag may have changed, avoid waiting
                    break
                flush_request = self._get_and_unset_flush_request()
                if (
                    len(self.queue) < self.max_export_batch_size
                    and flush_request is None
                ):
                    self.condition.wait(timeout)
                    flush_request = self._get_and_unset_flush_request()
                    if not self.queue:
                        # spurious notification, let's wait again, reset timeout
                        timeout = self.schedule_delay_millis / 1e3
                        self._notify_flush_request_finished(flush_request)
                        flush_request = None
                        continue
                    if self.done:
                        # missing spans will be sent when calling flush
                        break

            # subtract the duration of this export call to the next timeout
            start = time_ns()
            self._export(flush_request)
            end = time_ns()
            duration = (end - start) / 1e9
            timeout = self.schedule_delay_millis / 1e3 - duration

            self._notify_flush_request_finished(flush_request)
            flush_request = None

        # there might have been a new flush request while export was running
        # and before the done flag switched to true
        with self.condition:
            shutdown_flush_request = self._get_and_unset_flush_request()

        # be sure that all spans are sent
        self._drain_queue()
        self._notify_flush_request_finished(flush_request)
        self._notify_flush_request_finished(shutdown_flush_request)

    def _get_and_unset_flush_request(
        self,
    ) -> typing.Optional[_FlushRequest]:
        """Returns the current flush request and makes it invisible to the
        worker thread for subsequent calls.
        """
        flush_request = self._flush_request
        self._flush_request = None
        if flush_request is not None:
            flush_request.num_spans = len(self.queue)
        return flush_request

    @staticmethod
    def _notify_flush_request_finished(
        flush_request: typing.Optional[_FlushRequest],
    ):
        """Notifies the flush initiator(s) waiting on the given request/event
        that the flush operation was finished.
        """
        if flush_request is not None:
            flush_request.event.set()

    def _get_or_create_flush_request(self) -> _FlushRequest:
        """Either returns the current active flush event or creates a new one.

        The flush event will be visible and read by the worker thread before an
        export operation starts. Callers of a flush operation may wait on the
        returned event to be notified when the flush/export operation was
        finished.

        This method is not thread-safe, i.e. callers need to take care about
        synchronization/locking.
        """
        if self._flush_request is None:
            self._flush_request = _FlushRequest()
        return self._flush_request

    def _export(self, flush_request: typing.Optional[_FlushRequest]):
        """Exports spans considering the given flush_request.

        In case of a given flush_requests spans are exported in batches until
        the number of exported spans reached or exceeded the number of spans in
        the flush request.
        In no flush_request was given at most max_export_batch_size spans are
        exported.
        """
        if not flush_request:
            self._export_batch()
            return

        num_spans = flush_request.num_spans
        while self.queue:
            num_exported = self._export_batch()
            num_spans -= num_exported

            if num_spans <= 0:
                break

    def _export_batch(self) -> int:
        """Exports at most max_export_batch_size spans and returns the number of
        exported spans.
        """
        idx = 0
        # currently only a single thread acts as consumer, so queue.pop() will
        # not raise an exception
        while idx < self.max_export_batch_size and self.queue:
            self.spans_list[idx] = self.queue.pop()
            idx += 1
        token = attach(set_value(_SUPPRESS_INSTRUMENTATION_KEY, True))
        try:
            # Ignore type b/c the Optional[None]+slicing is too "clever"
            # for mypy
            self.span_exporter.export(self.spans_list[:idx])  # type: ignore
        except Exception:  # pylint: disable=broad-except
            logger.exception("Exception while exporting Span batch.")
        detach(token)

        # clean up list
        for index in range(idx):
            self.spans_list[index] = None
        return idx

    def _drain_queue(self):
        """Export all elements until queue is empty.

        Can only be called from the worker thread context because it invokes
        `export` that is not thread safe.
        """
        while self.queue:
            self._export_batch()

    def force_flush(self, timeout_millis: int = None) -> bool:
        if timeout_millis is None:
            timeout_millis = self.export_timeout_millis

        if self.done:
            logger.warning("Already shutdown, ignoring call to force_flush().")
            return True

        with self.condition:
            flush_request = self._get_or_create_flush_request()
            # signal the worker thread to flush and wait for it to finish
            self.condition.notify_all()

        # wait for token to be processed
        ret = flush_request.event.wait(timeout_millis / 1e3)
        if not ret:
            logger.warning("Timeout was exceeded in force_flush().")
        return ret

    def shutdown(self) -> None:
        # signal the worker thread to finish and then wait for it
        self.done = True
        with self.condition:
            self.condition.notify_all()
        self.worker_thread.join()
        self.span_exporter.shutdown()

    @staticmethod
    def _default_max_queue_size():
        try:
            return int(environ.get(OTEL_BSP_MAX_QUEUE_SIZE, _DEFAULT_MAX_QUEUE_SIZE))
        except ValueError:
            logger.exception(
                _ENV_VAR_INT_VALUE_ERROR_MESSAGE,
                OTEL_BSP_MAX_QUEUE_SIZE,
                _DEFAULT_MAX_QUEUE_SIZE,
            )
            return _DEFAULT_MAX_QUEUE_SIZE

    @staticmethod
    def _default_schedule_delay_millis():
        try:
            return int(
                environ.get(OTEL_BSP_SCHEDULE_DELAY, _DEFAULT_SCHEDULE_DELAY_MILLIS)
            )
        except ValueError:
            logger.exception(
                _ENV_VAR_INT_VALUE_ERROR_MESSAGE,
                OTEL_BSP_SCHEDULE_DELAY,
                _DEFAULT_SCHEDULE_DELAY_MILLIS,
            )
            return _DEFAULT_SCHEDULE_DELAY_MILLIS

    @staticmethod
    def _default_max_export_batch_size():
        try:
            return int(
                environ.get(
                    OTEL_BSP_MAX_EXPORT_BATCH_SIZE,
                    _DEFAULT_MAX_EXPORT_BATCH_SIZE,
                )
            )
        except ValueError:
            logger.exception(
                _ENV_VAR_INT_VALUE_ERROR_MESSAGE,
                OTEL_BSP_MAX_EXPORT_BATCH_SIZE,
                _DEFAULT_MAX_EXPORT_BATCH_SIZE,
            )
            return _DEFAULT_MAX_EXPORT_BATCH_SIZE

    @staticmethod
    def _default_export_timeout_millis():
        try:
            return int(
                environ.get(OTEL_BSP_EXPORT_TIMEOUT, _DEFAULT_EXPORT_TIMEOUT_MILLIS)
            )
        except ValueError:
            logger.exception(
                _ENV_VAR_INT_VALUE_ERROR_MESSAGE,
                OTEL_BSP_EXPORT_TIMEOUT,
                _DEFAULT_EXPORT_TIMEOUT_MILLIS,
            )
            return _DEFAULT_EXPORT_TIMEOUT_MILLIS

    @staticmethod
    def _validate_arguments(
        max_queue_size, schedule_delay_millis, max_export_batch_size
    ):
        if max_queue_size <= 0:
            raise ValueError("max_queue_size must be a positive integer.")

        if schedule_delay_millis <= 0:
            raise ValueError("schedule_delay_millis must be positive.")

        if max_export_batch_size <= 0:
            raise ValueError("max_export_batch_size must be a positive integer.")

        if max_export_batch_size > max_queue_size:
            raise ValueError(
                "max_export_batch_size must be less than or equal to max_queue_size."
            )


class ConsoleSpanExporter(SpanExporter):
    """Implementation of :class:`SpanExporter` that prints spans to the
    console.

    This class can be used for diagnostic purposes. It prints the exported
    spans to the console STDOUT.
    """

    def __init__(
        self,
        service_name: Optional[str] = None,
        out: typing.IO = sys.stdout,
        formatter: typing.Callable[[ReadableSpan], str] = lambda span: span.to_json()
        + linesep,
    ):
        self.out = out
        self.formatter = formatter
        self.service_name = service_name

    def export(self, spans: typing.Sequence[ReadableSpan]) -> SpanExportResult:
        for span in spans:
            self.out.write(self.formatter(span))
        self.out.flush()
        return SpanExportResult.SUCCESS

    def force_flush(self, timeout_millis: int = 30000) -> bool:
        return True

virt/lib/python3.9/site-packages/mysql/opentelemetry/sdk/trace/export/__init__ 2.py
PrinceDisant/LostInNL
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import collections
import logging
import os
import sys
import threading
import typing

from enum import Enum
from os import environ, linesep
from time import time_ns
from typing import Optional

from mysql.opentelemetry.context import (
    _SUPPRESS_INSTRUMENTATION_KEY,
    Context,
    attach,
    detach,
    set_value,
)
from mysql.opentelemetry.sdk.environment_variables import (
    OTEL_BSP_EXPORT_TIMEOUT,
    OTEL_BSP_MAX_EXPORT_BATCH_SIZE,
    OTEL_BSP_MAX_QUEUE_SIZE,
    OTEL_BSP_SCHEDULE_DELAY,
)
from mysql.opentelemetry.sdk.trace import ReadableSpan, Span, SpanProcessor
from mysql.opentelemetry.util._once import Once

_DEFAULT_SCHEDULE_DELAY_MILLIS = 5000
_DEFAULT_MAX_EXPORT_BATCH_SIZE = 512
_DEFAULT_EXPORT_TIMEOUT_MILLIS = 30000
_DEFAULT_MAX_QUEUE_SIZE = 2048
_ENV_VAR_INT_VALUE_ERROR_MESSAGE = (
    "Unable to parse value for %s as integer. Defaulting to %s."
)

logger = logging.getLogger(__name__)


class SpanExportResult(Enum):
    SUCCESS = 0
    FAILURE = 1


class SpanExporter:
    """Interface for exporting spans.

    Interface to be implemented by services that want to export spans recorded
    in their own format.

    To export data this MUST be registered to the :class`mysql.opentelemetry.sdk.trace.Tracer` using a
    `SimpleSpanProcessor` or a `BatchSpanProcessor`.
    """

    def export(self, spans: typing.Sequence[ReadableSpan]) -> "SpanExportResult":
        """Exports a batch of telemetry data.

        Args:
            spans: The list of `mysql.opentelemetry.trace.Span` objects to be exported

        Returns:
            The result of the export
        """

    def shutdown(self) -> None:
        """Shuts down the exporter.

        Called when the SDK is shut down.
        """

    def force_flush(self, timeout_millis: int = 30000) -> bool:
        """Hint to ensure that the export of any spans the exporter has received
        prior to the call to ForceFlush SHOULD be completed as soon as possible, preferably
        before returning from this method.
        """


class SimpleSpanProcessor(SpanProcessor):
    """Simple SpanProcessor implementation.

    SimpleSpanProcessor is an implementation of `SpanProcessor` that
    passes ended spans directly to the configured `SpanExporter`.
    """

    def __init__(self, span_exporter: SpanExporter):
        self.span_exporter = span_exporter

    def on_start(
        self, span: Span, parent_context: typing.Optional[Context] = None
    ) -> None:
        pass

    def on_end(self, span: ReadableSpan) -> None:
        if not span.context.trace_flags.sampled:
            return
        token = attach(set_value(_SUPPRESS_INSTRUMENTATION_KEY, True))
        try:
            self.span_exporter.export((span,))
        # pylint: disable=broad-except
        except Exception:
            logger.exception("Exception while exporting Span.")
        detach(token)

    def shutdown(self) -> None:
        self.span_exporter.shutdown()

    def force_flush(self, timeout_millis: int = 30000) -> bool:
        # pylint: disable=unused-argument
        return True


class _FlushRequest:
    """Represents a request for the BatchSpanProcessor to flush spans."""

    __slots__ = ["event", "num_spans"]

    def __init__(self):
        self.event = threading.Event()
        self.num_spans = 0


_BSP_RESET_ONCE = Once()


class BatchSpanProcessor(SpanProcessor):
    """Batch span processor implementation.

    `BatchSpanProcessor` is an implementation of `SpanProcessor` that
    batches ended spans and pushes them to the configured `SpanExporter`.

    `BatchSpanProcessor` is configurable with the following environment
    variables which correspond to constructor parameters:

    - :envvar:`OTEL_BSP_SCHEDULE_DELAY`
    - :envvar:`OTEL_BSP_MAX_QUEUE_SIZE`
    - :envvar:`OTEL_BSP_MAX_EXPORT_BATCH_SIZE`
    - :envvar:`OTEL_BSP_EXPORT_TIMEOUT`
    """

    def __init__(
        self,
        span_exporter: SpanExporter,
        max_queue_size: int = None,
        schedule_delay_millis: float = None,
        max_export_batch_size: int = None,
        export_timeout_millis: float = None,
    ):
        if max_queue_size is None:
            max_queue_size = BatchSpanProcessor._default_max_queue_size()

        if schedule_delay_millis is None:
            schedule_delay_millis = BatchSpanProcessor._default_schedule_delay_millis()

        if max_export_batch_size is None:
            max_export_batch_size = BatchSpanProcessor._default_max_export_batch_size()

        if export_timeout_millis is None:
            export_timeout_millis = BatchSpanProcessor._default_export_timeout_millis()

        BatchSpanProcessor._validate_arguments(
            max_queue_size, schedule_delay_millis, max_export_batch_size
        )

        self.span_exporter = span_exporter
        self.queue = collections.deque([], max_queue_size)  # type: typing.Deque[Span]
        self.worker_thread = threading.Thread(
            name="OtelBatchSpanProcessor", target=self.worker, daemon=True
        )
        self.condition = threading.Condition(threading.Lock())
        self._flush_request = None  # type: typing.Optional[_FlushRequest]
        self.schedule_delay_millis = schedule_delay_millis
        self.max_export_batch_size = max_export_batch_size
        self.max_queue_size = max_queue_size
        self.export_timeout_millis = export_timeout_millis
        self.done = False
        # flag that indicates that spans are being dropped
        self._spans_dropped = False
        # precallocated list to send spans to exporter
        self.spans_list = [
            None
        ] * self.max_export_batch_size  # type: typing.List[typing.Optional[Span]]
        self.worker_thread.start()
        # Only available in *nix since py37.
        if hasattr(os, "register_at_fork"):
            os.register_at_fork(
                after_in_child=self._at_fork_reinit
            )  # pylint: disable=protected-access
        self._pid = os.getpid()

    def on_start(
        self, span: Span, parent_context: typing.Optional[Context] = None
    ) -> None:
        pass

    def on_end(self, span: ReadableSpan) -> None:
        if self.done:
            logger.warning("Already shutdown, dropping span.")
            return
        if not span.context.trace_flags.sampled:
            return
        if self._pid != os.getpid():
            _BSP_RESET_ONCE.do_once(self._at_fork_reinit)

        if len(self.queue) == self.max_queue_size:
            if not self._spans_dropped:
                logger.warning("Queue is full, likely spans will be dropped.")
                self._spans_dropped = True

        self.queue.appendleft(span)

        if len(self.queue) >= self.max_export_batch_size:
            with self.condition:
                self.condition.notify()

    def _at_fork_reinit(self):
        self.condition = threading.Condition(threading.Lock())
        self.queue.clear()

        # worker_thread is local to a process, only the thread that issued fork continues
        # to exist. A new worker thread must be started in child process.
        self.worker_thread = threading.Thread(
            name="OtelBatchSpanProcessor", target=self.worker, daemon=True
        )
        self.worker_thread.start()
        self._pid = os.getpid()

    def worker(self):
        timeout = self.schedule_delay_millis / 1e3
        flush_request = None  # type: typing.Optional[_FlushRequest]
        while not self.done:
            with self.condition:
                if self.done:
                    # done flag may have changed, avoid waiting
                    break
                flush_request = self._get_and_unset_flush_request()
                if (
                    len(self.queue) < self.max_export_batch_size
                    and flush_request is None
                ):
                    self.condition.wait(timeout)
                    flush_request = self._get_and_unset_flush_request()
                    if not self.queue:
                        # spurious notification, let's wait again, reset timeout
                        timeout = self.schedule_delay_millis / 1e3
                        self._notify_flush_request_finished(flush_request)
                        flush_request = None
                        continue
                    if self.done:
                        # missing spans will be sent when calling flush
                        break

            # subtract the duration of this export call to the next timeout
            start = time_ns()
            self._export(flush_request)
            end = time_ns()
            duration = (end - start) / 1e9
            timeout = self.schedule_delay_millis / 1e3 - duration

            self._notify_flush_request_finished(flush_request)
            flush_request = None

        # there might have been a new flush request while export was running
        # and before the done flag switched to true
        with self.condition:
            shutdown_flush_request = self._get_and_unset_flush_request()

        # be sure that all spans are sent
        self._drain_queue()
        self._notify_flush_request_finished(flush_request)
        self._notify_flush_request_finished(shutdown_flush_request)

    def _get_and_unset_flush_request(
        self,
    ) -> typing.Optional[_FlushRequest]:
        """Returns the current flush request and makes it invisible to the
        worker thread for subsequent calls.
        """
        flush_request = self._flush_request
        self._flush_request = None
        if flush_request is not None:
            flush_request.num_spans = len(self.queue)
        return flush_request

    @staticmethod
    def _notify_flush_request_finished(
        flush_request: typing.Optional[_FlushRequest],
    ):
        """Notifies the flush initiator(s) waiting on the given request/event
        that the flush operation was finished.
        """
        if flush_request is not None:
            flush_request.event.set()

    def _get_or_create_flush_request(self) -> _FlushRequest:
        """Either returns the current active flush event or creates a new one.

        The flush event will be visible and read by the worker thread before an
        export operation starts. Callers of a flush operation may wait on the
        returned event to be notified when the flush/export operation was
        finished.

        This method is not thread-safe, i.e. callers need to take care about
        synchronization/locking.
        """
        if self._flush_request is None:
            self._flush_request = _FlushRequest()
        return self._flush_request

    def _export(self, flush_request: typing.Optional[_FlushRequest]):
        """Exports spans considering the given flush_request.

        In case of a given flush_requests spans are exported in batches until
        the number of exported spans reached or exceeded the number of spans in
        the flush request.
        In no flush_request was given at most max_export_batch_size spans are
        exported.
        """
        if not flush_request:
            self._export_batch()
            return

        num_spans = flush_request.num_spans
        while self.queue:
            num_exported = self._export_batch()
            num_spans -= num_exported

            if num_spans <= 0:
                break

    def _export_batch(self) -> int:
        """Exports at most max_export_batch_size spans and returns the number of
        exported spans.
        """
        idx = 0
        # currently only a single thread acts as consumer, so queue.pop() will
        # not raise an exception
        while idx < self.max_export_batch_size and self.queue:
            self.spans_list[idx] = self.queue.pop()
            idx += 1
        token = attach(set_value(_SUPPRESS_INSTRUMENTATION_KEY, True))
        try:
            # Ignore type b/c the Optional[None]+slicing is too "clever"
            # for mypy
            self.span_exporter.export(self.spans_list[:idx])  # type: ignore
        except Exception:  # pylint: disable=broad-except
            logger.exception("Exception while exporting Span batch.")
        detach(token)

        # clean up list
        for index in range(idx):
            self.spans_list[index] = None
        return idx

    def _drain_queue(self):
        """Export all elements until queue is empty.

        Can only be called from the worker thread context because it invokes
        `export` that is not thread safe.
        """
        while self.queue:
            self._export_batch()

    def force_flush(self, timeout_millis: int = None) -> bool:
        if timeout_millis is None:
            timeout_millis = self.export_timeout_millis

        if self.done:
            logger.warning("Already shutdown, ignoring call to force_flush().")
            return True

        with self.condition:
            flush_request = self._get_or_create_flush_request()
            # signal the worker thread to flush and wait for it to finish
            self.condition.notify_all()

        # wait for token to be processed
        ret = flush_request.event.wait(timeout_millis / 1e3)
        if not ret:
            logger.warning("Timeout was exceeded in force_flush().")
        return ret

    def shutdown(self) -> None:
        # signal the worker thread to finish and then wait for it
        self.done = True
        with self.condition:
            self.condition.notify_all()
        self.worker_thread.join()
        self.span_exporter.shutdown()

    @staticmethod
    def _default_max_queue_size():
        try:
            return int(environ.get(OTEL_BSP_MAX_QUEUE_SIZE, _DEFAULT_MAX_QUEUE_SIZE))
        except ValueError:
            logger.exception(
                _ENV_VAR_INT_VALUE_ERROR_MESSAGE,
                OTEL_BSP_MAX_QUEUE_SIZE,
                _DEFAULT_MAX_QUEUE_SIZE,
            )
            return _DEFAULT_MAX_QUEUE_SIZE

    @staticmethod
    def _default_schedule_delay_millis():
        try:
            return int(
                environ.get(OTEL_BSP_SCHEDULE_DELAY, _DEFAULT_SCHEDULE_DELAY_MILLIS)
            )
        except ValueError:
            logger.exception(
                _ENV_VAR_INT_VALUE_ERROR_MESSAGE,
                OTEL_BSP_SCHEDULE_DELAY,
                _DEFAULT_SCHEDULE_DELAY_MILLIS,
            )
            return _DEFAULT_SCHEDULE_DELAY_MILLIS

    @staticmethod
    def _default_max_export_batch_size():
        try:
            return int(
                environ.get(
                    OTEL_BSP_MAX_EXPORT_BATCH_SIZE,
                    _DEFAULT_MAX_EXPORT_BATCH_SIZE,
                )
            )
        except ValueError:
            logger.exception(
                _ENV_VAR_INT_VALUE_ERROR_MESSAGE,
                OTEL_BSP_MAX_EXPORT_BATCH_SIZE,
                _DEFAULT_MAX_EXPORT_BATCH_SIZE,
            )
            return _DEFAULT_MAX_EXPORT_BATCH_SIZE

    @staticmethod
    def _default_export_timeout_millis():
        try:
            return int(
                environ.get(OTEL_BSP_EXPORT_TIMEOUT, _DEFAULT_EXPORT_TIMEOUT_MILLIS)
            )
        except ValueError:
            logger.exception(
                _ENV_VAR_INT_VALUE_ERROR_MESSAGE,
                OTEL_BSP_EXPORT_TIMEOUT,
                _DEFAULT_EXPORT_TIMEOUT_MILLIS,
            )
            return _DEFAULT_EXPORT_TIMEOUT_MILLIS

    @staticmethod
    def _validate_arguments(
        max_queue_size, schedule_delay_millis, max_export_batch_size
    ):
        if max_queue_size <= 0:
            raise ValueError("max_queue_size must be a positive integer.")

        if schedule_delay_millis <= 0:
            raise ValueError("schedule_delay_millis must be positive.")

        if max_export_batch_size <= 0:
            raise ValueError("max_export_batch_size must be a positive integer.")

        if max_export_batch_size > max_queue_size:
            raise ValueError(
                "max_export_batch_size must be less than or equal to max_queue_size."
            )


class ConsoleSpanExporter(SpanExporter):
    """Implementation of :class:`SpanExporter` that prints spans to the
    console.

    This class can be used for diagnostic purposes. It prints the exported
    spans to the console STDOUT.
    """

    def __init__(
        self,
        service_name: Optional[str] = None,
        out: typing.IO = sys.stdout,
        formatter: typing.Callable[[ReadableSpan], str] = lambda span: span.to_json()
        + linesep,
    ):
        self.out = out
        self.formatter = formatter
        self.service_name = service_name

    def export(self, spans: typing.Sequence[ReadableSpan]) -> SpanExportResult:
        for span in spans:
            self.out.write(self.formatter(span))
        self.out.flush()
        return SpanExportResult.SUCCESS

    def force_flush(self, timeout_millis: int = 30000) -> bool:
        return True

virt/lib/python3.9/site-packages/mysql/opentelemetry/sdk/trace/export/__init__ 3.py
PrinceDisant/LostInNL
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import collections
import logging
import os
import sys
import threading
import typing

from enum import Enum
from os import environ, linesep
from time import time_ns
from typing import Optional

from mysql.opentelemetry.context import (
    _SUPPRESS_INSTRUMENTATION_KEY,
    Context,
    attach,
    detach,
    set_value,
)
from mysql.opentelemetry.sdk.environment_variables import (
    OTEL_BSP_EXPORT_TIMEOUT,
    OTEL_BSP_MAX_EXPORT_BATCH_SIZE,
    OTEL_BSP_MAX_QUEUE_SIZE,
    OTEL_BSP_SCHEDULE_DELAY,
)
from mysql.opentelemetry.sdk.trace import ReadableSpan, Span, SpanProcessor
from mysql.opentelemetry.util._once import Once

_DEFAULT_SCHEDULE_DELAY_MILLIS = 5000
_DEFAULT_MAX_EXPORT_BATCH_SIZE = 512
_DEFAULT_EXPORT_TIMEOUT_MILLIS = 30000
_DEFAULT_MAX_QUEUE_SIZE = 2048
_ENV_VAR_INT_VALUE_ERROR_MESSAGE = (
    "Unable to parse value for %s as integer. Defaulting to %s."
)

logger = logging.getLogger(__name__)


class SpanExportResult(Enum):
    SUCCESS = 0
    FAILURE = 1


class SpanExporter:
    """Interface for exporting spans.

    Interface to be implemented by services that want to export spans recorded
    in their own format.

    To export data this MUST be registered to the :class`mysql.opentelemetry.sdk.trace.Tracer` using a
    `SimpleSpanProcessor` or a `BatchSpanProcessor`.
    """

    def export(self, spans: typing.Sequence[ReadableSpan]) -> "SpanExportResult":
        """Exports a batch of telemetry data.

        Args:
            spans: The list of `mysql.opentelemetry.trace.Span` objects to be exported

        Returns:
            The result of the export
        """

    def shutdown(self) -> None:
        """Shuts down the exporter.

        Called when the SDK is shut down.
        """

    def force_flush(self, timeout_millis: int = 30000) -> bool:
        """Hint to ensure that the export of any spans the exporter has received
        prior to the call to ForceFlush SHOULD be completed as soon as possible, preferably
        before returning from this method.
        """


class SimpleSpanProcessor(SpanProcessor):
    """Simple SpanProcessor implementation.

    SimpleSpanProcessor is an implementation of `SpanProcessor` that
    passes ended spans directly to the configured `SpanExporter`.
    """

    def __init__(self, span_exporter: SpanExporter):
        self.span_exporter = span_exporter

    def on_start(
        self, span: Span, parent_context: typing.Optional[Context] = None
    ) -> None:
        pass

    def on_end(self, span: ReadableSpan) -> None:
        if not span.context.trace_flags.sampled:
            return
        token = attach(set_value(_SUPPRESS_INSTRUMENTATION_KEY, True))
        try:
            self.span_exporter.export((span,))
        # pylint: disable=broad-except
        except Exception:
            logger.exception("Exception while exporting Span.")
        detach(token)

    def shutdown(self) -> None:
        self.span_exporter.shutdown()

    def force_flush(self, timeout_millis: int = 30000) -> bool:
        # pylint: disable=unused-argument
        return True


class _FlushRequest:
    """Represents a request for the BatchSpanProcessor to flush spans."""

    __slots__ = ["event", "num_spans"]

    def __init__(self):
        self.event = threading.Event()
        self.num_spans = 0


_BSP_RESET_ONCE = Once()


class BatchSpanProcessor(SpanProcessor):
    """Batch span processor implementation.

    `BatchSpanProcessor` is an implementation of `SpanProcessor` that
    batches ended spans and pushes them to the configured `SpanExporter`.

    `BatchSpanProcessor` is configurable with the following environment
    variables which correspond to constructor parameters:

    - :envvar:`OTEL_BSP_SCHEDULE_DELAY`
    - :envvar:`OTEL_BSP_MAX_QUEUE_SIZE`
    - :envvar:`OTEL_BSP_MAX_EXPORT_BATCH_SIZE`
    - :envvar:`OTEL_BSP_EXPORT_TIMEOUT`
    """

    def __init__(
        self,
        span_exporter: SpanExporter,
        max_queue_size: int = None,
        schedule_delay_millis: float = None,
        max_export_batch_size: int = None,
        export_timeout_millis: float = None,
    ):
        if max_queue_size is None:
            max_queue_size = BatchSpanProcessor._default_max_queue_size()

        if schedule_delay_millis is None:
            schedule_delay_millis = BatchSpanProcessor._default_schedule_delay_millis()

        if max_export_batch_size is None:
            max_export_batch_size = BatchSpanProcessor._default_max_export_batch_size()

        if export_timeout_millis is None:
            export_timeout_millis = BatchSpanProcessor._default_export_timeout_millis()

        BatchSpanProcessor._validate_arguments(
            max_queue_size, schedule_delay_millis, max_export_batch_size
        )

        self.span_exporter = span_exporter
        self.queue = collections.deque([], max_queue_size)  # type: typing.Deque[Span]
        self.worker_thread = threading.Thread(
            name="OtelBatchSpanProcessor", target=self.worker, daemon=True
        )
        self.condition = threading.Condition(threading.Lock())
        self._flush_request = None  # type: typing.Optional[_FlushRequest]
        self.schedule_delay_millis = schedule_delay_millis
        self.max_export_batch_size = max_export_batch_size
        self.max_queue_size = max_queue_size
        self.export_timeout_millis = export_timeout_millis
        self.done = False
        # flag that indicates that spans are being dropped
        self._spans_dropped = False
        # precallocated list to send spans to exporter
        self.spans_list = [
            None
        ] * self.max_export_batch_size  # type: typing.List[typing.Optional[Span]]
        self.worker_thread.start()
        # Only available in *nix since py37.
        if hasattr(os, "register_at_fork"):
            os.register_at_fork(
                after_in_child=self._at_fork_reinit
            )  # pylint: disable=protected-access
        self._pid = os.getpid()

    def on_start(
        self, span: Span, parent_context: typing.Optional[Context] = None
    ) -> None:
        pass

    def on_end(self, span: ReadableSpan) -> None:
        if self.done:
            logger.warning("Already shutdown, dropping span.")
            return
        if not span.context.trace_flags.sampled:
            return
        if self._pid != os.getpid():
            _BSP_RESET_ONCE.do_once(self._at_fork_reinit)

        if len(self.queue) == self.max_queue_size:
            if not self._spans_dropped:
                logger.warning("Queue is full, likely spans will be dropped.")
                self._spans_dropped = True

        self.queue.appendleft(span)

        if len(self.queue) >= self.max_export_batch_size:
            with self.condition:
                self.condition.notify()

    def _at_fork_reinit(self):
        self.condition = threading.Condition(threading.Lock())
        self.queue.clear()

        # worker_thread is local to a process, only the thread that issued fork continues
        # to exist. A new worker thread must be started in child process.
        self.worker_thread = threading.Thread(
            name="OtelBatchSpanProcessor", target=self.worker, daemon=True
        )
        self.worker_thread.start()
        self._pid = os.getpid()

    def worker(self):
        timeout = self.schedule_delay_millis / 1e3
        flush_request = None  # type: typing.Optional[_FlushRequest]
        while not self.done:
            with self.condition:
                if self.done:
                    # done flag may have changed, avoid waiting
                    break
                flush_request = self._get_and_unset_flush_request()
                if (
                    len(self.queue) < self.max_export_batch_size
                    and flush_request is None
                ):
                    self.condition.wait(timeout)
                    flush_request = self._get_and_unset_flush_request()
                    if not self.queue:
                        # spurious notification, let's wait again, reset timeout
                        timeout = self.schedule_delay_millis / 1e3
                        self._notify_flush_request_finished(flush_request)
                        flush_request = None
                        continue
                    if self.done:
                        # missing spans will be sent when calling flush
                        break

            # subtract the duration of this export call to the next timeout
            start = time_ns()
            self._export(flush_request)
            end = time_ns()
            duration = (end - start) / 1e9
            timeout = self.schedule_delay_millis / 1e3 - duration

            self._notify_flush_request_finished(flush_request)
            flush_request = None

        # there might have been a new flush request while export was running
        # and before the done flag switched to true
        with self.condition:
            shutdown_flush_request = self._get_and_unset_flush_request()

        # be sure that all spans are sent
        self._drain_queue()
        self._notify_flush_request_finished(flush_request)
        self._notify_flush_request_finished(shutdown_flush_request)

    def _get_and_unset_flush_request(
        self,
    ) -> typing.Optional[_FlushRequest]:
        """Returns the current flush request and makes it invisible to the
        worker thread for subsequent calls.
        """
        flush_request = self._flush_request
        self._flush_request = None
        if flush_request is not None:
            flush_request.num_spans = len(self.queue)
        return flush_request

    @staticmethod
    def _notify_flush_request_finished(
        flush_request: typing.Optional[_FlushRequest],
    ):
        """Notifies the flush initiator(s) waiting on the given request/event
        that the flush operation was finished.
        """
        if flush_request is not None:
            flush_request.event.set()

    def _get_or_create_flush_request(self) -> _FlushRequest:
        """Either returns the current active flush event or creates a new one.

        The flush event will be visible and read by the worker thread before an
        export operation starts. Callers of a flush operation may wait on the
        returned event to be notified when the flush/export operation was
        finished.

        This method is not thread-safe, i.e. callers need to take care about
        synchronization/locking.
        """
        if self._flush_request is None:
            self._flush_request = _FlushRequest()
        return self._flush_request

    def _export(self, flush_request: typing.Optional[_FlushRequest]):
        """Exports spans considering the given flush_request.

        In case of a given flush_requests spans are exported in batches until
        the number of exported spans reached or exceeded the number of spans in
        the flush request.
        In no flush_request was given at most max_export_batch_size spans are
        exported.
        """
        if not flush_request:
            self._export_batch()
            return

        num_spans = flush_request.num_spans
        while self.queue:
            num_exported = self._export_batch()
            num_spans -= num_exported

            if num_spans <= 0:
                break

    def _export_batch(self) -> int:
        """Exports at most max_export_batch_size spans and returns the number of
        exported spans.
        """
        idx = 0
        # currently only a single thread acts as consumer, so queue.pop() will
        # not raise an exception
        while idx < self.max_export_batch_size and self.queue:
            self.spans_list[idx] = self.queue.pop()
            idx += 1
        token = attach(set_value(_SUPPRESS_INSTRUMENTATION_KEY, True))
        try:
            # Ignore type b/c the Optional[None]+slicing is too "clever"
            # for mypy
            self.span_exporter.export(self.spans_list[:idx])  # type: ignore
        except Exception:  # pylint: disable=broad-except
            logger.exception("Exception while exporting Span batch.")
        detach(token)

        # clean up list
        for index in range(idx):
            self.spans_list[index] = None
        return idx

    def _drain_queue(self):
        """Export all elements until queue is empty.

        Can only be called from the worker thread context because it invokes
        `export` that is not thread safe.
        """
        while self.queue:
            self._export_batch()

    def force_flush(self, timeout_millis: int = None) -> bool:
        if timeout_millis is None:
            timeout_millis = self.export_timeout_millis

        if self.done:
            logger.warning("Already shutdown, ignoring call to force_flush().")
            return True

        with self.condition:
            flush_request = self._get_or_create_flush_request()
            # signal the worker thread to flush and wait for it to finish
            self.condition.notify_all()

        # wait for token to be processed
        ret = flush_request.event.wait(timeout_millis / 1e3)
        if not ret:
            logger.warning("Timeout was exceeded in force_flush().")
        return ret

    def shutdown(self) -> None:
        # signal the worker thread to finish and then wait for it
        self.done = True
        with self.condition:
            self.condition.notify_all()
        self.worker_thread.join()
        self.span_exporter.shutdown()

    @staticmethod
    def _default_max_queue_size():
        try:
            return int(environ.get(OTEL_BSP_MAX_QUEUE_SIZE, _DEFAULT_MAX_QUEUE_SIZE))
        except ValueError:
            logger.exception(
                _ENV_VAR_INT_VALUE_ERROR_MESSAGE,
                OTEL_BSP_MAX_QUEUE_SIZE,
                _DEFAULT_MAX_QUEUE_SIZE,
            )
            return _DEFAULT_MAX_QUEUE_SIZE

    @staticmethod
    def _default_schedule_delay_millis():
        try:
            return int(
                environ.get(OTEL_BSP_SCHEDULE_DELAY, _DEFAULT_SCHEDULE_DELAY_MILLIS)
            )
        except ValueError:
            logger.exception(
                _ENV_VAR_INT_VALUE_ERROR_MESSAGE,
                OTEL_BSP_SCHEDULE_DELAY,
                _DEFAULT_SCHEDULE_DELAY_MILLIS,
            )
            return _DEFAULT_SCHEDULE_DELAY_MILLIS

    @staticmethod
    def _default_max_export_batch_size():
        try:
            return int(
                environ.get(
                    OTEL_BSP_MAX_EXPORT_BATCH_SIZE,
                    _DEFAULT_MAX_EXPORT_BATCH_SIZE,
                )
            )
        except ValueError:
            logger.exception(
                _ENV_VAR_INT_VALUE_ERROR_MESSAGE,
                OTEL_BSP_MAX_EXPORT_BATCH_SIZE,
                _DEFAULT_MAX_EXPORT_BATCH_SIZE,
            )
            return _DEFAULT_MAX_EXPORT_BATCH_SIZE

    @staticmethod
    def _default_export_timeout_millis():
        try:
            return int(
                environ.get(OTEL_BSP_EXPORT_TIMEOUT, _DEFAULT_EXPORT_TIMEOUT_MILLIS)
            )
        except ValueError:
            logger.exception(
                _ENV_VAR_INT_VALUE_ERROR_MESSAGE,
                OTEL_BSP_EXPORT_TIMEOUT,
                _DEFAULT_EXPORT_TIMEOUT_MILLIS,
            )
            return _DEFAULT_EXPORT_TIMEOUT_MILLIS

    @staticmethod
    def _validate_arguments(
        max_queue_size, schedule_delay_millis, max_export_batch_size
    ):
        if max_queue_size <= 0:
            raise ValueError("max_queue_size must be a positive integer.")

        if schedule_delay_millis <= 0:
            raise ValueError("schedule_delay_millis must be positive.")

        if max_export_batch_size <= 0:
            raise ValueError("max_export_batch_size must be a positive integer.")

        if max_export_batch_size > max_queue_size:
            raise ValueError(
                "max_export_batch_size must be less than or equal to max_queue_size."
            )


class ConsoleSpanExporter(SpanExporter):
    """Implementation of :class:`SpanExporter` that prints spans to the
    console.

    This class can be used for diagnostic purposes. It prints the exported
    spans to the console STDOUT.
    """

    def __init__(
        self,
        service_name: Optional[str] = None,
        out: typing.IO = sys.stdout,
        formatter: typing.Callable[[ReadableSpan], str] = lambda span: span.to_json()
        + linesep,
    ):
        self.out = out
        self.formatter = formatter
        self.service_name = service_name

    def export(self, spans: typing.Sequence[ReadableSpan]) -> SpanExportResult:
        for span in spans:
            self.out.write(self.formatter(span))
        self.out.flush()
        return SpanExportResult.SUCCESS

    def force_flush(self, timeout_millis: int = 30000) -> bool:
        return True

virt/lib/python3.9/site-packages/mysql/opentelemetry/sdk/trace/export/__init__.py
PrinceDisant/LostInNL
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import threading
import typing

from mysql.opentelemetry.sdk.trace import ReadableSpan
from mysql.opentelemetry.sdk.trace.export import SpanExporter, SpanExportResult


class InMemorySpanExporter(SpanExporter):
    """Implementation of :class:`.SpanExporter` that stores spans in memory.

    This class can be used for testing purposes. It stores the exported spans
    in a list in memory that can be retrieved using the
    :func:`.get_finished_spans` method.
    """

    def __init__(self):
        self._finished_spans = []
        self._stopped = False
        self._lock = threading.Lock()

    def clear(self):
        """Clear list of collected spans."""
        with self._lock:
            self._finished_spans.clear()

    def get_finished_spans(self):
        """Get list of collected spans."""
        with self._lock:
            return tuple(self._finished_spans)

    def export(self, spans: typing.Sequence[ReadableSpan]) -> SpanExportResult:
        """Stores a list of spans in memory."""
        if self._stopped:
            return SpanExportResult.FAILURE
        with self._lock:
            self._finished_spans.extend(spans)
        return SpanExportResult.SUCCESS

    def shutdown(self):
        """Shut downs the exporter.

        Calls to export after the exporter has been shut down will fail.
        """
        self._stopped = True

    def force_flush(self, timeout_millis: int = 30000) -> bool:
        return True

virt/lib/python3.9/site-packages/mysql/opentelemetry/sdk/trace/export/in_memory_span_exporter.py
PrinceDisant/LostInNL
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""
For general information about sampling, see `the specification <https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/sdk.md#sampling>`_.

OpenTelemetry provides two types of samplers:

- `StaticSampler`
- `TraceIdRatioBased`

A `StaticSampler` always returns the same sampling result regardless of the conditions. Both possible StaticSamplers are already created:

- Always sample spans: ALWAYS_ON
- Never sample spans: ALWAYS_OFF

A `TraceIdRatioBased` sampler makes a random sampling result based on the sampling probability given.

If the span being sampled has a parent, `ParentBased` will respect the parent delegate sampler. Otherwise, it returns the sampling result from the given root sampler.

Currently, sampling results are always made during the creation of the span. However, this might not always be the case in the future (see `OTEP #115 <https://github.com/open-telemetry/oteps/pull/115>`_).

Custom samplers can be created by subclassing `Sampler` and implementing `Sampler.should_sample` as well as `Sampler.get_description`.

Samplers are able to modify the `mysql.opentelemetry.trace.span.TraceState` of the parent of the span being created. For custom samplers, it is suggested to implement `Sampler.should_sample` to utilize the
parent span context's `mysql.opentelemetry.trace.span.TraceState` and pass into the `SamplingResult` instead of the explicit trace_state field passed into the parameter of `Sampler.should_sample`.

To use a sampler, pass it into the tracer provider constructor. For example:

.. code:: python

    from opentelemetry import trace
    from mysql.opentelemetry.sdk.trace import TracerProvider
    from mysql.opentelemetry.sdk.trace.export import (
        ConsoleSpanExporter,
        SimpleSpanProcessor,
    )
    from mysql.opentelemetry.sdk.trace.sampling import TraceIdRatioBased

    # sample 1 in every 1000 traces
    sampler = TraceIdRatioBased(1/1000)

    # set the sampler onto the global tracer provider
    trace.set_tracer_provider(TracerProvider(sampler=sampler))

    # set up an exporter for sampled spans
    trace.get_tracer_provider().add_span_processor(
        SimpleSpanProcessor(ConsoleSpanExporter())
    )

    # created spans will now be sampled by the TraceIdRatioBased sampler
    with trace.get_tracer(__name__).start_as_current_span("Test Span"):
        ...

The tracer sampler can also be configured via environment variables ``OTEL_TRACES_SAMPLER`` and ``OTEL_TRACES_SAMPLER_ARG`` (only if applicable).
The list of built-in values for ``OTEL_TRACES_SAMPLER`` are:

    * always_on - Sampler that always samples spans, regardless of the parent span's sampling decision.
    * always_off - Sampler that never samples spans, regardless of the parent span's sampling decision.
    * traceidratio - Sampler that samples probabalistically based on rate.
    * parentbased_always_on - (default) Sampler that respects its parent span's sampling decision, but otherwise always samples.
    * parentbased_always_off - Sampler that respects its parent span's sampling decision, but otherwise never samples.
    * parentbased_traceidratio - Sampler that respects its parent span's sampling decision, but otherwise samples probabalistically based on rate.

Sampling probability can be set with ``OTEL_TRACES_SAMPLER_ARG`` if the sampler is traceidratio or parentbased_traceidratio. Rate must be in the range [0.0,1.0]. When not provided rate will be set to
1.0 (maximum rate possible).

Prev example but with environment variables. Please make sure to set the env ``OTEL_TRACES_SAMPLER=traceidratio`` and ``OTEL_TRACES_SAMPLER_ARG=0.001``.

.. code:: python

    from opentelemetry import trace
    from mysql.opentelemetry.sdk.trace import TracerProvider
    from mysql.opentelemetry.sdk.trace.export import (
        ConsoleSpanExporter,
        SimpleSpanProcessor,
    )

    trace.set_tracer_provider(TracerProvider())

    # set up an exporter for sampled spans
    trace.get_tracer_provider().add_span_processor(
        SimpleSpanProcessor(ConsoleSpanExporter())
    )

    # created spans will now be sampled by the TraceIdRatioBased sampler with rate 1/1000.
    with trace.get_tracer(__name__).start_as_current_span("Test Span"):
        ...

When utilizing a configurator, you can configure a custom sampler. In order to create a configurable custom sampler, create an entry point for the custom sampler
factory method or function under the entry point group, ``opentelemetry_traces_sampler``. The custom sampler factory method must be of type ``Callable[[str], Sampler]``, taking a single string argument and
returning a Sampler object. The single input will come from the string value of the ``OTEL_TRACES_SAMPLER_ARG`` environment variable. If ``OTEL_TRACES_SAMPLER_ARG`` is not configured, the input will
be an empty string. For example:

.. code:: python

    setup(
        ...
        entry_points={
            ...
            "opentelemetry_traces_sampler": [
                "custom_sampler_name = path.to.sampler.factory.method:CustomSamplerFactory.get_sampler"
            ]
        }
    )
    # ...
    class CustomRatioSampler(Sampler):
        def __init__(rate):
            # ...
    # ...
    class CustomSamplerFactory:
        @staticmethod
        get_sampler(sampler_argument):
            try:
                rate = float(sampler_argument)
                return CustomSampler(rate)
            except ValueError: # In case argument is empty string.
                return CustomSampler(0.5)

In order to configure you application with a custom sampler's entry point, set the ``OTEL_TRACES_SAMPLER`` environment variable to the key name of the entry point. For example, to configured the
above sampler, set ``OTEL_TRACES_SAMPLER=custom_sampler_name`` and ``OTEL_TRACES_SAMPLER_ARG=0.5``.
"""
import abc
import enum
import os

from logging import getLogger
from types import MappingProxyType
from typing import Optional, Sequence

# pylint: disable=unused-import
from mysql.opentelemetry.context import Context
from mysql.opentelemetry.sdk.environment_variables import (
    OTEL_TRACES_SAMPLER,
    OTEL_TRACES_SAMPLER_ARG,
)
from mysql.opentelemetry.trace import Link, SpanKind, get_current_span
from mysql.opentelemetry.trace.span import TraceState
from mysql.opentelemetry.util.types import Attributes

_logger = getLogger(__name__)


class Decision(enum.Enum):
    # IsRecording() == false, span will not be recorded and all events and attributes will be dropped.
    DROP = 0
    # IsRecording() == true, but Sampled flag MUST NOT be set.
    RECORD_ONLY = 1
    # IsRecording() == true AND Sampled flag` MUST be set.
    RECORD_AND_SAMPLE = 2

    def is_recording(self):
        return self in (Decision.RECORD_ONLY, Decision.RECORD_AND_SAMPLE)

    def is_sampled(self):
        return self is Decision.RECORD_AND_SAMPLE


class SamplingResult:
    """A sampling result as applied to a newly-created Span.

    Args:
        decision: A sampling decision based off of whether the span is recorded
            and the sampled flag in trace flags in the span context.
        attributes: Attributes to add to the `mysql.opentelemetry.trace.Span`.
        trace_state: The tracestate used for the `mysql.opentelemetry.trace.Span`.
            Could possibly have been modified by the sampler.
    """

    def __repr__(self) -> str:
        return f"{type(self).__name__}({str(self.decision)}, attributes={str(self.attributes)})"

    def __init__(
        self,
        decision: Decision,
        attributes: "Attributes" = None,
        trace_state: "TraceState" = None,
    ) -> None:
        self.decision = decision
        if attributes is None:
            self.attributes = MappingProxyType({})
        else:
            self.attributes = MappingProxyType(attributes)
        self.trace_state = trace_state


class Sampler(abc.ABC):
    @abc.abstractmethod
    def should_sample(
        self,
        parent_context: Optional["Context"],
        trace_id: int,
        name: str,
        kind: SpanKind = None,
        attributes: Attributes = None,
        links: Sequence["Link"] = None,
        trace_state: "TraceState" = None,
    ) -> "SamplingResult":
        pass

    @abc.abstractmethod
    def get_description(self) -> str:
        pass


class StaticSampler(Sampler):
    """Sampler that always returns the same decision."""

    def __init__(self, decision: "Decision"):
        self._decision = decision

    def should_sample(
        self,
        parent_context: Optional["Context"],
        trace_id: int,
        name: str,
        kind: SpanKind = None,
        attributes: Attributes = None,
        links: Sequence["Link"] = None,
        trace_state: "TraceState" = None,
    ) -> "SamplingResult":
        if self._decision is Decision.DROP:
            attributes = None
        return SamplingResult(
            self._decision,
            attributes,
            _get_parent_trace_state(parent_context),
        )

    def get_description(self) -> str:
        if self._decision is Decision.DROP:
            return "AlwaysOffSampler"
        return "AlwaysOnSampler"


ALWAYS_OFF = StaticSampler(Decision.DROP)
"""Sampler that never samples spans, regardless of the parent span's sampling decision."""

ALWAYS_ON = StaticSampler(Decision.RECORD_AND_SAMPLE)
"""Sampler that always samples spans, regardless of the parent span's sampling decision."""


class TraceIdRatioBased(Sampler):
    """
    Sampler that makes sampling decisions probabilistically based on `rate`.

    Args:
        rate: Probability (between 0 and 1) that a span will be sampled
    """

    def __init__(self, rate: float):
        if rate < 0.0 or rate > 1.0:
            raise ValueError("Probability must be in range [0.0, 1.0].")
        self._rate = rate
        self._bound = self.get_bound_for_rate(self._rate)

    # For compatibility with 64 bit trace IDs, the sampler checks the 64
    # low-order bits of the trace ID to decide whether to sample a given trace.
    TRACE_ID_LIMIT = (1 << 64) - 1

    @classmethod
    def get_bound_for_rate(cls, rate: float) -> int:
        return round(rate * (cls.TRACE_ID_LIMIT + 1))

    @property
    def rate(self) -> float:
        return self._rate

    @property
    def bound(self) -> int:
        return self._bound

    def should_sample(
        self,
        parent_context: Optional["Context"],
        trace_id: int,
        name: str,
        kind: SpanKind = None,
        attributes: Attributes = None,
        links: Sequence["Link"] = None,
        trace_state: "TraceState" = None,
    ) -> "SamplingResult":
        decision = Decision.DROP
        if trace_id & self.TRACE_ID_LIMIT < self.bound:
            decision = Decision.RECORD_AND_SAMPLE
        if decision is Decision.DROP:
            attributes = None
        return SamplingResult(
            decision,
            attributes,
            _get_parent_trace_state(parent_context),
        )

    def get_description(self) -> str:
        return f"TraceIdRatioBased{{{self._rate}}}"


class ParentBased(Sampler):
    """
    If a parent is set, applies the respective delegate sampler.
    Otherwise, uses the root provided at initialization to make a
    decision.

    Args:
        root: Sampler called for spans with no parent (root spans).
        remote_parent_sampled: Sampler called for a remote sampled parent.
        remote_parent_not_sampled: Sampler called for a remote parent that is
            not sampled.
        local_parent_sampled: Sampler called for a local sampled parent.
        local_parent_not_sampled: Sampler called for a local parent that is
            not sampled.
    """

    def __init__(
        self,
        root: Sampler,
        remote_parent_sampled: Sampler = ALWAYS_ON,
        remote_parent_not_sampled: Sampler = ALWAYS_OFF,
        local_parent_sampled: Sampler = ALWAYS_ON,
        local_parent_not_sampled: Sampler = ALWAYS_OFF,
    ):
        self._root = root
        self._remote_parent_sampled = remote_parent_sampled
        self._remote_parent_not_sampled = remote_parent_not_sampled
        self._local_parent_sampled = local_parent_sampled
        self._local_parent_not_sampled = local_parent_not_sampled

    def should_sample(
        self,
        parent_context: Optional["Context"],
        trace_id: int,
        name: str,
        kind: SpanKind = None,
        attributes: Attributes = None,
        links: Sequence["Link"] = None,
        trace_state: "TraceState" = None,
    ) -> "SamplingResult":
        parent_span_context = get_current_span(parent_context).get_span_context()
        # default to the root sampler
        sampler = self._root
        # respect the sampling and remote flag of the parent if present
        if parent_span_context is not None and parent_span_context.is_valid:
            if parent_span_context.is_remote:
                if parent_span_context.trace_flags.sampled:
                    sampler = self._remote_parent_sampled
                else:
                    sampler = self._remote_parent_not_sampled
            else:
                if parent_span_context.trace_flags.sampled:
                    sampler = self._local_parent_sampled
                else:
                    sampler = self._local_parent_not_sampled

        return sampler.should_sample(
            parent_context=parent_context,
            trace_id=trace_id,
            name=name,
            kind=kind,
            attributes=attributes,
            links=links,
        )

    def get_description(self):
        return f"ParentBased{{root:{self._root.get_description()},remoteParentSampled:{self._remote_parent_sampled.get_description()},remoteParentNotSampled:{self._remote_parent_not_sampled.get_description()},localParentSampled:{self._local_parent_sampled.get_description()},localParentNotSampled:{self._local_parent_not_sampled.get_description()}}}"


DEFAULT_OFF = ParentBased(ALWAYS_OFF)
"""Sampler that respects its parent span's sampling decision, but otherwise never samples."""

DEFAULT_ON = ParentBased(ALWAYS_ON)
"""Sampler that respects its parent span's sampling decision, but otherwise always samples."""


class ParentBasedTraceIdRatio(ParentBased):
    """
    Sampler that respects its parent span's sampling decision, but otherwise
    samples probabalistically based on `rate`.
    """

    def __init__(self, rate: float):
        root = TraceIdRatioBased(rate=rate)
        super().__init__(root=root)


class _AlwaysOff(StaticSampler):
    def __init__(self, _):
        super().__init__(Decision.DROP)


class _AlwaysOn(StaticSampler):
    def __init__(self, _):
        super().__init__(Decision.RECORD_AND_SAMPLE)


class _ParentBasedAlwaysOff(ParentBased):
    def __init__(self, _):
        super().__init__(ALWAYS_OFF)


class _ParentBasedAlwaysOn(ParentBased):
    def __init__(self, _):
        super().__init__(ALWAYS_ON)


_KNOWN_SAMPLERS = {
    "always_on": ALWAYS_ON,
    "always_off": ALWAYS_OFF,
    "parentbased_always_on": DEFAULT_ON,
    "parentbased_always_off": DEFAULT_OFF,
    "traceidratio": TraceIdRatioBased,
    "parentbased_traceidratio": ParentBasedTraceIdRatio,
}


def _get_from_env_or_default() -> Sampler:
    trace_sampler = os.getenv(OTEL_TRACES_SAMPLER, "parentbased_always_on").lower()
    if trace_sampler not in _KNOWN_SAMPLERS:
        _logger.warning("Couldn't recognize sampler %s.", trace_sampler)
        trace_sampler = "parentbased_always_on"

    if trace_sampler in ("traceidratio", "parentbased_traceidratio"):
        try:
            rate = float(os.getenv(OTEL_TRACES_SAMPLER_ARG))
        except (ValueError, TypeError):
            _logger.warning("Could not convert TRACES_SAMPLER_ARG to float.")
            rate = 1.0
        return _KNOWN_SAMPLERS[trace_sampler](rate)

    return _KNOWN_SAMPLERS[trace_sampler]


def _get_parent_trace_state(parent_context) -> Optional["TraceState"]:
    parent_span_context = get_current_span(parent_context).get_span_context()
    if parent_span_context is None or not parent_span_context.is_valid:
        return None
    return parent_span_context.trace_state

virt/lib/python3.9/site-packages/mysql/opentelemetry/sdk/trace/sampling 2.py
PrinceDisant/LostInNL
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""
For general information about sampling, see `the specification <https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/sdk.md#sampling>`_.

OpenTelemetry provides two types of samplers:

- `StaticSampler`
- `TraceIdRatioBased`

A `StaticSampler` always returns the same sampling result regardless of the conditions. Both possible StaticSamplers are already created:

- Always sample spans: ALWAYS_ON
- Never sample spans: ALWAYS_OFF

A `TraceIdRatioBased` sampler makes a random sampling result based on the sampling probability given.

If the span being sampled has a parent, `ParentBased` will respect the parent delegate sampler. Otherwise, it returns the sampling result from the given root sampler.

Currently, sampling results are always made during the creation of the span. However, this might not always be the case in the future (see `OTEP #115 <https://github.com/open-telemetry/oteps/pull/115>`_).

Custom samplers can be created by subclassing `Sampler` and implementing `Sampler.should_sample` as well as `Sampler.get_description`.

Samplers are able to modify the `mysql.opentelemetry.trace.span.TraceState` of the parent of the span being created. For custom samplers, it is suggested to implement `Sampler.should_sample` to utilize the
parent span context's `mysql.opentelemetry.trace.span.TraceState` and pass into the `SamplingResult` instead of the explicit trace_state field passed into the parameter of `Sampler.should_sample`.

To use a sampler, pass it into the tracer provider constructor. For example:

.. code:: python

    from opentelemetry import trace
    from mysql.opentelemetry.sdk.trace import TracerProvider
    from mysql.opentelemetry.sdk.trace.export import (
        ConsoleSpanExporter,
        SimpleSpanProcessor,
    )
    from mysql.opentelemetry.sdk.trace.sampling import TraceIdRatioBased

    # sample 1 in every 1000 traces
    sampler = TraceIdRatioBased(1/1000)

    # set the sampler onto the global tracer provider
    trace.set_tracer_provider(TracerProvider(sampler=sampler))

    # set up an exporter for sampled spans
    trace.get_tracer_provider().add_span_processor(
        SimpleSpanProcessor(ConsoleSpanExporter())
    )

    # created spans will now be sampled by the TraceIdRatioBased sampler
    with trace.get_tracer(__name__).start_as_current_span("Test Span"):
        ...

The tracer sampler can also be configured via environment variables ``OTEL_TRACES_SAMPLER`` and ``OTEL_TRACES_SAMPLER_ARG`` (only if applicable).
The list of built-in values for ``OTEL_TRACES_SAMPLER`` are:

    * always_on - Sampler that always samples spans, regardless of the parent span's sampling decision.
    * always_off - Sampler that never samples spans, regardless of the parent span's sampling decision.
    * traceidratio - Sampler that samples probabalistically based on rate.
    * parentbased_always_on - (default) Sampler that respects its parent span's sampling decision, but otherwise always samples.
    * parentbased_always_off - Sampler that respects its parent span's sampling decision, but otherwise never samples.
    * parentbased_traceidratio - Sampler that respects its parent span's sampling decision, but otherwise samples probabalistically based on rate.

Sampling probability can be set with ``OTEL_TRACES_SAMPLER_ARG`` if the sampler is traceidratio or parentbased_traceidratio. Rate must be in the range [0.0,1.0]. When not provided rate will be set to
1.0 (maximum rate possible).

Prev example but with environment variables. Please make sure to set the env ``OTEL_TRACES_SAMPLER=traceidratio`` and ``OTEL_TRACES_SAMPLER_ARG=0.001``.

.. code:: python

    from opentelemetry import trace
    from mysql.opentelemetry.sdk.trace import TracerProvider
    from mysql.opentelemetry.sdk.trace.export import (
        ConsoleSpanExporter,
        SimpleSpanProcessor,
    )

    trace.set_tracer_provider(TracerProvider())

    # set up an exporter for sampled spans
    trace.get_tracer_provider().add_span_processor(
        SimpleSpanProcessor(ConsoleSpanExporter())
    )

    # created spans will now be sampled by the TraceIdRatioBased sampler with rate 1/1000.
    with trace.get_tracer(__name__).start_as_current_span("Test Span"):
        ...

When utilizing a configurator, you can configure a custom sampler. In order to create a configurable custom sampler, create an entry point for the custom sampler
factory method or function under the entry point group, ``opentelemetry_traces_sampler``. The custom sampler factory method must be of type ``Callable[[str], Sampler]``, taking a single string argument and
returning a Sampler object. The single input will come from the string value of the ``OTEL_TRACES_SAMPLER_ARG`` environment variable. If ``OTEL_TRACES_SAMPLER_ARG`` is not configured, the input will
be an empty string. For example:

.. code:: python

    setup(
        ...
        entry_points={
            ...
            "opentelemetry_traces_sampler": [
                "custom_sampler_name = path.to.sampler.factory.method:CustomSamplerFactory.get_sampler"
            ]
        }
    )
    # ...
    class CustomRatioSampler(Sampler):
        def __init__(rate):
            # ...
    # ...
    class CustomSamplerFactory:
        @staticmethod
        get_sampler(sampler_argument):
            try:
                rate = float(sampler_argument)
                return CustomSampler(rate)
            except ValueError: # In case argument is empty string.
                return CustomSampler(0.5)

In order to configure you application with a custom sampler's entry point, set the ``OTEL_TRACES_SAMPLER`` environment variable to the key name of the entry point. For example, to configured the
above sampler, set ``OTEL_TRACES_SAMPLER=custom_sampler_name`` and ``OTEL_TRACES_SAMPLER_ARG=0.5``.
"""
import abc
import enum
import os

from logging import getLogger
from types import MappingProxyType
from typing import Optional, Sequence

# pylint: disable=unused-import
from mysql.opentelemetry.context import Context
from mysql.opentelemetry.sdk.environment_variables import (
    OTEL_TRACES_SAMPLER,
    OTEL_TRACES_SAMPLER_ARG,
)
from mysql.opentelemetry.trace import Link, SpanKind, get_current_span
from mysql.opentelemetry.trace.span import TraceState
from mysql.opentelemetry.util.types import Attributes

_logger = getLogger(__name__)


class Decision(enum.Enum):
    # IsRecording() == false, span will not be recorded and all events and attributes will be dropped.
    DROP = 0
    # IsRecording() == true, but Sampled flag MUST NOT be set.
    RECORD_ONLY = 1
    # IsRecording() == true AND Sampled flag` MUST be set.
    RECORD_AND_SAMPLE = 2

    def is_recording(self):
        return self in (Decision.RECORD_ONLY, Decision.RECORD_AND_SAMPLE)

    def is_sampled(self):
        return self is Decision.RECORD_AND_SAMPLE


class SamplingResult:
    """A sampling result as applied to a newly-created Span.

    Args:
        decision: A sampling decision based off of whether the span is recorded
            and the sampled flag in trace flags in the span context.
        attributes: Attributes to add to the `mysql.opentelemetry.trace.Span`.
        trace_state: The tracestate used for the `mysql.opentelemetry.trace.Span`.
            Could possibly have been modified by the sampler.
    """

    def __repr__(self) -> str:
        return f"{type(self).__name__}({str(self.decision)}, attributes={str(self.attributes)})"

    def __init__(
        self,
        decision: Decision,
        attributes: "Attributes" = None,
        trace_state: "TraceState" = None,
    ) -> None:
        self.decision = decision
        if attributes is None:
            self.attributes = MappingProxyType({})
        else:
            self.attributes = MappingProxyType(attributes)
        self.trace_state = trace_state


class Sampler(abc.ABC):
    @abc.abstractmethod
    def should_sample(
        self,
        parent_context: Optional["Context"],
        trace_id: int,
        name: str,
        kind: SpanKind = None,
        attributes: Attributes = None,
        links: Sequence["Link"] = None,
        trace_state: "TraceState" = None,
    ) -> "SamplingResult":
        pass

    @abc.abstractmethod
    def get_description(self) -> str:
        pass


class StaticSampler(Sampler):
    """Sampler that always returns the same decision."""

    def __init__(self, decision: "Decision"):
        self._decision = decision

    def should_sample(
        self,
        parent_context: Optional["Context"],
        trace_id: int,
        name: str,
        kind: SpanKind = None,
        attributes: Attributes = None,
        links: Sequence["Link"] = None,
        trace_state: "TraceState" = None,
    ) -> "SamplingResult":
        if self._decision is Decision.DROP:
            attributes = None
        return SamplingResult(
            self._decision,
            attributes,
            _get_parent_trace_state(parent_context),
        )

    def get_description(self) -> str:
        if self._decision is Decision.DROP:
            return "AlwaysOffSampler"
        return "AlwaysOnSampler"


ALWAYS_OFF = StaticSampler(Decision.DROP)
"""Sampler that never samples spans, regardless of the parent span's sampling decision."""

ALWAYS_ON = StaticSampler(Decision.RECORD_AND_SAMPLE)
"""Sampler that always samples spans, regardless of the parent span's sampling decision."""


class TraceIdRatioBased(Sampler):
    """
    Sampler that makes sampling decisions probabilistically based on `rate`.

    Args:
        rate: Probability (between 0 and 1) that a span will be sampled
    """

    def __init__(self, rate: float):
        if rate < 0.0 or rate > 1.0:
            raise ValueError("Probability must be in range [0.0, 1.0].")
        self._rate = rate
        self._bound = self.get_bound_for_rate(self._rate)

    # For compatibility with 64 bit trace IDs, the sampler checks the 64
    # low-order bits of the trace ID to decide whether to sample a given trace.
    TRACE_ID_LIMIT = (1 << 64) - 1

    @classmethod
    def get_bound_for_rate(cls, rate: float) -> int:
        return round(rate * (cls.TRACE_ID_LIMIT + 1))

    @property
    def rate(self) -> float:
        return self._rate

    @property
    def bound(self) -> int:
        return self._bound

    def should_sample(
        self,
        parent_context: Optional["Context"],
        trace_id: int,
        name: str,
        kind: SpanKind = None,
        attributes: Attributes = None,
        links: Sequence["Link"] = None,
        trace_state: "TraceState" = None,
    ) -> "SamplingResult":
        decision = Decision.DROP
        if trace_id & self.TRACE_ID_LIMIT < self.bound:
            decision = Decision.RECORD_AND_SAMPLE
        if decision is Decision.DROP:
            attributes = None
        return SamplingResult(
            decision,
            attributes,
            _get_parent_trace_state(parent_context),
        )

    def get_description(self) -> str:
        return f"TraceIdRatioBased{{{self._rate}}}"


class ParentBased(Sampler):
    """
    If a parent is set, applies the respective delegate sampler.
    Otherwise, uses the root provided at initialization to make a
    decision.

    Args:
        root: Sampler called for spans with no parent (root spans).
        remote_parent_sampled: Sampler called for a remote sampled parent.
        remote_parent_not_sampled: Sampler called for a remote parent that is
            not sampled.
        local_parent_sampled: Sampler called for a local sampled parent.
        local_parent_not_sampled: Sampler called for a local parent that is
            not sampled.
    """

    def __init__(
        self,
        root: Sampler,
        remote_parent_sampled: Sampler = ALWAYS_ON,
        remote_parent_not_sampled: Sampler = ALWAYS_OFF,
        local_parent_sampled: Sampler = ALWAYS_ON,
        local_parent_not_sampled: Sampler = ALWAYS_OFF,
    ):
        self._root = root
        self._remote_parent_sampled = remote_parent_sampled
        self._remote_parent_not_sampled = remote_parent_not_sampled
        self._local_parent_sampled = local_parent_sampled
        self._local_parent_not_sampled = local_parent_not_sampled

    def should_sample(
        self,
        parent_context: Optional["Context"],
        trace_id: int,
        name: str,
        kind: SpanKind = None,
        attributes: Attributes = None,
        links: Sequence["Link"] = None,
        trace_state: "TraceState" = None,
    ) -> "SamplingResult":
        parent_span_context = get_current_span(parent_context).get_span_context()
        # default to the root sampler
        sampler = self._root
        # respect the sampling and remote flag of the parent if present
        if parent_span_context is not None and parent_span_context.is_valid:
            if parent_span_context.is_remote:
                if parent_span_context.trace_flags.sampled:
                    sampler = self._remote_parent_sampled
                else:
                    sampler = self._remote_parent_not_sampled
            else:
                if parent_span_context.trace_flags.sampled:
                    sampler = self._local_parent_sampled
                else:
                    sampler = self._local_parent_not_sampled

        return sampler.should_sample(
            parent_context=parent_context,
            trace_id=trace_id,
            name=name,
            kind=kind,
            attributes=attributes,
            links=links,
        )

    def get_description(self):
        return f"ParentBased{{root:{self._root.get_description()},remoteParentSampled:{self._remote_parent_sampled.get_description()},remoteParentNotSampled:{self._remote_parent_not_sampled.get_description()},localParentSampled:{self._local_parent_sampled.get_description()},localParentNotSampled:{self._local_parent_not_sampled.get_description()}}}"


DEFAULT_OFF = ParentBased(ALWAYS_OFF)
"""Sampler that respects its parent span's sampling decision, but otherwise never samples."""

DEFAULT_ON = ParentBased(ALWAYS_ON)
"""Sampler that respects its parent span's sampling decision, but otherwise always samples."""


class ParentBasedTraceIdRatio(ParentBased):
    """
    Sampler that respects its parent span's sampling decision, but otherwise
    samples probabalistically based on `rate`.
    """

    def __init__(self, rate: float):
        root = TraceIdRatioBased(rate=rate)
        super().__init__(root=root)


class _AlwaysOff(StaticSampler):
    def __init__(self, _):
        super().__init__(Decision.DROP)


class _AlwaysOn(StaticSampler):
    def __init__(self, _):
        super().__init__(Decision.RECORD_AND_SAMPLE)


class _ParentBasedAlwaysOff(ParentBased):
    def __init__(self, _):
        super().__init__(ALWAYS_OFF)


class _ParentBasedAlwaysOn(ParentBased):
    def __init__(self, _):
        super().__init__(ALWAYS_ON)


_KNOWN_SAMPLERS = {
    "always_on": ALWAYS_ON,
    "always_off": ALWAYS_OFF,
    "parentbased_always_on": DEFAULT_ON,
    "parentbased_always_off": DEFAULT_OFF,
    "traceidratio": TraceIdRatioBased,
    "parentbased_traceidratio": ParentBasedTraceIdRatio,
}


def _get_from_env_or_default() -> Sampler:
    trace_sampler = os.getenv(OTEL_TRACES_SAMPLER, "parentbased_always_on").lower()
    if trace_sampler not in _KNOWN_SAMPLERS:
        _logger.warning("Couldn't recognize sampler %s.", trace_sampler)
        trace_sampler = "parentbased_always_on"

    if trace_sampler in ("traceidratio", "parentbased_traceidratio"):
        try:
            rate = float(os.getenv(OTEL_TRACES_SAMPLER_ARG))
        except (ValueError, TypeError):
            _logger.warning("Could not convert TRACES_SAMPLER_ARG to float.")
            rate = 1.0
        return _KNOWN_SAMPLERS[trace_sampler](rate)

    return _KNOWN_SAMPLERS[trace_sampler]


def _get_parent_trace_state(parent_context) -> Optional["TraceState"]:
    parent_span_context = get_current_span(parent_context).get_span_context()
    if parent_span_context is None or not parent_span_context.is_valid:
        return None
    return parent_span_context.trace_state

virt/lib/python3.9/site-packages/mysql/opentelemetry/sdk/trace/sampling 3.py
PrinceDisant/LostInNL
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""
For general information about sampling, see `the specification <https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/sdk.md#sampling>`_.

OpenTelemetry provides two types of samplers:

- `StaticSampler`
- `TraceIdRatioBased`

A `StaticSampler` always returns the same sampling result regardless of the conditions. Both possible StaticSamplers are already created:

- Always sample spans: ALWAYS_ON
- Never sample spans: ALWAYS_OFF

A `TraceIdRatioBased` sampler makes a random sampling result based on the sampling probability given.

If the span being sampled has a parent, `ParentBased` will respect the parent delegate sampler. Otherwise, it returns the sampling result from the given root sampler.

Currently, sampling results are always made during the creation of the span. However, this might not always be the case in the future (see `OTEP #115 <https://github.com/open-telemetry/oteps/pull/115>`_).

Custom samplers can be created by subclassing `Sampler` and implementing `Sampler.should_sample` as well as `Sampler.get_description`.

Samplers are able to modify the `mysql.opentelemetry.trace.span.TraceState` of the parent of the span being created. For custom samplers, it is suggested to implement `Sampler.should_sample` to utilize the
parent span context's `mysql.opentelemetry.trace.span.TraceState` and pass into the `SamplingResult` instead of the explicit trace_state field passed into the parameter of `Sampler.should_sample`.

To use a sampler, pass it into the tracer provider constructor. For example:

.. code:: python

    from opentelemetry import trace
    from mysql.opentelemetry.sdk.trace import TracerProvider
    from mysql.opentelemetry.sdk.trace.export import (
        ConsoleSpanExporter,
        SimpleSpanProcessor,
    )
    from mysql.opentelemetry.sdk.trace.sampling import TraceIdRatioBased

    # sample 1 in every 1000 traces
    sampler = TraceIdRatioBased(1/1000)

    # set the sampler onto the global tracer provider
    trace.set_tracer_provider(TracerProvider(sampler=sampler))

    # set up an exporter for sampled spans
    trace.get_tracer_provider().add_span_processor(
        SimpleSpanProcessor(ConsoleSpanExporter())
    )

    # created spans will now be sampled by the TraceIdRatioBased sampler
    with trace.get_tracer(__name__).start_as_current_span("Test Span"):
        ...

The tracer sampler can also be configured via environment variables ``OTEL_TRACES_SAMPLER`` and ``OTEL_TRACES_SAMPLER_ARG`` (only if applicable).
The list of built-in values for ``OTEL_TRACES_SAMPLER`` are:

    * always_on - Sampler that always samples spans, regardless of the parent span's sampling decision.
    * always_off - Sampler that never samples spans, regardless of the parent span's sampling decision.
    * traceidratio - Sampler that samples probabalistically based on rate.
    * parentbased_always_on - (default) Sampler that respects its parent span's sampling decision, but otherwise always samples.
    * parentbased_always_off - Sampler that respects its parent span's sampling decision, but otherwise never samples.
    * parentbased_traceidratio - Sampler that respects its parent span's sampling decision, but otherwise samples probabalistically based on rate.

Sampling probability can be set with ``OTEL_TRACES_SAMPLER_ARG`` if the sampler is traceidratio or parentbased_traceidratio. Rate must be in the range [0.0,1.0]. When not provided rate will be set to
1.0 (maximum rate possible).

Prev example but with environment variables. Please make sure to set the env ``OTEL_TRACES_SAMPLER=traceidratio`` and ``OTEL_TRACES_SAMPLER_ARG=0.001``.

.. code:: python

    from opentelemetry import trace
    from mysql.opentelemetry.sdk.trace import TracerProvider
    from mysql.opentelemetry.sdk.trace.export import (
        ConsoleSpanExporter,
        SimpleSpanProcessor,
    )

    trace.set_tracer_provider(TracerProvider())

    # set up an exporter for sampled spans
    trace.get_tracer_provider().add_span_processor(
        SimpleSpanProcessor(ConsoleSpanExporter())
    )

    # created spans will now be sampled by the TraceIdRatioBased sampler with rate 1/1000.
    with trace.get_tracer(__name__).start_as_current_span("Test Span"):
        ...

When utilizing a configurator, you can configure a custom sampler. In order to create a configurable custom sampler, create an entry point for the custom sampler
factory method or function under the entry point group, ``opentelemetry_traces_sampler``. The custom sampler factory method must be of type ``Callable[[str], Sampler]``, taking a single string argument and
returning a Sampler object. The single input will come from the string value of the ``OTEL_TRACES_SAMPLER_ARG`` environment variable. If ``OTEL_TRACES_SAMPLER_ARG`` is not configured, the input will
be an empty string. For example:

.. code:: python

    setup(
        ...
        entry_points={
            ...
            "opentelemetry_traces_sampler": [
                "custom_sampler_name = path.to.sampler.factory.method:CustomSamplerFactory.get_sampler"
            ]
        }
    )
    # ...
    class CustomRatioSampler(Sampler):
        def __init__(rate):
            # ...
    # ...
    class CustomSamplerFactory:
        @staticmethod
        get_sampler(sampler_argument):
            try:
                rate = float(sampler_argument)
                return CustomSampler(rate)
            except ValueError: # In case argument is empty string.
                return CustomSampler(0.5)

In order to configure you application with a custom sampler's entry point, set the ``OTEL_TRACES_SAMPLER`` environment variable to the key name of the entry point. For example, to configured the
above sampler, set ``OTEL_TRACES_SAMPLER=custom_sampler_name`` and ``OTEL_TRACES_SAMPLER_ARG=0.5``.
"""
import abc
import enum
import os

from logging import getLogger
from types import MappingProxyType
from typing import Optional, Sequence

# pylint: disable=unused-import
from mysql.opentelemetry.context import Context
from mysql.opentelemetry.sdk.environment_variables import (
    OTEL_TRACES_SAMPLER,
    OTEL_TRACES_SAMPLER_ARG,
)
from mysql.opentelemetry.trace import Link, SpanKind, get_current_span
from mysql.opentelemetry.trace.span import TraceState
from mysql.opentelemetry.util.types import Attributes

_logger = getLogger(__name__)


class Decision(enum.Enum):
    # IsRecording() == false, span will not be recorded and all events and attributes will be dropped.
    DROP = 0
    # IsRecording() == true, but Sampled flag MUST NOT be set.
    RECORD_ONLY = 1
    # IsRecording() == true AND Sampled flag` MUST be set.
    RECORD_AND_SAMPLE = 2

    def is_recording(self):
        return self in (Decision.RECORD_ONLY, Decision.RECORD_AND_SAMPLE)

    def is_sampled(self):
        return self is Decision.RECORD_AND_SAMPLE


class SamplingResult:
    """A sampling result as applied to a newly-created Span.

    Args:
        decision: A sampling decision based off of whether the span is recorded
            and the sampled flag in trace flags in the span context.
        attributes: Attributes to add to the `mysql.opentelemetry.trace.Span`.
        trace_state: The tracestate used for the `mysql.opentelemetry.trace.Span`.
            Could possibly have been modified by the sampler.
    """

    def __repr__(self) -> str:
        return f"{type(self).__name__}({str(self.decision)}, attributes={str(self.attributes)})"

    def __init__(
        self,
        decision: Decision,
        attributes: "Attributes" = None,
        trace_state: "TraceState" = None,
    ) -> None:
        self.decision = decision
        if attributes is None:
            self.attributes = MappingProxyType({})
        else:
            self.attributes = MappingProxyType(attributes)
        self.trace_state = trace_state


class Sampler(abc.ABC):
    @abc.abstractmethod
    def should_sample(
        self,
        parent_context: Optional["Context"],
        trace_id: int,
        name: str,
        kind: SpanKind = None,
        attributes: Attributes = None,
        links: Sequence["Link"] = None,
        trace_state: "TraceState" = None,
    ) -> "SamplingResult":
        pass

    @abc.abstractmethod
    def get_description(self) -> str:
        pass


class StaticSampler(Sampler):
    """Sampler that always returns the same decision."""

    def __init__(self, decision: "Decision"):
        self._decision = decision

    def should_sample(
        self,
        parent_context: Optional["Context"],
        trace_id: int,
        name: str,
        kind: SpanKind = None,
        attributes: Attributes = None,
        links: Sequence["Link"] = None,
        trace_state: "TraceState" = None,
    ) -> "SamplingResult":
        if self._decision is Decision.DROP:
            attributes = None
        return SamplingResult(
            self._decision,
            attributes,
            _get_parent_trace_state(parent_context),
        )

    def get_description(self) -> str:
        if self._decision is Decision.DROP:
            return "AlwaysOffSampler"
        return "AlwaysOnSampler"


ALWAYS_OFF = StaticSampler(Decision.DROP)
"""Sampler that never samples spans, regardless of the parent span's sampling decision."""

ALWAYS_ON = StaticSampler(Decision.RECORD_AND_SAMPLE)
"""Sampler that always samples spans, regardless of the parent span's sampling decision."""


class TraceIdRatioBased(Sampler):
    """
    Sampler that makes sampling decisions probabilistically based on `rate`.

    Args:
        rate: Probability (between 0 and 1) that a span will be sampled
    """

    def __init__(self, rate: float):
        if rate < 0.0 or rate > 1.0:
            raise ValueError("Probability must be in range [0.0, 1.0].")
        self._rate = rate
        self._bound = self.get_bound_for_rate(self._rate)

    # For compatibility with 64 bit trace IDs, the sampler checks the 64
    # low-order bits of the trace ID to decide whether to sample a given trace.
    TRACE_ID_LIMIT = (1 << 64) - 1

    @classmethod
    def get_bound_for_rate(cls, rate: float) -> int:
        return round(rate * (cls.TRACE_ID_LIMIT + 1))

    @property
    def rate(self) -> float:
        return self._rate

    @property
    def bound(self) -> int:
        return self._bound

    def should_sample(
        self,
        parent_context: Optional["Context"],
        trace_id: int,
        name: str,
        kind: SpanKind = None,
        attributes: Attributes = None,
        links: Sequence["Link"] = None,
        trace_state: "TraceState" = None,
    ) -> "SamplingResult":
        decision = Decision.DROP
        if trace_id & self.TRACE_ID_LIMIT < self.bound:
            decision = Decision.RECORD_AND_SAMPLE
        if decision is Decision.DROP:
            attributes = None
        return SamplingResult(
            decision,
            attributes,
            _get_parent_trace_state(parent_context),
        )

    def get_description(self) -> str:
        return f"TraceIdRatioBased{{{self._rate}}}"


class ParentBased(Sampler):
    """
    If a parent is set, applies the respective delegate sampler.
    Otherwise, uses the root provided at initialization to make a
    decision.

    Args:
        root: Sampler called for spans with no parent (root spans).
        remote_parent_sampled: Sampler called for a remote sampled parent.
        remote_parent_not_sampled: Sampler called for a remote parent that is
            not sampled.
        local_parent_sampled: Sampler called for a local sampled parent.
        local_parent_not_sampled: Sampler called for a local parent that is
            not sampled.
    """

    def __init__(
        self,
        root: Sampler,
        remote_parent_sampled: Sampler = ALWAYS_ON,
        remote_parent_not_sampled: Sampler = ALWAYS_OFF,
        local_parent_sampled: Sampler = ALWAYS_ON,
        local_parent_not_sampled: Sampler = ALWAYS_OFF,
    ):
        self._root = root
        self._remote_parent_sampled = remote_parent_sampled
        self._remote_parent_not_sampled = remote_parent_not_sampled
        self._local_parent_sampled = local_parent_sampled
        self._local_parent_not_sampled = local_parent_not_sampled

    def should_sample(
        self,
        parent_context: Optional["Context"],
        trace_id: int,
        name: str,
        kind: SpanKind = None,
        attributes: Attributes = None,
        links: Sequence["Link"] = None,
        trace_state: "TraceState" = None,
    ) -> "SamplingResult":
        parent_span_context = get_current_span(parent_context).get_span_context()
        # default to the root sampler
        sampler = self._root
        # respect the sampling and remote flag of the parent if present
        if parent_span_context is not None and parent_span_context.is_valid:
            if parent_span_context.is_remote:
                if parent_span_context.trace_flags.sampled:
                    sampler = self._remote_parent_sampled
                else:
                    sampler = self._remote_parent_not_sampled
            else:
                if parent_span_context.trace_flags.sampled:
                    sampler = self._local_parent_sampled
                else:
                    sampler = self._local_parent_not_sampled

        return sampler.should_sample(
            parent_context=parent_context,
            trace_id=trace_id,
            name=name,
            kind=kind,
            attributes=attributes,
            links=links,
        )

    def get_description(self):
        return f"ParentBased{{root:{self._root.get_description()},remoteParentSampled:{self._remote_parent_sampled.get_description()},remoteParentNotSampled:{self._remote_parent_not_sampled.get_description()},localParentSampled:{self._local_parent_sampled.get_description()},localParentNotSampled:{self._local_parent_not_sampled.get_description()}}}"


DEFAULT_OFF = ParentBased(ALWAYS_OFF)
"""Sampler that respects its parent span's sampling decision, but otherwise never samples."""

DEFAULT_ON = ParentBased(ALWAYS_ON)
"""Sampler that respects its parent span's sampling decision, but otherwise always samples."""


class ParentBasedTraceIdRatio(ParentBased):
    """
    Sampler that respects its parent span's sampling decision, but otherwise
    samples probabalistically based on `rate`.
    """

    def __init__(self, rate: float):
        root = TraceIdRatioBased(rate=rate)
        super().__init__(root=root)


class _AlwaysOff(StaticSampler):
    def __init__(self, _):
        super().__init__(Decision.DROP)


class _AlwaysOn(StaticSampler):
    def __init__(self, _):
        super().__init__(Decision.RECORD_AND_SAMPLE)


class _ParentBasedAlwaysOff(ParentBased):
    def __init__(self, _):
        super().__init__(ALWAYS_OFF)


class _ParentBasedAlwaysOn(ParentBased):
    def __init__(self, _):
        super().__init__(ALWAYS_ON)


_KNOWN_SAMPLERS = {
    "always_on": ALWAYS_ON,
    "always_off": ALWAYS_OFF,
    "parentbased_always_on": DEFAULT_ON,
    "parentbased_always_off": DEFAULT_OFF,
    "traceidratio": TraceIdRatioBased,
    "parentbased_traceidratio": ParentBasedTraceIdRatio,
}


def _get_from_env_or_default() -> Sampler:
    trace_sampler = os.getenv(OTEL_TRACES_SAMPLER, "parentbased_always_on").lower()
    if trace_sampler not in _KNOWN_SAMPLERS:
        _logger.warning("Couldn't recognize sampler %s.", trace_sampler)
        trace_sampler = "parentbased_always_on"

    if trace_sampler in ("traceidratio", "parentbased_traceidratio"):
        try:
            rate = float(os.getenv(OTEL_TRACES_SAMPLER_ARG))
        except (ValueError, TypeError):
            _logger.warning("Could not convert TRACES_SAMPLER_ARG to float.")
            rate = 1.0
        return _KNOWN_SAMPLERS[trace_sampler](rate)

    return _KNOWN_SAMPLERS[trace_sampler]


def _get_parent_trace_state(parent_context) -> Optional["TraceState"]:
    parent_span_context = get_current_span(parent_context).get_span_context()
    if parent_span_context is None or not parent_span_context.is_valid:
        return None
    return parent_span_context.trace_state

virt/lib/python3.9/site-packages/mysql/opentelemetry/sdk/trace/sampling.py
PrinceDisant/LostInNL
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""
The OpenTelemetry tracing API describes the classes used to generate
distributed traces.

The :class:`.Tracer` class controls access to the execution context, and
manages span creation. Each operation in a trace is represented by a
:class:`.Span`, which records the start, end time, and metadata associated with
the operation.

This module provides abstract (i.e. unimplemented) classes required for
tracing, and a concrete no-op :class:`.NonRecordingSpan` that allows applications
to use the API package alone without a supporting implementation.

To get a tracer, you need to provide the package name from which you are
calling the tracer APIs to OpenTelemetry by calling `TracerProvider.get_tracer`
with the calling module name and the version of your package.

The tracer supports creating spans that are "attached" or "detached" from the
context. New spans are "attached" to the context in that they are
created as children of the currently active span, and the newly-created span
can optionally become the new active span::

    from opentelemetry import trace

    tracer = trace.get_tracer(__name__)

    # Create a new root span, set it as the current span in context
    with tracer.start_as_current_span("parent"):
        # Attach a new child and update the current span
        with tracer.start_as_current_span("child"):
            do_work():
        # Close child span, set parent as current
    # Close parent span, set default span as current

When creating a span that's "detached" from the context the active span doesn't
change, and the caller is responsible for managing the span's lifetime::

    # Explicit parent span assignment is done via the Context
    from mysql.opentelemetry.trace import set_span_in_context

    context = set_span_in_context(parent)
    child = tracer.start_span("child", context=context)

    try:
        do_work(span=child)
    finally:
        child.end()

Applications should generally use a single global TracerProvider, and use
either implicit or explicit context propagation consistently throughout.

.. versionadded:: 0.1.0
.. versionchanged:: 0.3.0
    `TracerProvider` was introduced and the global ``tracer`` getter was
    replaced by ``tracer_provider``.
.. versionchanged:: 0.5.0
    ``tracer_provider`` was replaced by `get_tracer_provider`,
    ``set_preferred_tracer_provider_implementation`` was replaced by
    `set_tracer_provider`.
"""

import os
import typing

from abc import ABC, abstractmethod
from contextlib import contextmanager
from enum import Enum
from logging import getLogger
from typing import Iterator, Optional, Sequence, cast

from deprecated import deprecated
from mysql.opentelemetry import context as context_api
from mysql.opentelemetry.attributes import BoundedAttributes  # type: ignore
from mysql.opentelemetry.context.context import Context
from mysql.opentelemetry.environment_variables import OTEL_PYTHON_TRACER_PROVIDER
from mysql.opentelemetry.trace.propagation import (
    _SPAN_KEY,
    get_current_span,
    set_span_in_context,
)
from mysql.opentelemetry.trace.span import (
    DEFAULT_TRACE_OPTIONS,
    DEFAULT_TRACE_STATE,
    INVALID_SPAN,
    INVALID_SPAN_CONTEXT,
    INVALID_SPAN_ID,
    INVALID_TRACE_ID,
    NonRecordingSpan,
    Span,
    SpanContext,
    TraceFlags,
    TraceState,
    format_span_id,
    format_trace_id,
)
from mysql.opentelemetry.trace.status import Status, StatusCode
from mysql.opentelemetry.util import types
from mysql.opentelemetry.util._once import Once
from mysql.opentelemetry.util._providers import _load_provider

logger = getLogger(__name__)


class _LinkBase(ABC):
    def __init__(self, context: "SpanContext") -> None:
        self._context = context

    @property
    def context(self) -> "SpanContext":
        return self._context

    @property
    @abstractmethod
    def attributes(self) -> types.Attributes:
        pass


class Link(_LinkBase):
    """A link to a `Span`. The attributes of a Link are immutable.

    Args:
        context: `SpanContext` of the `Span` to link to.
        attributes: Link's attributes.
    """

    def __init__(
        self,
        context: "SpanContext",
        attributes: types.Attributes = None,
    ) -> None:
        super().__init__(context)
        self._attributes = BoundedAttributes(
            attributes=attributes
        )  # type: types.Attributes

    @property
    def attributes(self) -> types.Attributes:
        return self._attributes


_Links = Optional[Sequence[Link]]


class SpanKind(Enum):
    """Specifies additional details on how this span relates to its parent span.

    Note that this enumeration is experimental and likely to change. See
    https://github.com/open-telemetry/opentelemetry-specification/pull/226.
    """

    #: Default value. Indicates that the span is used internally in the
    # application.
    INTERNAL = 0

    #: Indicates that the span describes an operation that handles a remote
    # request.
    SERVER = 1

    #: Indicates that the span describes a request to some remote service.
    CLIENT = 2

    #: Indicates that the span describes a producer sending a message to a
    #: broker. Unlike client and server, there is usually no direct critical
    #: path latency relationship between producer and consumer spans.
    PRODUCER = 3

    #: Indicates that the span describes a consumer receiving a message from a
    #: broker. Unlike client and server, there is usually no direct critical
    #: path latency relationship between producer and consumer spans.
    CONSUMER = 4


class TracerProvider(ABC):
    @abstractmethod
    def get_tracer(
        self,
        instrumenting_module_name: str,
        instrumenting_library_version: typing.Optional[str] = None,
        schema_url: typing.Optional[str] = None,
    ) -> "Tracer":
        """Returns a `Tracer` for use by the given instrumentation library.

        For any two calls it is undefined whether the same or different
        `Tracer` instances are returned, even for different library names.

        This function may return different `Tracer` types (e.g. a no-op tracer
        vs.  a functional tracer).

        Args:
            instrumenting_module_name: The uniquely identifiable name for instrumentation
                scope, such as instrumentation library, package, module or class name.
                ``__name__`` may not be used as this can result in
                different tracer names if the tracers are in different files.
                It is better to use a fixed string that can be imported where
                needed and used consistently as the name of the tracer.

                This should *not* be the name of the module that is
                instrumented but the name of the module doing the instrumentation.
                E.g., instead of ``"requests"``, use
                ``"mysql.opentelemetry.instrumentation.requests"``.

            instrumenting_library_version: Optional. The version string of the
                instrumenting library.  Usually this should be the same as
                ``importlib.metadata.version(instrumenting_library_name)``.

            schema_url: Optional. Specifies the Schema URL of the emitted telemetry.
        """


class NoOpTracerProvider(TracerProvider):
    """The default TracerProvider, used when no implementation is available.

    All operations are no-op.
    """

    def get_tracer(
        self,
        instrumenting_module_name: str,
        instrumenting_library_version: typing.Optional[str] = None,
        schema_url: typing.Optional[str] = None,
    ) -> "Tracer":
        # pylint:disable=no-self-use,unused-argument
        return NoOpTracer()


@deprecated(version="1.9.0", reason="You should use NoOpTracerProvider")  # type: ignore
class _DefaultTracerProvider(NoOpTracerProvider):
    """The default TracerProvider, used when no implementation is available.

    All operations are no-op.
    """


class ProxyTracerProvider(TracerProvider):
    def get_tracer(
        self,
        instrumenting_module_name: str,
        instrumenting_library_version: typing.Optional[str] = None,
        schema_url: typing.Optional[str] = None,
    ) -> "Tracer":
        if _TRACER_PROVIDER:
            return _TRACER_PROVIDER.get_tracer(
                instrumenting_module_name,
                instrumenting_library_version,
                schema_url,
            )
        return ProxyTracer(
            instrumenting_module_name,
            instrumenting_library_version,
            schema_url,
        )


class Tracer(ABC):
    """Handles span creation and in-process context propagation.

    This class provides methods for manipulating the context, creating spans,
    and controlling spans' lifecycles.
    """

    @abstractmethod
    def start_span(
        self,
        name: str,
        context: Optional[Context] = None,
        kind: SpanKind = SpanKind.INTERNAL,
        attributes: types.Attributes = None,
        links: _Links = None,
        start_time: Optional[int] = None,
        record_exception: bool = True,
        set_status_on_exception: bool = True,
    ) -> "Span":
        """Starts a span.

        Create a new span. Start the span without setting it as the current
        span in the context. To start the span and use the context in a single
        method, see :meth:`start_as_current_span`.

        By default the current span in the context will be used as parent, but an
        explicit context can also be specified, by passing in a `Context` containing
        a current `Span`. If there is no current span in the global `Context` or in
        the specified context, the created span will be a root span.

        The span can be used as a context manager. On exiting the context manager,
        the span's end() method will be called.

        Example::

            # trace.get_current_span() will be used as the implicit parent.
            # If none is found, the created span will be a root instance.
            with tracer.start_span("one") as child:
                child.add_event("child's event")

        Args:
            name: The name of the span to be created.
            context: An optional Context containing the span's parent. Defaults to the
                global context.
            kind: The span's kind (relationship to parent). Note that is
                meaningful even if there is no parent.
            attributes: The span's attributes.
            links: Links span to other spans
            start_time: Sets the start time of a span
            record_exception: Whether to record any exceptions raised within the
                context as error event on the span.
            set_status_on_exception: Only relevant if the returned span is used
                in a with/context manager. Defines whether the span status will
                be automatically set to ERROR when an uncaught exception is
                raised in the span with block. The span status won't be set by
                this mechanism if it was previously set manually.

        Returns:
            The newly-created span.
        """

    @contextmanager
    @abstractmethod
    def start_as_current_span(
        self,
        name: str,
        context: Optional[Context] = None,
        kind: SpanKind = SpanKind.INTERNAL,
        attributes: types.Attributes = None,
        links: _Links = None,
        start_time: Optional[int] = None,
        record_exception: bool = True,
        set_status_on_exception: bool = True,
        end_on_exit: bool = True,
    ) -> Iterator["Span"]:
        """Context manager for creating a new span and set it
        as the current span in this tracer's context.

        Exiting the context manager will call the span's end method,
        as well as return the current span to its previous value by
        returning to the previous context.

        Example::

            with tracer.start_as_current_span("one") as parent:
                parent.add_event("parent's event")
                with tracer.start_as_current_span("two") as child:
                    child.add_event("child's event")
                    trace.get_current_span()  # returns child
                trace.get_current_span()      # returns parent
            trace.get_current_span()          # returns previously active span

        This is a convenience method for creating spans attached to the
        tracer's context. Applications that need more control over the span
        lifetime should use :meth:`start_span` instead. For example::

            with tracer.start_as_current_span(name) as span:
                do_work()

        is equivalent to::

            span = tracer.start_span(name)
            with mysql.opentelemetry.trace.use_span(span, end_on_exit=True):
                do_work()

        This can also be used as a decorator::

            @tracer.start_as_current_span("name")
            def function():
                ...

            function()

        Args:
            name: The name of the span to be created.
            context: An optional Context containing the span's parent. Defaults to the
                global context.
            kind: The span's kind (relationship to parent). Note that is
                meaningful even if there is no parent.
            attributes: The span's attributes.
            links: Links span to other spans
            start_time: Sets the start time of a span
            record_exception: Whether to record any exceptions raised within the
                context as error event on the span.
            set_status_on_exception: Only relevant if the returned span is used
                in a with/context manager. Defines whether the span status will
                be automatically set to ERROR when an uncaught exception is
                raised in the span with block. The span status won't be set by
                this mechanism if it was previously set manually.
            end_on_exit: Whether to end the span automatically when leaving the
                context manager.

        Yields:
            The newly-created span.
        """


class ProxyTracer(Tracer):
    # pylint: disable=W0222,signature-differs
    def __init__(
        self,
        instrumenting_module_name: str,
        instrumenting_library_version: typing.Optional[str] = None,
        schema_url: typing.Optional[str] = None,
    ):
        self._instrumenting_module_name = instrumenting_module_name
        self._instrumenting_library_version = instrumenting_library_version
        self._schema_url = schema_url
        self._real_tracer: Optional[Tracer] = None
        self._noop_tracer = NoOpTracer()

    @property
    def _tracer(self) -> Tracer:
        if self._real_tracer:
            return self._real_tracer

        if _TRACER_PROVIDER:
            self._real_tracer = _TRACER_PROVIDER.get_tracer(
                self._instrumenting_module_name,
                self._instrumenting_library_version,
                self._schema_url,
            )
            return self._real_tracer
        return self._noop_tracer

    def start_span(self, *args, **kwargs) -> Span:  # type: ignore
        return self._tracer.start_span(*args, **kwargs)  # type: ignore

    @contextmanager  # type: ignore
    def start_as_current_span(self, *args, **kwargs) -> Iterator[Span]:  # type: ignore
        with self._tracer.start_as_current_span(*args, **kwargs) as span:  # type: ignore
            yield span


class NoOpTracer(Tracer):
    """The default Tracer, used when no Tracer implementation is available.

    All operations are no-op.
    """

    def start_span(
        self,
        name: str,
        context: Optional[Context] = None,
        kind: SpanKind = SpanKind.INTERNAL,
        attributes: types.Attributes = None,
        links: _Links = None,
        start_time: Optional[int] = None,
        record_exception: bool = True,
        set_status_on_exception: bool = True,
    ) -> "Span":
        # pylint: disable=unused-argument,no-self-use
        return INVALID_SPAN

    @contextmanager
    def start_as_current_span(
        self,
        name: str,
        context: Optional[Context] = None,
        kind: SpanKind = SpanKind.INTERNAL,
        attributes: types.Attributes = None,
        links: _Links = None,
        start_time: Optional[int] = None,
        record_exception: bool = True,
        set_status_on_exception: bool = True,
        end_on_exit: bool = True,
    ) -> Iterator["Span"]:
        # pylint: disable=unused-argument,no-self-use
        yield INVALID_SPAN


@deprecated(version="1.9.0", reason="You should use NoOpTracer")  # type: ignore
class _DefaultTracer(NoOpTracer):
    """The default Tracer, used when no Tracer implementation is available.

    All operations are no-op.
    """


_TRACER_PROVIDER_SET_ONCE = Once()
_TRACER_PROVIDER: Optional[TracerProvider] = None
_PROXY_TRACER_PROVIDER = ProxyTracerProvider()


def get_tracer(
    instrumenting_module_name: str,
    instrumenting_library_version: typing.Optional[str] = None,
    tracer_provider: Optional[TracerProvider] = None,
    schema_url: typing.Optional[str] = None,
) -> "Tracer":
    """Returns a `Tracer` for use by the given instrumentation library.

    This function is a convenience wrapper for
    mysql.opentelemetry.trace.TracerProvider.get_tracer.

    If tracer_provider is omitted the current configured one is used.
    """
    if tracer_provider is None:
        tracer_provider = get_tracer_provider()
    return tracer_provider.get_tracer(
        instrumenting_module_name, instrumenting_library_version, schema_url
    )


def _set_tracer_provider(tracer_provider: TracerProvider, log: bool) -> None:
    def set_tp() -> None:
        global _TRACER_PROVIDER  # pylint: disable=global-statement
        _TRACER_PROVIDER = tracer_provider

    did_set = _TRACER_PROVIDER_SET_ONCE.do_once(set_tp)

    if log and not did_set:
        logger.warning("Overriding of current TracerProvider is not allowed")


def set_tracer_provider(tracer_provider: TracerProvider) -> None:
    """Sets the current global :class:`~.TracerProvider` object.

    This can only be done once, a warning will be logged if any further attempt
    is made.
    """
    _set_tracer_provider(tracer_provider, log=True)


def get_tracer_provider() -> TracerProvider:
    """Gets the current global :class:`~.TracerProvider` object."""
    if _TRACER_PROVIDER is None:
        # if a global tracer provider has not been set either via code or env
        # vars, return a proxy tracer provider
        if OTEL_PYTHON_TRACER_PROVIDER not in os.environ:
            return _PROXY_TRACER_PROVIDER

        tracer_provider: TracerProvider = _load_provider(
            OTEL_PYTHON_TRACER_PROVIDER, "tracer_provider"
        )
        _set_tracer_provider(tracer_provider, log=False)
    # _TRACER_PROVIDER will have been set by one thread
    return cast("TracerProvider", _TRACER_PROVIDER)


@contextmanager
def use_span(
    span: Span,
    end_on_exit: bool = False,
    record_exception: bool = True,
    set_status_on_exception: bool = True,
) -> Iterator[Span]:
    """Takes a non-active span and activates it in the current context.

    Args:
        span: The span that should be activated in the current context.
        end_on_exit: Whether to end the span automatically when leaving the
            context manager scope.
        record_exception: Whether to record any exceptions raised within the
            context as error event on the span.
        set_status_on_exception: Only relevant if the returned span is used
            in a with/context manager. Defines whether the span status will
            be automatically set to ERROR when an uncaught exception is
            raised in the span with block. The span status won't be set by
            this mechanism if it was previously set manually.
    """
    try:
        token = context_api.attach(context_api.set_value(_SPAN_KEY, span))
        try:
            yield span
        finally:
            context_api.detach(token)

    except Exception as exc:  # pylint: disable=broad-except
        if isinstance(span, Span) and span.is_recording():
            # Record the exception as an event
            if record_exception:
                span.record_exception(exc)

            # Set status in case exception was raised
            if set_status_on_exception:
                span.set_status(
                    Status(
                        status_code=StatusCode.ERROR,
                        description=f"{type(exc).__name__}: {exc}",
                    )
                )
        raise

    finally:
        if end_on_exit:
            span.end()


__all__ = [
    "DEFAULT_TRACE_OPTIONS",
    "DEFAULT_TRACE_STATE",
    "INVALID_SPAN",
    "INVALID_SPAN_CONTEXT",
    "INVALID_SPAN_ID",
    "INVALID_TRACE_ID",
    "NonRecordingSpan",
    "Link",
    "Span",
    "SpanContext",
    "SpanKind",
    "TraceFlags",
    "TraceState",
    "TracerProvider",
    "Tracer",
    "format_span_id",
    "format_trace_id",
    "get_current_span",
    "get_tracer",
    "get_tracer_provider",
    "set_tracer_provider",
    "set_span_in_context",
    "use_span",
    "Status",
    "StatusCode",
]

virt/lib/python3.9/site-packages/mysql/opentelemetry/trace/__init__ 2.py
PrinceDisant/LostInNL
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""
The OpenTelemetry tracing API describes the classes used to generate
distributed traces.

The :class:`.Tracer` class controls access to the execution context, and
manages span creation. Each operation in a trace is represented by a
:class:`.Span`, which records the start, end time, and metadata associated with
the operation.

This module provides abstract (i.e. unimplemented) classes required for
tracing, and a concrete no-op :class:`.NonRecordingSpan` that allows applications
to use the API package alone without a supporting implementation.

To get a tracer, you need to provide the package name from which you are
calling the tracer APIs to OpenTelemetry by calling `TracerProvider.get_tracer`
with the calling module name and the version of your package.

The tracer supports creating spans that are "attached" or "detached" from the
context. New spans are "attached" to the context in that they are
created as children of the currently active span, and the newly-created span
can optionally become the new active span::

    from opentelemetry import trace

    tracer = trace.get_tracer(__name__)

    # Create a new root span, set it as the current span in context
    with tracer.start_as_current_span("parent"):
        # Attach a new child and update the current span
        with tracer.start_as_current_span("child"):
            do_work():
        # Close child span, set parent as current
    # Close parent span, set default span as current

When creating a span that's "detached" from the context the active span doesn't
change, and the caller is responsible for managing the span's lifetime::

    # Explicit parent span assignment is done via the Context
    from mysql.opentelemetry.trace import set_span_in_context

    context = set_span_in_context(parent)
    child = tracer.start_span("child", context=context)

    try:
        do_work(span=child)
    finally:
        child.end()

Applications should generally use a single global TracerProvider, and use
either implicit or explicit context propagation consistently throughout.

.. versionadded:: 0.1.0
.. versionchanged:: 0.3.0
    `TracerProvider` was introduced and the global ``tracer`` getter was
    replaced by ``tracer_provider``.
.. versionchanged:: 0.5.0
    ``tracer_provider`` was replaced by `get_tracer_provider`,
    ``set_preferred_tracer_provider_implementation`` was replaced by
    `set_tracer_provider`.
"""

import os
import typing

from abc import ABC, abstractmethod
from contextlib import contextmanager
from enum import Enum
from logging import getLogger
from typing import Iterator, Optional, Sequence, cast

from deprecated import deprecated
from mysql.opentelemetry import context as context_api
from mysql.opentelemetry.attributes import BoundedAttributes  # type: ignore
from mysql.opentelemetry.context.context import Context
from mysql.opentelemetry.environment_variables import OTEL_PYTHON_TRACER_PROVIDER
from mysql.opentelemetry.trace.propagation import (
    _SPAN_KEY,
    get_current_span,
    set_span_in_context,
)
from mysql.opentelemetry.trace.span import (
    DEFAULT_TRACE_OPTIONS,
    DEFAULT_TRACE_STATE,
    INVALID_SPAN,
    INVALID_SPAN_CONTEXT,
    INVALID_SPAN_ID,
    INVALID_TRACE_ID,
    NonRecordingSpan,
    Span,
    SpanContext,
    TraceFlags,
    TraceState,
    format_span_id,
    format_trace_id,
)
from mysql.opentelemetry.trace.status import Status, StatusCode
from mysql.opentelemetry.util import types
from mysql.opentelemetry.util._once import Once
from mysql.opentelemetry.util._providers import _load_provider

logger = getLogger(__name__)


class _LinkBase(ABC):
    def __init__(self, context: "SpanContext") -> None:
        self._context = context

    @property
    def context(self) -> "SpanContext":
        return self._context

    @property
    @abstractmethod
    def attributes(self) -> types.Attributes:
        pass


class Link(_LinkBase):
    """A link to a `Span`. The attributes of a Link are immutable.

    Args:
        context: `SpanContext` of the `Span` to link to.
        attributes: Link's attributes.
    """

    def __init__(
        self,
        context: "SpanContext",
        attributes: types.Attributes = None,
    ) -> None:
        super().__init__(context)
        self._attributes = BoundedAttributes(
            attributes=attributes
        )  # type: types.Attributes

    @property
    def attributes(self) -> types.Attributes:
        return self._attributes


_Links = Optional[Sequence[Link]]


class SpanKind(Enum):
    """Specifies additional details on how this span relates to its parent span.

    Note that this enumeration is experimental and likely to change. See
    https://github.com/open-telemetry/opentelemetry-specification/pull/226.
    """

    #: Default value. Indicates that the span is used internally in the
    # application.
    INTERNAL = 0

    #: Indicates that the span describes an operation that handles a remote
    # request.
    SERVER = 1

    #: Indicates that the span describes a request to some remote service.
    CLIENT = 2

    #: Indicates that the span describes a producer sending a message to a
    #: broker. Unlike client and server, there is usually no direct critical
    #: path latency relationship between producer and consumer spans.
    PRODUCER = 3

    #: Indicates that the span describes a consumer receiving a message from a
    #: broker. Unlike client and server, there is usually no direct critical
    #: path latency relationship between producer and consumer spans.
    CONSUMER = 4


class TracerProvider(ABC):
    @abstractmethod
    def get_tracer(
        self,
        instrumenting_module_name: str,
        instrumenting_library_version: typing.Optional[str] = None,
        schema_url: typing.Optional[str] = None,
    ) -> "Tracer":
        """Returns a `Tracer` for use by the given instrumentation library.

        For any two calls it is undefined whether the same or different
        `Tracer` instances are returned, even for different library names.

        This function may return different `Tracer` types (e.g. a no-op tracer
        vs.  a functional tracer).

        Args:
            instrumenting_module_name: The uniquely identifiable name for instrumentation
                scope, such as instrumentation library, package, module or class name.
                ``__name__`` may not be used as this can result in
                different tracer names if the tracers are in different files.
                It is better to use a fixed string that can be imported where
                needed and used consistently as the name of the tracer.

                This should *not* be the name of the module that is
                instrumented but the name of the module doing the instrumentation.
                E.g., instead of ``"requests"``, use
                ``"mysql.opentelemetry.instrumentation.requests"``.

            instrumenting_library_version: Optional. The version string of the
                instrumenting library.  Usually this should be the same as
                ``importlib.metadata.version(instrumenting_library_name)``.

            schema_url: Optional. Specifies the Schema URL of the emitted telemetry.
        """


class NoOpTracerProvider(TracerProvider):
    """The default TracerProvider, used when no implementation is available.

    All operations are no-op.
    """

    def get_tracer(
        self,
        instrumenting_module_name: str,
        instrumenting_library_version: typing.Optional[str] = None,
        schema_url: typing.Optional[str] = None,
    ) -> "Tracer":
        # pylint:disable=no-self-use,unused-argument
        return NoOpTracer()


@deprecated(version="1.9.0", reason="You should use NoOpTracerProvider")  # type: ignore
class _DefaultTracerProvider(NoOpTracerProvider):
    """The default TracerProvider, used when no implementation is available.

    All operations are no-op.
    """


class ProxyTracerProvider(TracerProvider):
    def get_tracer(
        self,
        instrumenting_module_name: str,
        instrumenting_library_version: typing.Optional[str] = None,
        schema_url: typing.Optional[str] = None,
    ) -> "Tracer":
        if _TRACER_PROVIDER:
            return _TRACER_PROVIDER.get_tracer(
                instrumenting_module_name,
                instrumenting_library_version,
                schema_url,
            )
        return ProxyTracer(
            instrumenting_module_name,
            instrumenting_library_version,
            schema_url,
        )


class Tracer(ABC):
    """Handles span creation and in-process context propagation.

    This class provides methods for manipulating the context, creating spans,
    and controlling spans' lifecycles.
    """

    @abstractmethod
    def start_span(
        self,
        name: str,
        context: Optional[Context] = None,
        kind: SpanKind = SpanKind.INTERNAL,
        attributes: types.Attributes = None,
        links: _Links = None,
        start_time: Optional[int] = None,
        record_exception: bool = True,
        set_status_on_exception: bool = True,
    ) -> "Span":
        """Starts a span.

        Create a new span. Start the span without setting it as the current
        span in the context. To start the span and use the context in a single
        method, see :meth:`start_as_current_span`.

        By default the current span in the context will be used as parent, but an
        explicit context can also be specified, by passing in a `Context` containing
        a current `Span`. If there is no current span in the global `Context` or in
        the specified context, the created span will be a root span.

        The span can be used as a context manager. On exiting the context manager,
        the span's end() method will be called.

        Example::

            # trace.get_current_span() will be used as the implicit parent.
            # If none is found, the created span will be a root instance.
            with tracer.start_span("one") as child:
                child.add_event("child's event")

        Args:
            name: The name of the span to be created.
            context: An optional Context containing the span's parent. Defaults to the
                global context.
            kind: The span's kind (relationship to parent). Note that is
                meaningful even if there is no parent.
            attributes: The span's attributes.
            links: Links span to other spans
            start_time: Sets the start time of a span
            record_exception: Whether to record any exceptions raised within the
                context as error event on the span.
            set_status_on_exception: Only relevant if the returned span is used
                in a with/context manager. Defines whether the span status will
                be automatically set to ERROR when an uncaught exception is
                raised in the span with block. The span status won't be set by
                this mechanism if it was previously set manually.

        Returns:
            The newly-created span.
        """

    @contextmanager
    @abstractmethod
    def start_as_current_span(
        self,
        name: str,
        context: Optional[Context] = None,
        kind: SpanKind = SpanKind.INTERNAL,
        attributes: types.Attributes = None,
        links: _Links = None,
        start_time: Optional[int] = None,
        record_exception: bool = True,
        set_status_on_exception: bool = True,
        end_on_exit: bool = True,
    ) -> Iterator["Span"]:
        """Context manager for creating a new span and set it
        as the current span in this tracer's context.

        Exiting the context manager will call the span's end method,
        as well as return the current span to its previous value by
        returning to the previous context.

        Example::

            with tracer.start_as_current_span("one") as parent:
                parent.add_event("parent's event")
                with tracer.start_as_current_span("two") as child:
                    child.add_event("child's event")
                    trace.get_current_span()  # returns child
                trace.get_current_span()      # returns parent
            trace.get_current_span()          # returns previously active span

        This is a convenience method for creating spans attached to the
        tracer's context. Applications that need more control over the span
        lifetime should use :meth:`start_span` instead. For example::

            with tracer.start_as_current_span(name) as span:
                do_work()

        is equivalent to::

            span = tracer.start_span(name)
            with mysql.opentelemetry.trace.use_span(span, end_on_exit=True):
                do_work()

        This can also be used as a decorator::

            @tracer.start_as_current_span("name")
            def function():
                ...

            function()

        Args:
            name: The name of the span to be created.
            context: An optional Context containing the span's parent. Defaults to the
                global context.
            kind: The span's kind (relationship to parent). Note that is
                meaningful even if there is no parent.
            attributes: The span's attributes.
            links: Links span to other spans
            start_time: Sets the start time of a span
            record_exception: Whether to record any exceptions raised within the
                context as error event on the span.
            set_status_on_exception: Only relevant if the returned span is used
                in a with/context manager. Defines whether the span status will
                be automatically set to ERROR when an uncaught exception is
                raised in the span with block. The span status won't be set by
                this mechanism if it was previously set manually.
            end_on_exit: Whether to end the span automatically when leaving the
                context manager.

        Yields:
            The newly-created span.
        """


class ProxyTracer(Tracer):
    # pylint: disable=W0222,signature-differs
    def __init__(
        self,
        instrumenting_module_name: str,
        instrumenting_library_version: typing.Optional[str] = None,
        schema_url: typing.Optional[str] = None,
    ):
        self._instrumenting_module_name = instrumenting_module_name
        self._instrumenting_library_version = instrumenting_library_version
        self._schema_url = schema_url
        self._real_tracer: Optional[Tracer] = None
        self._noop_tracer = NoOpTracer()

    @property
    def _tracer(self) -> Tracer:
        if self._real_tracer:
            return self._real_tracer

        if _TRACER_PROVIDER:
            self._real_tracer = _TRACER_PROVIDER.get_tracer(
                self._instrumenting_module_name,
                self._instrumenting_library_version,
                self._schema_url,
            )
            return self._real_tracer
        return self._noop_tracer

    def start_span(self, *args, **kwargs) -> Span:  # type: ignore
        return self._tracer.start_span(*args, **kwargs)  # type: ignore

    @contextmanager  # type: ignore
    def start_as_current_span(self, *args, **kwargs) -> Iterator[Span]:  # type: ignore
        with self._tracer.start_as_current_span(*args, **kwargs) as span:  # type: ignore
            yield span


class NoOpTracer(Tracer):
    """The default Tracer, used when no Tracer implementation is available.

    All operations are no-op.
    """

    def start_span(
        self,
        name: str,
        context: Optional[Context] = None,
        kind: SpanKind = SpanKind.INTERNAL,
        attributes: types.Attributes = None,
        links: _Links = None,
        start_time: Optional[int] = None,
        record_exception: bool = True,
        set_status_on_exception: bool = True,
    ) -> "Span":
        # pylint: disable=unused-argument,no-self-use
        return INVALID_SPAN

    @contextmanager
    def start_as_current_span(
        self,
        name: str,
        context: Optional[Context] = None,
        kind: SpanKind = SpanKind.INTERNAL,
        attributes: types.Attributes = None,
        links: _Links = None,
        start_time: Optional[int] = None,
        record_exception: bool = True,
        set_status_on_exception: bool = True,
        end_on_exit: bool = True,
    ) -> Iterator["Span"]:
        # pylint: disable=unused-argument,no-self-use
        yield INVALID_SPAN


@deprecated(version="1.9.0", reason="You should use NoOpTracer")  # type: ignore
class _DefaultTracer(NoOpTracer):
    """The default Tracer, used when no Tracer implementation is available.

    All operations are no-op.
    """


_TRACER_PROVIDER_SET_ONCE = Once()
_TRACER_PROVIDER: Optional[TracerProvider] = None
_PROXY_TRACER_PROVIDER = ProxyTracerProvider()


def get_tracer(
    instrumenting_module_name: str,
    instrumenting_library_version: typing.Optional[str] = None,
    tracer_provider: Optional[TracerProvider] = None,
    schema_url: typing.Optional[str] = None,
) -> "Tracer":
    """Returns a `Tracer` for use by the given instrumentation library.

    This function is a convenience wrapper for
    mysql.opentelemetry.trace.TracerProvider.get_tracer.

    If tracer_provider is omitted the current configured one is used.
    """
    if tracer_provider is None:
        tracer_provider = get_tracer_provider()
    return tracer_provider.get_tracer(
        instrumenting_module_name, instrumenting_library_version, schema_url
    )


def _set_tracer_provider(tracer_provider: TracerProvider, log: bool) -> None:
    def set_tp() -> None:
        global _TRACER_PROVIDER  # pylint: disable=global-statement
        _TRACER_PROVIDER = tracer_provider

    did_set = _TRACER_PROVIDER_SET_ONCE.do_once(set_tp)

    if log and not did_set:
        logger.warning("Overriding of current TracerProvider is not allowed")


def set_tracer_provider(tracer_provider: TracerProvider) -> None:
    """Sets the current global :class:`~.TracerProvider` object.

    This can only be done once, a warning will be logged if any further attempt
    is made.
    """
    _set_tracer_provider(tracer_provider, log=True)


def get_tracer_provider() -> TracerProvider:
    """Gets the current global :class:`~.TracerProvider` object."""
    if _TRACER_PROVIDER is None:
        # if a global tracer provider has not been set either via code or env
        # vars, return a proxy tracer provider
        if OTEL_PYTHON_TRACER_PROVIDER not in os.environ:
            return _PROXY_TRACER_PROVIDER

        tracer_provider: TracerProvider = _load_provider(
            OTEL_PYTHON_TRACER_PROVIDER, "tracer_provider"
        )
        _set_tracer_provider(tracer_provider, log=False)
    # _TRACER_PROVIDER will have been set by one thread
    return cast("TracerProvider", _TRACER_PROVIDER)


@contextmanager
def use_span(
    span: Span,
    end_on_exit: bool = False,
    record_exception: bool = True,
    set_status_on_exception: bool = True,
) -> Iterator[Span]:
    """Takes a non-active span and activates it in the current context.

    Args:
        span: The span that should be activated in the current context.
        end_on_exit: Whether to end the span automatically when leaving the
            context manager scope.
        record_exception: Whether to record any exceptions raised within the
            context as error event on the span.
        set_status_on_exception: Only relevant if the returned span is used
            in a with/context manager. Defines whether the span status will
            be automatically set to ERROR when an uncaught exception is
            raised in the span with block. The span status won't be set by
            this mechanism if it was previously set manually.
    """
    try:
        token = context_api.attach(context_api.set_value(_SPAN_KEY, span))
        try:
            yield span
        finally:
            context_api.detach(token)

    except Exception as exc:  # pylint: disable=broad-except
        if isinstance(span, Span) and span.is_recording():
            # Record the exception as an event
            if record_exception:
                span.record_exception(exc)

            # Set status in case exception was raised
            if set_status_on_exception:
                span.set_status(
                    Status(
                        status_code=StatusCode.ERROR,
                        description=f"{type(exc).__name__}: {exc}",
                    )
                )
        raise

    finally:
        if end_on_exit:
            span.end()


__all__ = [
    "DEFAULT_TRACE_OPTIONS",
    "DEFAULT_TRACE_STATE",
    "INVALID_SPAN",
    "INVALID_SPAN_CONTEXT",
    "INVALID_SPAN_ID",
    "INVALID_TRACE_ID",
    "NonRecordingSpan",
    "Link",
    "Span",
    "SpanContext",
    "SpanKind",
    "TraceFlags",
    "TraceState",
    "TracerProvider",
    "Tracer",
    "format_span_id",
    "format_trace_id",
    "get_current_span",
    "get_tracer",
    "get_tracer_provider",
    "set_tracer_provider",
    "set_span_in_context",
    "use_span",
    "Status",
    "StatusCode",
]

virt/lib/python3.9/site-packages/mysql/opentelemetry/trace/__init__ 3.py
PrinceDisant/LostInNL
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""
The OpenTelemetry tracing API describes the classes used to generate
distributed traces.

The :class:`.Tracer` class controls access to the execution context, and
manages span creation. Each operation in a trace is represented by a
:class:`.Span`, which records the start, end time, and metadata associated with
the operation.

This module provides abstract (i.e. unimplemented) classes required for
tracing, and a concrete no-op :class:`.NonRecordingSpan` that allows applications
to use the API package alone without a supporting implementation.

To get a tracer, you need to provide the package name from which you are
calling the tracer APIs to OpenTelemetry by calling `TracerProvider.get_tracer`
with the calling module name and the version of your package.

The tracer supports creating spans that are "attached" or "detached" from the
context. New spans are "attached" to the context in that they are
created as children of the currently active span, and the newly-created span
can optionally become the new active span::

    from opentelemetry import trace

    tracer = trace.get_tracer(__name__)

    # Create a new root span, set it as the current span in context
    with tracer.start_as_current_span("parent"):
        # Attach a new child and update the current span
        with tracer.start_as_current_span("child"):
            do_work():
        # Close child span, set parent as current
    # Close parent span, set default span as current

When creating a span that's "detached" from the context the active span doesn't
change, and the caller is responsible for managing the span's lifetime::

    # Explicit parent span assignment is done via the Context
    from mysql.opentelemetry.trace import set_span_in_context

    context = set_span_in_context(parent)
    child = tracer.start_span("child", context=context)

    try:
        do_work(span=child)
    finally:
        child.end()

Applications should generally use a single global TracerProvider, and use
either implicit or explicit context propagation consistently throughout.

.. versionadded:: 0.1.0
.. versionchanged:: 0.3.0
    `TracerProvider` was introduced and the global ``tracer`` getter was
    replaced by ``tracer_provider``.
.. versionchanged:: 0.5.0
    ``tracer_provider`` was replaced by `get_tracer_provider`,
    ``set_preferred_tracer_provider_implementation`` was replaced by
    `set_tracer_provider`.
"""

import os
import typing

from abc import ABC, abstractmethod
from contextlib import contextmanager
from enum import Enum
from logging import getLogger
from typing import Iterator, Optional, Sequence, cast

from deprecated import deprecated
from mysql.opentelemetry import context as context_api
from mysql.opentelemetry.attributes import BoundedAttributes  # type: ignore
from mysql.opentelemetry.context.context import Context
from mysql.opentelemetry.environment_variables import OTEL_PYTHON_TRACER_PROVIDER
from mysql.opentelemetry.trace.propagation import (
    _SPAN_KEY,
    get_current_span,
    set_span_in_context,
)
from mysql.opentelemetry.trace.span import (
    DEFAULT_TRACE_OPTIONS,
    DEFAULT_TRACE_STATE,
    INVALID_SPAN,
    INVALID_SPAN_CONTEXT,
    INVALID_SPAN_ID,
    INVALID_TRACE_ID,
    NonRecordingSpan,
    Span,
    SpanContext,
    TraceFlags,
    TraceState,
    format_span_id,
    format_trace_id,
)
from mysql.opentelemetry.trace.status import Status, StatusCode
from mysql.opentelemetry.util import types
from mysql.opentelemetry.util._once import Once
from mysql.opentelemetry.util._providers import _load_provider

logger = getLogger(__name__)


class _LinkBase(ABC):
    def __init__(self, context: "SpanContext") -> None:
        self._context = context

    @property
    def context(self) -> "SpanContext":
        return self._context

    @property
    @abstractmethod
    def attributes(self) -> types.Attributes:
        pass


class Link(_LinkBase):
    """A link to a `Span`. The attributes of a Link are immutable.

    Args:
        context: `SpanContext` of the `Span` to link to.
        attributes: Link's attributes.
    """

    def __init__(
        self,
        context: "SpanContext",
        attributes: types.Attributes = None,
    ) -> None:
        super().__init__(context)
        self._attributes = BoundedAttributes(
            attributes=attributes
        )  # type: types.Attributes

    @property
    def attributes(self) -> types.Attributes:
        return self._attributes


_Links = Optional[Sequence[Link]]


class SpanKind(Enum):
    """Specifies additional details on how this span relates to its parent span.

    Note that this enumeration is experimental and likely to change. See
    https://github.com/open-telemetry/opentelemetry-specification/pull/226.
    """

    #: Default value. Indicates that the span is used internally in the
    # application.
    INTERNAL = 0

    #: Indicates that the span describes an operation that handles a remote
    # request.
    SERVER = 1

    #: Indicates that the span describes a request to some remote service.
    CLIENT = 2

    #: Indicates that the span describes a producer sending a message to a
    #: broker. Unlike client and server, there is usually no direct critical
    #: path latency relationship between producer and consumer spans.
    PRODUCER = 3

    #: Indicates that the span describes a consumer receiving a message from a
    #: broker. Unlike client and server, there is usually no direct critical
    #: path latency relationship between producer and consumer spans.
    CONSUMER = 4


class TracerProvider(ABC):
    @abstractmethod
    def get_tracer(
        self,
        instrumenting_module_name: str,
        instrumenting_library_version: typing.Optional[str] = None,
        schema_url: typing.Optional[str] = None,
    ) -> "Tracer":
        """Returns a `Tracer` for use by the given instrumentation library.

        For any two calls it is undefined whether the same or different
        `Tracer` instances are returned, even for different library names.

        This function may return different `Tracer` types (e.g. a no-op tracer
        vs.  a functional tracer).

        Args:
            instrumenting_module_name: The uniquely identifiable name for instrumentation
                scope, such as instrumentation library, package, module or class name.
                ``__name__`` may not be used as this can result in
                different tracer names if the tracers are in different files.
                It is better to use a fixed string that can be imported where
                needed and used consistently as the name of the tracer.

                This should *not* be the name of the module that is
                instrumented but the name of the module doing the instrumentation.
                E.g., instead of ``"requests"``, use
                ``"mysql.opentelemetry.instrumentation.requests"``.

            instrumenting_library_version: Optional. The version string of the
                instrumenting library.  Usually this should be the same as
                ``importlib.metadata.version(instrumenting_library_name)``.

            schema_url: Optional. Specifies the Schema URL of the emitted telemetry.
        """


class NoOpTracerProvider(TracerProvider):
    """The default TracerProvider, used when no implementation is available.

    All operations are no-op.
    """

    def get_tracer(
        self,
        instrumenting_module_name: str,
        instrumenting_library_version: typing.Optional[str] = None,
        schema_url: typing.Optional[str] = None,
    ) -> "Tracer":
        # pylint:disable=no-self-use,unused-argument
        return NoOpTracer()


@deprecated(version="1.9.0", reason="You should use NoOpTracerProvider")  # type: ignore
class _DefaultTracerProvider(NoOpTracerProvider):
    """The default TracerProvider, used when no implementation is available.

    All operations are no-op.
    """


class ProxyTracerProvider(TracerProvider):
    def get_tracer(
        self,
        instrumenting_module_name: str,
        instrumenting_library_version: typing.Optional[str] = None,
        schema_url: typing.Optional[str] = None,
    ) -> "Tracer":
        if _TRACER_PROVIDER:
            return _TRACER_PROVIDER.get_tracer(
                instrumenting_module_name,
                instrumenting_library_version,
                schema_url,
            )
        return ProxyTracer(
            instrumenting_module_name,
            instrumenting_library_version,
            schema_url,
        )


class Tracer(ABC):
    """Handles span creation and in-process context propagation.

    This class provides methods for manipulating the context, creating spans,
    and controlling spans' lifecycles.
    """

    @abstractmethod
    def start_span(
        self,
        name: str,
        context: Optional[Context] = None,
        kind: SpanKind = SpanKind.INTERNAL,
        attributes: types.Attributes = None,
        links: _Links = None,
        start_time: Optional[int] = None,
        record_exception: bool = True,
        set_status_on_exception: bool = True,
    ) -> "Span":
        """Starts a span.

        Create a new span. Start the span without setting it as the current
        span in the context. To start the span and use the context in a single
        method, see :meth:`start_as_current_span`.

        By default the current span in the context will be used as parent, but an
        explicit context can also be specified, by passing in a `Context` containing
        a current `Span`. If there is no current span in the global `Context` or in
        the specified context, the created span will be a root span.

        The span can be used as a context manager. On exiting the context manager,
        the span's end() method will be called.

        Example::

            # trace.get_current_span() will be used as the implicit parent.
            # If none is found, the created span will be a root instance.
            with tracer.start_span("one") as child:
                child.add_event("child's event")

        Args:
            name: The name of the span to be created.
            context: An optional Context containing the span's parent. Defaults to the
                global context.
            kind: The span's kind (relationship to parent). Note that is
                meaningful even if there is no parent.
            attributes: The span's attributes.
            links: Links span to other spans
            start_time: Sets the start time of a span
            record_exception: Whether to record any exceptions raised within the
                context as error event on the span.
            set_status_on_exception: Only relevant if the returned span is used
                in a with/context manager. Defines whether the span status will
                be automatically set to ERROR when an uncaught exception is
                raised in the span with block. The span status won't be set by
                this mechanism if it was previously set manually.

        Returns:
            The newly-created span.
        """

    @contextmanager
    @abstractmethod
    def start_as_current_span(
        self,
        name: str,
        context: Optional[Context] = None,
        kind: SpanKind = SpanKind.INTERNAL,
        attributes: types.Attributes = None,
        links: _Links = None,
        start_time: Optional[int] = None,
        record_exception: bool = True,
        set_status_on_exception: bool = True,
        end_on_exit: bool = True,
    ) -> Iterator["Span"]:
        """Context manager for creating a new span and set it
        as the current span in this tracer's context.

        Exiting the context manager will call the span's end method,
        as well as return the current span to its previous value by
        returning to the previous context.

        Example::

            with tracer.start_as_current_span("one") as parent:
                parent.add_event("parent's event")
                with tracer.start_as_current_span("two") as child:
                    child.add_event("child's event")
                    trace.get_current_span()  # returns child
                trace.get_current_span()      # returns parent
            trace.get_current_span()          # returns previously active span

        This is a convenience method for creating spans attached to the
        tracer's context. Applications that need more control over the span
        lifetime should use :meth:`start_span` instead. For example::

            with tracer.start_as_current_span(name) as span:
                do_work()

        is equivalent to::

            span = tracer.start_span(name)
            with mysql.opentelemetry.trace.use_span(span, end_on_exit=True):
                do_work()

        This can also be used as a decorator::

            @tracer.start_as_current_span("name")
            def function():
                ...

            function()

        Args:
            name: The name of the span to be created.
            context: An optional Context containing the span's parent. Defaults to the
                global context.
            kind: The span's kind (relationship to parent). Note that is
                meaningful even if there is no parent.
            attributes: The span's attributes.
            links: Links span to other spans
            start_time: Sets the start time of a span
            record_exception: Whether to record any exceptions raised within the
                context as error event on the span.
            set_status_on_exception: Only relevant if the returned span is used
                in a with/context manager. Defines whether the span status will
                be automatically set to ERROR when an uncaught exception is
                raised in the span with block. The span status won't be set by
                this mechanism if it was previously set manually.
            end_on_exit: Whether to end the span automatically when leaving the
                context manager.

        Yields:
            The newly-created span.
        """


class ProxyTracer(Tracer):
    # pylint: disable=W0222,signature-differs
    def __init__(
        self,
        instrumenting_module_name: str,
        instrumenting_library_version: typing.Optional[str] = None,
        schema_url: typing.Optional[str] = None,
    ):
        self._instrumenting_module_name = instrumenting_module_name
        self._instrumenting_library_version = instrumenting_library_version
        self._schema_url = schema_url
        self._real_tracer: Optional[Tracer] = None
        self._noop_tracer = NoOpTracer()

    @property
    def _tracer(self) -> Tracer:
        if self._real_tracer:
            return self._real_tracer

        if _TRACER_PROVIDER:
            self._real_tracer = _TRACER_PROVIDER.get_tracer(
                self._instrumenting_module_name,
                self._instrumenting_library_version,
                self._schema_url,
            )
            return self._real_tracer
        return self._noop_tracer

    def start_span(self, *args, **kwargs) -> Span:  # type: ignore
        return self._tracer.start_span(*args, **kwargs)  # type: ignore

    @contextmanager  # type: ignore
    def start_as_current_span(self, *args, **kwargs) -> Iterator[Span]:  # type: ignore
        with self._tracer.start_as_current_span(*args, **kwargs) as span:  # type: ignore
            yield span


class NoOpTracer(Tracer):
    """The default Tracer, used when no Tracer implementation is available.

    All operations are no-op.
    """

    def start_span(
        self,
        name: str,
        context: Optional[Context] = None,
        kind: SpanKind = SpanKind.INTERNAL,
        attributes: types.Attributes = None,
        links: _Links = None,
        start_time: Optional[int] = None,
        record_exception: bool = True,
        set_status_on_exception: bool = True,
    ) -> "Span":
        # pylint: disable=unused-argument,no-self-use
        return INVALID_SPAN

    @contextmanager
    def start_as_current_span(
        self,
        name: str,
        context: Optional[Context] = None,
        kind: SpanKind = SpanKind.INTERNAL,
        attributes: types.Attributes = None,
        links: _Links = None,
        start_time: Optional[int] = None,
        record_exception: bool = True,
        set_status_on_exception: bool = True,
        end_on_exit: bool = True,
    ) -> Iterator["Span"]:
        # pylint: disable=unused-argument,no-self-use
        yield INVALID_SPAN


@deprecated(version="1.9.0", reason="You should use NoOpTracer")  # type: ignore
class _DefaultTracer(NoOpTracer):
    """The default Tracer, used when no Tracer implementation is available.

    All operations are no-op.
    """


_TRACER_PROVIDER_SET_ONCE = Once()
_TRACER_PROVIDER: Optional[TracerProvider] = None
_PROXY_TRACER_PROVIDER = ProxyTracerProvider()


def get_tracer(
    instrumenting_module_name: str,
    instrumenting_library_version: typing.Optional[str] = None,
    tracer_provider: Optional[TracerProvider] = None,
    schema_url: typing.Optional[str] = None,
) -> "Tracer":
    """Returns a `Tracer` for use by the given instrumentation library.

    This function is a convenience wrapper for
    mysql.opentelemetry.trace.TracerProvider.get_tracer.

    If tracer_provider is omitted the current configured one is used.
    """
    if tracer_provider is None:
        tracer_provider = get_tracer_provider()
    return tracer_provider.get_tracer(
        instrumenting_module_name, instrumenting_library_version, schema_url
    )


def _set_tracer_provider(tracer_provider: TracerProvider, log: bool) -> None:
    def set_tp() -> None:
        global _TRACER_PROVIDER  # pylint: disable=global-statement
        _TRACER_PROVIDER = tracer_provider

    did_set = _TRACER_PROVIDER_SET_ONCE.do_once(set_tp)

    if log and not did_set:
        logger.warning("Overriding of current TracerProvider is not allowed")


def set_tracer_provider(tracer_provider: TracerProvider) -> None:
    """Sets the current global :class:`~.TracerProvider` object.

    This can only be done once, a warning will be logged if any further attempt
    is made.
    """
    _set_tracer_provider(tracer_provider, log=True)


def get_tracer_provider() -> TracerProvider:
    """Gets the current global :class:`~.TracerProvider` object."""
    if _TRACER_PROVIDER is None:
        # if a global tracer provider has not been set either via code or env
        # vars, return a proxy tracer provider
        if OTEL_PYTHON_TRACER_PROVIDER not in os.environ:
            return _PROXY_TRACER_PROVIDER

        tracer_provider: TracerProvider = _load_provider(
            OTEL_PYTHON_TRACER_PROVIDER, "tracer_provider"
        )
        _set_tracer_provider(tracer_provider, log=False)
    # _TRACER_PROVIDER will have been set by one thread
    return cast("TracerProvider", _TRACER_PROVIDER)


@contextmanager
def use_span(
    span: Span,
    end_on_exit: bool = False,
    record_exception: bool = True,
    set_status_on_exception: bool = True,
) -> Iterator[Span]:
    """Takes a non-active span and activates it in the current context.

    Args:
        span: The span that should be activated in the current context.
        end_on_exit: Whether to end the span automatically when leaving the
            context manager scope.
        record_exception: Whether to record any exceptions raised within the
            context as error event on the span.
        set_status_on_exception: Only relevant if the returned span is used
            in a with/context manager. Defines whether the span status will
            be automatically set to ERROR when an uncaught exception is
            raised in the span with block. The span status won't be set by
            this mechanism if it was previously set manually.
    """
    try:
        token = context_api.attach(context_api.set_value(_SPAN_KEY, span))
        try:
            yield span
        finally:
            context_api.detach(token)

    except Exception as exc:  # pylint: disable=broad-except
        if isinstance(span, Span) and span.is_recording():
            # Record the exception as an event
            if record_exception:
                span.record_exception(exc)

            # Set status in case exception was raised
            if set_status_on_exception:
                span.set_status(
                    Status(
                        status_code=StatusCode.ERROR,
                        description=f"{type(exc).__name__}: {exc}",
                    )
                )
        raise

    finally:
        if end_on_exit:
            span.end()


__all__ = [
    "DEFAULT_TRACE_OPTIONS",
    "DEFAULT_TRACE_STATE",
    "INVALID_SPAN",
    "INVALID_SPAN_CONTEXT",
    "INVALID_SPAN_ID",
    "INVALID_TRACE_ID",
    "NonRecordingSpan",
    "Link",
    "Span",
    "SpanContext",
    "SpanKind",
    "TraceFlags",
    "TraceState",
    "TracerProvider",
    "Tracer",
    "format_span_id",
    "format_trace_id",
    "get_current_span",
    "get_tracer",
    "get_tracer_provider",
    "set_tracer_provider",
    "set_span_in_context",
    "use_span",
    "Status",
    "StatusCode",
]

virt/lib/python3.9/site-packages/mysql/opentelemetry/trace/__init__.py
PrinceDisant/LostInNL
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from typing import Optional

from mysql.opentelemetry.context import create_key, get_value, set_value
from mysql.opentelemetry.context.context import Context
from mysql.opentelemetry.trace.span import INVALID_SPAN, Span

SPAN_KEY = "current-span"
_SPAN_KEY = create_key("current-span")


def set_span_in_context(span: Span, context: Optional[Context] = None) -> Context:
    """Set the span in the given context.

    Args:
        span: The Span to set.
        context: a Context object. if one is not passed, the
            default current context is used instead.
    """
    ctx = set_value(_SPAN_KEY, span, context=context)
    return ctx


def get_current_span(context: Optional[Context] = None) -> Span:
    """Retrieve the current span.

    Args:
        context: A Context object. If one is not passed, the
            default current context is used instead.

    Returns:
        The Span set in the context if it exists. INVALID_SPAN otherwise.
    """
    span = get_value(_SPAN_KEY, context=context)
    if span is None or not isinstance(span, Span):
        return INVALID_SPAN
    return span

virt/lib/python3.9/site-packages/mysql/opentelemetry/trace/propagation/__init__.py
PrinceDisant/LostInNL
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
import re
import typing

from mysql.opentelemetry import trace
from mysql.opentelemetry.context.context import Context
from mysql.opentelemetry.propagators import textmap
from mysql.opentelemetry.trace import format_span_id, format_trace_id
from mysql.opentelemetry.trace.span import TraceState


class TraceContextTextMapPropagator(textmap.TextMapPropagator):
    """Extracts and injects using w3c TraceContext's headers."""

    _TRACEPARENT_HEADER_NAME = "traceparent"
    _TRACESTATE_HEADER_NAME = "tracestate"
    _TRACEPARENT_HEADER_FORMAT = (
        "^[ \t]*([0-9a-f]{2})-([0-9a-f]{32})-([0-9a-f]{16})-([0-9a-f]{2})"
        + "(-.*)?[ \t]*$"
    )
    _TRACEPARENT_HEADER_FORMAT_RE = re.compile(_TRACEPARENT_HEADER_FORMAT)

    def extract(
        self,
        carrier: textmap.CarrierT,
        context: typing.Optional[Context] = None,
        getter: textmap.Getter[textmap.CarrierT] = textmap.default_getter,
    ) -> Context:
        """Extracts SpanContext from the carrier.

        See `mysql.opentelemetry.propagators.textmap.TextMapPropagator.extract`
        """
        if context is None:
            context = Context()

        header = getter.get(carrier, self._TRACEPARENT_HEADER_NAME)

        if not header:
            return context

        match = re.search(self._TRACEPARENT_HEADER_FORMAT_RE, header[0])
        if not match:
            return context

        version: str = match.group(1)
        trace_id: str = match.group(2)
        span_id: str = match.group(3)
        trace_flags: str = match.group(4)

        if trace_id == "0" * 32 or span_id == "0" * 16:
            return context

        if version == "00":
            if match.group(5):  # type: ignore
                return context
        if version == "ff":
            return context

        tracestate_headers = getter.get(carrier, self._TRACESTATE_HEADER_NAME)
        if tracestate_headers is None:
            tracestate = None
        else:
            tracestate = TraceState.from_header(tracestate_headers)

        span_context = trace.SpanContext(
            trace_id=int(trace_id, 16),
            span_id=int(span_id, 16),
            is_remote=True,
            trace_flags=trace.TraceFlags(int(trace_flags, 16)),
            trace_state=tracestate,
        )
        return trace.set_span_in_context(trace.NonRecordingSpan(span_context), context)

    def inject(
        self,
        carrier: textmap.CarrierT,
        context: typing.Optional[Context] = None,
        setter: textmap.Setter[textmap.CarrierT] = textmap.default_setter,
    ) -> None:
        """Injects SpanContext into the carrier.

        See `mysql.opentelemetry.propagators.textmap.TextMapPropagator.inject`
        """
        span = trace.get_current_span(context)
        span_context = span.get_span_context()
        if span_context == trace.INVALID_SPAN_CONTEXT:
            return
        traceparent_string = f"00-{format_trace_id(span_context.trace_id)}-{format_span_id(span_context.span_id)}-{span_context.trace_flags:02x}"
        setter.set(carrier, self._TRACEPARENT_HEADER_NAME, traceparent_string)
        if span_context.trace_state:
            tracestate_string = span_context.trace_state.to_header()
            setter.set(carrier, self._TRACESTATE_HEADER_NAME, tracestate_string)

    @property
    def fields(self) -> typing.Set[str]:
        """Returns a set with the fields set in `inject`.

        See
        `mysql.opentelemetry.propagators.textmap.TextMapPropagator.fields`
        """
        return {self._TRACEPARENT_HEADER_NAME, self._TRACESTATE_HEADER_NAME}

virt/lib/python3.9/site-packages/mysql/opentelemetry/trace/propagation/tracecontext.py
PrinceDisant/LostInNL
import abc
import logging
import re
import types as python_types
import typing

from collections import OrderedDict

from mysql.opentelemetry.trace.status import Status, StatusCode
from mysql.opentelemetry.util import types

# The key MUST begin with a lowercase letter or a digit,
# and can only contain lowercase letters (a-z), digits (0-9),
# underscores (_), dashes (-), asterisks (*), and forward slashes (/).
# For multi-tenant vendor scenarios, an at sign (@) can be used to
# prefix the vendor name. Vendors SHOULD set the tenant ID
# at the beginning of the key.

# key = ( lcalpha ) 0*255( lcalpha / DIGIT / "_" / "-"/ "*" / "/" )
# key = ( lcalpha / DIGIT ) 0*240( lcalpha / DIGIT / "_" / "-"/ "*" / "/" ) "@" lcalpha 0*13( lcalpha / DIGIT / "_" / "-"/ "*" / "/" )
# lcalpha = %x61-7A ; a-z

_KEY_FORMAT = (
    r"[a-z][_0-9a-z\-\*\/]{0,255}|"
    r"[a-z0-9][_0-9a-z\-\*\/]{0,240}@[a-z][_0-9a-z\-\*\/]{0,13}"
)
_KEY_PATTERN = re.compile(_KEY_FORMAT)

# The value is an opaque string containing up to 256 printable
# ASCII [RFC0020] characters (i.e., the range 0x20 to 0x7E)
# except comma (,) and (=).
# value    = 0*255(chr) nblk-chr
# nblk-chr = %x21-2B / %x2D-3C / %x3E-7E
# chr      = %x20 / nblk-chr

_VALUE_FORMAT = r"[\x20-\x2b\x2d-\x3c\x3e-\x7e]{0,255}[\x21-\x2b\x2d-\x3c\x3e-\x7e]"
_VALUE_PATTERN = re.compile(_VALUE_FORMAT)


_TRACECONTEXT_MAXIMUM_TRACESTATE_KEYS = 32
_delimiter_pattern = re.compile(r"[ \t]*,[ \t]*")
_member_pattern = re.compile(f"({_KEY_FORMAT})(=)({_VALUE_FORMAT})[ \t]*")
_logger = logging.getLogger(__name__)


def _is_valid_pair(key: str, value: str) -> bool:
    return (
        isinstance(key, str)
        and _KEY_PATTERN.fullmatch(key) is not None
        and isinstance(value, str)
        and _VALUE_PATTERN.fullmatch(value) is not None
    )


class Span(abc.ABC):
    """A span represents a single operation within a trace."""

    @abc.abstractmethod
    def end(self, end_time: typing.Optional[int] = None) -> None:
        """Sets the current time as the span's end time.

        The span's end time is the wall time at which the operation finished.

        Only the first call to `end` should modify the span, and
        implementations are free to ignore or raise on further calls.
        """

    @abc.abstractmethod
    def get_span_context(self) -> "SpanContext":
        """Gets the span's SpanContext.

        Get an immutable, serializable identifier for this span that can be
        used to create new child spans.

        Returns:
            A :class:`opentelemetry.trace.SpanContext` with a copy of this span's immutable state.
        """

    @abc.abstractmethod
    def set_attributes(
        self, attributes: typing.Dict[str, types.AttributeValue]
    ) -> None:
        """Sets Attributes.

        Sets Attributes with the key and value passed as arguments dict.

        Note: The behavior of `None` value attributes is undefined, and hence
        strongly discouraged. It is also preferred to set attributes at span
        creation, instead of calling this method later since samplers can only
        consider information already present during span creation.
        """

    @abc.abstractmethod
    def set_attribute(self, key: str, value: types.AttributeValue) -> None:
        """Sets an Attribute.

        Sets a single Attribute with the key and value passed as arguments.

        Note: The behavior of `None` value attributes is undefined, and hence
        strongly discouraged. It is also preferred to set attributes at span
        creation, instead of calling this method later since samplers can only
        consider information already present during span creation.
        """

    @abc.abstractmethod
    def add_event(
        self,
        name: str,
        attributes: types.Attributes = None,
        timestamp: typing.Optional[int] = None,
    ) -> None:
        """Adds an `Event`.

        Adds a single `Event` with the name and, optionally, a timestamp and
        attributes passed as arguments. Implementations should generate a
        timestamp if the `timestamp` argument is omitted.
        """

    @abc.abstractmethod
    def update_name(self, name: str) -> None:
        """Updates the `Span` name.

        This will override the name provided via :func:`opentelemetry.trace.Tracer.start_span`.

        Upon this update, any sampling behavior based on Span name will depend
        on the implementation.
        """

    @abc.abstractmethod
    def is_recording(self) -> bool:
        """Returns whether this span will be recorded.

        Returns true if this Span is active and recording information like
        events with the add_event operation and attributes using set_attribute.
        """

    @abc.abstractmethod
    def set_status(
        self,
        status: typing.Union[Status, StatusCode],
        description: typing.Optional[str] = None,
    ) -> None:
        """Sets the Status of the Span. If used, this will override the default
        Span status.
        """

    @abc.abstractmethod
    def record_exception(
        self,
        exception: Exception,
        attributes: types.Attributes = None,
        timestamp: typing.Optional[int] = None,
        escaped: bool = False,
    ) -> None:
        """Records an exception as a span event."""

    def __enter__(self) -> "Span":
        """Invoked when `Span` is used as a context manager.

        Returns the `Span` itself.
        """
        return self

    def __exit__(
        self,
        exc_type: typing.Optional[typing.Type[BaseException]],
        exc_val: typing.Optional[BaseException],
        exc_tb: typing.Optional[python_types.TracebackType],
    ) -> None:
        """Ends context manager and calls `end` on the `Span`."""

        self.end()


class TraceFlags(int):
    """A bitmask that represents options specific to the trace.

    The only supported option is the "sampled" flag (``0x01``). If set, this
    flag indicates that the trace may have been sampled upstream.

    See the `W3C Trace Context - Traceparent`_ spec for details.

    .. _W3C Trace Context - Traceparent:
        https://www.w3.org/TR/trace-context/#trace-flags
    """

    DEFAULT = 0x00
    SAMPLED = 0x01

    @classmethod
    def get_default(cls) -> "TraceFlags":
        return cls(cls.DEFAULT)

    @property
    def sampled(self) -> bool:
        return bool(self & TraceFlags.SAMPLED)


DEFAULT_TRACE_OPTIONS = TraceFlags.get_default()


class TraceState(typing.Mapping[str, str]):
    """A list of key-value pairs representing vendor-specific trace info.

    Keys and values are strings of up to 256 printable US-ASCII characters.
    Implementations should conform to the `W3C Trace Context - Tracestate`_
    spec, which describes additional restrictions on valid field values.

    .. _W3C Trace Context - Tracestate:
        https://www.w3.org/TR/trace-context/#tracestate-field
    """

    def __init__(
        self,
        entries: typing.Optional[typing.Sequence[typing.Tuple[str, str]]] = None,
    ) -> None:
        self._dict = OrderedDict()  # type: OrderedDict[str, str]
        if entries is None:
            return
        if len(entries) > _TRACECONTEXT_MAXIMUM_TRACESTATE_KEYS:
            _logger.warning(
                "There can't be more than %s key/value pairs.",
                _TRACECONTEXT_MAXIMUM_TRACESTATE_KEYS,
            )
            return

        for key, value in entries:
            if _is_valid_pair(key, value):
                if key in self._dict:
                    _logger.warning("Duplicate key: %s found.", key)
                    continue
                self._dict[key] = value
            else:
                _logger.warning("Invalid key/value pair (%s, %s) found.", key, value)

    def __contains__(self, item: object) -> bool:
        return item in self._dict

    def __getitem__(self, key: str) -> str:
        return self._dict[key]

    def __iter__(self) -> typing.Iterator[str]:
        return iter(self._dict)

    def __len__(self) -> int:
        return len(self._dict)

    def __repr__(self) -> str:
        pairs = [f"{{key={key}, value={value}}}" for key, value in self._dict.items()]
        return str(pairs)

    def add(self, key: str, value: str) -> "TraceState":
        """Adds a key-value pair to tracestate. The provided pair should
        adhere to w3c tracestate identifiers format.

        Args:
            key: A valid tracestate key to add
            value: A valid tracestate value to add

        Returns:
            A new TraceState with the modifications applied.

            If the provided key-value pair is invalid or results in tracestate
            that violates tracecontext specification, they are discarded and
            same tracestate will be returned.
        """
        if not _is_valid_pair(key, value):
            _logger.warning("Invalid key/value pair (%s, %s) found.", key, value)
            return self
        # There can be a maximum of 32 pairs
        if len(self) >= _TRACECONTEXT_MAXIMUM_TRACESTATE_KEYS:
            _logger.warning("There can't be more 32 key/value pairs.")
            return self
        # Duplicate entries are not allowed
        if key in self._dict:
            _logger.warning("The provided key %s already exists.", key)
            return self
        new_state = [(key, value)] + list(self._dict.items())
        return TraceState(new_state)

    def update(self, key: str, value: str) -> "TraceState":
        """Updates a key-value pair in tracestate. The provided pair should
        adhere to w3c tracestate identifiers format.

        Args:
            key: A valid tracestate key to update
            value: A valid tracestate value to update for key

        Returns:
            A new TraceState with the modifications applied.

            If the provided key-value pair is invalid or results in tracestate
            that violates tracecontext specification, they are discarded and
            same tracestate will be returned.
        """
        if not _is_valid_pair(key, value):
            _logger.warning("Invalid key/value pair (%s, %s) found.", key, value)
            return self
        prev_state = self._dict.copy()
        prev_state[key] = value
        prev_state.move_to_end(key, last=False)
        new_state = list(prev_state.items())
        return TraceState(new_state)

    def delete(self, key: str) -> "TraceState":
        """Deletes a key-value from tracestate.

        Args:
            key: A valid tracestate key to remove key-value pair from tracestate

        Returns:
            A new TraceState with the modifications applied.

            If the provided key-value pair is invalid or results in tracestate
            that violates tracecontext specification, they are discarded and
            same tracestate will be returned.
        """
        if key not in self._dict:
            _logger.warning("The provided key %s doesn't exist.", key)
            return self
        prev_state = self._dict.copy()
        prev_state.pop(key)
        new_state = list(prev_state.items())
        return TraceState(new_state)

    def to_header(self) -> str:
        """Creates a w3c tracestate header from a TraceState.

        Returns:
            A string that adheres to the w3c tracestate
            header format.
        """
        return ",".join(key + "=" + value for key, value in self._dict.items())

    @classmethod
    def from_header(cls, header_list: typing.List[str]) -> "TraceState":
        """Parses one or more w3c tracestate header into a TraceState.

        Args:
            header_list: one or more w3c tracestate headers.

        Returns:
            A valid TraceState that contains values extracted from
            the tracestate header.

            If the format of one headers is illegal, all values will
            be discarded and an empty tracestate will be returned.

            If the number of keys is beyond the maximum, all values
            will be discarded and an empty tracestate will be returned.
        """
        pairs = OrderedDict()  # type: OrderedDict[str, str]
        for header in header_list:
            members: typing.List[str] = re.split(_delimiter_pattern, header)
            for member in members:
                # empty members are valid, but no need to process further.
                if not member:
                    continue
                match = _member_pattern.fullmatch(member)
                if not match:
                    _logger.warning(
                        "Member doesn't match the w3c identifiers format %s",
                        member,
                    )
                    return cls()
                groups: typing.Tuple[str, ...] = match.groups()
                key, _eq, value = groups
                # duplicate keys are not legal in header
                if key in pairs:
                    return cls()
                pairs[key] = value
        return cls(list(pairs.items()))

    @classmethod
    def get_default(cls) -> "TraceState":
        return cls()

    def keys(self) -> typing.KeysView[str]:
        return self._dict.keys()

    def items(self) -> typing.ItemsView[str, str]:
        return self._dict.items()

    def values(self) -> typing.ValuesView[str]:
        return self._dict.values()


DEFAULT_TRACE_STATE = TraceState.get_default()
_TRACE_ID_MAX_VALUE = 2**128 - 1
_SPAN_ID_MAX_VALUE = 2**64 - 1


class SpanContext(typing.Tuple[int, int, bool, "TraceFlags", "TraceState", bool]):
    """The state of a Span to propagate between processes.

    This class includes the immutable attributes of a :class:`.Span` that must
    be propagated to a span's children and across process boundaries.

    Args:
        trace_id: The ID of the trace that this span belongs to.
        span_id: This span's ID.
        is_remote: True if propagated from a remote parent.
        trace_flags: Trace options to propagate.
        trace_state: Tracing-system-specific info to propagate.
    """

    def __new__(
        cls,
        trace_id: int,
        span_id: int,
        is_remote: bool,
        trace_flags: typing.Optional["TraceFlags"] = DEFAULT_TRACE_OPTIONS,
        trace_state: typing.Optional["TraceState"] = DEFAULT_TRACE_STATE,
    ) -> "SpanContext":
        if trace_flags is None:
            trace_flags = DEFAULT_TRACE_OPTIONS
        if trace_state is None:
            trace_state = DEFAULT_TRACE_STATE

        is_valid = (
            INVALID_TRACE_ID < trace_id <= _TRACE_ID_MAX_VALUE
            and INVALID_SPAN_ID < span_id <= _SPAN_ID_MAX_VALUE
        )

        return tuple.__new__(
            cls,
            (trace_id, span_id, is_remote, trace_flags, trace_state, is_valid),
        )

    def __getnewargs__(
        self,
    ) -> typing.Tuple[int, int, bool, "TraceFlags", "TraceState"]:
        return (
            self.trace_id,
            self.span_id,
            self.is_remote,
            self.trace_flags,
            self.trace_state,
        )

    @property
    def trace_id(self) -> int:
        return self[0]  # pylint: disable=unsubscriptable-object

    @property
    def span_id(self) -> int:
        return self[1]  # pylint: disable=unsubscriptable-object

    @property
    def is_remote(self) -> bool:
        return self[2]  # pylint: disable=unsubscriptable-object

    @property
    def trace_flags(self) -> "TraceFlags":
        return self[3]  # pylint: disable=unsubscriptable-object

    @property
    def trace_state(self) -> "TraceState":
        return self[4]  # pylint: disable=unsubscriptable-object

    @property
    def is_valid(self) -> bool:
        return self[5]  # pylint: disable=unsubscriptable-object

    def __setattr__(self, *args: str) -> None:
        _logger.debug("Immutable type, ignoring call to set attribute", stack_info=True)

    def __delattr__(self, *args: str) -> None:
        _logger.debug("Immutable type, ignoring call to set attribute", stack_info=True)

    def __repr__(self) -> str:
        return f"{type(self).__name__}(trace_id=0x{format_trace_id(self.trace_id)}, span_id=0x{format_span_id(self.span_id)}, trace_flags=0x{self.trace_flags:02x}, trace_state={self.trace_state!r}, is_remote={self.is_remote})"


class NonRecordingSpan(Span):
    """The Span that is used when no Span implementation is available.

    All operations are no-op except context propagation.
    """

    def __init__(self, context: "SpanContext") -> None:
        self._context = context

    def get_span_context(self) -> "SpanContext":
        return self._context

    def is_recording(self) -> bool:
        return False

    def end(self, end_time: typing.Optional[int] = None) -> None:
        pass

    def set_attributes(
        self, attributes: typing.Dict[str, types.AttributeValue]
    ) -> None:
        pass

    def set_attribute(self, key: str, value: types.AttributeValue) -> None:
        pass

    def add_event(
        self,
        name: str,
        attributes: types.Attributes = None,
        timestamp: typing.Optional[int] = None,
    ) -> None:
        pass

    def update_name(self, name: str) -> None:
        pass

    def set_status(
        self,
        status: typing.Union[Status, StatusCode],
        description: typing.Optional[str] = None,
    ) -> None:
        pass

    def record_exception(
        self,
        exception: Exception,
        attributes: types.Attributes = None,
        timestamp: typing.Optional[int] = None,
        escaped: bool = False,
    ) -> None:
        pass

    def __repr__(self) -> str:
        return f"NonRecordingSpan({self._context!r})"


INVALID_SPAN_ID = 0x0000000000000000
INVALID_TRACE_ID = 0x00000000000000000000000000000000
INVALID_SPAN_CONTEXT = SpanContext(
    trace_id=INVALID_TRACE_ID,
    span_id=INVALID_SPAN_ID,
    is_remote=False,
    trace_flags=DEFAULT_TRACE_OPTIONS,
    trace_state=DEFAULT_TRACE_STATE,
)
INVALID_SPAN = NonRecordingSpan(INVALID_SPAN_CONTEXT)


def format_trace_id(trace_id: int) -> str:
    """Convenience trace ID formatting method
    Args:
        trace_id: Trace ID int

    Returns:
        The trace ID as 32-byte hexadecimal string
    """
    return format(trace_id, "032x")


def format_span_id(span_id: int) -> str:
    """Convenience span ID formatting method
    Args:
        span_id: Span ID int

    Returns:
        The span ID as 16-byte hexadecimal string
    """
    return format(span_id, "016x")

virt/lib/python3.9/site-packages/mysql/opentelemetry/trace/span 2.py
PrinceDisant/LostInNL
import abc
import logging
import re
import types as python_types
import typing

from collections import OrderedDict

from mysql.opentelemetry.trace.status import Status, StatusCode
from mysql.opentelemetry.util import types

# The key MUST begin with a lowercase letter or a digit,
# and can only contain lowercase letters (a-z), digits (0-9),
# underscores (_), dashes (-), asterisks (*), and forward slashes (/).
# For multi-tenant vendor scenarios, an at sign (@) can be used to
# prefix the vendor name. Vendors SHOULD set the tenant ID
# at the beginning of the key.

# key = ( lcalpha ) 0*255( lcalpha / DIGIT / "_" / "-"/ "*" / "/" )
# key = ( lcalpha / DIGIT ) 0*240( lcalpha / DIGIT / "_" / "-"/ "*" / "/" ) "@" lcalpha 0*13( lcalpha / DIGIT / "_" / "-"/ "*" / "/" )
# lcalpha = %x61-7A ; a-z

_KEY_FORMAT = (
    r"[a-z][_0-9a-z\-\*\/]{0,255}|"
    r"[a-z0-9][_0-9a-z\-\*\/]{0,240}@[a-z][_0-9a-z\-\*\/]{0,13}"
)
_KEY_PATTERN = re.compile(_KEY_FORMAT)

# The value is an opaque string containing up to 256 printable
# ASCII [RFC0020] characters (i.e., the range 0x20 to 0x7E)
# except comma (,) and (=).
# value    = 0*255(chr) nblk-chr
# nblk-chr = %x21-2B / %x2D-3C / %x3E-7E
# chr      = %x20 / nblk-chr

_VALUE_FORMAT = r"[\x20-\x2b\x2d-\x3c\x3e-\x7e]{0,255}[\x21-\x2b\x2d-\x3c\x3e-\x7e]"
_VALUE_PATTERN = re.compile(_VALUE_FORMAT)


_TRACECONTEXT_MAXIMUM_TRACESTATE_KEYS = 32
_delimiter_pattern = re.compile(r"[ \t]*,[ \t]*")
_member_pattern = re.compile(f"({_KEY_FORMAT})(=)({_VALUE_FORMAT})[ \t]*")
_logger = logging.getLogger(__name__)


def _is_valid_pair(key: str, value: str) -> bool:
    return (
        isinstance(key, str)
        and _KEY_PATTERN.fullmatch(key) is not None
        and isinstance(value, str)
        and _VALUE_PATTERN.fullmatch(value) is not None
    )


class Span(abc.ABC):
    """A span represents a single operation within a trace."""

    @abc.abstractmethod
    def end(self, end_time: typing.Optional[int] = None) -> None:
        """Sets the current time as the span's end time.

        The span's end time is the wall time at which the operation finished.

        Only the first call to `end` should modify the span, and
        implementations are free to ignore or raise on further calls.
        """

    @abc.abstractmethod
    def get_span_context(self) -> "SpanContext":
        """Gets the span's SpanContext.

        Get an immutable, serializable identifier for this span that can be
        used to create new child spans.

        Returns:
            A :class:`opentelemetry.trace.SpanContext` with a copy of this span's immutable state.
        """

    @abc.abstractmethod
    def set_attributes(
        self, attributes: typing.Dict[str, types.AttributeValue]
    ) -> None:
        """Sets Attributes.

        Sets Attributes with the key and value passed as arguments dict.

        Note: The behavior of `None` value attributes is undefined, and hence
        strongly discouraged. It is also preferred to set attributes at span
        creation, instead of calling this method later since samplers can only
        consider information already present during span creation.
        """

    @abc.abstractmethod
    def set_attribute(self, key: str, value: types.AttributeValue) -> None:
        """Sets an Attribute.

        Sets a single Attribute with the key and value passed as arguments.

        Note: The behavior of `None` value attributes is undefined, and hence
        strongly discouraged. It is also preferred to set attributes at span
        creation, instead of calling this method later since samplers can only
        consider information already present during span creation.
        """

    @abc.abstractmethod
    def add_event(
        self,
        name: str,
        attributes: types.Attributes = None,
        timestamp: typing.Optional[int] = None,
    ) -> None:
        """Adds an `Event`.

        Adds a single `Event` with the name and, optionally, a timestamp and
        attributes passed as arguments. Implementations should generate a
        timestamp if the `timestamp` argument is omitted.
        """

    @abc.abstractmethod
    def update_name(self, name: str) -> None:
        """Updates the `Span` name.

        This will override the name provided via :func:`opentelemetry.trace.Tracer.start_span`.

        Upon this update, any sampling behavior based on Span name will depend
        on the implementation.
        """

    @abc.abstractmethod
    def is_recording(self) -> bool:
        """Returns whether this span will be recorded.

        Returns true if this Span is active and recording information like
        events with the add_event operation and attributes using set_attribute.
        """

    @abc.abstractmethod
    def set_status(
        self,
        status: typing.Union[Status, StatusCode],
        description: typing.Optional[str] = None,
    ) -> None:
        """Sets the Status of the Span. If used, this will override the default
        Span status.
        """

    @abc.abstractmethod
    def record_exception(
        self,
        exception: Exception,
        attributes: types.Attributes = None,
        timestamp: typing.Optional[int] = None,
        escaped: bool = False,
    ) -> None:
        """Records an exception as a span event."""

    def __enter__(self) -> "Span":
        """Invoked when `Span` is used as a context manager.

        Returns the `Span` itself.
        """
        return self

    def __exit__(
        self,
        exc_type: typing.Optional[typing.Type[BaseException]],
        exc_val: typing.Optional[BaseException],
        exc_tb: typing.Optional[python_types.TracebackType],
    ) -> None:
        """Ends context manager and calls `end` on the `Span`."""

        self.end()


class TraceFlags(int):
    """A bitmask that represents options specific to the trace.

    The only supported option is the "sampled" flag (``0x01``). If set, this
    flag indicates that the trace may have been sampled upstream.

    See the `W3C Trace Context - Traceparent`_ spec for details.

    .. _W3C Trace Context - Traceparent:
        https://www.w3.org/TR/trace-context/#trace-flags
    """

    DEFAULT = 0x00
    SAMPLED = 0x01

    @classmethod
    def get_default(cls) -> "TraceFlags":
        return cls(cls.DEFAULT)

    @property
    def sampled(self) -> bool:
        return bool(self & TraceFlags.SAMPLED)


DEFAULT_TRACE_OPTIONS = TraceFlags.get_default()


class TraceState(typing.Mapping[str, str]):
    """A list of key-value pairs representing vendor-specific trace info.

    Keys and values are strings of up to 256 printable US-ASCII characters.
    Implementations should conform to the `W3C Trace Context - Tracestate`_
    spec, which describes additional restrictions on valid field values.

    .. _W3C Trace Context - Tracestate:
        https://www.w3.org/TR/trace-context/#tracestate-field
    """

    def __init__(
        self,
        entries: typing.Optional[typing.Sequence[typing.Tuple[str, str]]] = None,
    ) -> None:
        self._dict = OrderedDict()  # type: OrderedDict[str, str]
        if entries is None:
            return
        if len(entries) > _TRACECONTEXT_MAXIMUM_TRACESTATE_KEYS:
            _logger.warning(
                "There can't be more than %s key/value pairs.",
                _TRACECONTEXT_MAXIMUM_TRACESTATE_KEYS,
            )
            return

        for key, value in entries:
            if _is_valid_pair(key, value):
                if key in self._dict:
                    _logger.warning("Duplicate key: %s found.", key)
                    continue
                self._dict[key] = value
            else:
                _logger.warning("Invalid key/value pair (%s, %s) found.", key, value)

    def __contains__(self, item: object) -> bool:
        return item in self._dict

    def __getitem__(self, key: str) -> str:
        return self._dict[key]

    def __iter__(self) -> typing.Iterator[str]:
        return iter(self._dict)

    def __len__(self) -> int:
        return len(self._dict)

    def __repr__(self) -> str:
        pairs = [f"{{key={key}, value={value}}}" for key, value in self._dict.items()]
        return str(pairs)

    def add(self, key: str, value: str) -> "TraceState":
        """Adds a key-value pair to tracestate. The provided pair should
        adhere to w3c tracestate identifiers format.

        Args:
            key: A valid tracestate key to add
            value: A valid tracestate value to add

        Returns:
            A new TraceState with the modifications applied.

            If the provided key-value pair is invalid or results in tracestate
            that violates tracecontext specification, they are discarded and
            same tracestate will be returned.
        """
        if not _is_valid_pair(key, value):
            _logger.warning("Invalid key/value pair (%s, %s) found.", key, value)
            return self
        # There can be a maximum of 32 pairs
        if len(self) >= _TRACECONTEXT_MAXIMUM_TRACESTATE_KEYS:
            _logger.warning("There can't be more 32 key/value pairs.")
            return self
        # Duplicate entries are not allowed
        if key in self._dict:
            _logger.warning("The provided key %s already exists.", key)
            return self
        new_state = [(key, value)] + list(self._dict.items())
        return TraceState(new_state)

    def update(self, key: str, value: str) -> "TraceState":
        """Updates a key-value pair in tracestate. The provided pair should
        adhere to w3c tracestate identifiers format.

        Args:
            key: A valid tracestate key to update
            value: A valid tracestate value to update for key

        Returns:
            A new TraceState with the modifications applied.

            If the provided key-value pair is invalid or results in tracestate
            that violates tracecontext specification, they are discarded and
            same tracestate will be returned.
        """
        if not _is_valid_pair(key, value):
            _logger.warning("Invalid key/value pair (%s, %s) found.", key, value)
            return self
        prev_state = self._dict.copy()
        prev_state[key] = value
        prev_state.move_to_end(key, last=False)
        new_state = list(prev_state.items())
        return TraceState(new_state)

    def delete(self, key: str) -> "TraceState":
        """Deletes a key-value from tracestate.

        Args:
            key: A valid tracestate key to remove key-value pair from tracestate

        Returns:
            A new TraceState with the modifications applied.

            If the provided key-value pair is invalid or results in tracestate
            that violates tracecontext specification, they are discarded and
            same tracestate will be returned.
        """
        if key not in self._dict:
            _logger.warning("The provided key %s doesn't exist.", key)
            return self
        prev_state = self._dict.copy()
        prev_state.pop(key)
        new_state = list(prev_state.items())
        return TraceState(new_state)

    def to_header(self) -> str:
        """Creates a w3c tracestate header from a TraceState.

        Returns:
            A string that adheres to the w3c tracestate
            header format.
        """
        return ",".join(key + "=" + value for key, value in self._dict.items())

    @classmethod
    def from_header(cls, header_list: typing.List[str]) -> "TraceState":
        """Parses one or more w3c tracestate header into a TraceState.

        Args:
            header_list: one or more w3c tracestate headers.

        Returns:
            A valid TraceState that contains values extracted from
            the tracestate header.

            If the format of one headers is illegal, all values will
            be discarded and an empty tracestate will be returned.

            If the number of keys is beyond the maximum, all values
            will be discarded and an empty tracestate will be returned.
        """
        pairs = OrderedDict()  # type: OrderedDict[str, str]
        for header in header_list:
            members: typing.List[str] = re.split(_delimiter_pattern, header)
            for member in members:
                # empty members are valid, but no need to process further.
                if not member:
                    continue
                match = _member_pattern.fullmatch(member)
                if not match:
                    _logger.warning(
                        "Member doesn't match the w3c identifiers format %s",
                        member,
                    )
                    return cls()
                groups: typing.Tuple[str, ...] = match.groups()
                key, _eq, value = groups
                # duplicate keys are not legal in header
                if key in pairs:
                    return cls()
                pairs[key] = value
        return cls(list(pairs.items()))

    @classmethod
    def get_default(cls) -> "TraceState":
        return cls()

    def keys(self) -> typing.KeysView[str]:
        return self._dict.keys()

    def items(self) -> typing.ItemsView[str, str]:
        return self._dict.items()

    def values(self) -> typing.ValuesView[str]:
        return self._dict.values()


DEFAULT_TRACE_STATE = TraceState.get_default()
_TRACE_ID_MAX_VALUE = 2**128 - 1
_SPAN_ID_MAX_VALUE = 2**64 - 1


class SpanContext(typing.Tuple[int, int, bool, "TraceFlags", "TraceState", bool]):
    """The state of a Span to propagate between processes.

    This class includes the immutable attributes of a :class:`.Span` that must
    be propagated to a span's children and across process boundaries.

    Args:
        trace_id: The ID of the trace that this span belongs to.
        span_id: This span's ID.
        is_remote: True if propagated from a remote parent.
        trace_flags: Trace options to propagate.
        trace_state: Tracing-system-specific info to propagate.
    """

    def __new__(
        cls,
        trace_id: int,
        span_id: int,
        is_remote: bool,
        trace_flags: typing.Optional["TraceFlags"] = DEFAULT_TRACE_OPTIONS,
        trace_state: typing.Optional["TraceState"] = DEFAULT_TRACE_STATE,
    ) -> "SpanContext":
        if trace_flags is None:
            trace_flags = DEFAULT_TRACE_OPTIONS
        if trace_state is None:
            trace_state = DEFAULT_TRACE_STATE

        is_valid = (
            INVALID_TRACE_ID < trace_id <= _TRACE_ID_MAX_VALUE
            and INVALID_SPAN_ID < span_id <= _SPAN_ID_MAX_VALUE
        )

        return tuple.__new__(
            cls,
            (trace_id, span_id, is_remote, trace_flags, trace_state, is_valid),
        )

    def __getnewargs__(
        self,
    ) -> typing.Tuple[int, int, bool, "TraceFlags", "TraceState"]:
        return (
            self.trace_id,
            self.span_id,
            self.is_remote,
            self.trace_flags,
            self.trace_state,
        )

    @property
    def trace_id(self) -> int:
        return self[0]  # pylint: disable=unsubscriptable-object

    @property
    def span_id(self) -> int:
        return self[1]  # pylint: disable=unsubscriptable-object

    @property
    def is_remote(self) -> bool:
        return self[2]  # pylint: disable=unsubscriptable-object

    @property
    def trace_flags(self) -> "TraceFlags":
        return self[3]  # pylint: disable=unsubscriptable-object

    @property
    def trace_state(self) -> "TraceState":
        return self[4]  # pylint: disable=unsubscriptable-object

    @property
    def is_valid(self) -> bool:
        return self[5]  # pylint: disable=unsubscriptable-object

    def __setattr__(self, *args: str) -> None:
        _logger.debug("Immutable type, ignoring call to set attribute", stack_info=True)

    def __delattr__(self, *args: str) -> None:
        _logger.debug("Immutable type, ignoring call to set attribute", stack_info=True)

    def __repr__(self) -> str:
        return f"{type(self).__name__}(trace_id=0x{format_trace_id(self.trace_id)}, span_id=0x{format_span_id(self.span_id)}, trace_flags=0x{self.trace_flags:02x}, trace_state={self.trace_state!r}, is_remote={self.is_remote})"


class NonRecordingSpan(Span):
    """The Span that is used when no Span implementation is available.

    All operations are no-op except context propagation.
    """

    def __init__(self, context: "SpanContext") -> None:
        self._context = context

    def get_span_context(self) -> "SpanContext":
        return self._context

    def is_recording(self) -> bool:
        return False

    def end(self, end_time: typing.Optional[int] = None) -> None:
        pass

    def set_attributes(
        self, attributes: typing.Dict[str, types.AttributeValue]
    ) -> None:
        pass

    def set_attribute(self, key: str, value: types.AttributeValue) -> None:
        pass

    def add_event(
        self,
        name: str,
        attributes: types.Attributes = None,
        timestamp: typing.Optional[int] = None,
    ) -> None:
        pass

    def update_name(self, name: str) -> None:
        pass

    def set_status(
        self,
        status: typing.Union[Status, StatusCode],
        description: typing.Optional[str] = None,
    ) -> None:
        pass

    def record_exception(
        self,
        exception: Exception,
        attributes: types.Attributes = None,
        timestamp: typing.Optional[int] = None,
        escaped: bool = False,
    ) -> None:
        pass

    def __repr__(self) -> str:
        return f"NonRecordingSpan({self._context!r})"


INVALID_SPAN_ID = 0x0000000000000000
INVALID_TRACE_ID = 0x00000000000000000000000000000000
INVALID_SPAN_CONTEXT = SpanContext(
    trace_id=INVALID_TRACE_ID,
    span_id=INVALID_SPAN_ID,
    is_remote=False,
    trace_flags=DEFAULT_TRACE_OPTIONS,
    trace_state=DEFAULT_TRACE_STATE,
)
INVALID_SPAN = NonRecordingSpan(INVALID_SPAN_CONTEXT)


def format_trace_id(trace_id: int) -> str:
    """Convenience trace ID formatting method
    Args:
        trace_id: Trace ID int

    Returns:
        The trace ID as 32-byte hexadecimal string
    """
    return format(trace_id, "032x")


def format_span_id(span_id: int) -> str:
    """Convenience span ID formatting method
    Args:
        span_id: Span ID int

    Returns:
        The span ID as 16-byte hexadecimal string
    """
    return format(span_id, "016x")

virt/lib/python3.9/site-packages/mysql/opentelemetry/trace/span 3.py
PrinceDisant/LostInNL
import abc
import logging
import re
import types as python_types
import typing

from collections import OrderedDict

from mysql.opentelemetry.trace.status import Status, StatusCode
from mysql.opentelemetry.util import types

# The key MUST begin with a lowercase letter or a digit,
# and can only contain lowercase letters (a-z), digits (0-9),
# underscores (_), dashes (-), asterisks (*), and forward slashes (/).
# For multi-tenant vendor scenarios, an at sign (@) can be used to
# prefix the vendor name. Vendors SHOULD set the tenant ID
# at the beginning of the key.

# key = ( lcalpha ) 0*255( lcalpha / DIGIT / "_" / "-"/ "*" / "/" )
# key = ( lcalpha / DIGIT ) 0*240( lcalpha / DIGIT / "_" / "-"/ "*" / "/" ) "@" lcalpha 0*13( lcalpha / DIGIT / "_" / "-"/ "*" / "/" )
# lcalpha = %x61-7A ; a-z

_KEY_FORMAT = (
    r"[a-z][_0-9a-z\-\*\/]{0,255}|"
    r"[a-z0-9][_0-9a-z\-\*\/]{0,240}@[a-z][_0-9a-z\-\*\/]{0,13}"
)
_KEY_PATTERN = re.compile(_KEY_FORMAT)

# The value is an opaque string containing up to 256 printable
# ASCII [RFC0020] characters (i.e., the range 0x20 to 0x7E)
# except comma (,) and (=).
# value    = 0*255(chr) nblk-chr
# nblk-chr = %x21-2B / %x2D-3C / %x3E-7E
# chr      = %x20 / nblk-chr

_VALUE_FORMAT = r"[\x20-\x2b\x2d-\x3c\x3e-\x7e]{0,255}[\x21-\x2b\x2d-\x3c\x3e-\x7e]"
_VALUE_PATTERN = re.compile(_VALUE_FORMAT)


_TRACECONTEXT_MAXIMUM_TRACESTATE_KEYS = 32
_delimiter_pattern = re.compile(r"[ \t]*,[ \t]*")
_member_pattern = re.compile(f"({_KEY_FORMAT})(=)({_VALUE_FORMAT})[ \t]*")
_logger = logging.getLogger(__name__)


def _is_valid_pair(key: str, value: str) -> bool:
    return (
        isinstance(key, str)
        and _KEY_PATTERN.fullmatch(key) is not None
        and isinstance(value, str)
        and _VALUE_PATTERN.fullmatch(value) is not None
    )


class Span(abc.ABC):
    """A span represents a single operation within a trace."""

    @abc.abstractmethod
    def end(self, end_time: typing.Optional[int] = None) -> None:
        """Sets the current time as the span's end time.

        The span's end time is the wall time at which the operation finished.

        Only the first call to `end` should modify the span, and
        implementations are free to ignore or raise on further calls.
        """

    @abc.abstractmethod
    def get_span_context(self) -> "SpanContext":
        """Gets the span's SpanContext.

        Get an immutable, serializable identifier for this span that can be
        used to create new child spans.

        Returns:
            A :class:`opentelemetry.trace.SpanContext` with a copy of this span's immutable state.
        """

    @abc.abstractmethod
    def set_attributes(
        self, attributes: typing.Dict[str, types.AttributeValue]
    ) -> None:
        """Sets Attributes.

        Sets Attributes with the key and value passed as arguments dict.

        Note: The behavior of `None` value attributes is undefined, and hence
        strongly discouraged. It is also preferred to set attributes at span
        creation, instead of calling this method later since samplers can only
        consider information already present during span creation.
        """

    @abc.abstractmethod
    def set_attribute(self, key: str, value: types.AttributeValue) -> None:
        """Sets an Attribute.

        Sets a single Attribute with the key and value passed as arguments.

        Note: The behavior of `None` value attributes is undefined, and hence
        strongly discouraged. It is also preferred to set attributes at span
        creation, instead of calling this method later since samplers can only
        consider information already present during span creation.
        """

    @abc.abstractmethod
    def add_event(
        self,
        name: str,
        attributes: types.Attributes = None,
        timestamp: typing.Optional[int] = None,
    ) -> None:
        """Adds an `Event`.

        Adds a single `Event` with the name and, optionally, a timestamp and
        attributes passed as arguments. Implementations should generate a
        timestamp if the `timestamp` argument is omitted.
        """

    @abc.abstractmethod
    def update_name(self, name: str) -> None:
        """Updates the `Span` name.

        This will override the name provided via :func:`opentelemetry.trace.Tracer.start_span`.

        Upon this update, any sampling behavior based on Span name will depend
        on the implementation.
        """

    @abc.abstractmethod
    def is_recording(self) -> bool:
        """Returns whether this span will be recorded.

        Returns true if this Span is active and recording information like
        events with the add_event operation and attributes using set_attribute.
        """

    @abc.abstractmethod
    def set_status(
        self,
        status: typing.Union[Status, StatusCode],
        description: typing.Optional[str] = None,
    ) -> None:
        """Sets the Status of the Span. If used, this will override the default
        Span status.
        """

    @abc.abstractmethod
    def record_exception(
        self,
        exception: Exception,
        attributes: types.Attributes = None,
        timestamp: typing.Optional[int] = None,
        escaped: bool = False,
    ) -> None:
        """Records an exception as a span event."""

    def __enter__(self) -> "Span":
        """Invoked when `Span` is used as a context manager.

        Returns the `Span` itself.
        """
        return self

    def __exit__(
        self,
        exc_type: typing.Optional[typing.Type[BaseException]],
        exc_val: typing.Optional[BaseException],
        exc_tb: typing.Optional[python_types.TracebackType],
    ) -> None:
        """Ends context manager and calls `end` on the `Span`."""

        self.end()


class TraceFlags(int):
    """A bitmask that represents options specific to the trace.

    The only supported option is the "sampled" flag (``0x01``). If set, this
    flag indicates that the trace may have been sampled upstream.

    See the `W3C Trace Context - Traceparent`_ spec for details.

    .. _W3C Trace Context - Traceparent:
        https://www.w3.org/TR/trace-context/#trace-flags
    """

    DEFAULT = 0x00
    SAMPLED = 0x01

    @classmethod
    def get_default(cls) -> "TraceFlags":
        return cls(cls.DEFAULT)

    @property
    def sampled(self) -> bool:
        return bool(self & TraceFlags.SAMPLED)


DEFAULT_TRACE_OPTIONS = TraceFlags.get_default()


class TraceState(typing.Mapping[str, str]):
    """A list of key-value pairs representing vendor-specific trace info.

    Keys and values are strings of up to 256 printable US-ASCII characters.
    Implementations should conform to the `W3C Trace Context - Tracestate`_
    spec, which describes additional restrictions on valid field values.

    .. _W3C Trace Context - Tracestate:
        https://www.w3.org/TR/trace-context/#tracestate-field
    """

    def __init__(
        self,
        entries: typing.Optional[typing.Sequence[typing.Tuple[str, str]]] = None,
    ) -> None:
        self._dict = OrderedDict()  # type: OrderedDict[str, str]
        if entries is None:
            return
        if len(entries) > _TRACECONTEXT_MAXIMUM_TRACESTATE_KEYS:
            _logger.warning(
                "There can't be more than %s key/value pairs.",
                _TRACECONTEXT_MAXIMUM_TRACESTATE_KEYS,
            )
            return

        for key, value in entries:
            if _is_valid_pair(key, value):
                if key in self._dict:
                    _logger.warning("Duplicate key: %s found.", key)
                    continue
                self._dict[key] = value
            else:
                _logger.warning("Invalid key/value pair (%s, %s) found.", key, value)

    def __contains__(self, item: object) -> bool:
        return item in self._dict

    def __getitem__(self, key: str) -> str:
        return self._dict[key]

    def __iter__(self) -> typing.Iterator[str]:
        return iter(self._dict)

    def __len__(self) -> int:
        return len(self._dict)

    def __repr__(self) -> str:
        pairs = [f"{{key={key}, value={value}}}" for key, value in self._dict.items()]
        return str(pairs)

    def add(self, key: str, value: str) -> "TraceState":
        """Adds a key-value pair to tracestate. The provided pair should
        adhere to w3c tracestate identifiers format.

        Args:
            key: A valid tracestate key to add
            value: A valid tracestate value to add

        Returns:
            A new TraceState with the modifications applied.

            If the provided key-value pair is invalid or results in tracestate
            that violates tracecontext specification, they are discarded and
            same tracestate will be returned.
        """
        if not _is_valid_pair(key, value):
            _logger.warning("Invalid key/value pair (%s, %s) found.", key, value)
            return self
        # There can be a maximum of 32 pairs
        if len(self) >= _TRACECONTEXT_MAXIMUM_TRACESTATE_KEYS:
            _logger.warning("There can't be more 32 key/value pairs.")
            return self
        # Duplicate entries are not allowed
        if key in self._dict:
            _logger.warning("The provided key %s already exists.", key)
            return self
        new_state = [(key, value)] + list(self._dict.items())
        return TraceState(new_state)

    def update(self, key: str, value: str) -> "TraceState":
        """Updates a key-value pair in tracestate. The provided pair should
        adhere to w3c tracestate identifiers format.

        Args:
            key: A valid tracestate key to update
            value: A valid tracestate value to update for key

        Returns:
            A new TraceState with the modifications applied.

            If the provided key-value pair is invalid or results in tracestate
            that violates tracecontext specification, they are discarded and
            same tracestate will be returned.
        """
        if not _is_valid_pair(key, value):
            _logger.warning("Invalid key/value pair (%s, %s) found.", key, value)
            return self
        prev_state = self._dict.copy()
        prev_state[key] = value
        prev_state.move_to_end(key, last=False)
        new_state = list(prev_state.items())
        return TraceState(new_state)

    def delete(self, key: str) -> "TraceState":
        """Deletes a key-value from tracestate.

        Args:
            key: A valid tracestate key to remove key-value pair from tracestate

        Returns:
            A new TraceState with the modifications applied.

            If the provided key-value pair is invalid or results in tracestate
            that violates tracecontext specification, they are discarded and
            same tracestate will be returned.
        """
        if key not in self._dict:
            _logger.warning("The provided key %s doesn't exist.", key)
            return self
        prev_state = self._dict.copy()
        prev_state.pop(key)
        new_state = list(prev_state.items())
        return TraceState(new_state)

    def to_header(self) -> str:
        """Creates a w3c tracestate header from a TraceState.

        Returns:
            A string that adheres to the w3c tracestate
            header format.
        """
        return ",".join(key + "=" + value for key, value in self._dict.items())

    @classmethod
    def from_header(cls, header_list: typing.List[str]) -> "TraceState":
        """Parses one or more w3c tracestate header into a TraceState.

        Args:
            header_list: one or more w3c tracestate headers.

        Returns:
            A valid TraceState that contains values extracted from
            the tracestate header.

            If the format of one headers is illegal, all values will
            be discarded and an empty tracestate will be returned.

            If the number of keys is beyond the maximum, all values
            will be discarded and an empty tracestate will be returned.
        """
        pairs = OrderedDict()  # type: OrderedDict[str, str]
        for header in header_list:
            members: typing.List[str] = re.split(_delimiter_pattern, header)
            for member in members:
                # empty members are valid, but no need to process further.
                if not member:
                    continue
                match = _member_pattern.fullmatch(member)
                if not match:
                    _logger.warning(
                        "Member doesn't match the w3c identifiers format %s",
                        member,
                    )
                    return cls()
                groups: typing.Tuple[str, ...] = match.groups()
                key, _eq, value = groups
                # duplicate keys are not legal in header
                if key in pairs:
                    return cls()
                pairs[key] = value
        return cls(list(pairs.items()))

    @classmethod
    def get_default(cls) -> "TraceState":
        return cls()

    def keys(self) -> typing.KeysView[str]:
        return self._dict.keys()

    def items(self) -> typing.ItemsView[str, str]:
        return self._dict.items()

    def values(self) -> typing.ValuesView[str]:
        return self._dict.values()


DEFAULT_TRACE_STATE = TraceState.get_default()
_TRACE_ID_MAX_VALUE = 2**128 - 1
_SPAN_ID_MAX_VALUE = 2**64 - 1


class SpanContext(typing.Tuple[int, int, bool, "TraceFlags", "TraceState", bool]):
    """The state of a Span to propagate between processes.

    This class includes the immutable attributes of a :class:`.Span` that must
    be propagated to a span's children and across process boundaries.

    Args:
        trace_id: The ID of the trace that this span belongs to.
        span_id: This span's ID.
        is_remote: True if propagated from a remote parent.
        trace_flags: Trace options to propagate.
        trace_state: Tracing-system-specific info to propagate.
    """

    def __new__(
        cls,
        trace_id: int,
        span_id: int,
        is_remote: bool,
        trace_flags: typing.Optional["TraceFlags"] = DEFAULT_TRACE_OPTIONS,
        trace_state: typing.Optional["TraceState"] = DEFAULT_TRACE_STATE,
    ) -> "SpanContext":
        if trace_flags is None:
            trace_flags = DEFAULT_TRACE_OPTIONS
        if trace_state is None:
            trace_state = DEFAULT_TRACE_STATE

        is_valid = (
            INVALID_TRACE_ID < trace_id <= _TRACE_ID_MAX_VALUE
            and INVALID_SPAN_ID < span_id <= _SPAN_ID_MAX_VALUE
        )

        return tuple.__new__(
            cls,
            (trace_id, span_id, is_remote, trace_flags, trace_state, is_valid),
        )

    def __getnewargs__(
        self,
    ) -> typing.Tuple[int, int, bool, "TraceFlags", "TraceState"]:
        return (
            self.trace_id,
            self.span_id,
            self.is_remote,
            self.trace_flags,
            self.trace_state,
        )

    @property
    def trace_id(self) -> int:
        return self[0]  # pylint: disable=unsubscriptable-object

    @property
    def span_id(self) -> int:
        return self[1]  # pylint: disable=unsubscriptable-object

    @property
    def is_remote(self) -> bool:
        return self[2]  # pylint: disable=unsubscriptable-object

    @property
    def trace_flags(self) -> "TraceFlags":
        return self[3]  # pylint: disable=unsubscriptable-object

    @property
    def trace_state(self) -> "TraceState":
        return self[4]  # pylint: disable=unsubscriptable-object

    @property
    def is_valid(self) -> bool:
        return self[5]  # pylint: disable=unsubscriptable-object

    def __setattr__(self, *args: str) -> None:
        _logger.debug("Immutable type, ignoring call to set attribute", stack_info=True)

    def __delattr__(self, *args: str) -> None:
        _logger.debug("Immutable type, ignoring call to set attribute", stack_info=True)

    def __repr__(self) -> str:
        return f"{type(self).__name__}(trace_id=0x{format_trace_id(self.trace_id)}, span_id=0x{format_span_id(self.span_id)}, trace_flags=0x{self.trace_flags:02x}, trace_state={self.trace_state!r}, is_remote={self.is_remote})"


class NonRecordingSpan(Span):
    """The Span that is used when no Span implementation is available.

    All operations are no-op except context propagation.
    """

    def __init__(self, context: "SpanContext") -> None:
        self._context = context

    def get_span_context(self) -> "SpanContext":
        return self._context

    def is_recording(self) -> bool:
        return False

    def end(self, end_time: typing.Optional[int] = None) -> None:
        pass

    def set_attributes(
        self, attributes: typing.Dict[str, types.AttributeValue]
    ) -> None:
        pass

    def set_attribute(self, key: str, value: types.AttributeValue) -> None:
        pass

    def add_event(
        self,
        name: str,
        attributes: types.Attributes = None,
        timestamp: typing.Optional[int] = None,
    ) -> None:
        pass

    def update_name(self, name: str) -> None:
        pass

    def set_status(
        self,
        status: typing.Union[Status, StatusCode],
        description: typing.Optional[str] = None,
    ) -> None:
        pass

    def record_exception(
        self,
        exception: Exception,
        attributes: types.Attributes = None,
        timestamp: typing.Optional[int] = None,
        escaped: bool = False,
    ) -> None:
        pass

    def __repr__(self) -> str:
        return f"NonRecordingSpan({self._context!r})"


INVALID_SPAN_ID = 0x0000000000000000
INVALID_TRACE_ID = 0x00000000000000000000000000000000
INVALID_SPAN_CONTEXT = SpanContext(
    trace_id=INVALID_TRACE_ID,
    span_id=INVALID_SPAN_ID,
    is_remote=False,
    trace_flags=DEFAULT_TRACE_OPTIONS,
    trace_state=DEFAULT_TRACE_STATE,
)
INVALID_SPAN = NonRecordingSpan(INVALID_SPAN_CONTEXT)


def format_trace_id(trace_id: int) -> str:
    """Convenience trace ID formatting method
    Args:
        trace_id: Trace ID int

    Returns:
        The trace ID as 32-byte hexadecimal string
    """
    return format(trace_id, "032x")


def format_span_id(span_id: int) -> str:
    """Convenience span ID formatting method
    Args:
        span_id: Span ID int

    Returns:
        The span ID as 16-byte hexadecimal string
    """
    return format(span_id, "016x")

virt/lib/python3.9/site-packages/mysql/opentelemetry/trace/span.py
PrinceDisant/LostInNL
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import os
import sys

# FIXME: Use importlib.metadata when support for 3.11 is dropped if the rest of
# the supported versions at that time have the same API.
from mysql.opentelemetry.importlib_metadata import (  # type: ignore
    EntryPoint,
    EntryPoints,
    entry_points,
    version,
)

path_to_otel, _ = os.path.split(os.path.dirname(__file__))
sys.path.append(os.path.join(path_to_otel, "_dist_info"))

# The importlib-metadata library has introduced breaking changes before to its
# API, this module is kept just to act as a layer between the
# importlib-metadata library and our project if in any case it is necessary to
# do so.

__all__ = ["entry_points", "version", "EntryPoint", "EntryPoints"]

virt/lib/python3.9/site-packages/mysql/opentelemetry/util/_importlib_metadata.py
PrinceDisant/LostInNL
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from logging import getLogger
from os import environ
from typing import TYPE_CHECKING, TypeVar, cast

from mysql.opentelemetry.util._importlib_metadata import entry_points

if TYPE_CHECKING:
    from mysql.opentelemetry.metrics import MeterProvider
    from mysql.opentelemetry.trace import TracerProvider

Provider = TypeVar("Provider", "TracerProvider", "MeterProvider")

logger = getLogger(__name__)


def _load_provider(provider_environment_variable: str, provider: str) -> Provider:
    try:
        provider_name = cast(
            str,
            environ.get(provider_environment_variable, f"default_{provider}"),
        )

        return cast(
            Provider,
            next(  # type: ignore
                iter(  # type: ignore
                    entry_points(  # type: ignore
                        group=f"opentelemetry_{provider}",
                        name=provider_name,
                    )
                )
            ).load()(),
        )
    except Exception:  # pylint: disable=broad-except
        logger.exception("Failed to load configured provider %s", provider)
        raise

virt/lib/python3.9/site-packages/mysql/opentelemetry/util/_providers.py
PrinceDisant/LostInNL
# Copyright (c) 2016, 2022, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

"""MySQL X DevAPI Python implementation."""

from .connection import Client, Session, get_client, get_session
from .constants import Auth, Compression, LockContention, SSLMode
from .crud import Collection, Schema, Table, View
from .dbdoc import DbDoc
from .errors import (  # pylint: disable=redefined-builtin
    DatabaseError,
    DataError,
    Error,
    IntegrityError,
    InterfaceError,
    InternalError,
    NotSupportedError,
    OperationalError,
    PoolError,
    ProgrammingError,
    TimeoutError,
)
from .expr import ExprParser as expr
from .result import (
    BufferingResult,
    Column,
    ColumnType,
    DocResult,
    Result,
    Row,
    RowResult,
    SqlResult,
)
from .statement import (
    AddStatement,
    CreateCollectionIndexStatement,
    DeleteStatement,
    Expr,
    FilterableStatement,
    FindStatement,
    InsertStatement,
    ModifyStatement,
    ReadStatement,
    RemoveStatement,
    SelectStatement,
    SqlStatement,
    Statement,
    UpdateStatement,
    WriteStatement,
)

__all__ = [
    # mysqlx.connection
    "Client",
    "Session",
    "get_client",
    "get_session",
    "expr",
    # mysqlx.constants
    "Auth",
    "Compression",
    "LockContention",
    "SSLMode",
    # mysqlx.crud
    "Schema",
    "Collection",
    "Table",
    "View",
    # mysqlx.errors
    "Error",
    "InterfaceError",
    "DatabaseError",
    "NotSupportedError",
    "DataError",
    "IntegrityError",
    "ProgrammingError",
    "OperationalError",
    "InternalError",
    "PoolError",
    "TimeoutError",
    # mysqlx.result
    "Column",
    "Row",
    "Result",
    "BufferingResult",
    "RowResult",
    "SqlResult",
    "DocResult",
    "ColumnType",
    # mysqlx.statement
    "DbDoc",
    "Statement",
    "FilterableStatement",
    "SqlStatement",
    "FindStatement",
    "AddStatement",
    "RemoveStatement",
    "ModifyStatement",
    "SelectStatement",
    "InsertStatement",
    "DeleteStatement",
    "UpdateStatement",
    "ReadStatement",
    "WriteStatement",
    "CreateCollectionIndexStatement",
    "Expr",
]

virt/lib/python3.9/site-packages/mysqlx/__init__.py
PrinceDisant/LostInNL
# Copyright (c) 2016, 2022, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

"""Implementation of MySQL Authentication Plugin."""

import hashlib
import struct

from typing import Optional

from .helpers import hexlify


def xor_string(hash1: bytes, hash2: bytes, hash_size: int) -> bytes:
    """Encrypt/Decrypt function used for password encryption in
    authentication, using a simple XOR.

    Args:
        hash1 (str): The first hash.
        hash2 (str): The second hash.

    Returns:
        str: A string with the xor applied.
    """
    xored = [h1 ^ h2 for (h1, h2) in zip(hash1, hash2)]
    return struct.pack(f"{hash_size}B", *xored)


class BaseAuthPlugin:
    """Base class for implementing the authentication plugins."""

    def __init__(self, username: Optional[str] = None, password: Optional[str] = None):
        self._username: Optional[str] = username
        self._password: Optional[str] = password

    def name(self) -> str:
        """Returns the plugin name.

        Returns:
            str: The plugin name.
        """
        raise NotImplementedError

    def auth_name(self) -> str:
        """Returns the authentication name.

        Returns:
            str: The authentication name.
        """
        raise NotImplementedError


class MySQL41AuthPlugin(BaseAuthPlugin):
    """Class implementing the MySQL Native Password authentication plugin."""

    def name(self) -> str:
        """Returns the plugin name.

        Returns:
            str: The plugin name.
        """
        return "MySQL 4.1 Authentication Plugin"

    def auth_name(self) -> str:
        """Returns the authentication name.

        Returns:
            str: The authentication name.
        """
        return "MYSQL41"

    def auth_data(self, data: bytes) -> str:
        """Hashing for MySQL 4.1 authentication.

        Args:
            data (bytes): The authentication data.

        Returns:
            str: The authentication response.
        """
        if self._password:
            password = (
                self._password.encode("utf-8")
                if isinstance(self._password, str)
                else self._password
            )
            hash1 = hashlib.sha1(password).digest()
            hash2 = hashlib.sha1(hash1).digest()
            xored = xor_string(hash1, hashlib.sha1(data + hash2).digest(), 20)
            return f"\0{self._username}\0*{hexlify(xored)}\0"
        return f"\0{self._username}\0"


class PlainAuthPlugin(BaseAuthPlugin):
    """Class implementing the MySQL Plain authentication plugin."""

    def name(self) -> str:
        """Returns the plugin name.

        Returns:
            str: The plugin name.
        """
        return "Plain Authentication Plugin"

    def auth_name(self) -> str:
        """Returns the authentication name.

        Returns:
            str: The authentication name.
        """
        return "PLAIN"

    def auth_data(self) -> str:
        """Returns the authentication data.

        Returns:
            str: The authentication data.
        """
        return f"\0{self._username}\0{self._password}"


class Sha256MemoryAuthPlugin(BaseAuthPlugin):
    """Class implementing the SHA256_MEMORY authentication plugin."""

    def name(self) -> str:
        """Returns the plugin name.

        Returns:
            str: The plugin name.
        """
        return "SHA256_MEMORY Authentication Plugin"

    def auth_name(self) -> str:
        """Returns the authentication name.

        Returns:
            str: The authentication name.
        """
        return "SHA256_MEMORY"

    def auth_data(self, data: bytes) -> str:
        """Hashing for SHA256_MEMORY authentication.

        The scramble is of the form:
            SHA256(SHA256(SHA256(PASSWORD)),NONCE) XOR SHA256(PASSWORD)

        Args:
            data (bytes): The authentication data.

        Returns:
            str: The authentication response.
        """
        password = (
            self._password.encode("utf-8")
            if isinstance(self._password, str)
            else self._password
        )
        hash1 = hashlib.sha256(password).digest()
        hash2 = hashlib.sha256(hashlib.sha256(hash1).digest() + data).digest()
        xored = xor_string(hash2, hash1, 32)
        return f"\0{self._username}\0{hexlify(xored)}"

virt/lib/python3.9/site-packages/mysqlx/authentication.py
PrinceDisant/LostInNL
# -*- coding: utf-8 -*-

# Copyright (c) 2013, 2022, Oracle and/or its affiliates. All rights reserved.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

"""This module contains the MySQL Server Character Sets."""

# This file was auto-generated.
from typing import List, Optional, Tuple

_GENERATED_ON = "2022-05-09"
_MYSQL_VERSION = (8, 0, 30)

MYSQL_CHARACTER_SETS: List[Optional[Tuple[str, str, bool]]] = [
    # (character set name, collation, default)
    None,
    ("big5", "big5_chinese_ci", True),  # 1
    ("latin2", "latin2_czech_cs", False),  # 2
    ("dec8", "dec8_swedish_ci", True),  # 3
    ("cp850", "cp850_general_ci", True),  # 4
    ("latin1", "latin1_german1_ci", False),  # 5
    ("hp8", "hp8_english_ci", True),  # 6
    ("koi8r", "koi8r_general_ci", True),  # 7
    ("latin1", "latin1_swedish_ci", True),  # 8
    ("latin2", "latin2_general_ci", True),  # 9
    ("swe7", "swe7_swedish_ci", True),  # 10
    ("ascii", "ascii_general_ci", True),  # 11
    ("ujis", "ujis_japanese_ci", True),  # 12
    ("sjis", "sjis_japanese_ci", True),  # 13
    ("cp1251", "cp1251_bulgarian_ci", False),  # 14
    ("latin1", "latin1_danish_ci", False),  # 15
    ("hebrew", "hebrew_general_ci", True),  # 16
    None,
    ("tis620", "tis620_thai_ci", True),  # 18
    ("euckr", "euckr_korean_ci", True),  # 19
    ("latin7", "latin7_estonian_cs", False),  # 20
    ("latin2", "latin2_hungarian_ci", False),  # 21
    ("koi8u", "koi8u_general_ci", True),  # 22
    ("cp1251", "cp1251_ukrainian_ci", False),  # 23
    ("gb2312", "gb2312_chinese_ci", True),  # 24
    ("greek", "greek_general_ci", True),  # 25
    ("cp1250", "cp1250_general_ci", True),  # 26
    ("latin2", "latin2_croatian_ci", False),  # 27
    ("gbk", "gbk_chinese_ci", True),  # 28
    ("cp1257", "cp1257_lithuanian_ci", False),  # 29
    ("latin5", "latin5_turkish_ci", True),  # 30
    ("latin1", "latin1_german2_ci", False),  # 31
    ("armscii8", "armscii8_general_ci", True),  # 32
    ("utf8mb3", "utf8mb3_general_ci", True),  # 33
    ("cp1250", "cp1250_czech_cs", False),  # 34
    ("ucs2", "ucs2_general_ci", True),  # 35
    ("cp866", "cp866_general_ci", True),  # 36
    ("keybcs2", "keybcs2_general_ci", True),  # 37
    ("macce", "macce_general_ci", True),  # 38
    ("macroman", "macroman_general_ci", True),  # 39
    ("cp852", "cp852_general_ci", True),  # 40
    ("latin7", "latin7_general_ci", True),  # 41
    ("latin7", "latin7_general_cs", False),  # 42
    ("macce", "macce_bin", False),  # 43
    ("cp1250", "cp1250_croatian_ci", False),  # 44
    ("utf8mb4", "utf8mb4_general_ci", False),  # 45
    ("utf8mb4", "utf8mb4_bin", False),  # 46
    ("latin1", "latin1_bin", False),  # 47
    ("latin1", "latin1_general_ci", False),  # 48
    ("latin1", "latin1_general_cs", False),  # 49
    ("cp1251", "cp1251_bin", False),  # 50
    ("cp1251", "cp1251_general_ci", True),  # 51
    ("cp1251", "cp1251_general_cs", False),  # 52
    ("macroman", "macroman_bin", False),  # 53
    ("utf16", "utf16_general_ci", True),  # 54
    ("utf16", "utf16_bin", False),  # 55
    ("utf16le", "utf16le_general_ci", True),  # 56
    ("cp1256", "cp1256_general_ci", True),  # 57
    ("cp1257", "cp1257_bin", False),  # 58
    ("cp1257", "cp1257_general_ci", True),  # 59
    ("utf32", "utf32_general_ci", True),  # 60
    ("utf32", "utf32_bin", False),  # 61
    ("utf16le", "utf16le_bin", False),  # 62
    ("binary", "binary", True),  # 63
    ("armscii8", "armscii8_bin", False),  # 64
    ("ascii", "ascii_bin", False),  # 65
    ("cp1250", "cp1250_bin", False),  # 66
    ("cp1256", "cp1256_bin", False),  # 67
    ("cp866", "cp866_bin", False),  # 68
    ("dec8", "dec8_bin", False),  # 69
    ("greek", "greek_bin", False),  # 70
    ("hebrew", "hebrew_bin", False),  # 71
    ("hp8", "hp8_bin", False),  # 72
    ("keybcs2", "keybcs2_bin", False),  # 73
    ("koi8r", "koi8r_bin", False),  # 74
    ("koi8u", "koi8u_bin", False),  # 75
    ("utf8mb3", "utf8mb3_tolower_ci", False),  # 76
    ("latin2", "latin2_bin", False),  # 77
    ("latin5", "latin5_bin", False),  # 78
    ("latin7", "latin7_bin", False),  # 79
    ("cp850", "cp850_bin", False),  # 80
    ("cp852", "cp852_bin", False),  # 81
    ("swe7", "swe7_bin", False),  # 82
    ("utf8mb3", "utf8mb3_bin", False),  # 83
    ("big5", "big5_bin", False),  # 84
    ("euckr", "euckr_bin", False),  # 85
    ("gb2312", "gb2312_bin", False),  # 86
    ("gbk", "gbk_bin", False),  # 87
    ("sjis", "sjis_bin", False),  # 88
    ("tis620", "tis620_bin", False),  # 89
    ("ucs2", "ucs2_bin", False),  # 90
    ("ujis", "ujis_bin", False),  # 91
    ("geostd8", "geostd8_general_ci", True),  # 92
    ("geostd8", "geostd8_bin", False),  # 93
    ("latin1", "latin1_spanish_ci", False),  # 94
    ("cp932", "cp932_japanese_ci", True),  # 95
    ("cp932", "cp932_bin", False),  # 96
    ("eucjpms", "eucjpms_japanese_ci", True),  # 97
    ("eucjpms", "eucjpms_bin", False),  # 98
    ("cp1250", "cp1250_polish_ci", False),  # 99
    None,
    ("utf16", "utf16_unicode_ci", False),  # 101
    ("utf16", "utf16_icelandic_ci", False),  # 102
    ("utf16", "utf16_latvian_ci", False),  # 103
    ("utf16", "utf16_romanian_ci", False),  # 104
    ("utf16", "utf16_slovenian_ci", False),  # 105
    ("utf16", "utf16_polish_ci", False),  # 106
    ("utf16", "utf16_estonian_ci", False),  # 107
    ("utf16", "utf16_spanish_ci", False),  # 108
    ("utf16", "utf16_swedish_ci", False),  # 109
    ("utf16", "utf16_turkish_ci", False),  # 110
    ("utf16", "utf16_czech_ci", False),  # 111
    ("utf16", "utf16_danish_ci", False),  # 112
    ("utf16", "utf16_lithuanian_ci", False),  # 113
    ("utf16", "utf16_slovak_ci", False),  # 114
    ("utf16", "utf16_spanish2_ci", False),  # 115
    ("utf16", "utf16_roman_ci", False),  # 116
    ("utf16", "utf16_persian_ci", False),  # 117
    ("utf16", "utf16_esperanto_ci", False),  # 118
    ("utf16", "utf16_hungarian_ci", False),  # 119
    ("utf16", "utf16_sinhala_ci", False),  # 120
    ("utf16", "utf16_german2_ci", False),  # 121
    ("utf16", "utf16_croatian_ci", False),  # 122
    ("utf16", "utf16_unicode_520_ci", False),  # 123
    ("utf16", "utf16_vietnamese_ci", False),  # 124
    None,
    None,
    None,
    ("ucs2", "ucs2_unicode_ci", False),  # 128
    ("ucs2", "ucs2_icelandic_ci", False),  # 129
    ("ucs2", "ucs2_latvian_ci", False),  # 130
    ("ucs2", "ucs2_romanian_ci", False),  # 131
    ("ucs2", "ucs2_slovenian_ci", False),  # 132
    ("ucs2", "ucs2_polish_ci", False),  # 133
    ("ucs2", "ucs2_estonian_ci", False),  # 134
    ("ucs2", "ucs2_spanish_ci", False),  # 135
    ("ucs2", "ucs2_swedish_ci", False),  # 136
    ("ucs2", "ucs2_turkish_ci", False),  # 137
    ("ucs2", "ucs2_czech_ci", False),  # 138
    ("ucs2", "ucs2_danish_ci", False),  # 139
    ("ucs2", "ucs2_lithuanian_ci", False),  # 140
    ("ucs2", "ucs2_slovak_ci", False),  # 141
    ("ucs2", "ucs2_spanish2_ci", False),  # 142
    ("ucs2", "ucs2_roman_ci", False),  # 143
    ("ucs2", "ucs2_persian_ci", False),  # 144
    ("ucs2", "ucs2_esperanto_ci", False),  # 145
    ("ucs2", "ucs2_hungarian_ci", False),  # 146
    ("ucs2", "ucs2_sinhala_ci", False),  # 147
    ("ucs2", "ucs2_german2_ci", False),  # 148
    ("ucs2", "ucs2_croatian_ci", False),  # 149
    ("ucs2", "ucs2_unicode_520_ci", False),  # 150
    ("ucs2", "ucs2_vietnamese_ci", False),  # 151
    None,
    None,
    None,
    None,
    None,
    None,
    None,
    ("ucs2", "ucs2_general_mysql500_ci", False),  # 159
    ("utf32", "utf32_unicode_ci", False),  # 160
    ("utf32", "utf32_icelandic_ci", False),  # 161
    ("utf32", "utf32_latvian_ci", False),  # 162
    ("utf32", "utf32_romanian_ci", False),  # 163
    ("utf32", "utf32_slovenian_ci", False),  # 164
    ("utf32", "utf32_polish_ci", False),  # 165
    ("utf32", "utf32_estonian_ci", False),  # 166
    ("utf32", "utf32_spanish_ci", False),  # 167
    ("utf32", "utf32_swedish_ci", False),  # 168
    ("utf32", "utf32_turkish_ci", False),  # 169
    ("utf32", "utf32_czech_ci", False),  # 170
    ("utf32", "utf32_danish_ci", False),  # 171
    ("utf32", "utf32_lithuanian_ci", False),  # 172
    ("utf32", "utf32_slovak_ci", False),  # 173
    ("utf32", "utf32_spanish2_ci", False),  # 174
    ("utf32", "utf32_roman_ci", False),  # 175
    ("utf32", "utf32_persian_ci", False),  # 176
    ("utf32", "utf32_esperanto_ci", False),  # 177
    ("utf32", "utf32_hungarian_ci", False),  # 178
    ("utf32", "utf32_sinhala_ci", False),  # 179
    ("utf32", "utf32_german2_ci", False),  # 180
    ("utf32", "utf32_croatian_ci", False),  # 181
    ("utf32", "utf32_unicode_520_ci", False),  # 182
    ("utf32", "utf32_vietnamese_ci", False),  # 183
    None,
    None,
    None,
    None,
    None,
    None,
    None,
    None,
    ("utf8mb3", "utf8mb3_unicode_ci", False),  # 192
    ("utf8mb3", "utf8mb3_icelandic_ci", False),  # 193
    ("utf8mb3", "utf8mb3_latvian_ci", False),  # 194
    ("utf8mb3", "utf8mb3_romanian_ci", False),  # 195
    ("utf8mb3", "utf8mb3_slovenian_ci", False),  # 196
    ("utf8mb3", "utf8mb3_polish_ci", False),  # 197
    ("utf8mb3", "utf8mb3_estonian_ci", False),  # 198
    ("utf8mb3", "utf8mb3_spanish_ci", False),  # 199
    ("utf8mb3", "utf8mb3_swedish_ci", False),  # 200
    ("utf8mb3", "utf8mb3_turkish_ci", False),  # 201
    ("utf8mb3", "utf8mb3_czech_ci", False),  # 202
    ("utf8mb3", "utf8mb3_danish_ci", False),  # 203
    ("utf8mb3", "utf8mb3_lithuanian_ci", False),  # 204
    ("utf8mb3", "utf8mb3_slovak_ci", False),  # 205
    ("utf8mb3", "utf8mb3_spanish2_ci", False),  # 206
    ("utf8mb3", "utf8mb3_roman_ci", False),  # 207
    ("utf8mb3", "utf8mb3_persian_ci", False),  # 208
    ("utf8mb3", "utf8mb3_esperanto_ci", False),  # 209
    ("utf8mb3", "utf8mb3_hungarian_ci", False),  # 210
    ("utf8mb3", "utf8mb3_sinhala_ci", False),  # 211
    ("utf8mb3", "utf8mb3_german2_ci", False),  # 212
    ("utf8mb3", "utf8mb3_croatian_ci", False),  # 213
    ("utf8mb3", "utf8mb3_unicode_520_ci", False),  # 214
    ("utf8mb3", "utf8mb3_vietnamese_ci", False),  # 215
    None,
    None,
    None,
    None,
    None,
    None,
    None,
    ("utf8mb3", "utf8mb3_general_mysql500_ci", False),  # 223
    ("utf8mb4", "utf8mb4_unicode_ci", False),  # 224
    ("utf8mb4", "utf8mb4_icelandic_ci", False),  # 225
    ("utf8mb4", "utf8mb4_latvian_ci", False),  # 226
    ("utf8mb4", "utf8mb4_romanian_ci", False),  # 227
    ("utf8mb4", "utf8mb4_slovenian_ci", False),  # 228
    ("utf8mb4", "utf8mb4_polish_ci", False),  # 229
    ("utf8mb4", "utf8mb4_estonian_ci", False),  # 230
    ("utf8mb4", "utf8mb4_spanish_ci", False),  # 231
    ("utf8mb4", "utf8mb4_swedish_ci", False),  # 232
    ("utf8mb4", "utf8mb4_turkish_ci", False),  # 233
    ("utf8mb4", "utf8mb4_czech_ci", False),  # 234
    ("utf8mb4", "utf8mb4_danish_ci", False),  # 235
    ("utf8mb4", "utf8mb4_lithuanian_ci", False),  # 236
    ("utf8mb4", "utf8mb4_slovak_ci", False),  # 237
    ("utf8mb4", "utf8mb4_spanish2_ci", False),  # 238
    ("utf8mb4", "utf8mb4_roman_ci", False),  # 239
    ("utf8mb4", "utf8mb4_persian_ci", False),  # 240
    ("utf8mb4", "utf8mb4_esperanto_ci", False),  # 241
    ("utf8mb4", "utf8mb4_hungarian_ci", False),  # 242
    ("utf8mb4", "utf8mb4_sinhala_ci", False),  # 243
    ("utf8mb4", "utf8mb4_german2_ci", False),  # 244
    ("utf8mb4", "utf8mb4_croatian_ci", False),  # 245
    ("utf8mb4", "utf8mb4_unicode_520_ci", False),  # 246
    ("utf8mb4", "utf8mb4_vietnamese_ci", False),  # 247
    ("gb18030", "gb18030_chinese_ci", True),  # 248
    ("gb18030", "gb18030_bin", False),  # 249
    ("gb18030", "gb18030_unicode_520_ci", False),  # 250
    None,
    None,
    None,
    None,
    ("utf8mb4", "utf8mb4_0900_ai_ci", True),  # 255
    ("utf8mb4", "utf8mb4_de_pb_0900_ai_ci", False),  # 256
    ("utf8mb4", "utf8mb4_is_0900_ai_ci", False),  # 257
    ("utf8mb4", "utf8mb4_lv_0900_ai_ci", False),  # 258
    ("utf8mb4", "utf8mb4_ro_0900_ai_ci", False),  # 259
    ("utf8mb4", "utf8mb4_sl_0900_ai_ci", False),  # 260
    ("utf8mb4", "utf8mb4_pl_0900_ai_ci", False),  # 261
    ("utf8mb4", "utf8mb4_et_0900_ai_ci", False),  # 262
    ("utf8mb4", "utf8mb4_es_0900_ai_ci", False),  # 263
    ("utf8mb4", "utf8mb4_sv_0900_ai_ci", False),  # 264
    ("utf8mb4", "utf8mb4_tr_0900_ai_ci", False),  # 265
    ("utf8mb4", "utf8mb4_cs_0900_ai_ci", False),  # 266
    ("utf8mb4", "utf8mb4_da_0900_ai_ci", False),  # 267
    ("utf8mb4", "utf8mb4_lt_0900_ai_ci", False),  # 268
    ("utf8mb4", "utf8mb4_sk_0900_ai_ci", False),  # 269
    ("utf8mb4", "utf8mb4_es_trad_0900_ai_ci", False),  # 270
    ("utf8mb4", "utf8mb4_la_0900_ai_ci", False),  # 271
    None,
    ("utf8mb4", "utf8mb4_eo_0900_ai_ci", False),  # 273
    ("utf8mb4", "utf8mb4_hu_0900_ai_ci", False),  # 274
    ("utf8mb4", "utf8mb4_hr_0900_ai_ci", False),  # 275
    None,
    ("utf8mb4", "utf8mb4_vi_0900_ai_ci", False),  # 277
    ("utf8mb4", "utf8mb4_0900_as_cs", False),  # 278
    ("utf8mb4", "utf8mb4_de_pb_0900_as_cs", False),  # 279
    ("utf8mb4", "utf8mb4_is_0900_as_cs", False),  # 280
    ("utf8mb4", "utf8mb4_lv_0900_as_cs", False),  # 281
    ("utf8mb4", "utf8mb4_ro_0900_as_cs", False),  # 282
    ("utf8mb4", "utf8mb4_sl_0900_as_cs", False),  # 283
    ("utf8mb4", "utf8mb4_pl_0900_as_cs", False),  # 284
    ("utf8mb4", "utf8mb4_et_0900_as_cs", False),  # 285
    ("utf8mb4", "utf8mb4_es_0900_as_cs", False),  # 286
    ("utf8mb4", "utf8mb4_sv_0900_as_cs", False),  # 287
    ("utf8mb4", "utf8mb4_tr_0900_as_cs", False),  # 288
    ("utf8mb4", "utf8mb4_cs_0900_as_cs", False),  # 289
    ("utf8mb4", "utf8mb4_da_0900_as_cs", False),  # 290
    ("utf8mb4", "utf8mb4_lt_0900_as_cs", False),  # 291
    ("utf8mb4", "utf8mb4_sk_0900_as_cs", False),  # 292
    ("utf8mb4", "utf8mb4_es_trad_0900_as_cs", False),  # 293
    ("utf8mb4", "utf8mb4_la_0900_as_cs", False),  # 294
    None,
    ("utf8mb4", "utf8mb4_eo_0900_as_cs", False),  # 296
    ("utf8mb4", "utf8mb4_hu_0900_as_cs", False),  # 297
    ("utf8mb4", "utf8mb4_hr_0900_as_cs", False),  # 298
    None,
    ("utf8mb4", "utf8mb4_vi_0900_as_cs", False),  # 300
    None,
    None,
    ("utf8mb4", "utf8mb4_ja_0900_as_cs", False),  # 303
    ("utf8mb4", "utf8mb4_ja_0900_as_cs_ks", False),  # 304
    ("utf8mb4", "utf8mb4_0900_as_ci", False),  # 305
    ("utf8mb4", "utf8mb4_ru_0900_ai_ci", False),  # 306
    ("utf8mb4", "utf8mb4_ru_0900_as_cs", False),  # 307
    ("utf8mb4", "utf8mb4_zh_0900_as_cs", False),  # 308
    ("utf8mb4", "utf8mb4_0900_bin", False),  # 309
    ("utf8mb4", "utf8mb4_nb_0900_ai_ci", False),  # 310
    ("utf8mb4", "utf8mb4_nb_0900_as_cs", False),  # 311
    ("utf8mb4", "utf8mb4_nn_0900_ai_ci", False),  # 312
    ("utf8mb4", "utf8mb4_nn_0900_as_cs", False),  # 313
    ("utf8mb4", "utf8mb4_sr_latn_0900_ai_ci", False),  # 314
    ("utf8mb4", "utf8mb4_sr_latn_0900_as_cs", False),  # 315
    ("utf8mb4", "utf8mb4_bs_0900_ai_ci", False),  # 316
    ("utf8mb4", "utf8mb4_bs_0900_as_cs", False),  # 317
    ("utf8mb4", "utf8mb4_bg_0900_ai_ci", False),  # 318
    ("utf8mb4", "utf8mb4_bg_0900_as_cs", False),  # 319
    ("utf8mb4", "utf8mb4_gl_0900_ai_ci", False),  # 320
    ("utf8mb4", "utf8mb4_gl_0900_as_cs", False),  # 321
    ("utf8mb4", "utf8mb4_mn_cyrl_0900_ai_ci", False),  # 322
    ("utf8mb4", "utf8mb4_mn_cyrl_0900_as_cs", False),  # 323
]

MYSQL_CHARACTER_SETS_57: List[Optional[Tuple[str, str, bool]]] = [
    # (character set name, collation, default)
    None,
    ("big5", "big5_chinese_ci", True),  # 1
    ("latin2", "latin2_czech_cs", False),  # 2
    ("dec8", "dec8_swedish_ci", True),  # 3
    ("cp850", "cp850_general_ci", True),  # 4
    ("latin1", "latin1_german1_ci", False),  # 5
    ("hp8", "hp8_english_ci", True),  # 6
    ("koi8r", "koi8r_general_ci", True),  # 7
    ("latin1", "latin1_swedish_ci", True),  # 8
    ("latin2", "latin2_general_ci", True),  # 9
    ("swe7", "swe7_swedish_ci", True),  # 10
    ("ascii", "ascii_general_ci", True),  # 11
    ("ujis", "ujis_japanese_ci", True),  # 12
    ("sjis", "sjis_japanese_ci", True),  # 13
    ("cp1251", "cp1251_bulgarian_ci", False),  # 14
    ("latin1", "latin1_danish_ci", False),  # 15
    ("hebrew", "hebrew_general_ci", True),  # 16
    None,
    ("tis620", "tis620_thai_ci", True),  # 18
    ("euckr", "euckr_korean_ci", True),  # 19
    ("latin7", "latin7_estonian_cs", False),  # 20
    ("latin2", "latin2_hungarian_ci", False),  # 21
    ("koi8u", "koi8u_general_ci", True),  # 22
    ("cp1251", "cp1251_ukrainian_ci", False),  # 23
    ("gb2312", "gb2312_chinese_ci", True),  # 24
    ("greek", "greek_general_ci", True),  # 25
    ("cp1250", "cp1250_general_ci", True),  # 26
    ("latin2", "latin2_croatian_ci", False),  # 27
    ("gbk", "gbk_chinese_ci", True),  # 28
    ("cp1257", "cp1257_lithuanian_ci", False),  # 29
    ("latin5", "latin5_turkish_ci", True),  # 30
    ("latin1", "latin1_german2_ci", False),  # 31
    ("armscii8", "armscii8_general_ci", True),  # 32
    ("utf8", "utf8_general_ci", True),  # 33
    ("cp1250", "cp1250_czech_cs", False),  # 34
    ("ucs2", "ucs2_general_ci", True),  # 35
    ("cp866", "cp866_general_ci", True),  # 36
    ("keybcs2", "keybcs2_general_ci", True),  # 37
    ("macce", "macce_general_ci", True),  # 38
    ("macroman", "macroman_general_ci", True),  # 39
    ("cp852", "cp852_general_ci", True),  # 40
    ("latin7", "latin7_general_ci", True),  # 41
    ("latin7", "latin7_general_cs", False),  # 42
    ("macce", "macce_bin", False),  # 43
    ("cp1250", "cp1250_croatian_ci", False),  # 44
    ("utf8mb4", "utf8mb4_general_ci", True),  # 45
    ("utf8mb4", "utf8mb4_bin", False),  # 46
    ("latin1", "latin1_bin", False),  # 47
    ("latin1", "latin1_general_ci", False),  # 48
    ("latin1", "latin1_general_cs", False),  # 49
    ("cp1251", "cp1251_bin", False),  # 50
    ("cp1251", "cp1251_general_ci", True),  # 51
    ("cp1251", "cp1251_general_cs", False),  # 52
    ("macroman", "macroman_bin", False),  # 53
    ("utf16", "utf16_general_ci", True),  # 54
    ("utf16", "utf16_bin", False),  # 55
    ("utf16le", "utf16le_general_ci", True),  # 56
    ("cp1256", "cp1256_general_ci", True),  # 57
    ("cp1257", "cp1257_bin", False),  # 58
    ("cp1257", "cp1257_general_ci", True),  # 59
    ("utf32", "utf32_general_ci", True),  # 60
    ("utf32", "utf32_bin", False),  # 61
    ("utf16le", "utf16le_bin", False),  # 62
    ("binary", "binary", True),  # 63
    ("armscii8", "armscii8_bin", False),  # 64
    ("ascii", "ascii_bin", False),  # 65
    ("cp1250", "cp1250_bin", False),  # 66
    ("cp1256", "cp1256_bin", False),  # 67
    ("cp866", "cp866_bin", False),  # 68
    ("dec8", "dec8_bin", False),  # 69
    ("greek", "greek_bin", False),  # 70
    ("hebrew", "hebrew_bin", False),  # 71
    ("hp8", "hp8_bin", False),  # 72
    ("keybcs2", "keybcs2_bin", False),  # 73
    ("koi8r", "koi8r_bin", False),  # 74
    ("koi8u", "koi8u_bin", False),  # 75
    None,
    ("latin2", "latin2_bin", False),  # 77
    ("latin5", "latin5_bin", False),  # 78
    ("latin7", "latin7_bin", False),  # 79
    ("cp850", "cp850_bin", False),  # 80
    ("cp852", "cp852_bin", False),  # 81
    ("swe7", "swe7_bin", False),  # 82
    ("utf8", "utf8_bin", False),  # 83
    ("big5", "big5_bin", False),  # 84
    ("euckr", "euckr_bin", False),  # 85
    ("gb2312", "gb2312_bin", False),  # 86
    ("gbk", "gbk_bin", False),  # 87
    ("sjis", "sjis_bin", False),  # 88
    ("tis620", "tis620_bin", False),  # 89
    ("ucs2", "ucs2_bin", False),  # 90
    ("ujis", "ujis_bin", False),  # 91
    ("geostd8", "geostd8_general_ci", True),  # 92
    ("geostd8", "geostd8_bin", False),  # 93
    ("latin1", "latin1_spanish_ci", False),  # 94
    ("cp932", "cp932_japanese_ci", True),  # 95
    ("cp932", "cp932_bin", False),  # 96
    ("eucjpms", "eucjpms_japanese_ci", True),  # 97
    ("eucjpms", "eucjpms_bin", False),  # 98
    ("cp1250", "cp1250_polish_ci", False),  # 99
    None,
    ("utf16", "utf16_unicode_ci", False),  # 101
    ("utf16", "utf16_icelandic_ci", False),  # 102
    ("utf16", "utf16_latvian_ci", False),  # 103
    ("utf16", "utf16_romanian_ci", False),  # 104
    ("utf16", "utf16_slovenian_ci", False),  # 105
    ("utf16", "utf16_polish_ci", False),  # 106
    ("utf16", "utf16_estonian_ci", False),  # 107
    ("utf16", "utf16_spanish_ci", False),  # 108
    ("utf16", "utf16_swedish_ci", False),  # 109
    ("utf16", "utf16_turkish_ci", False),  # 110
    ("utf16", "utf16_czech_ci", False),  # 111
    ("utf16", "utf16_danish_ci", False),  # 112
    ("utf16", "utf16_lithuanian_ci", False),  # 113
    ("utf16", "utf16_slovak_ci", False),  # 114
    ("utf16", "utf16_spanish2_ci", False),  # 115
    ("utf16", "utf16_roman_ci", False),  # 116
    ("utf16", "utf16_persian_ci", False),  # 117
    ("utf16", "utf16_esperanto_ci", False),  # 118
    ("utf16", "utf16_hungarian_ci", False),  # 119
    ("utf16", "utf16_sinhala_ci", False),  # 120
    ("utf16", "utf16_german2_ci", False),  # 121
    ("utf16", "utf16_croatian_ci", False),  # 122
    ("utf16", "utf16_unicode_520_ci", False),  # 123
    ("utf16", "utf16_vietnamese_ci", False),  # 124
    None,
    None,
    None,
    ("ucs2", "ucs2_unicode_ci", False),  # 128
    ("ucs2", "ucs2_icelandic_ci", False),  # 129
    ("ucs2", "ucs2_latvian_ci", False),  # 130
    ("ucs2", "ucs2_romanian_ci", False),  # 131
    ("ucs2", "ucs2_slovenian_ci", False),  # 132
    ("ucs2", "ucs2_polish_ci", False),  # 133
    ("ucs2", "ucs2_estonian_ci", False),  # 134
    ("ucs2", "ucs2_spanish_ci", False),  # 135
    ("ucs2", "ucs2_swedish_ci", False),  # 136
    ("ucs2", "ucs2_turkish_ci", False),  # 137
    ("ucs2", "ucs2_czech_ci", False),  # 138
    ("ucs2", "ucs2_danish_ci", False),  # 139
    ("ucs2", "ucs2_lithuanian_ci", False),  # 140
    ("ucs2", "ucs2_slovak_ci", False),  # 141
    ("ucs2", "ucs2_spanish2_ci", False),  # 142
    ("ucs2", "ucs2_roman_ci", False),  # 143
    ("ucs2", "ucs2_persian_ci", False),  # 144
    ("ucs2", "ucs2_esperanto_ci", False),  # 145
    ("ucs2", "ucs2_hungarian_ci", False),  # 146
    ("ucs2", "ucs2_sinhala_ci", False),  # 147
    ("ucs2", "ucs2_german2_ci", False),  # 148
    ("ucs2", "ucs2_croatian_ci", False),  # 149
    ("ucs2", "ucs2_unicode_520_ci", False),  # 150
    ("ucs2", "ucs2_vietnamese_ci", False),  # 151
    None,
    None,
    None,
    None,
    None,
    None,
    None,
    ("ucs2", "ucs2_general_mysql500_ci", False),  # 159
    ("utf32", "utf32_unicode_ci", False),  # 160
    ("utf32", "utf32_icelandic_ci", False),  # 161
    ("utf32", "utf32_latvian_ci", False),  # 162
    ("utf32", "utf32_romanian_ci", False),  # 163
    ("utf32", "utf32_slovenian_ci", False),  # 164
    ("utf32", "utf32_polish_ci", False),  # 165
    ("utf32", "utf32_estonian_ci", False),  # 166
    ("utf32", "utf32_spanish_ci", False),  # 167
    ("utf32", "utf32_swedish_ci", False),  # 168
    ("utf32", "utf32_turkish_ci", False),  # 169
    ("utf32", "utf32_czech_ci", False),  # 170
    ("utf32", "utf32_danish_ci", False),  # 171
    ("utf32", "utf32_lithuanian_ci", False),  # 172
    ("utf32", "utf32_slovak_ci", False),  # 173
    ("utf32", "utf32_spanish2_ci", False),  # 174
    ("utf32", "utf32_roman_ci", False),  # 175
    ("utf32", "utf32_persian_ci", False),  # 176
    ("utf32", "utf32_esperanto_ci", False),  # 177
    ("utf32", "utf32_hungarian_ci", False),  # 178
    ("utf32", "utf32_sinhala_ci", False),  # 179
    ("utf32", "utf32_german2_ci", False),  # 180
    ("utf32", "utf32_croatian_ci", False),  # 181
    ("utf32", "utf32_unicode_520_ci", False),  # 182
    ("utf32", "utf32_vietnamese_ci", False),  # 183
    None,
    None,
    None,
    None,
    None,
    None,
    None,
    None,
    ("utf8", "utf8_unicode_ci", False),  # 192
    ("utf8", "utf8_icelandic_ci", False),  # 193
    ("utf8", "utf8_latvian_ci", False),  # 194
    ("utf8", "utf8_romanian_ci", False),  # 195
    ("utf8", "utf8_slovenian_ci", False),  # 196
    ("utf8", "utf8_polish_ci", False),  # 197
    ("utf8", "utf8_estonian_ci", False),  # 198
    ("utf8", "utf8_spanish_ci", False),  # 199
    ("utf8", "utf8_swedish_ci", False),  # 200
    ("utf8", "utf8_turkish_ci", False),  # 201
    ("utf8", "utf8_czech_ci", False),  # 202
    ("utf8", "utf8_danish_ci", False),  # 203
    ("utf8", "utf8_lithuanian_ci", False),  # 204
    ("utf8", "utf8_slovak_ci", False),  # 205
    ("utf8", "utf8_spanish2_ci", False),  # 206
    ("utf8", "utf8_roman_ci", False),  # 207
    ("utf8", "utf8_persian_ci", False),  # 208
    ("utf8", "utf8_esperanto_ci", False),  # 209
    ("utf8", "utf8_hungarian_ci", False),  # 210
    ("utf8", "utf8_sinhala_ci", False),  # 211
    ("utf8", "utf8_german2_ci", False),  # 212
    ("utf8", "utf8_croatian_ci", False),  # 213
    ("utf8", "utf8_unicode_520_ci", False),  # 214
    ("utf8", "utf8_vietnamese_ci", False),  # 215
    None,
    None,
    None,
    None,
    None,
    None,
    None,
    ("utf8", "utf8_general_mysql500_ci", False),  # 223
    ("utf8mb4", "utf8mb4_unicode_ci", False),  # 224
    ("utf8mb4", "utf8mb4_icelandic_ci", False),  # 225
    ("utf8mb4", "utf8mb4_latvian_ci", False),  # 226
    ("utf8mb4", "utf8mb4_romanian_ci", False),  # 227
    ("utf8mb4", "utf8mb4_slovenian_ci", False),  # 228
    ("utf8mb4", "utf8mb4_polish_ci", False),  # 229
    ("utf8mb4", "utf8mb4_estonian_ci", False),  # 230
    ("utf8mb4", "utf8mb4_spanish_ci", False),  # 231
    ("utf8mb4", "utf8mb4_swedish_ci", False),  # 232
    ("utf8mb4", "utf8mb4_turkish_ci", False),  # 233
    ("utf8mb4", "utf8mb4_czech_ci", False),  # 234
    ("utf8mb4", "utf8mb4_danish_ci", False),  # 235
    ("utf8mb4", "utf8mb4_lithuanian_ci", False),  # 236
    ("utf8mb4", "utf8mb4_slovak_ci", False),  # 237
    ("utf8mb4", "utf8mb4_spanish2_ci", False),  # 238
    ("utf8mb4", "utf8mb4_roman_ci", False),  # 239
    ("utf8mb4", "utf8mb4_persian_ci", False),  # 240
    ("utf8mb4", "utf8mb4_esperanto_ci", False),  # 241
    ("utf8mb4", "utf8mb4_hungarian_ci", False),  # 242
    ("utf8mb4", "utf8mb4_sinhala_ci", False),  # 243
    ("utf8mb4", "utf8mb4_german2_ci", False),  # 244
    ("utf8mb4", "utf8mb4_croatian_ci", False),  # 245
    ("utf8mb4", "utf8mb4_unicode_520_ci", False),  # 246
    ("utf8mb4", "utf8mb4_vietnamese_ci", False),  # 247
    ("gb18030", "gb18030_chinese_ci", True),  # 248
    ("gb18030", "gb18030_bin", False),  # 249
    ("gb18030", "gb18030_unicode_520_ci", False),  # 250
]

virt/lib/python3.9/site-packages/mysqlx/charsets 2.py
PrinceDisant/LostInNL
# -*- coding: utf-8 -*-

# Copyright (c) 2013, 2022, Oracle and/or its affiliates. All rights reserved.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

"""This module contains the MySQL Server Character Sets."""

# This file was auto-generated.
from typing import List, Optional, Tuple

_GENERATED_ON = "2022-05-09"
_MYSQL_VERSION = (8, 0, 30)

MYSQL_CHARACTER_SETS: List[Optional[Tuple[str, str, bool]]] = [
    # (character set name, collation, default)
    None,
    ("big5", "big5_chinese_ci", True),  # 1
    ("latin2", "latin2_czech_cs", False),  # 2
    ("dec8", "dec8_swedish_ci", True),  # 3
    ("cp850", "cp850_general_ci", True),  # 4
    ("latin1", "latin1_german1_ci", False),  # 5
    ("hp8", "hp8_english_ci", True),  # 6
    ("koi8r", "koi8r_general_ci", True),  # 7
    ("latin1", "latin1_swedish_ci", True),  # 8
    ("latin2", "latin2_general_ci", True),  # 9
    ("swe7", "swe7_swedish_ci", True),  # 10
    ("ascii", "ascii_general_ci", True),  # 11
    ("ujis", "ujis_japanese_ci", True),  # 12
    ("sjis", "sjis_japanese_ci", True),  # 13
    ("cp1251", "cp1251_bulgarian_ci", False),  # 14
    ("latin1", "latin1_danish_ci", False),  # 15
    ("hebrew", "hebrew_general_ci", True),  # 16
    None,
    ("tis620", "tis620_thai_ci", True),  # 18
    ("euckr", "euckr_korean_ci", True),  # 19
    ("latin7", "latin7_estonian_cs", False),  # 20
    ("latin2", "latin2_hungarian_ci", False),  # 21
    ("koi8u", "koi8u_general_ci", True),  # 22
    ("cp1251", "cp1251_ukrainian_ci", False),  # 23
    ("gb2312", "gb2312_chinese_ci", True),  # 24
    ("greek", "greek_general_ci", True),  # 25
    ("cp1250", "cp1250_general_ci", True),  # 26
    ("latin2", "latin2_croatian_ci", False),  # 27
    ("gbk", "gbk_chinese_ci", True),  # 28
    ("cp1257", "cp1257_lithuanian_ci", False),  # 29
    ("latin5", "latin5_turkish_ci", True),  # 30
    ("latin1", "latin1_german2_ci", False),  # 31
    ("armscii8", "armscii8_general_ci", True),  # 32
    ("utf8mb3", "utf8mb3_general_ci", True),  # 33
    ("cp1250", "cp1250_czech_cs", False),  # 34
    ("ucs2", "ucs2_general_ci", True),  # 35
    ("cp866", "cp866_general_ci", True),  # 36
    ("keybcs2", "keybcs2_general_ci", True),  # 37
    ("macce", "macce_general_ci", True),  # 38
    ("macroman", "macroman_general_ci", True),  # 39
    ("cp852", "cp852_general_ci", True),  # 40
    ("latin7", "latin7_general_ci", True),  # 41
    ("latin7", "latin7_general_cs", False),  # 42
    ("macce", "macce_bin", False),  # 43
    ("cp1250", "cp1250_croatian_ci", False),  # 44
    ("utf8mb4", "utf8mb4_general_ci", False),  # 45
    ("utf8mb4", "utf8mb4_bin", False),  # 46
    ("latin1", "latin1_bin", False),  # 47
    ("latin1", "latin1_general_ci", False),  # 48
    ("latin1", "latin1_general_cs", False),  # 49
    ("cp1251", "cp1251_bin", False),  # 50
    ("cp1251", "cp1251_general_ci", True),  # 51
    ("cp1251", "cp1251_general_cs", False),  # 52
    ("macroman", "macroman_bin", False),  # 53
    ("utf16", "utf16_general_ci", True),  # 54
    ("utf16", "utf16_bin", False),  # 55
    ("utf16le", "utf16le_general_ci", True),  # 56
    ("cp1256", "cp1256_general_ci", True),  # 57
    ("cp1257", "cp1257_bin", False),  # 58
    ("cp1257", "cp1257_general_ci", True),  # 59
    ("utf32", "utf32_general_ci", True),  # 60
    ("utf32", "utf32_bin", False),  # 61
    ("utf16le", "utf16le_bin", False),  # 62
    ("binary", "binary", True),  # 63
    ("armscii8", "armscii8_bin", False),  # 64
    ("ascii", "ascii_bin", False),  # 65
    ("cp1250", "cp1250_bin", False),  # 66
    ("cp1256", "cp1256_bin", False),  # 67
    ("cp866", "cp866_bin", False),  # 68
    ("dec8", "dec8_bin", False),  # 69
    ("greek", "greek_bin", False),  # 70
    ("hebrew", "hebrew_bin", False),  # 71
    ("hp8", "hp8_bin", False),  # 72
    ("keybcs2", "keybcs2_bin", False),  # 73
    ("koi8r", "koi8r_bin", False),  # 74
    ("koi8u", "koi8u_bin", False),  # 75
    ("utf8mb3", "utf8mb3_tolower_ci", False),  # 76
    ("latin2", "latin2_bin", False),  # 77
    ("latin5", "latin5_bin", False),  # 78
    ("latin7", "latin7_bin", False),  # 79
    ("cp850", "cp850_bin", False),  # 80
    ("cp852", "cp852_bin", False),  # 81
    ("swe7", "swe7_bin", False),  # 82
    ("utf8mb3", "utf8mb3_bin", False),  # 83
    ("big5", "big5_bin", False),  # 84
    ("euckr", "euckr_bin", False),  # 85
    ("gb2312", "gb2312_bin", False),  # 86
    ("gbk", "gbk_bin", False),  # 87
    ("sjis", "sjis_bin", False),  # 88
    ("tis620", "tis620_bin", False),  # 89
    ("ucs2", "ucs2_bin", False),  # 90
    ("ujis", "ujis_bin", False),  # 91
    ("geostd8", "geostd8_general_ci", True),  # 92
    ("geostd8", "geostd8_bin", False),  # 93
    ("latin1", "latin1_spanish_ci", False),  # 94
    ("cp932", "cp932_japanese_ci", True),  # 95
    ("cp932", "cp932_bin", False),  # 96
    ("eucjpms", "eucjpms_japanese_ci", True),  # 97
    ("eucjpms", "eucjpms_bin", False),  # 98
    ("cp1250", "cp1250_polish_ci", False),  # 99
    None,
    ("utf16", "utf16_unicode_ci", False),  # 101
    ("utf16", "utf16_icelandic_ci", False),  # 102
    ("utf16", "utf16_latvian_ci", False),  # 103
    ("utf16", "utf16_romanian_ci", False),  # 104
    ("utf16", "utf16_slovenian_ci", False),  # 105
    ("utf16", "utf16_polish_ci", False),  # 106
    ("utf16", "utf16_estonian_ci", False),  # 107
    ("utf16", "utf16_spanish_ci", False),  # 108
    ("utf16", "utf16_swedish_ci", False),  # 109
    ("utf16", "utf16_turkish_ci", False),  # 110
    ("utf16", "utf16_czech_ci", False),  # 111
    ("utf16", "utf16_danish_ci", False),  # 112
    ("utf16", "utf16_lithuanian_ci", False),  # 113
    ("utf16", "utf16_slovak_ci", False),  # 114
    ("utf16", "utf16_spanish2_ci", False),  # 115
    ("utf16", "utf16_roman_ci", False),  # 116
    ("utf16", "utf16_persian_ci", False),  # 117
    ("utf16", "utf16_esperanto_ci", False),  # 118
    ("utf16", "utf16_hungarian_ci", False),  # 119
    ("utf16", "utf16_sinhala_ci", False),  # 120
    ("utf16", "utf16_german2_ci", False),  # 121
    ("utf16", "utf16_croatian_ci", False),  # 122
    ("utf16", "utf16_unicode_520_ci", False),  # 123
    ("utf16", "utf16_vietnamese_ci", False),  # 124
    None,
    None,
    None,
    ("ucs2", "ucs2_unicode_ci", False),  # 128
    ("ucs2", "ucs2_icelandic_ci", False),  # 129
    ("ucs2", "ucs2_latvian_ci", False),  # 130
    ("ucs2", "ucs2_romanian_ci", False),  # 131
    ("ucs2", "ucs2_slovenian_ci", False),  # 132
    ("ucs2", "ucs2_polish_ci", False),  # 133
    ("ucs2", "ucs2_estonian_ci", False),  # 134
    ("ucs2", "ucs2_spanish_ci", False),  # 135
    ("ucs2", "ucs2_swedish_ci", False),  # 136
    ("ucs2", "ucs2_turkish_ci", False),  # 137
    ("ucs2", "ucs2_czech_ci", False),  # 138
    ("ucs2", "ucs2_danish_ci", False),  # 139
    ("ucs2", "ucs2_lithuanian_ci", False),  # 140
    ("ucs2", "ucs2_slovak_ci", False),  # 141
    ("ucs2", "ucs2_spanish2_ci", False),  # 142
    ("ucs2", "ucs2_roman_ci", False),  # 143
    ("ucs2", "ucs2_persian_ci", False),  # 144
    ("ucs2", "ucs2_esperanto_ci", False),  # 145
    ("ucs2", "ucs2_hungarian_ci", False),  # 146
    ("ucs2", "ucs2_sinhala_ci", False),  # 147
    ("ucs2", "ucs2_german2_ci", False),  # 148
    ("ucs2", "ucs2_croatian_ci", False),  # 149
    ("ucs2", "ucs2_unicode_520_ci", False),  # 150
    ("ucs2", "ucs2_vietnamese_ci", False),  # 151
    None,
    None,
    None,
    None,
    None,
    None,
    None,
    ("ucs2", "ucs2_general_mysql500_ci", False),  # 159
    ("utf32", "utf32_unicode_ci", False),  # 160
    ("utf32", "utf32_icelandic_ci", False),  # 161
    ("utf32", "utf32_latvian_ci", False),  # 162
    ("utf32", "utf32_romanian_ci", False),  # 163
    ("utf32", "utf32_slovenian_ci", False),  # 164
    ("utf32", "utf32_polish_ci", False),  # 165
    ("utf32", "utf32_estonian_ci", False),  # 166
    ("utf32", "utf32_spanish_ci", False),  # 167
    ("utf32", "utf32_swedish_ci", False),  # 168
    ("utf32", "utf32_turkish_ci", False),  # 169
    ("utf32", "utf32_czech_ci", False),  # 170
    ("utf32", "utf32_danish_ci", False),  # 171
    ("utf32", "utf32_lithuanian_ci", False),  # 172
    ("utf32", "utf32_slovak_ci", False),  # 173
    ("utf32", "utf32_spanish2_ci", False),  # 174
    ("utf32", "utf32_roman_ci", False),  # 175
    ("utf32", "utf32_persian_ci", False),  # 176
    ("utf32", "utf32_esperanto_ci", False),  # 177
    ("utf32", "utf32_hungarian_ci", False),  # 178
    ("utf32", "utf32_sinhala_ci", False),  # 179
    ("utf32", "utf32_german2_ci", False),  # 180
    ("utf32", "utf32_croatian_ci", False),  # 181
    ("utf32", "utf32_unicode_520_ci", False),  # 182
    ("utf32", "utf32_vietnamese_ci", False),  # 183
    None,
    None,
    None,
    None,
    None,
    None,
    None,
    None,
    ("utf8mb3", "utf8mb3_unicode_ci", False),  # 192
    ("utf8mb3", "utf8mb3_icelandic_ci", False),  # 193
    ("utf8mb3", "utf8mb3_latvian_ci", False),  # 194
    ("utf8mb3", "utf8mb3_romanian_ci", False),  # 195
    ("utf8mb3", "utf8mb3_slovenian_ci", False),  # 196
    ("utf8mb3", "utf8mb3_polish_ci", False),  # 197
    ("utf8mb3", "utf8mb3_estonian_ci", False),  # 198
    ("utf8mb3", "utf8mb3_spanish_ci", False),  # 199
    ("utf8mb3", "utf8mb3_swedish_ci", False),  # 200
    ("utf8mb3", "utf8mb3_turkish_ci", False),  # 201
    ("utf8mb3", "utf8mb3_czech_ci", False),  # 202
    ("utf8mb3", "utf8mb3_danish_ci", False),  # 203
    ("utf8mb3", "utf8mb3_lithuanian_ci", False),  # 204
    ("utf8mb3", "utf8mb3_slovak_ci", False),  # 205
    ("utf8mb3", "utf8mb3_spanish2_ci", False),  # 206
    ("utf8mb3", "utf8mb3_roman_ci", False),  # 207
    ("utf8mb3", "utf8mb3_persian_ci", False),  # 208
    ("utf8mb3", "utf8mb3_esperanto_ci", False),  # 209
    ("utf8mb3", "utf8mb3_hungarian_ci", False),  # 210
    ("utf8mb3", "utf8mb3_sinhala_ci", False),  # 211
    ("utf8mb3", "utf8mb3_german2_ci", False),  # 212
    ("utf8mb3", "utf8mb3_croatian_ci", False),  # 213
    ("utf8mb3", "utf8mb3_unicode_520_ci", False),  # 214
    ("utf8mb3", "utf8mb3_vietnamese_ci", False),  # 215
    None,
    None,
    None,
    None,
    None,
    None,
    None,
    ("utf8mb3", "utf8mb3_general_mysql500_ci", False),  # 223
    ("utf8mb4", "utf8mb4_unicode_ci", False),  # 224
    ("utf8mb4", "utf8mb4_icelandic_ci", False),  # 225
    ("utf8mb4", "utf8mb4_latvian_ci", False),  # 226
    ("utf8mb4", "utf8mb4_romanian_ci", False),  # 227
    ("utf8mb4", "utf8mb4_slovenian_ci", False),  # 228
    ("utf8mb4", "utf8mb4_polish_ci", False),  # 229
    ("utf8mb4", "utf8mb4_estonian_ci", False),  # 230
    ("utf8mb4", "utf8mb4_spanish_ci", False),  # 231
    ("utf8mb4", "utf8mb4_swedish_ci", False),  # 232
    ("utf8mb4", "utf8mb4_turkish_ci", False),  # 233
    ("utf8mb4", "utf8mb4_czech_ci", False),  # 234
    ("utf8mb4", "utf8mb4_danish_ci", False),  # 235
    ("utf8mb4", "utf8mb4_lithuanian_ci", False),  # 236
    ("utf8mb4", "utf8mb4_slovak_ci", False),  # 237
    ("utf8mb4", "utf8mb4_spanish2_ci", False),  # 238
    ("utf8mb4", "utf8mb4_roman_ci", False),  # 239
    ("utf8mb4", "utf8mb4_persian_ci", False),  # 240
    ("utf8mb4", "utf8mb4_esperanto_ci", False),  # 241
    ("utf8mb4", "utf8mb4_hungarian_ci", False),  # 242
    ("utf8mb4", "utf8mb4_sinhala_ci", False),  # 243
    ("utf8mb4", "utf8mb4_german2_ci", False),  # 244
    ("utf8mb4", "utf8mb4_croatian_ci", False),  # 245
    ("utf8mb4", "utf8mb4_unicode_520_ci", False),  # 246
    ("utf8mb4", "utf8mb4_vietnamese_ci", False),  # 247
    ("gb18030", "gb18030_chinese_ci", True),  # 248
    ("gb18030", "gb18030_bin", False),  # 249
    ("gb18030", "gb18030_unicode_520_ci", False),  # 250
    None,
    None,
    None,
    None,
    ("utf8mb4", "utf8mb4_0900_ai_ci", True),  # 255
    ("utf8mb4", "utf8mb4_de_pb_0900_ai_ci", False),  # 256
    ("utf8mb4", "utf8mb4_is_0900_ai_ci", False),  # 257
    ("utf8mb4", "utf8mb4_lv_0900_ai_ci", False),  # 258
    ("utf8mb4", "utf8mb4_ro_0900_ai_ci", False),  # 259
    ("utf8mb4", "utf8mb4_sl_0900_ai_ci", False),  # 260
    ("utf8mb4", "utf8mb4_pl_0900_ai_ci", False),  # 261
    ("utf8mb4", "utf8mb4_et_0900_ai_ci", False),  # 262
    ("utf8mb4", "utf8mb4_es_0900_ai_ci", False),  # 263
    ("utf8mb4", "utf8mb4_sv_0900_ai_ci", False),  # 264
    ("utf8mb4", "utf8mb4_tr_0900_ai_ci", False),  # 265
    ("utf8mb4", "utf8mb4_cs_0900_ai_ci", False),  # 266
    ("utf8mb4", "utf8mb4_da_0900_ai_ci", False),  # 267
    ("utf8mb4", "utf8mb4_lt_0900_ai_ci", False),  # 268
    ("utf8mb4", "utf8mb4_sk_0900_ai_ci", False),  # 269
    ("utf8mb4", "utf8mb4_es_trad_0900_ai_ci", False),  # 270
    ("utf8mb4", "utf8mb4_la_0900_ai_ci", False),  # 271
    None,
    ("utf8mb4", "utf8mb4_eo_0900_ai_ci", False),  # 273
    ("utf8mb4", "utf8mb4_hu_0900_ai_ci", False),  # 274
    ("utf8mb4", "utf8mb4_hr_0900_ai_ci", False),  # 275
    None,
    ("utf8mb4", "utf8mb4_vi_0900_ai_ci", False),  # 277
    ("utf8mb4", "utf8mb4_0900_as_cs", False),  # 278
    ("utf8mb4", "utf8mb4_de_pb_0900_as_cs", False),  # 279
    ("utf8mb4", "utf8mb4_is_0900_as_cs", False),  # 280
    ("utf8mb4", "utf8mb4_lv_0900_as_cs", False),  # 281
    ("utf8mb4", "utf8mb4_ro_0900_as_cs", False),  # 282
    ("utf8mb4", "utf8mb4_sl_0900_as_cs", False),  # 283
    ("utf8mb4", "utf8mb4_pl_0900_as_cs", False),  # 284
    ("utf8mb4", "utf8mb4_et_0900_as_cs", False),  # 285
    ("utf8mb4", "utf8mb4_es_0900_as_cs", False),  # 286
    ("utf8mb4", "utf8mb4_sv_0900_as_cs", False),  # 287
    ("utf8mb4", "utf8mb4_tr_0900_as_cs", False),  # 288
    ("utf8mb4", "utf8mb4_cs_0900_as_cs", False),  # 289
    ("utf8mb4", "utf8mb4_da_0900_as_cs", False),  # 290
    ("utf8mb4", "utf8mb4_lt_0900_as_cs", False),  # 291
    ("utf8mb4", "utf8mb4_sk_0900_as_cs", False),  # 292
    ("utf8mb4", "utf8mb4_es_trad_0900_as_cs", False),  # 293
    ("utf8mb4", "utf8mb4_la_0900_as_cs", False),  # 294
    None,
    ("utf8mb4", "utf8mb4_eo_0900_as_cs", False),  # 296
    ("utf8mb4", "utf8mb4_hu_0900_as_cs", False),  # 297
    ("utf8mb4", "utf8mb4_hr_0900_as_cs", False),  # 298
    None,
    ("utf8mb4", "utf8mb4_vi_0900_as_cs", False),  # 300
    None,
    None,
    ("utf8mb4", "utf8mb4_ja_0900_as_cs", False),  # 303
    ("utf8mb4", "utf8mb4_ja_0900_as_cs_ks", False),  # 304
    ("utf8mb4", "utf8mb4_0900_as_ci", False),  # 305
    ("utf8mb4", "utf8mb4_ru_0900_ai_ci", False),  # 306
    ("utf8mb4", "utf8mb4_ru_0900_as_cs", False),  # 307
    ("utf8mb4", "utf8mb4_zh_0900_as_cs", False),  # 308
    ("utf8mb4", "utf8mb4_0900_bin", False),  # 309
    ("utf8mb4", "utf8mb4_nb_0900_ai_ci", False),  # 310
    ("utf8mb4", "utf8mb4_nb_0900_as_cs", False),  # 311
    ("utf8mb4", "utf8mb4_nn_0900_ai_ci", False),  # 312
    ("utf8mb4", "utf8mb4_nn_0900_as_cs", False),  # 313
    ("utf8mb4", "utf8mb4_sr_latn_0900_ai_ci", False),  # 314
    ("utf8mb4", "utf8mb4_sr_latn_0900_as_cs", False),  # 315
    ("utf8mb4", "utf8mb4_bs_0900_ai_ci", False),  # 316
    ("utf8mb4", "utf8mb4_bs_0900_as_cs", False),  # 317
    ("utf8mb4", "utf8mb4_bg_0900_ai_ci", False),  # 318
    ("utf8mb4", "utf8mb4_bg_0900_as_cs", False),  # 319
    ("utf8mb4", "utf8mb4_gl_0900_ai_ci", False),  # 320
    ("utf8mb4", "utf8mb4_gl_0900_as_cs", False),  # 321
    ("utf8mb4", "utf8mb4_mn_cyrl_0900_ai_ci", False),  # 322
    ("utf8mb4", "utf8mb4_mn_cyrl_0900_as_cs", False),  # 323
]

MYSQL_CHARACTER_SETS_57: List[Optional[Tuple[str, str, bool]]] = [
    # (character set name, collation, default)
    None,
    ("big5", "big5_chinese_ci", True),  # 1
    ("latin2", "latin2_czech_cs", False),  # 2
    ("dec8", "dec8_swedish_ci", True),  # 3
    ("cp850", "cp850_general_ci", True),  # 4
    ("latin1", "latin1_german1_ci", False),  # 5
    ("hp8", "hp8_english_ci", True),  # 6
    ("koi8r", "koi8r_general_ci", True),  # 7
    ("latin1", "latin1_swedish_ci", True),  # 8
    ("latin2", "latin2_general_ci", True),  # 9
    ("swe7", "swe7_swedish_ci", True),  # 10
    ("ascii", "ascii_general_ci", True),  # 11
    ("ujis", "ujis_japanese_ci", True),  # 12
    ("sjis", "sjis_japanese_ci", True),  # 13
    ("cp1251", "cp1251_bulgarian_ci", False),  # 14
    ("latin1", "latin1_danish_ci", False),  # 15
    ("hebrew", "hebrew_general_ci", True),  # 16
    None,
    ("tis620", "tis620_thai_ci", True),  # 18
    ("euckr", "euckr_korean_ci", True),  # 19
    ("latin7", "latin7_estonian_cs", False),  # 20
    ("latin2", "latin2_hungarian_ci", False),  # 21
    ("koi8u", "koi8u_general_ci", True),  # 22
    ("cp1251", "cp1251_ukrainian_ci", False),  # 23
    ("gb2312", "gb2312_chinese_ci", True),  # 24
    ("greek", "greek_general_ci", True),  # 25
    ("cp1250", "cp1250_general_ci", True),  # 26
    ("latin2", "latin2_croatian_ci", False),  # 27
    ("gbk", "gbk_chinese_ci", True),  # 28
    ("cp1257", "cp1257_lithuanian_ci", False),  # 29
    ("latin5", "latin5_turkish_ci", True),  # 30
    ("latin1", "latin1_german2_ci", False),  # 31
    ("armscii8", "armscii8_general_ci", True),  # 32
    ("utf8", "utf8_general_ci", True),  # 33
    ("cp1250", "cp1250_czech_cs", False),  # 34
    ("ucs2", "ucs2_general_ci", True),  # 35
    ("cp866", "cp866_general_ci", True),  # 36
    ("keybcs2", "keybcs2_general_ci", True),  # 37
    ("macce", "macce_general_ci", True),  # 38
    ("macroman", "macroman_general_ci", True),  # 39
    ("cp852", "cp852_general_ci", True),  # 40
    ("latin7", "latin7_general_ci", True),  # 41
    ("latin7", "latin7_general_cs", False),  # 42
    ("macce", "macce_bin", False),  # 43
    ("cp1250", "cp1250_croatian_ci", False),  # 44
    ("utf8mb4", "utf8mb4_general_ci", True),  # 45
    ("utf8mb4", "utf8mb4_bin", False),  # 46
    ("latin1", "latin1_bin", False),  # 47
    ("latin1", "latin1_general_ci", False),  # 48
    ("latin1", "latin1_general_cs", False),  # 49
    ("cp1251", "cp1251_bin", False),  # 50
    ("cp1251", "cp1251_general_ci", True),  # 51
    ("cp1251", "cp1251_general_cs", False),  # 52
    ("macroman", "macroman_bin", False),  # 53
    ("utf16", "utf16_general_ci", True),  # 54
    ("utf16", "utf16_bin", False),  # 55
    ("utf16le", "utf16le_general_ci", True),  # 56
    ("cp1256", "cp1256_general_ci", True),  # 57
    ("cp1257", "cp1257_bin", False),  # 58
    ("cp1257", "cp1257_general_ci", True),  # 59
    ("utf32", "utf32_general_ci", True),  # 60
    ("utf32", "utf32_bin", False),  # 61
    ("utf16le", "utf16le_bin", False),  # 62
    ("binary", "binary", True),  # 63
    ("armscii8", "armscii8_bin", False),  # 64
    ("ascii", "ascii_bin", False),  # 65
    ("cp1250", "cp1250_bin", False),  # 66
    ("cp1256", "cp1256_bin", False),  # 67
    ("cp866", "cp866_bin", False),  # 68
    ("dec8", "dec8_bin", False),  # 69
    ("greek", "greek_bin", False),  # 70
    ("hebrew", "hebrew_bin", False),  # 71
    ("hp8", "hp8_bin", False),  # 72
    ("keybcs2", "keybcs2_bin", False),  # 73
    ("koi8r", "koi8r_bin", False),  # 74
    ("koi8u", "koi8u_bin", False),  # 75
    None,
    ("latin2", "latin2_bin", False),  # 77
    ("latin5", "latin5_bin", False),  # 78
    ("latin7", "latin7_bin", False),  # 79
    ("cp850", "cp850_bin", False),  # 80
    ("cp852", "cp852_bin", False),  # 81
    ("swe7", "swe7_bin", False),  # 82
    ("utf8", "utf8_bin", False),  # 83
    ("big5", "big5_bin", False),  # 84
    ("euckr", "euckr_bin", False),  # 85
    ("gb2312", "gb2312_bin", False),  # 86
    ("gbk", "gbk_bin", False),  # 87
    ("sjis", "sjis_bin", False),  # 88
    ("tis620", "tis620_bin", False),  # 89
    ("ucs2", "ucs2_bin", False),  # 90
    ("ujis", "ujis_bin", False),  # 91
    ("geostd8", "geostd8_general_ci", True),  # 92
    ("geostd8", "geostd8_bin", False),  # 93
    ("latin1", "latin1_spanish_ci", False),  # 94
    ("cp932", "cp932_japanese_ci", True),  # 95
    ("cp932", "cp932_bin", False),  # 96
    ("eucjpms", "eucjpms_japanese_ci", True),  # 97
    ("eucjpms", "eucjpms_bin", False),  # 98
    ("cp1250", "cp1250_polish_ci", False),  # 99
    None,
    ("utf16", "utf16_unicode_ci", False),  # 101
    ("utf16", "utf16_icelandic_ci", False),  # 102
    ("utf16", "utf16_latvian_ci", False),  # 103
    ("utf16", "utf16_romanian_ci", False),  # 104
    ("utf16", "utf16_slovenian_ci", False),  # 105
    ("utf16", "utf16_polish_ci", False),  # 106
    ("utf16", "utf16_estonian_ci", False),  # 107
    ("utf16", "utf16_spanish_ci", False),  # 108
    ("utf16", "utf16_swedish_ci", False),  # 109
    ("utf16", "utf16_turkish_ci", False),  # 110
    ("utf16", "utf16_czech_ci", False),  # 111
    ("utf16", "utf16_danish_ci", False),  # 112
    ("utf16", "utf16_lithuanian_ci", False),  # 113
    ("utf16", "utf16_slovak_ci", False),  # 114
    ("utf16", "utf16_spanish2_ci", False),  # 115
    ("utf16", "utf16_roman_ci", False),  # 116
    ("utf16", "utf16_persian_ci", False),  # 117
    ("utf16", "utf16_esperanto_ci", False),  # 118
    ("utf16", "utf16_hungarian_ci", False),  # 119
    ("utf16", "utf16_sinhala_ci", False),  # 120
    ("utf16", "utf16_german2_ci", False),  # 121
    ("utf16", "utf16_croatian_ci", False),  # 122
    ("utf16", "utf16_unicode_520_ci", False),  # 123
    ("utf16", "utf16_vietnamese_ci", False),  # 124
    None,
    None,
    None,
    ("ucs2", "ucs2_unicode_ci", False),  # 128
    ("ucs2", "ucs2_icelandic_ci", False),  # 129
    ("ucs2", "ucs2_latvian_ci", False),  # 130
    ("ucs2", "ucs2_romanian_ci", False),  # 131
    ("ucs2", "ucs2_slovenian_ci", False),  # 132
    ("ucs2", "ucs2_polish_ci", False),  # 133
    ("ucs2", "ucs2_estonian_ci", False),  # 134
    ("ucs2", "ucs2_spanish_ci", False),  # 135
    ("ucs2", "ucs2_swedish_ci", False),  # 136
    ("ucs2", "ucs2_turkish_ci", False),  # 137
    ("ucs2", "ucs2_czech_ci", False),  # 138
    ("ucs2", "ucs2_danish_ci", False),  # 139
    ("ucs2", "ucs2_lithuanian_ci", False),  # 140
    ("ucs2", "ucs2_slovak_ci", False),  # 141
    ("ucs2", "ucs2_spanish2_ci", False),  # 142
    ("ucs2", "ucs2_roman_ci", False),  # 143
    ("ucs2", "ucs2_persian_ci", False),  # 144
    ("ucs2", "ucs2_esperanto_ci", False),  # 145
    ("ucs2", "ucs2_hungarian_ci", False),  # 146
    ("ucs2", "ucs2_sinhala_ci", False),  # 147
    ("ucs2", "ucs2_german2_ci", False),  # 148
    ("ucs2", "ucs2_croatian_ci", False),  # 149
    ("ucs2", "ucs2_unicode_520_ci", False),  # 150
    ("ucs2", "ucs2_vietnamese_ci", False),  # 151
    None,
    None,
    None,
    None,
    None,
    None,
    None,
    ("ucs2", "ucs2_general_mysql500_ci", False),  # 159
    ("utf32", "utf32_unicode_ci", False),  # 160
    ("utf32", "utf32_icelandic_ci", False),  # 161
    ("utf32", "utf32_latvian_ci", False),  # 162
    ("utf32", "utf32_romanian_ci", False),  # 163
    ("utf32", "utf32_slovenian_ci", False),  # 164
    ("utf32", "utf32_polish_ci", False),  # 165
    ("utf32", "utf32_estonian_ci", False),  # 166
    ("utf32", "utf32_spanish_ci", False),  # 167
    ("utf32", "utf32_swedish_ci", False),  # 168
    ("utf32", "utf32_turkish_ci", False),  # 169
    ("utf32", "utf32_czech_ci", False),  # 170
    ("utf32", "utf32_danish_ci", False),  # 171
    ("utf32", "utf32_lithuanian_ci", False),  # 172
    ("utf32", "utf32_slovak_ci", False),  # 173
    ("utf32", "utf32_spanish2_ci", False),  # 174
    ("utf32", "utf32_roman_ci", False),  # 175
    ("utf32", "utf32_persian_ci", False),  # 176
    ("utf32", "utf32_esperanto_ci", False),  # 177
    ("utf32", "utf32_hungarian_ci", False),  # 178
    ("utf32", "utf32_sinhala_ci", False),  # 179
    ("utf32", "utf32_german2_ci", False),  # 180
    ("utf32", "utf32_croatian_ci", False),  # 181
    ("utf32", "utf32_unicode_520_ci", False),  # 182
    ("utf32", "utf32_vietnamese_ci", False),  # 183
    None,
    None,
    None,
    None,
    None,
    None,
    None,
    None,
    ("utf8", "utf8_unicode_ci", False),  # 192
    ("utf8", "utf8_icelandic_ci", False),  # 193
    ("utf8", "utf8_latvian_ci", False),  # 194
    ("utf8", "utf8_romanian_ci", False),  # 195
    ("utf8", "utf8_slovenian_ci", False),  # 196
    ("utf8", "utf8_polish_ci", False),  # 197
    ("utf8", "utf8_estonian_ci", False),  # 198
    ("utf8", "utf8_spanish_ci", False),  # 199
    ("utf8", "utf8_swedish_ci", False),  # 200
    ("utf8", "utf8_turkish_ci", False),  # 201
    ("utf8", "utf8_czech_ci", False),  # 202
    ("utf8", "utf8_danish_ci", False),  # 203
    ("utf8", "utf8_lithuanian_ci", False),  # 204
    ("utf8", "utf8_slovak_ci", False),  # 205
    ("utf8", "utf8_spanish2_ci", False),  # 206
    ("utf8", "utf8_roman_ci", False),  # 207
    ("utf8", "utf8_persian_ci", False),  # 208
    ("utf8", "utf8_esperanto_ci", False),  # 209
    ("utf8", "utf8_hungarian_ci", False),  # 210
    ("utf8", "utf8_sinhala_ci", False),  # 211
    ("utf8", "utf8_german2_ci", False),  # 212
    ("utf8", "utf8_croatian_ci", False),  # 213
    ("utf8", "utf8_unicode_520_ci", False),  # 214
    ("utf8", "utf8_vietnamese_ci", False),  # 215
    None,
    None,
    None,
    None,
    None,
    None,
    None,
    ("utf8", "utf8_general_mysql500_ci", False),  # 223
    ("utf8mb4", "utf8mb4_unicode_ci", False),  # 224
    ("utf8mb4", "utf8mb4_icelandic_ci", False),  # 225
    ("utf8mb4", "utf8mb4_latvian_ci", False),  # 226
    ("utf8mb4", "utf8mb4_romanian_ci", False),  # 227
    ("utf8mb4", "utf8mb4_slovenian_ci", False),  # 228
    ("utf8mb4", "utf8mb4_polish_ci", False),  # 229
    ("utf8mb4", "utf8mb4_estonian_ci", False),  # 230
    ("utf8mb4", "utf8mb4_spanish_ci", False),  # 231
    ("utf8mb4", "utf8mb4_swedish_ci", False),  # 232
    ("utf8mb4", "utf8mb4_turkish_ci", False),  # 233
    ("utf8mb4", "utf8mb4_czech_ci", False),  # 234
    ("utf8mb4", "utf8mb4_danish_ci", False),  # 235
    ("utf8mb4", "utf8mb4_lithuanian_ci", False),  # 236
    ("utf8mb4", "utf8mb4_slovak_ci", False),  # 237
    ("utf8mb4", "utf8mb4_spanish2_ci", False),  # 238
    ("utf8mb4", "utf8mb4_roman_ci", False),  # 239
    ("utf8mb4", "utf8mb4_persian_ci", False),  # 240
    ("utf8mb4", "utf8mb4_esperanto_ci", False),  # 241
    ("utf8mb4", "utf8mb4_hungarian_ci", False),  # 242
    ("utf8mb4", "utf8mb4_sinhala_ci", False),  # 243
    ("utf8mb4", "utf8mb4_german2_ci", False),  # 244
    ("utf8mb4", "utf8mb4_croatian_ci", False),  # 245
    ("utf8mb4", "utf8mb4_unicode_520_ci", False),  # 246
    ("utf8mb4", "utf8mb4_vietnamese_ci", False),  # 247
    ("gb18030", "gb18030_chinese_ci", True),  # 248
    ("gb18030", "gb18030_bin", False),  # 249
    ("gb18030", "gb18030_unicode_520_ci", False),  # 250
]

virt/lib/python3.9/site-packages/mysqlx/charsets 3.py
PrinceDisant/LostInNL
# -*- coding: utf-8 -*-

# Copyright (c) 2013, 2022, Oracle and/or its affiliates. All rights reserved.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

"""This module contains the MySQL Server Character Sets."""

# This file was auto-generated.
from typing import List, Optional, Tuple

_GENERATED_ON = "2022-05-09"
_MYSQL_VERSION = (8, 0, 30)

MYSQL_CHARACTER_SETS: List[Optional[Tuple[str, str, bool]]] = [
    # (character set name, collation, default)
    None,
    ("big5", "big5_chinese_ci", True),  # 1
    ("latin2", "latin2_czech_cs", False),  # 2
    ("dec8", "dec8_swedish_ci", True),  # 3
    ("cp850", "cp850_general_ci", True),  # 4
    ("latin1", "latin1_german1_ci", False),  # 5
    ("hp8", "hp8_english_ci", True),  # 6
    ("koi8r", "koi8r_general_ci", True),  # 7
    ("latin1", "latin1_swedish_ci", True),  # 8
    ("latin2", "latin2_general_ci", True),  # 9
    ("swe7", "swe7_swedish_ci", True),  # 10
    ("ascii", "ascii_general_ci", True),  # 11
    ("ujis", "ujis_japanese_ci", True),  # 12
    ("sjis", "sjis_japanese_ci", True),  # 13
    ("cp1251", "cp1251_bulgarian_ci", False),  # 14
    ("latin1", "latin1_danish_ci", False),  # 15
    ("hebrew", "hebrew_general_ci", True),  # 16
    None,
    ("tis620", "tis620_thai_ci", True),  # 18
    ("euckr", "euckr_korean_ci", True),  # 19
    ("latin7", "latin7_estonian_cs", False),  # 20
    ("latin2", "latin2_hungarian_ci", False),  # 21
    ("koi8u", "koi8u_general_ci", True),  # 22
    ("cp1251", "cp1251_ukrainian_ci", False),  # 23
    ("gb2312", "gb2312_chinese_ci", True),  # 24
    ("greek", "greek_general_ci", True),  # 25
    ("cp1250", "cp1250_general_ci", True),  # 26
    ("latin2", "latin2_croatian_ci", False),  # 27
    ("gbk", "gbk_chinese_ci", True),  # 28
    ("cp1257", "cp1257_lithuanian_ci", False),  # 29
    ("latin5", "latin5_turkish_ci", True),  # 30
    ("latin1", "latin1_german2_ci", False),  # 31
    ("armscii8", "armscii8_general_ci", True),  # 32
    ("utf8mb3", "utf8mb3_general_ci", True),  # 33
    ("cp1250", "cp1250_czech_cs", False),  # 34
    ("ucs2", "ucs2_general_ci", True),  # 35
    ("cp866", "cp866_general_ci", True),  # 36
    ("keybcs2", "keybcs2_general_ci", True),  # 37
    ("macce", "macce_general_ci", True),  # 38
    ("macroman", "macroman_general_ci", True),  # 39
    ("cp852", "cp852_general_ci", True),  # 40
    ("latin7", "latin7_general_ci", True),  # 41
    ("latin7", "latin7_general_cs", False),  # 42
    ("macce", "macce_bin", False),  # 43
    ("cp1250", "cp1250_croatian_ci", False),  # 44
    ("utf8mb4", "utf8mb4_general_ci", False),  # 45
    ("utf8mb4", "utf8mb4_bin", False),  # 46
    ("latin1", "latin1_bin", False),  # 47
    ("latin1", "latin1_general_ci", False),  # 48
    ("latin1", "latin1_general_cs", False),  # 49
    ("cp1251", "cp1251_bin", False),  # 50
    ("cp1251", "cp1251_general_ci", True),  # 51
    ("cp1251", "cp1251_general_cs", False),  # 52
    ("macroman", "macroman_bin", False),  # 53
    ("utf16", "utf16_general_ci", True),  # 54
    ("utf16", "utf16_bin", False),  # 55
    ("utf16le", "utf16le_general_ci", True),  # 56
    ("cp1256", "cp1256_general_ci", True),  # 57
    ("cp1257", "cp1257_bin", False),  # 58
    ("cp1257", "cp1257_general_ci", True),  # 59
    ("utf32", "utf32_general_ci", True),  # 60
    ("utf32", "utf32_bin", False),  # 61
    ("utf16le", "utf16le_bin", False),  # 62
    ("binary", "binary", True),  # 63
    ("armscii8", "armscii8_bin", False),  # 64
    ("ascii", "ascii_bin", False),  # 65
    ("cp1250", "cp1250_bin", False),  # 66
    ("cp1256", "cp1256_bin", False),  # 67
    ("cp866", "cp866_bin", False),  # 68
    ("dec8", "dec8_bin", False),  # 69
    ("greek", "greek_bin", False),  # 70
    ("hebrew", "hebrew_bin", False),  # 71
    ("hp8", "hp8_bin", False),  # 72
    ("keybcs2", "keybcs2_bin", False),  # 73
    ("koi8r", "koi8r_bin", False),  # 74
    ("koi8u", "koi8u_bin", False),  # 75
    ("utf8mb3", "utf8mb3_tolower_ci", False),  # 76
    ("latin2", "latin2_bin", False),  # 77
    ("latin5", "latin5_bin", False),  # 78
    ("latin7", "latin7_bin", False),  # 79
    ("cp850", "cp850_bin", False),  # 80
    ("cp852", "cp852_bin", False),  # 81
    ("swe7", "swe7_bin", False),  # 82
    ("utf8mb3", "utf8mb3_bin", False),  # 83
    ("big5", "big5_bin", False),  # 84
    ("euckr", "euckr_bin", False),  # 85
    ("gb2312", "gb2312_bin", False),  # 86
    ("gbk", "gbk_bin", False),  # 87
    ("sjis", "sjis_bin", False),  # 88
    ("tis620", "tis620_bin", False),  # 89
    ("ucs2", "ucs2_bin", False),  # 90
    ("ujis", "ujis_bin", False),  # 91
    ("geostd8", "geostd8_general_ci", True),  # 92
    ("geostd8", "geostd8_bin", False),  # 93
    ("latin1", "latin1_spanish_ci", False),  # 94
    ("cp932", "cp932_japanese_ci", True),  # 95
    ("cp932", "cp932_bin", False),  # 96
    ("eucjpms", "eucjpms_japanese_ci", True),  # 97
    ("eucjpms", "eucjpms_bin", False),  # 98
    ("cp1250", "cp1250_polish_ci", False),  # 99
    None,
    ("utf16", "utf16_unicode_ci", False),  # 101
    ("utf16", "utf16_icelandic_ci", False),  # 102
    ("utf16", "utf16_latvian_ci", False),  # 103
    ("utf16", "utf16_romanian_ci", False),  # 104
    ("utf16", "utf16_slovenian_ci", False),  # 105
    ("utf16", "utf16_polish_ci", False),  # 106
    ("utf16", "utf16_estonian_ci", False),  # 107
    ("utf16", "utf16_spanish_ci", False),  # 108
    ("utf16", "utf16_swedish_ci", False),  # 109
    ("utf16", "utf16_turkish_ci", False),  # 110
    ("utf16", "utf16_czech_ci", False),  # 111
    ("utf16", "utf16_danish_ci", False),  # 112
    ("utf16", "utf16_lithuanian_ci", False),  # 113
    ("utf16", "utf16_slovak_ci", False),  # 114
    ("utf16", "utf16_spanish2_ci", False),  # 115
    ("utf16", "utf16_roman_ci", False),  # 116
    ("utf16", "utf16_persian_ci", False),  # 117
    ("utf16", "utf16_esperanto_ci", False),  # 118
    ("utf16", "utf16_hungarian_ci", False),  # 119
    ("utf16", "utf16_sinhala_ci", False),  # 120
    ("utf16", "utf16_german2_ci", False),  # 121
    ("utf16", "utf16_croatian_ci", False),  # 122
    ("utf16", "utf16_unicode_520_ci", False),  # 123
    ("utf16", "utf16_vietnamese_ci", False),  # 124
    None,
    None,
    None,
    ("ucs2", "ucs2_unicode_ci", False),  # 128
    ("ucs2", "ucs2_icelandic_ci", False),  # 129
    ("ucs2", "ucs2_latvian_ci", False),  # 130
    ("ucs2", "ucs2_romanian_ci", False),  # 131
    ("ucs2", "ucs2_slovenian_ci", False),  # 132
    ("ucs2", "ucs2_polish_ci", False),  # 133
    ("ucs2", "ucs2_estonian_ci", False),  # 134
    ("ucs2", "ucs2_spanish_ci", False),  # 135
    ("ucs2", "ucs2_swedish_ci", False),  # 136
    ("ucs2", "ucs2_turkish_ci", False),  # 137
    ("ucs2", "ucs2_czech_ci", False),  # 138
    ("ucs2", "ucs2_danish_ci", False),  # 139
    ("ucs2", "ucs2_lithuanian_ci", False),  # 140
    ("ucs2", "ucs2_slovak_ci", False),  # 141
    ("ucs2", "ucs2_spanish2_ci", False),  # 142
    ("ucs2", "ucs2_roman_ci", False),  # 143
    ("ucs2", "ucs2_persian_ci", False),  # 144
    ("ucs2", "ucs2_esperanto_ci", False),  # 145
    ("ucs2", "ucs2_hungarian_ci", False),  # 146
    ("ucs2", "ucs2_sinhala_ci", False),  # 147
    ("ucs2", "ucs2_german2_ci", False),  # 148
    ("ucs2", "ucs2_croatian_ci", False),  # 149
    ("ucs2", "ucs2_unicode_520_ci", False),  # 150
    ("ucs2", "ucs2_vietnamese_ci", False),  # 151
    None,
    None,
    None,
    None,
    None,
    None,
    None,
    ("ucs2", "ucs2_general_mysql500_ci", False),  # 159
    ("utf32", "utf32_unicode_ci", False),  # 160
    ("utf32", "utf32_icelandic_ci", False),  # 161
    ("utf32", "utf32_latvian_ci", False),  # 162
    ("utf32", "utf32_romanian_ci", False),  # 163
    ("utf32", "utf32_slovenian_ci", False),  # 164
    ("utf32", "utf32_polish_ci", False),  # 165
    ("utf32", "utf32_estonian_ci", False),  # 166
    ("utf32", "utf32_spanish_ci", False),  # 167
    ("utf32", "utf32_swedish_ci", False),  # 168
    ("utf32", "utf32_turkish_ci", False),  # 169
    ("utf32", "utf32_czech_ci", False),  # 170
    ("utf32", "utf32_danish_ci", False),  # 171
    ("utf32", "utf32_lithuanian_ci", False),  # 172
    ("utf32", "utf32_slovak_ci", False),  # 173
    ("utf32", "utf32_spanish2_ci", False),  # 174
    ("utf32", "utf32_roman_ci", False),  # 175
    ("utf32", "utf32_persian_ci", False),  # 176
    ("utf32", "utf32_esperanto_ci", False),  # 177
    ("utf32", "utf32_hungarian_ci", False),  # 178
    ("utf32", "utf32_sinhala_ci", False),  # 179
    ("utf32", "utf32_german2_ci", False),  # 180
    ("utf32", "utf32_croatian_ci", False),  # 181
    ("utf32", "utf32_unicode_520_ci", False),  # 182
    ("utf32", "utf32_vietnamese_ci", False),  # 183
    None,
    None,
    None,
    None,
    None,
    None,
    None,
    None,
    ("utf8mb3", "utf8mb3_unicode_ci", False),  # 192
    ("utf8mb3", "utf8mb3_icelandic_ci", False),  # 193
    ("utf8mb3", "utf8mb3_latvian_ci", False),  # 194
    ("utf8mb3", "utf8mb3_romanian_ci", False),  # 195
    ("utf8mb3", "utf8mb3_slovenian_ci", False),  # 196
    ("utf8mb3", "utf8mb3_polish_ci", False),  # 197
    ("utf8mb3", "utf8mb3_estonian_ci", False),  # 198
    ("utf8mb3", "utf8mb3_spanish_ci", False),  # 199
    ("utf8mb3", "utf8mb3_swedish_ci", False),  # 200
    ("utf8mb3", "utf8mb3_turkish_ci", False),  # 201
    ("utf8mb3", "utf8mb3_czech_ci", False),  # 202
    ("utf8mb3", "utf8mb3_danish_ci", False),  # 203
    ("utf8mb3", "utf8mb3_lithuanian_ci", False),  # 204
    ("utf8mb3", "utf8mb3_slovak_ci", False),  # 205
    ("utf8mb3", "utf8mb3_spanish2_ci", False),  # 206
    ("utf8mb3", "utf8mb3_roman_ci", False),  # 207
    ("utf8mb3", "utf8mb3_persian_ci", False),  # 208
    ("utf8mb3", "utf8mb3_esperanto_ci", False),  # 209
    ("utf8mb3", "utf8mb3_hungarian_ci", False),  # 210
    ("utf8mb3", "utf8mb3_sinhala_ci", False),  # 211
    ("utf8mb3", "utf8mb3_german2_ci", False),  # 212
    ("utf8mb3", "utf8mb3_croatian_ci", False),  # 213
    ("utf8mb3", "utf8mb3_unicode_520_ci", False),  # 214
    ("utf8mb3", "utf8mb3_vietnamese_ci", False),  # 215
    None,
    None,
    None,
    None,
    None,
    None,
    None,
    ("utf8mb3", "utf8mb3_general_mysql500_ci", False),  # 223
    ("utf8mb4", "utf8mb4_unicode_ci", False),  # 224
    ("utf8mb4", "utf8mb4_icelandic_ci", False),  # 225
    ("utf8mb4", "utf8mb4_latvian_ci", False),  # 226
    ("utf8mb4", "utf8mb4_romanian_ci", False),  # 227
    ("utf8mb4", "utf8mb4_slovenian_ci", False),  # 228
    ("utf8mb4", "utf8mb4_polish_ci", False),  # 229
    ("utf8mb4", "utf8mb4_estonian_ci", False),  # 230
    ("utf8mb4", "utf8mb4_spanish_ci", False),  # 231
    ("utf8mb4", "utf8mb4_swedish_ci", False),  # 232
    ("utf8mb4", "utf8mb4_turkish_ci", False),  # 233
    ("utf8mb4", "utf8mb4_czech_ci", False),  # 234
    ("utf8mb4", "utf8mb4_danish_ci", False),  # 235
    ("utf8mb4", "utf8mb4_lithuanian_ci", False),  # 236
    ("utf8mb4", "utf8mb4_slovak_ci", False),  # 237
    ("utf8mb4", "utf8mb4_spanish2_ci", False),  # 238
    ("utf8mb4", "utf8mb4_roman_ci", False),  # 239
    ("utf8mb4", "utf8mb4_persian_ci", False),  # 240
    ("utf8mb4", "utf8mb4_esperanto_ci", False),  # 241
    ("utf8mb4", "utf8mb4_hungarian_ci", False),  # 242
    ("utf8mb4", "utf8mb4_sinhala_ci", False),  # 243
    ("utf8mb4", "utf8mb4_german2_ci", False),  # 244
    ("utf8mb4", "utf8mb4_croatian_ci", False),  # 245
    ("utf8mb4", "utf8mb4_unicode_520_ci", False),  # 246
    ("utf8mb4", "utf8mb4_vietnamese_ci", False),  # 247
    ("gb18030", "gb18030_chinese_ci", True),  # 248
    ("gb18030", "gb18030_bin", False),  # 249
    ("gb18030", "gb18030_unicode_520_ci", False),  # 250
    None,
    None,
    None,
    None,
    ("utf8mb4", "utf8mb4_0900_ai_ci", True),  # 255
    ("utf8mb4", "utf8mb4_de_pb_0900_ai_ci", False),  # 256
    ("utf8mb4", "utf8mb4_is_0900_ai_ci", False),  # 257
    ("utf8mb4", "utf8mb4_lv_0900_ai_ci", False),  # 258
    ("utf8mb4", "utf8mb4_ro_0900_ai_ci", False),  # 259
    ("utf8mb4", "utf8mb4_sl_0900_ai_ci", False),  # 260
    ("utf8mb4", "utf8mb4_pl_0900_ai_ci", False),  # 261
    ("utf8mb4", "utf8mb4_et_0900_ai_ci", False),  # 262
    ("utf8mb4", "utf8mb4_es_0900_ai_ci", False),  # 263
    ("utf8mb4", "utf8mb4_sv_0900_ai_ci", False),  # 264
    ("utf8mb4", "utf8mb4_tr_0900_ai_ci", False),  # 265
    ("utf8mb4", "utf8mb4_cs_0900_ai_ci", False),  # 266
    ("utf8mb4", "utf8mb4_da_0900_ai_ci", False),  # 267
    ("utf8mb4", "utf8mb4_lt_0900_ai_ci", False),  # 268
    ("utf8mb4", "utf8mb4_sk_0900_ai_ci", False),  # 269
    ("utf8mb4", "utf8mb4_es_trad_0900_ai_ci", False),  # 270
    ("utf8mb4", "utf8mb4_la_0900_ai_ci", False),  # 271
    None,
    ("utf8mb4", "utf8mb4_eo_0900_ai_ci", False),  # 273
    ("utf8mb4", "utf8mb4_hu_0900_ai_ci", False),  # 274
    ("utf8mb4", "utf8mb4_hr_0900_ai_ci", False),  # 275
    None,
    ("utf8mb4", "utf8mb4_vi_0900_ai_ci", False),  # 277
    ("utf8mb4", "utf8mb4_0900_as_cs", False),  # 278
    ("utf8mb4", "utf8mb4_de_pb_0900_as_cs", False),  # 279
    ("utf8mb4", "utf8mb4_is_0900_as_cs", False),  # 280
    ("utf8mb4", "utf8mb4_lv_0900_as_cs", False),  # 281
    ("utf8mb4", "utf8mb4_ro_0900_as_cs", False),  # 282
    ("utf8mb4", "utf8mb4_sl_0900_as_cs", False),  # 283
    ("utf8mb4", "utf8mb4_pl_0900_as_cs", False),  # 284
    ("utf8mb4", "utf8mb4_et_0900_as_cs", False),  # 285
    ("utf8mb4", "utf8mb4_es_0900_as_cs", False),  # 286
    ("utf8mb4", "utf8mb4_sv_0900_as_cs", False),  # 287
    ("utf8mb4", "utf8mb4_tr_0900_as_cs", False),  # 288
    ("utf8mb4", "utf8mb4_cs_0900_as_cs", False),  # 289
    ("utf8mb4", "utf8mb4_da_0900_as_cs", False),  # 290
    ("utf8mb4", "utf8mb4_lt_0900_as_cs", False),  # 291
    ("utf8mb4", "utf8mb4_sk_0900_as_cs", False),  # 292
    ("utf8mb4", "utf8mb4_es_trad_0900_as_cs", False),  # 293
    ("utf8mb4", "utf8mb4_la_0900_as_cs", False),  # 294
    None,
    ("utf8mb4", "utf8mb4_eo_0900_as_cs", False),  # 296
    ("utf8mb4", "utf8mb4_hu_0900_as_cs", False),  # 297
    ("utf8mb4", "utf8mb4_hr_0900_as_cs", False),  # 298
    None,
    ("utf8mb4", "utf8mb4_vi_0900_as_cs", False),  # 300
    None,
    None,
    ("utf8mb4", "utf8mb4_ja_0900_as_cs", False),  # 303
    ("utf8mb4", "utf8mb4_ja_0900_as_cs_ks", False),  # 304
    ("utf8mb4", "utf8mb4_0900_as_ci", False),  # 305
    ("utf8mb4", "utf8mb4_ru_0900_ai_ci", False),  # 306
    ("utf8mb4", "utf8mb4_ru_0900_as_cs", False),  # 307
    ("utf8mb4", "utf8mb4_zh_0900_as_cs", False),  # 308
    ("utf8mb4", "utf8mb4_0900_bin", False),  # 309
    ("utf8mb4", "utf8mb4_nb_0900_ai_ci", False),  # 310
    ("utf8mb4", "utf8mb4_nb_0900_as_cs", False),  # 311
    ("utf8mb4", "utf8mb4_nn_0900_ai_ci", False),  # 312
    ("utf8mb4", "utf8mb4_nn_0900_as_cs", False),  # 313
    ("utf8mb4", "utf8mb4_sr_latn_0900_ai_ci", False),  # 314
    ("utf8mb4", "utf8mb4_sr_latn_0900_as_cs", False),  # 315
    ("utf8mb4", "utf8mb4_bs_0900_ai_ci", False),  # 316
    ("utf8mb4", "utf8mb4_bs_0900_as_cs", False),  # 317
    ("utf8mb4", "utf8mb4_bg_0900_ai_ci", False),  # 318
    ("utf8mb4", "utf8mb4_bg_0900_as_cs", False),  # 319
    ("utf8mb4", "utf8mb4_gl_0900_ai_ci", False),  # 320
    ("utf8mb4", "utf8mb4_gl_0900_as_cs", False),  # 321
    ("utf8mb4", "utf8mb4_mn_cyrl_0900_ai_ci", False),  # 322
    ("utf8mb4", "utf8mb4_mn_cyrl_0900_as_cs", False),  # 323
]

MYSQL_CHARACTER_SETS_57: List[Optional[Tuple[str, str, bool]]] = [
    # (character set name, collation, default)
    None,
    ("big5", "big5_chinese_ci", True),  # 1
    ("latin2", "latin2_czech_cs", False),  # 2
    ("dec8", "dec8_swedish_ci", True),  # 3
    ("cp850", "cp850_general_ci", True),  # 4
    ("latin1", "latin1_german1_ci", False),  # 5
    ("hp8", "hp8_english_ci", True),  # 6
    ("koi8r", "koi8r_general_ci", True),  # 7
    ("latin1", "latin1_swedish_ci", True),  # 8
    ("latin2", "latin2_general_ci", True),  # 9
    ("swe7", "swe7_swedish_ci", True),  # 10
    ("ascii", "ascii_general_ci", True),  # 11
    ("ujis", "ujis_japanese_ci", True),  # 12
    ("sjis", "sjis_japanese_ci", True),  # 13
    ("cp1251", "cp1251_bulgarian_ci", False),  # 14
    ("latin1", "latin1_danish_ci", False),  # 15
    ("hebrew", "hebrew_general_ci", True),  # 16
    None,
    ("tis620", "tis620_thai_ci", True),  # 18
    ("euckr", "euckr_korean_ci", True),  # 19
    ("latin7", "latin7_estonian_cs", False),  # 20
    ("latin2", "latin2_hungarian_ci", False),  # 21
    ("koi8u", "koi8u_general_ci", True),  # 22
    ("cp1251", "cp1251_ukrainian_ci", False),  # 23
    ("gb2312", "gb2312_chinese_ci", True),  # 24
    ("greek", "greek_general_ci", True),  # 25
    ("cp1250", "cp1250_general_ci", True),  # 26
    ("latin2", "latin2_croatian_ci", False),  # 27
    ("gbk", "gbk_chinese_ci", True),  # 28
    ("cp1257", "cp1257_lithuanian_ci", False),  # 29
    ("latin5", "latin5_turkish_ci", True),  # 30
    ("latin1", "latin1_german2_ci", False),  # 31
    ("armscii8", "armscii8_general_ci", True),  # 32
    ("utf8", "utf8_general_ci", True),  # 33
    ("cp1250", "cp1250_czech_cs", False),  # 34
    ("ucs2", "ucs2_general_ci", True),  # 35
    ("cp866", "cp866_general_ci", True),  # 36
    ("keybcs2", "keybcs2_general_ci", True),  # 37
    ("macce", "macce_general_ci", True),  # 38
    ("macroman", "macroman_general_ci", True),  # 39
    ("cp852", "cp852_general_ci", True),  # 40
    ("latin7", "latin7_general_ci", True),  # 41
    ("latin7", "latin7_general_cs", False),  # 42
    ("macce", "macce_bin", False),  # 43
    ("cp1250", "cp1250_croatian_ci", False),  # 44
    ("utf8mb4", "utf8mb4_general_ci", True),  # 45
    ("utf8mb4", "utf8mb4_bin", False),  # 46
    ("latin1", "latin1_bin", False),  # 47
    ("latin1", "latin1_general_ci", False),  # 48
    ("latin1", "latin1_general_cs", False),  # 49
    ("cp1251", "cp1251_bin", False),  # 50
    ("cp1251", "cp1251_general_ci", True),  # 51
    ("cp1251", "cp1251_general_cs", False),  # 52
    ("macroman", "macroman_bin", False),  # 53
    ("utf16", "utf16_general_ci", True),  # 54
    ("utf16", "utf16_bin", False),  # 55
    ("utf16le", "utf16le_general_ci", True),  # 56
    ("cp1256", "cp1256_general_ci", True),  # 57
    ("cp1257", "cp1257_bin", False),  # 58
    ("cp1257", "cp1257_general_ci", True),  # 59
    ("utf32", "utf32_general_ci", True),  # 60
    ("utf32", "utf32_bin", False),  # 61
    ("utf16le", "utf16le_bin", False),  # 62
    ("binary", "binary", True),  # 63
    ("armscii8", "armscii8_bin", False),  # 64
    ("ascii", "ascii_bin", False),  # 65
    ("cp1250", "cp1250_bin", False),  # 66
    ("cp1256", "cp1256_bin", False),  # 67
    ("cp866", "cp866_bin", False),  # 68
    ("dec8", "dec8_bin", False),  # 69
    ("greek", "greek_bin", False),  # 70
    ("hebrew", "hebrew_bin", False),  # 71
    ("hp8", "hp8_bin", False),  # 72
    ("keybcs2", "keybcs2_bin", False),  # 73
    ("koi8r", "koi8r_bin", False),  # 74
    ("koi8u", "koi8u_bin", False),  # 75
    None,
    ("latin2", "latin2_bin", False),  # 77
    ("latin5", "latin5_bin", False),  # 78
    ("latin7", "latin7_bin", False),  # 79
    ("cp850", "cp850_bin", False),  # 80
    ("cp852", "cp852_bin", False),  # 81
    ("swe7", "swe7_bin", False),  # 82
    ("utf8", "utf8_bin", False),  # 83
    ("big5", "big5_bin", False),  # 84
    ("euckr", "euckr_bin", False),  # 85
    ("gb2312", "gb2312_bin", False),  # 86
    ("gbk", "gbk_bin", False),  # 87
    ("sjis", "sjis_bin", False),  # 88
    ("tis620", "tis620_bin", False),  # 89
    ("ucs2", "ucs2_bin", False),  # 90
    ("ujis", "ujis_bin", False),  # 91
    ("geostd8", "geostd8_general_ci", True),  # 92
    ("geostd8", "geostd8_bin", False),  # 93
    ("latin1", "latin1_spanish_ci", False),  # 94
    ("cp932", "cp932_japanese_ci", True),  # 95
    ("cp932", "cp932_bin", False),  # 96
    ("eucjpms", "eucjpms_japanese_ci", True),  # 97
    ("eucjpms", "eucjpms_bin", False),  # 98
    ("cp1250", "cp1250_polish_ci", False),  # 99
    None,
    ("utf16", "utf16_unicode_ci", False),  # 101
    ("utf16", "utf16_icelandic_ci", False),  # 102
    ("utf16", "utf16_latvian_ci", False),  # 103
    ("utf16", "utf16_romanian_ci", False),  # 104
    ("utf16", "utf16_slovenian_ci", False),  # 105
    ("utf16", "utf16_polish_ci", False),  # 106
    ("utf16", "utf16_estonian_ci", False),  # 107
    ("utf16", "utf16_spanish_ci", False),  # 108
    ("utf16", "utf16_swedish_ci", False),  # 109
    ("utf16", "utf16_turkish_ci", False),  # 110
    ("utf16", "utf16_czech_ci", False),  # 111
    ("utf16", "utf16_danish_ci", False),  # 112
    ("utf16", "utf16_lithuanian_ci", False),  # 113
    ("utf16", "utf16_slovak_ci", False),  # 114
    ("utf16", "utf16_spanish2_ci", False),  # 115
    ("utf16", "utf16_roman_ci", False),  # 116
    ("utf16", "utf16_persian_ci", False),  # 117
    ("utf16", "utf16_esperanto_ci", False),  # 118
    ("utf16", "utf16_hungarian_ci", False),  # 119
    ("utf16", "utf16_sinhala_ci", False),  # 120
    ("utf16", "utf16_german2_ci", False),  # 121
    ("utf16", "utf16_croatian_ci", False),  # 122
    ("utf16", "utf16_unicode_520_ci", False),  # 123
    ("utf16", "utf16_vietnamese_ci", False),  # 124
    None,
    None,
    None,
    ("ucs2", "ucs2_unicode_ci", False),  # 128
    ("ucs2", "ucs2_icelandic_ci", False),  # 129
    ("ucs2", "ucs2_latvian_ci", False),  # 130
    ("ucs2", "ucs2_romanian_ci", False),  # 131
    ("ucs2", "ucs2_slovenian_ci", False),  # 132
    ("ucs2", "ucs2_polish_ci", False),  # 133
    ("ucs2", "ucs2_estonian_ci", False),  # 134
    ("ucs2", "ucs2_spanish_ci", False),  # 135
    ("ucs2", "ucs2_swedish_ci", False),  # 136
    ("ucs2", "ucs2_turkish_ci", False),  # 137
    ("ucs2", "ucs2_czech_ci", False),  # 138
    ("ucs2", "ucs2_danish_ci", False),  # 139
    ("ucs2", "ucs2_lithuanian_ci", False),  # 140
    ("ucs2", "ucs2_slovak_ci", False),  # 141
    ("ucs2", "ucs2_spanish2_ci", False),  # 142
    ("ucs2", "ucs2_roman_ci", False),  # 143
    ("ucs2", "ucs2_persian_ci", False),  # 144
    ("ucs2", "ucs2_esperanto_ci", False),  # 145
    ("ucs2", "ucs2_hungarian_ci", False),  # 146
    ("ucs2", "ucs2_sinhala_ci", False),  # 147
    ("ucs2", "ucs2_german2_ci", False),  # 148
    ("ucs2", "ucs2_croatian_ci", False),  # 149
    ("ucs2", "ucs2_unicode_520_ci", False),  # 150
    ("ucs2", "ucs2_vietnamese_ci", False),  # 151
    None,
    None,
    None,
    None,
    None,
    None,
    None,
    ("ucs2", "ucs2_general_mysql500_ci", False),  # 159
    ("utf32", "utf32_unicode_ci", False),  # 160
    ("utf32", "utf32_icelandic_ci", False),  # 161
    ("utf32", "utf32_latvian_ci", False),  # 162
    ("utf32", "utf32_romanian_ci", False),  # 163
    ("utf32", "utf32_slovenian_ci", False),  # 164
    ("utf32", "utf32_polish_ci", False),  # 165
    ("utf32", "utf32_estonian_ci", False),  # 166
    ("utf32", "utf32_spanish_ci", False),  # 167
    ("utf32", "utf32_swedish_ci", False),  # 168
    ("utf32", "utf32_turkish_ci", False),  # 169
    ("utf32", "utf32_czech_ci", False),  # 170
    ("utf32", "utf32_danish_ci", False),  # 171
    ("utf32", "utf32_lithuanian_ci", False),  # 172
    ("utf32", "utf32_slovak_ci", False),  # 173
    ("utf32", "utf32_spanish2_ci", False),  # 174
    ("utf32", "utf32_roman_ci", False),  # 175
    ("utf32", "utf32_persian_ci", False),  # 176
    ("utf32", "utf32_esperanto_ci", False),  # 177
    ("utf32", "utf32_hungarian_ci", False),  # 178
    ("utf32", "utf32_sinhala_ci", False),  # 179
    ("utf32", "utf32_german2_ci", False),  # 180
    ("utf32", "utf32_croatian_ci", False),  # 181
    ("utf32", "utf32_unicode_520_ci", False),  # 182
    ("utf32", "utf32_vietnamese_ci", False),  # 183
    None,
    None,
    None,
    None,
    None,
    None,
    None,
    None,
    ("utf8", "utf8_unicode_ci", False),  # 192
    ("utf8", "utf8_icelandic_ci", False),  # 193
    ("utf8", "utf8_latvian_ci", False),  # 194
    ("utf8", "utf8_romanian_ci", False),  # 195
    ("utf8", "utf8_slovenian_ci", False),  # 196
    ("utf8", "utf8_polish_ci", False),  # 197
    ("utf8", "utf8_estonian_ci", False),  # 198
    ("utf8", "utf8_spanish_ci", False),  # 199
    ("utf8", "utf8_swedish_ci", False),  # 200
    ("utf8", "utf8_turkish_ci", False),  # 201
    ("utf8", "utf8_czech_ci", False),  # 202
    ("utf8", "utf8_danish_ci", False),  # 203
    ("utf8", "utf8_lithuanian_ci", False),  # 204
    ("utf8", "utf8_slovak_ci", False),  # 205
    ("utf8", "utf8_spanish2_ci", False),  # 206
    ("utf8", "utf8_roman_ci", False),  # 207
    ("utf8", "utf8_persian_ci", False),  # 208
    ("utf8", "utf8_esperanto_ci", False),  # 209
    ("utf8", "utf8_hungarian_ci", False),  # 210
    ("utf8", "utf8_sinhala_ci", False),  # 211
    ("utf8", "utf8_german2_ci", False),  # 212
    ("utf8", "utf8_croatian_ci", False),  # 213
    ("utf8", "utf8_unicode_520_ci", False),  # 214
    ("utf8", "utf8_vietnamese_ci", False),  # 215
    None,
    None,
    None,
    None,
    None,
    None,
    None,
    ("utf8", "utf8_general_mysql500_ci", False),  # 223
    ("utf8mb4", "utf8mb4_unicode_ci", False),  # 224
    ("utf8mb4", "utf8mb4_icelandic_ci", False),  # 225
    ("utf8mb4", "utf8mb4_latvian_ci", False),  # 226
    ("utf8mb4", "utf8mb4_romanian_ci", False),  # 227
    ("utf8mb4", "utf8mb4_slovenian_ci", False),  # 228
    ("utf8mb4", "utf8mb4_polish_ci", False),  # 229
    ("utf8mb4", "utf8mb4_estonian_ci", False),  # 230
    ("utf8mb4", "utf8mb4_spanish_ci", False),  # 231
    ("utf8mb4", "utf8mb4_swedish_ci", False),  # 232
    ("utf8mb4", "utf8mb4_turkish_ci", False),  # 233
    ("utf8mb4", "utf8mb4_czech_ci", False),  # 234
    ("utf8mb4", "utf8mb4_danish_ci", False),  # 235
    ("utf8mb4", "utf8mb4_lithuanian_ci", False),  # 236
    ("utf8mb4", "utf8mb4_slovak_ci", False),  # 237
    ("utf8mb4", "utf8mb4_spanish2_ci", False),  # 238
    ("utf8mb4", "utf8mb4_roman_ci", False),  # 239
    ("utf8mb4", "utf8mb4_persian_ci", False),  # 240
    ("utf8mb4", "utf8mb4_esperanto_ci", False),  # 241
    ("utf8mb4", "utf8mb4_hungarian_ci", False),  # 242
    ("utf8mb4", "utf8mb4_sinhala_ci", False),  # 243
    ("utf8mb4", "utf8mb4_german2_ci", False),  # 244
    ("utf8mb4", "utf8mb4_croatian_ci", False),  # 245
    ("utf8mb4", "utf8mb4_unicode_520_ci", False),  # 246
    ("utf8mb4", "utf8mb4_vietnamese_ci", False),  # 247
    ("gb18030", "gb18030_chinese_ci", True),  # 248
    ("gb18030", "gb18030_bin", False),  # 249
    ("gb18030", "gb18030_unicode_520_ci", False),  # 250
]

virt/lib/python3.9/site-packages/mysqlx/charsets.py
PrinceDisant/LostInNL
# Copyright (c) 2016, 2023, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

# mypy: disable-error-code="index,attr-defined"

"""Implementation of communication for MySQL X servers."""

from __future__ import annotations

from types import TracebackType

try:
    import ssl

    SSL_AVAILABLE = True
    TLS_VERSIONS = {
        "TLSv1": ssl.PROTOCOL_TLSv1,
        "TLSv1.1": ssl.PROTOCOL_TLSv1_1,
        "TLSv1.2": ssl.PROTOCOL_TLSv1_2,
    }
    # TLSv1.3 included in PROTOCOL_TLS, but PROTOCOL_TLS is not included on 3.4
    TLS_VERSIONS["TLSv1.3"] = (
        ssl.PROTOCOL_TLS
        if hasattr(ssl, "PROTOCOL_TLS")
        else ssl.PROTOCOL_SSLv23  # Alias of PROTOCOL_TLS
    )
    TLS_V1_3_SUPPORTED = hasattr(ssl, "HAS_TLSv1_3") and ssl.HAS_TLSv1_3
except ImportError:
    SSL_AVAILABLE = False
    TLS_V1_3_SUPPORTED = False
    TLS_VERSIONS = {}

import json
import os
import platform
import queue
import random
import re
import socket
import sys
import threading
import uuid
import warnings

from typing import Any, Callable, Dict, List, Mapping, Optional, Tuple, Type, Union

try:
    import dns.exception
    import dns.resolver
except ImportError:
    HAVE_DNSPYTHON = False
else:
    HAVE_DNSPYTHON = True

from datetime import datetime, timedelta
from functools import wraps
from json.decoder import JSONDecodeError
from urllib.parse import parse_qsl, unquote, urlparse

from .authentication import MySQL41AuthPlugin, PlainAuthPlugin, Sha256MemoryAuthPlugin
from .constants import (
    COMPRESSION_ALGORITHMS,
    DEPRECATED_TLS_VERSIONS,
    OPENSSL_CS_NAMES,
    SUPPORTED_TLS_VERSIONS,
    TLS_CIPHER_SUITES,
    Auth,
    Compression,
    SSLMode,
)
from .crud import Schema

# pylint: disable=redefined-builtin
from .errors import (
    InterfaceError,
    NotSupportedError,
    OperationalError,
    PoolError,
    ProgrammingError,
    TimeoutError,
)
from .helpers import escape, get_item_or_attr, iani_to_openssl_cs_name
from .logger import logger
from .protobuf import Protobuf
from .protocol import HAVE_LZ4, HAVE_ZSTD, MessageReader, MessageWriter, Protocol
from .result import BaseResult, DocResult, Result, RowResult, SqlResult
from .statement import (
    AddStatement,
    DeleteStatement,
    FindStatement,
    InsertStatement,
    ModifyStatement,
    ReadStatement,
    RemoveStatement,
    SelectStatement,
    SqlStatement,
    UpdateStatement,
    quote_identifier,
)
from .types import ColumnType, MessageType, ResultBaseType, StatementType

sys.path.append("..")

from mysql.connector.abstracts import (
    DUPLICATED_IN_LIST_ERROR,
    TLS_VER_NO_SUPPORTED,
    TLS_VERSION_DEPRECATED_ERROR,
    TLS_VERSION_ERROR,
)
from mysql.connector.utils import linux_distribution
from mysql.connector.version import LICENSE, VERSION

CONNECTION_CLOSED_ERROR = {
    1810: "This session was closed because the connection has been idle for "
    "too long. Use 'mysqlx.getSession()' or 'mysqlx.getClient()' to create a "
    "new one.",
    1053: "This session was closed because the server is shutting down.",
    3169: "This session was closed because the connection has been killed in "
    "a different session. Use 'mysqlx.getSession()' or 'mysqlx.getClient()' "
    "to create a new one.",
}

_CONNECT_TIMEOUT = 10000  # Default connect timeout in milliseconds
_DROP_DATABASE_QUERY = "DROP DATABASE IF EXISTS {}"
_CREATE_DATABASE_QUERY = "CREATE DATABASE IF NOT EXISTS {}"
_SELECT_SCHEMA_NAME_QUERY = (
    "SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = '{}'"
)
_SELECT_VERSION_QUERY = "SELECT @@version"

_CNX_POOL_MAXSIZE = 99
_CNX_POOL_MAX_NAME_SIZE = 120
_CNX_POOL_NAME_REGEX = re.compile(r"[^a-zA-Z0-9._:\-*$#]")
_CNX_POOL_MAX_IDLE_TIME = 2147483
_CNX_POOL_QUEUE_TIMEOUT = 2147483

# Time is on seconds
_PENALTY_SERVER_OFFLINE = 1000000
_PENALTY_MAXED_OUT = 60
_PENALTY_NO_ADD_INFO = 60 * 60
_PENALTY_CONN_TIMEOUT = 60 * 60
_PENALTY_WRONG_PASSW = 60 * 60 * 24
_PENALTY_RESTARTING = 60
_TIMEOUT_PENALTIES = {
    # Server denays service e.g Max connections reached
    "[WinError 10053]": _PENALTY_MAXED_OUT,  # Established connection was aborted
    "[Errno 32]": _PENALTY_MAXED_OUT,  # Broken pipe
    # Server is Offline
    "[WinError 10061]": _PENALTY_SERVER_OFFLINE,  # Target machine actively refused it
    "[Errno 111]": _PENALTY_SERVER_OFFLINE,  # Connection refused
    # Host is offline:
    "[WinError 10060]": _PENALTY_CONN_TIMEOUT,  # Not respond after a period of time
    # No route to Host:
    "[Errno 11001]": _PENALTY_NO_ADD_INFO,  # getaddrinfo failed
    "[Errno -2]": _PENALTY_NO_ADD_INFO,  # Name or service not known
    # Wrong Password
    "Access denied": _PENALTY_WRONG_PASSW,
}
_TIMEOUT_PENALTIES_BY_ERR_NO = {1053: _PENALTY_RESTARTING}
_SPLIT_RE = re.compile(r",(?![^\(\)]*\))")
_PRIORITY_RE = re.compile(r"^\(address=(.+),priority=(\d+)\)$", re.VERBOSE)
_ROUTER_RE = re.compile(r"^\(address=(.+)[,]*\)$", re.VERBOSE)
_URI_SCHEME_RE = re.compile(r"^([a-zA-Z][a-zA-Z0-9+\-.]+)://(.*)")
_SSL_OPTS = [
    "ssl-cert",
    "ssl-ca",
    "ssl-key",
    "ssl-crl",
    "tls-versions",
    "tls-ciphersuites",
]
_SESS_OPTS = _SSL_OPTS + [
    "user",
    "password",
    "schema",
    "host",
    "port",
    "routers",
    "socket",
    "ssl-mode",
    "auth",
    "use-pure",
    "connect-timeout",
    "connection-attributes",
    "compression",
    "compression-algorithms",
    "dns-srv",
]


def generate_pool_name(**kwargs: Any) -> str:
    """Generate a pool name.

    This function takes keyword arguments, usually the connection arguments and
    tries to generate a name for the pool.

    Args:
        **kwargs: Arbitrary keyword arguments with the connection arguments.

    Raises:
        PoolError: If the name can't be generated.

    Returns:
        str: The generated pool name.
    """
    parts = []
    for key in ("host", "port", "user", "database", "client_id"):
        try:
            parts.append(str(kwargs[key]))
        except KeyError:
            pass

    if not parts:
        raise PoolError("Failed generating pool name; specify pool_name")

    return "_".join(parts)


def update_timeout_penalties_by_error(penalty_dict: Mapping[str, Any]) -> None:
    """Update the timeout penalties directory.

    Update the timeout penalties by error dictionary used to deactivate a pool.
    Args:
        penalty_dict (dict): The dictionary with the new timeouts.
    """
    if penalty_dict and isinstance(penalty_dict, dict):
        _TIMEOUT_PENALTIES_BY_ERR_NO.update(penalty_dict)


class SocketStream:
    """Implements a socket stream."""

    def __init__(self) -> None:
        self._socket: Optional[socket.socket] = None
        self._is_ssl: bool = False
        self._is_socket: bool = False
        self._host: Optional[str] = None

    def connect(self, params: Tuple, connect_timeout: float = _CONNECT_TIMEOUT) -> None:
        """Connects to a TCP service.

        Args:
            params (tuple): The connection parameters.

        Raises:
            :class:`mysqlx.InterfaceError`: If Unix socket is not supported.
        """
        if connect_timeout is not None:
            connect_timeout = connect_timeout / 1000  # Convert to seconds
        try:
            self._socket = socket.create_connection(
                params, connect_timeout  # type: ignore[arg-type]
            )
            self._host = params[0]
        except ValueError:
            try:
                self._socket = socket.socket(socket.AF_UNIX)
                self._socket.settimeout(connect_timeout)
                self._socket.connect(params)
                self._is_socket = True
            except AttributeError:
                raise InterfaceError("Unix socket unsupported") from None
        self._socket.settimeout(None)

    def read(self, count: int) -> bytes:
        """Receive data from the socket.

        Args:
            count (int): Buffer size.

        Returns:
            bytes: The data received.
        """
        if self._socket is None:
            raise OperationalError("MySQLx Connection not available")
        buf = []
        while count > 0:
            data = self._socket.recv(count)
            if data == b"":
                raise RuntimeError("Unexpected connection close")
            buf.append(data)
            count -= len(data)
        return b"".join(buf)

    def sendall(self, data: bytes) -> None:
        """Send data to the socket.

        Args:
            data (bytes): The data to be sent.
        """
        if self._socket is None:
            raise OperationalError("MySQLx Connection not available")
        try:
            self._socket.sendall(data)
        except OSError as err:
            raise OperationalError(f"Unexpected socket error: {err}") from err

    def close(self) -> None:
        """Close the socket."""
        if not self._socket:
            return
        try:
            self._socket.shutdown(socket.SHUT_RDWR)
            self._socket.close()
        except OSError:
            # On [Errno 107] Transport endpoint is not connected
            pass
        self._socket = None

    def __del__(self) -> None:
        self.close()

    def set_ssl(
        self,
        ssl_protos: List[str],
        ssl_mode: str,
        ssl_ca: str,
        ssl_crl: str,
        ssl_cert: str,
        ssl_key: str,
        ssl_ciphers: List[str],
    ) -> None:
        """Set SSL parameters.

        Args:
            ssl_protos (list): SSL protocol to use.
            ssl_mode (str): SSL mode.
            ssl_ca (str): The certification authority certificate.
            ssl_crl (str): The certification revocation lists.
            ssl_cert (str): The certificate.
            ssl_key (str): The certificate key.
            ssl_ciphers (list): SSL ciphersuites to use.

        Raises:
            :class:`mysqlx.RuntimeError`: If Python installation has no SSL
                                          support.
            :class:`mysqlx.InterfaceError`: If the parameters are invalid.
        """
        if not SSL_AVAILABLE:
            self.close()
            raise RuntimeError("Python installation has no SSL support")

        if ssl_protos is None or not ssl_protos:
            context = ssl.create_default_context()
            if ssl_mode != SSLMode.VERIFY_IDENTITY:
                context.check_hostname = False
            if ssl_mode == SSLMode.REQUIRED:
                context.verify_mode = ssl.CERT_NONE
        else:
            ssl_protos.sort(reverse=True)
            tls_version = ssl_protos[0]
            if (
                not TLS_V1_3_SUPPORTED
                and tls_version == "TLSv1.3"
                and len(ssl_protos) > 1
            ):
                tls_version = ssl_protos[1]
            ssl_protocol = TLS_VERSIONS[tls_version]
            context = ssl.SSLContext(ssl_protocol)

            if tls_version == "TLSv1.3":
                if "TLSv1.2" not in ssl_protos:
                    context.options |= ssl.OP_NO_TLSv1_2
                if "TLSv1.1" not in ssl_protos:
                    context.options |= ssl.OP_NO_TLSv1_1
                if "TLSv1" not in ssl_protos:
                    context.options |= ssl.OP_NO_TLSv1

        if ssl_ca:
            try:
                context.load_verify_locations(ssl_ca)
                context.verify_mode = ssl.CERT_REQUIRED
            except (IOError, ssl.SSLError) as err:
                self.close()
                raise InterfaceError(f"Invalid CA Certificate: {err}") from err

        if ssl_crl:
            try:
                context.load_verify_locations(ssl_crl)
                context.verify_flags = ssl.VERIFY_CRL_CHECK_LEAF
            except (IOError, ssl.SSLError) as err:
                self.close()
                raise InterfaceError(f"Invalid CRL: {err}") from err

        if ssl_cert:
            try:
                context.load_cert_chain(ssl_cert, ssl_key)
            except (IOError, ssl.SSLError) as err:
                self.close()
                raise InterfaceError(f"Invalid Certificate/Key: {err}") from err

        if ssl_ciphers:
            context.set_ciphers(
                ":".join(iani_to_openssl_cs_name(ssl_protos[0], ssl_ciphers))
            )
        try:
            self._socket = context.wrap_socket(self._socket, server_hostname=self._host)
        except ssl.CertificateError as err:
            raise InterfaceError(f"{err}") from err
        if ssl_mode == SSLMode.VERIFY_IDENTITY:
            context.check_hostname = True
            hostnames = []
            # Windows does not return loopback aliases on gethostbyaddr
            if os.name == "nt" and self._host in ("localhost", "127.0.0.1"):
                hostnames = ["localhost", "127.0.0.1"]
            aliases = socket.gethostbyaddr(self._host)
            hostnames.extend([aliases[0]] + aliases[1])
            match_found = False
            errs = []
            for hostname in hostnames:
                try:
                    # Deprecated in Python 3.7 without a replacement and
                    # should be removed in the future, since OpenSSL now
                    # performs hostname matching
                    # pylint: disable=deprecated-method
                    ssl.match_hostname(self._socket.getpeercert(), hostname)
                    # pylint: enable=deprecated-method
                except ssl.CertificateError as err:
                    errs.append(str(err))
                else:
                    match_found = True
                    break
            if not match_found:
                self.close()
                raise InterfaceError(
                    f"Unable to verify server identity: {', '.join(errs)}"
                )

        self._is_ssl = True

        # Raise a deprecation warning if TLSv1 or TLSv1.1 is being used
        tls_version = self._socket.version()
        if tls_version in ("TLSv1", "TLSv1.1"):
            warn_msg = (
                f"This connection is using {tls_version} which is now "
                "deprecated and will be removed in a future release of "
                "MySQL Connector/Python"
            )
            warnings.warn(warn_msg, DeprecationWarning)

    def is_ssl(self) -> bool:
        """Verifies if SSL is being used.

        Returns:
            bool: Returns `True` if SSL is being used.
        """
        return self._is_ssl

    def is_socket(self) -> bool:
        """Verifies if socket connection is being used.

        Returns:
            bool: Returns `True` if socket connection is being used.
        """
        return self._is_socket

    def is_secure(self) -> bool:
        """Verifies if connection is secure.

        Returns:
            bool: Returns `True` if connection is secure.
        """
        return self._is_ssl or self._is_socket

    def is_open(self) -> bool:
        """Verifies if connection is open.

        Returns:
            bool: Returns `True` if connection is open.
        """
        return self._socket is not None


def catch_network_exception(func: Callable) -> Callable:
    """Decorator used to catch OSError or RuntimeError.

    Raises:
        :class:`mysqlx.InterfaceError`: If `OSError` or `RuntimeError`
                                        is raised.
    """

    @wraps(func)
    def wrapper(self: Any, *args: Any, **kwargs: Any) -> Any:
        """Wrapper function."""
        try:
            if (
                isinstance(self, (Connection, PooledConnection))
                and self.is_server_disconnected()
            ):
                raise InterfaceError(*self.get_disconnected_reason())
            result = func(self, *args, **kwargs)
            if isinstance(result, BaseResult):
                warns: Any = result.get_warnings()
                for warn in warns:
                    if warn["code"] in CONNECTION_CLOSED_ERROR:
                        error_msg = CONNECTION_CLOSED_ERROR[warn["code"]]
                        reason = (
                            f"Connection close: {warn['msg']}: {error_msg}",
                            warn["code"],
                        )
                        if isinstance(self, (Connection, PooledConnection)):
                            self.set_server_disconnected(reason)
                        break
            return result
        except (InterfaceError, OSError, RuntimeError, TimeoutError) as err:
            if (
                func.__name__ == "get_column_metadata"
                and args
                and isinstance(args[0], SqlResult)
            ):
                warns = args[0].get_warnings()
                if warns:
                    warn = warns[0]
                    error_msg = CONNECTION_CLOSED_ERROR[warn["code"]]
                    reason = (
                        f"Connection close: {warn['msg']}: {error_msg}",
                        warn["code"],
                    )
                    if isinstance(self, PooledConnection):
                        self.pool.remove_connections()
                        # pool must be listed as faulty if server is shutting down
                        if warn["code"] == 1053:
                            PoolsManager().set_pool_unavailable(
                                self.pool, InterfaceError(*reason)
                            )
                    if isinstance(self, (Connection, PooledConnection)):
                        self.set_server_disconnected(reason)
                    self.disconnect()
                    raise InterfaceError(*reason) from err
            self.disconnect()
            raise

    return wrapper


class Router(dict):
    """Represents a set of connection parameters.

    Args:
       settings (dict): Dictionary with connection settings
    .. versionadded:: 8.0.20
    """

    def __init__(self, connection_params: Mapping[str, Any]) -> None:
        super().__init__()
        self.update(connection_params)
        self["available"] = self.get("available", True)

    def available(self) -> bool:
        """Verifies if the Router is available to open connections.

        Returns:
            bool: True if this Router is available else False.
        """
        return self["available"]

    def set_unavailable(self) -> None:
        """Sets this Router unavailable to open connections."""
        self["available"] = False

    def get_connection_params(self) -> Union[str, Tuple[str, Optional[int]]]:
        """Verifies if the Router is available to open connections.

        Returns:
            tuple: host and port or socket information tuple.
        """
        if "socket" in self:
            return self["socket"]
        return (self["host"], self["port"])


class RouterManager:
    """Manages the connection parameters of all the routers.

    Args:
        Routers (list): A list of Router objects.
        settings (dict): Dictionary with connection settings.
    .. versionadded:: 8.0.20
    """

    def __init__(self, routers: List[Router], settings: Dict[str, Any]) -> None:
        self._routers = routers
        self._settings = settings
        self._cur_priority_idx: int = 0
        self._can_failover: bool = True
        # Reuters status
        self._routers_directory: Dict[int, List[Router]] = {}
        self.routers_priority_list: List[int] = []
        self._ensure_priorities()

    def _ensure_priorities(self) -> None:
        """Ensure priorities.

        Raises:
            :class:`mysqlx.ProgrammingError`: If priorities are invalid.
        """
        priority_count = 0

        for router in self._routers:
            priority = router.get("priority", None)
            if priority is None:
                priority_count += 1
                router["priority"] = 100
            elif priority > 100:
                raise ProgrammingError("The priorities must be between 0 and 100", 4007)

        if 0 < priority_count < len(self._routers):
            raise ProgrammingError(
                "You must either assign no priority to any "
                "of the routers or give a priority for "
                "every router",
                4000,
            )

        self._routers.sort(key=lambda x: x["priority"], reverse=True)

        # Group servers with the same priority
        for router in self._routers:
            priority = router["priority"]
            if priority not in self._routers_directory:
                self._routers_directory[priority] = [Router(router)]
                self.routers_priority_list.append(priority)
            else:
                self._routers_directory[priority].append(Router(router))

    def _get_available_routers(self, priority: int) -> List[Router]:
        """Get a list of the current available routers that shares the given priority.

        Returns:
            list: A list of the current available routers.
        """
        router_list = self._routers_directory[priority]
        router_list = [router for router in router_list if router.available()]
        return router_list

    def _get_random_connection_params(self, priority: int) -> Router:
        """Get a random router from the group with the given priority.

        Returns:
            Router: A random router.
        """
        router_list = self._get_available_routers(priority)
        if not router_list:
            return None
        if len(router_list) == 1:
            return router_list[0]

        last = len(router_list) - 1
        index = random.randint(0, last)
        return router_list[index]

    def can_failover(self) -> bool:
        """Returns the next connection parameters.

        Returns:
            bool: True if there is more server to failover to else False.
        """
        return self._can_failover

    def get_next_router(self) -> Router:
        """Returns the next connection parameters.

        Returns:
            Router: with the connection parameters.
        """
        if not self._routers:
            self._can_failover = False
            router_settings = self._settings.copy()
            router_settings["host"] = self._settings.get("host", "localhost")
            router_settings["port"] = self._settings.get("port", 33060)
            return Router(router_settings)

        cur_priority = self.routers_priority_list[self._cur_priority_idx]
        routers_priority_len = len(self.routers_priority_list)

        search = True
        while search:
            router = self._get_random_connection_params(cur_priority)

            if router is not None or self._cur_priority_idx >= routers_priority_len:
                if (
                    self._cur_priority_idx == routers_priority_len - 1
                    and len(self._get_available_routers(cur_priority)) < 2
                ):
                    self._can_failover = False
                break

            # Search on next group
            self._cur_priority_idx += 1
            if self._cur_priority_idx < routers_priority_len:
                cur_priority = self.routers_priority_list[self._cur_priority_idx]

        return router

    def get_routers_directory(self) -> Dict[int, List[Router]]:
        """Returns the directory containing all the routers managed.

        Returns:
            dict: Dictionary with priorities as connection settings.
        """
        return self._routers_directory


class Connection:
    """Connection to a MySQL Server.

    Args:
        settings (dict): Dictionary with connection settings.
    """

    def __init__(self, settings: Dict[str, Any]) -> None:
        self.settings: Dict[str, Any] = settings
        self.stream: SocketStream = SocketStream()
        self.protocol: Optional[Protocol] = None
        self.keep_open: Optional[bool] = None
        self._user: Optional[str] = settings.get("user")
        self._password: Optional[str] = settings.get("password")
        self._schema: Optional[str] = settings.get("schema")
        self._active_result: Optional[ResultBaseType] = None
        self._routers: List[Router] = settings.get("routers", [])

        if "host" in settings and settings["host"]:
            self._routers.append(
                {  # type: ignore[arg-type]
                    "host": settings.get("host"),
                    "port": settings.get("port", None),
                }
            )

        self.router_manager: RouterManager = RouterManager(self._routers, settings)
        self._connect_timeout: Optional[int] = settings.get(
            "connect-timeout", _CONNECT_TIMEOUT
        )
        if self._connect_timeout == 0:
            # None is assigned if connect timeout is 0, which disables timeouts
            # on socket operations
            self._connect_timeout = None

        self._stmt_counter: int = 0
        self._prepared_stmt_ids: List[int] = []
        self._prepared_stmt_supported: bool = True
        self._server_disconnected: bool = False
        self._server_disconnected_reason: Optional[Union[str, Tuple[str, int]]] = None

    def fetch_active_result(self) -> None:
        """Fetch active result."""
        if self._active_result is not None:
            self._active_result.fetch_all()
            self._active_result = None

    def set_active_result(self, result: ResultBaseType) -> None:
        """Set active result.

        Args:
            `Result`: It can be :class:`mysqlx.Result`,
                      :class:`mysqlx.BufferingResult`,
                      :class:`mysqlx.RowResult`, :class:`mysqlx.SqlResult` or
                      :class:`mysqlx.DocResult`.
        """
        self._active_result = result

    def connect(self) -> None:
        """Attempt to connect to the MySQL server.

        Raises:
            :class:`mysqlx.InterfaceError`: If fails to connect to the MySQL
                                            server.
            :class:`mysqlx.TimeoutError`: If connect timeout was exceeded.
        """
        # Loop and check
        error = None
        while self.router_manager.can_failover():
            try:
                router = self.router_manager.get_next_router()
                self.stream.connect(
                    router.get_connection_params(), self._connect_timeout  # type: ignore[arg-type]
                )
                reader = MessageReader(self.stream)
                writer = MessageWriter(self.stream)
                self.protocol = Protocol(reader, writer)

                caps_data = self.protocol.get_capabilites().capabilities
                caps = (
                    {get_item_or_attr(cap, "name").lower(): cap for cap in caps_data}
                    if caps_data
                    else {}
                )

                # Set TLS capabilities
                self._set_tls_capabilities(caps)

                # Set connection attributes capabilities
                if "attributes" in self.settings:
                    conn_attrs = self.settings["attributes"]
                    self.protocol.set_capabilities(session_connect_attrs=conn_attrs)

                # Set compression capabilities
                compression = self.settings.get("compression", Compression.PREFERRED)
                algorithms = self.settings.get("compression-algorithms")
                algorithm = (
                    None
                    if compression == Compression.DISABLED
                    else self._set_compression_capabilities(
                        caps, compression, algorithms
                    )
                )
                self._authenticate()
                self.protocol.set_compression(algorithm)
                return
            except (OSError, RuntimeError) as err:
                error = err
                router.set_unavailable()

        # Python 2.7 does not raise a socket.timeout exception when using
        # settimeout(), but it raises a socket.error with errno.EAGAIN (11)
        # or errno.EINPROGRESS (115) if connect-timeout value is too low
        if error is not None and isinstance(error, socket.timeout):
            if len(self._routers) <= 1:
                raise TimeoutError(
                    "Connection attempt to the server was aborted. "
                    f"Timeout of {self._connect_timeout} ms was exceeded"
                )
            raise TimeoutError(
                "All server connection attempts were aborted. "
                f"Timeout of {self._connect_timeout} ms was exceeded for each "
                "selected server"
            )
        if len(self._routers) <= 1:
            raise InterfaceError(f"Cannot connect to host: {error}")
        raise InterfaceError("Unable to connect to any of the target hosts", 4001)

    def _set_tls_capabilities(self, caps: Dict[str, Any]) -> None:
        """Set the TLS capabilities.

        Args:
            caps (dict): Dictionary with the server capabilities.

        Raises:
            :class:`mysqlx.OperationalError`: If SSL is not enabled at the
                                             server.
            :class:`mysqlx.RuntimeError`: If support for SSL is not available
                                          in Python.

        .. versionadded:: 8.0.21
        """
        if self.settings.get("ssl-mode") == SSLMode.DISABLED:
            return

        if self.stream.is_socket():
            if self.settings.get("ssl-mode"):
                logger.warning("SSL not required when using Unix socket.")
            return

        if "tls" not in caps:
            self.close_connection()
            raise OperationalError("SSL not enabled at server")

        is_ol7 = False
        if platform.system() == "Linux":
            distname, version, _ = linux_distribution()
            try:
                is_ol7 = "Oracle Linux" in distname and version.split(".")[0] == "7"
            except IndexError:
                is_ol7 = False

        if sys.version_info < (2, 7, 9) and not is_ol7:
            self.close_connection()
            raise RuntimeError(
                "The support for SSL is not available for this Python version"
            )

        self.protocol.set_capabilities(tls=True)
        self.stream.set_ssl(
            self.settings.get("tls-versions", None),
            self.settings.get("ssl-mode", SSLMode.REQUIRED),
            self.settings.get("ssl-ca"),
            self.settings.get("ssl-crl"),
            self.settings.get("ssl-cert"),
            self.settings.get("ssl-key"),
            self.settings.get("tls-ciphersuites"),
        )
        if "attributes" in self.settings:
            conn_attrs = self.settings["attributes"]
            self.protocol.set_capabilities(session_connect_attrs=conn_attrs)

    def _set_compression_capabilities(
        self,
        caps: Dict[str, Any],
        compression: str,
        algorithms: Optional[List[str]] = None,
    ) -> Optional[str]:
        """Set the compression capabilities.

        If compression is available, negociates client and server algorithms.
        By trying to find an algorithm from the requested compression
        algorithms list, which is supported by the server.

        If no compression algorithms list is provided, the following priority
        is used:

        1) zstd_stream
        2) lz4_message
        3) deflate_stream

        Args:
            caps (dict): Dictionary with the server capabilities.
            compression (str): The compression connection setting.
            algorithms (list): List of requested compression algorithms.

        Returns:
            str: The compression algorithm.

        .. versionadded:: 8.0.21
        .. versionchanged:: 8.0.22
        """
        compression_data = caps.get("compression")
        if compression_data is None:
            msg = "Compression requested but the server does not support it"
            if compression == Compression.REQUIRED:
                raise NotSupportedError(msg)
            logger.warning(msg)
            return None

        compression_dict = {}
        if isinstance(compression_data, dict):  # C extension is being used
            for fld in compression_data["value"]["obj"]["fld"]:
                compression_dict[fld["key"]] = [
                    value["scalar"]["v_string"]["value"].decode("utf-8")
                    for value in fld["value"]["array"]["value"]
                ]
        else:
            for fld in compression_data.value.obj.fld:
                compression_dict[fld.key] = [
                    value.scalar.v_string.value.decode("utf-8")
                    for value in fld.value.array.value
                ]

        server_algorithms = compression_dict.get("algorithm", [])
        algorithm = None

        # Try to find an algorithm from the requested compression algorithms
        # list, which is supported by the server
        if algorithms:
            # Resolve compression algorithms aliases and ignore unsupported
            client_algorithms = [
                COMPRESSION_ALGORITHMS[item]
                for item in algorithms
                if item in COMPRESSION_ALGORITHMS
            ]
            matched = [item for item in client_algorithms if item in server_algorithms]
            if matched:
                algorithm = COMPRESSION_ALGORITHMS.get(matched[0])
            elif compression == Compression.REQUIRED:
                raise InterfaceError(
                    "The connection compression is set as "
                    "required, but none of the provided "
                    "compression algorithms are supported."
                )
            else:
                return None  # Disable compression

        # No compression algorithms list was provided or couldn't found one
        # supported by the server
        if algorithm is None:
            if HAVE_ZSTD and "zstd_stream" in server_algorithms:
                algorithm = "zstd_stream"
            elif HAVE_LZ4 and "lz4_message" in server_algorithms:
                algorithm = "lz4_message"
            else:
                algorithm = "deflate_stream"

        if algorithm not in server_algorithms:
            msg = (
                "Compression requested but the compression algorithm "
                "negotiation failed"
            )
            if compression == Compression.REQUIRED:
                raise InterfaceError(msg)
            logger.warning(msg)
            return None

        self.protocol.set_capabilities(compression={"algorithm": algorithm})
        return algorithm

    def _authenticate(self) -> None:
        """Authenticate with the MySQL server."""
        auth = self.settings.get("auth")
        if auth:
            if auth == Auth.PLAIN:
                self._authenticate_plain()
            elif auth == Auth.SHA256_MEMORY:
                self._authenticate_sha256_memory()
            elif auth == Auth.MYSQL41:
                self._authenticate_mysql41()
        elif self.stream.is_secure():
            # Use PLAIN if no auth provided and connection is secure
            self._authenticate_plain()
        else:
            # Use MYSQL41 if connection is not secure
            try:
                self._authenticate_mysql41()
            except InterfaceError:
                pass
            else:
                return
            # Try SHA256_MEMORY if MYSQL41 fails
            try:
                self._authenticate_sha256_memory()
            except InterfaceError as err:
                raise InterfaceError(
                    "Authentication failed using MYSQL41 and "
                    "SHA256_MEMORY, check username and "
                    f"password or try a secure connection err:{err}"
                ) from err

    def _authenticate_mysql41(self) -> None:
        """Authenticate with the MySQL server using `MySQL41AuthPlugin`."""
        plugin = MySQL41AuthPlugin(self._user, self._password)
        self.protocol.send_auth_start(plugin.auth_name())
        extra_data = self.protocol.read_auth_continue()
        self.protocol.send_auth_continue(plugin.auth_data(extra_data))
        self.protocol.read_auth_ok()

    def _authenticate_plain(self) -> None:
        """Authenticate with the MySQL server using `PlainAuthPlugin`."""
        if not self.stream.is_secure():
            raise InterfaceError(
                "PLAIN authentication is not allowed via unencrypted connection"
            )
        plugin = PlainAuthPlugin(self._user, self._password)
        self.protocol.send_auth_start(plugin.auth_name(), auth_data=plugin.auth_data())
        self.protocol.read_auth_ok()

    def _authenticate_sha256_memory(self) -> None:
        """Authenticate with the MySQL server using `Sha256MemoryAuthPlugin`."""
        plugin = Sha256MemoryAuthPlugin(self._user, self._password)
        self.protocol.send_auth_start(plugin.auth_name())
        extra_data = self.protocol.read_auth_continue()
        self.protocol.send_auth_continue(plugin.auth_data(extra_data))
        self.protocol.read_auth_ok()

    def _deallocate_statement(self, statement: StatementType) -> None:
        """Deallocates statement.

        Args:
            statement (Statement): A `Statement` based type object.
        """
        if statement.prepared:
            self.protocol.send_prepare_deallocate(statement.stmt_id)
            self._prepared_stmt_ids.remove(statement.stmt_id)
            statement.prepared = False

    def _prepare_statement(
        self,
        msg_type: str,
        msg: MessageType,
        statement: Union[
            FindStatement,
            DeleteStatement,
            ModifyStatement,
            ReadStatement,
            RemoveStatement,
            UpdateStatement,
        ],
    ) -> None:
        """Prepares a statement.

        Args:
            msg_type (str): Message ID string.
            msg (mysqlx.protobuf.Message): MySQL X Protobuf Message.
            statement (Statement): A `Statement` based type object.
        """
        try:
            self.fetch_active_result()
            self.protocol.send_prepare_prepare(msg_type, msg, statement)
        except NotSupportedError:
            self._prepared_stmt_supported = False
            return
        self._prepared_stmt_ids.append(statement.stmt_id)
        statement.prepared = True

    def _execute_prepared_pipeline(
        self,
        msg_type: str,
        msg: MessageType,
        statement: Union[
            FindStatement,
            DeleteStatement,
            ModifyStatement,
            ReadStatement,
            RemoveStatement,
            UpdateStatement,
        ],
    ) -> None:
        """Executes the prepared statement pipeline.

        Args:
            msg_type (str): Message ID string.
            msg (mysqlx.protobuf.Message): MySQL X Protobuf Message.
            statement (Statement): A `Statement` based type object.
        """
        # For old servers without prepared statement support
        if not self._prepared_stmt_supported:
            # Crud::<Operation>
            self.protocol.send_msg_without_ps(msg_type, msg, statement)
            return

        if statement.deallocate_prepare_execute:
            # Prepare::Deallocate + Prepare::Prepare + Prepare::Execute
            self._deallocate_statement(statement)
            self._prepare_statement(msg_type, msg, statement)
            if not self._prepared_stmt_supported:
                self.protocol.send_msg_without_ps(msg_type, msg, statement)
                return
            self.protocol.send_prepare_execute(msg_type, msg, statement)
            statement.deallocate_prepare_execute = False
            statement.reset_exec_counter()
        elif statement.prepared and not statement.changed:
            # Prepare::Execute
            self.protocol.send_prepare_execute(msg_type, msg, statement)
        elif statement.changed and not statement.repeated:
            # Crud::<Operation>
            self._deallocate_statement(statement)
            self.protocol.send_msg_without_ps(msg_type, msg, statement)
            statement.changed = False
            statement.reset_exec_counter()
        elif not statement.changed and not statement.repeated:
            # Prepare::Prepare + Prepare::Execute
            if not statement.prepared:
                self._prepare_statement(msg_type, msg, statement)
            if not self._prepared_stmt_supported:
                self.protocol.send_msg_without_ps(msg_type, msg, statement)
                return
            self.protocol.send_prepare_execute(msg_type, msg, statement)
        elif statement.changed and statement.repeated:
            # Prepare::Deallocate + Crud::<Operation>
            self._deallocate_statement(statement)
            self.protocol.send_msg_without_ps(msg_type, msg, statement)
            statement.changed = False
            statement.reset_exec_counter()

        statement.increment_exec_counter()

    @catch_network_exception
    def send_sql(self, statement: SqlStatement) -> SqlResult:
        """Execute a SQL statement.

        Args:
            sql (str): The SQL statement.

        Raises:
            :class:`mysqlx.ProgrammingError`: If the SQL statement is not a
                                              valid string.
        """
        sql = statement.sql
        if self.protocol is None:
            raise OperationalError("MySQLx Connection not available")
        if not isinstance(sql, str):
            raise ProgrammingError("The SQL statement is not a valid string")
        msg_type, msg = self.protocol.build_execute_statement("sql", sql)
        self.protocol.send_msg_without_ps(msg_type, msg, statement)
        return SqlResult(self)

    @catch_network_exception
    def send_insert(self, statement: Union[AddStatement, InsertStatement]) -> Result:
        """Send an insert statement.

        Args:
            statement (`Statement`): It can be :class:`mysqlx.InsertStatement`
                                     or :class:`mysqlx.AddStatement`.

        Returns:
            :class:`mysqlx.Result`: A result object.
        """
        if self.protocol is None:
            raise OperationalError("MySQLx Connection not available")
        msg_type, msg = self.protocol.build_insert(statement)
        self.protocol.send_msg(msg_type, msg)
        ids = None
        if isinstance(statement, AddStatement):
            ids = statement.ids
        return Result(self, ids)

    @catch_network_exception
    def send_find(
        self, statement: Union[FindStatement, SelectStatement]
    ) -> Union[DocResult, RowResult]:
        """Send an find statement.

        Args:
            statement (`Statement`): It can be :class:`mysqlx.SelectStatement`
                                     or :class:`mysqlx.FindStatement`.

        Returns:
            `Result`: It can be class:`mysqlx.DocResult` or
                      :class:`mysqlx.RowResult`.
        """
        msg_type, msg = self.protocol.build_find(statement)
        self._execute_prepared_pipeline(msg_type, msg, statement)
        return DocResult(self) if statement.is_doc_based() else RowResult(self)

    @catch_network_exception
    def send_delete(self, statement: Union[DeleteStatement, RemoveStatement]) -> Result:
        """Send an delete statement.

        Args:
            statement (`Statement`): It can be :class:`mysqlx.RemoveStatement`
                                     or :class:`mysqlx.DeleteStatement`.

        Returns:
            :class:`mysqlx.Result`: The result object.
        """
        msg_type, msg = self.protocol.build_delete(statement)
        self._execute_prepared_pipeline(msg_type, msg, statement)
        return Result(self)

    @catch_network_exception
    def send_update(self, statement: Union[ModifyStatement, UpdateStatement]) -> Result:
        """Send an delete statement.

        Args:
            statement (`Statement`): It can be :class:`mysqlx.ModifyStatement`
                                     or :class:`mysqlx.UpdateStatement`.

        Returns:
            :class:`mysqlx.Result`: The result object.
        """
        msg_type, msg = self.protocol.build_update(statement)
        self._execute_prepared_pipeline(msg_type, msg, statement)
        return Result(self)

    @catch_network_exception
    def execute_nonquery(
        self,
        namespace: str,
        cmd: str,
        raise_on_fail: bool,
        fields: Optional[Dict[str, Any]] = None,
    ) -> Optional[Result]:
        """Execute a non query command.

        Args:
            namespace (str): The namespace.
            cmd (str): The command.
            raise_on_fail (bool): `True` to raise on fail.
            fields (Optional[dict]): The message fields.

        Raises:
            :class:`mysqlx.OperationalError`: On errors.

        Returns:
            :class:`mysqlx.Result`: The result object.
        """
        try:
            msg_type, msg = self.protocol.build_execute_statement(
                namespace, cmd, fields
            )
            self.protocol.send_msg(msg_type, msg)
            return Result(self)
        except OperationalError:
            if raise_on_fail:
                raise
        return None

    @catch_network_exception
    def execute_sql_scalar(self, sql: StatementType) -> int:
        """Execute a SQL scalar.

        Args:
            sql (str): The SQL statement.

        Raises:
            :class:`mysqlx.InterfaceError`: If no data found.

        Returns:
            :class:`mysqlx.Result`: The result.
        """
        msg_type, msg = self.protocol.build_execute_statement("sql", sql)
        self.protocol.send_msg(msg_type, msg)
        result = RowResult(self)
        result.fetch_all()
        if result.count == 0:
            raise InterfaceError("No data found")
        return result[0][0]  # type: ignore[index]

    @catch_network_exception
    def get_row_result(self, cmd: str, fields: Dict[str, Any]) -> RowResult:
        """Returns the row result.

        Args:
            cmd (str): The command.
            fields (dict): The message fields.

        Returns:
            :class:`mysqlx.RowResult`: The result object.
        """
        msg_type, msg = self.protocol.build_execute_statement("mysqlx", cmd, fields)
        self.protocol.send_msg(msg_type, msg)
        return RowResult(self)

    @catch_network_exception
    def read_row(self, result: RowResult) -> Optional[MessageType]:
        """Read row.

        Args:
            result (:class:`mysqlx.RowResult`): The result object.
        """
        return self.protocol.read_row(result)

    @catch_network_exception
    def close_result(self, result: Result) -> None:
        """Close result.

        Args:
            result (:class:`mysqlx.Result`): The result object.
        """
        self.protocol.close_result(result)

    @catch_network_exception
    def get_column_metadata(self, result: Result) -> List[ColumnType]:
        """Get column metadata.

        Args:
            result (:class:`mysqlx.Result`): The result object.
        """
        return self.protocol.get_column_metadata(result)

    def get_next_statement_id(self) -> int:
        """Returns the next statement ID.

        Returns:
            int: A statement ID.

        .. versionadded:: 8.0.16
        """
        self._stmt_counter += 1
        return self._stmt_counter

    def is_open(self) -> bool:
        """Check if connection is open.

        Returns:
            bool: `True` if connection is open.
        """
        return self.stream.is_open()

    def set_server_disconnected(self, reason: Union[str, Tuple[str, int]]) -> None:
        """Set the disconnection message from the server.

        Args:
            reason (str): disconnection reason from the server.
        """
        self._server_disconnected = True
        self._server_disconnected_reason = reason

    def is_server_disconnected(self) -> bool:
        """Verify if the session has been disconnect from the server.

        Returns:
            bool: `True` if the connection has been closed from the server
                  otherwise `False`.
        """
        return self._server_disconnected

    def get_disconnected_reason(self) -> Optional[Union[str, Tuple[str, int]]]:
        """Get the disconnection message sent by the server.

        Returns:
            string: disconnection reason from the server.
        """
        return self._server_disconnected_reason

    def disconnect(self) -> None:
        """Disconnect from server."""
        if not self.is_open():
            return
        self.stream.close()

    def close_session(self) -> None:
        """Close a sucessfully authenticated session."""
        if not self.is_open():
            return

        try:
            # Fetch any active result
            self.fetch_active_result()
            # Deallocate all prepared statements
            if self._prepared_stmt_supported:
                for stmt_id in self._prepared_stmt_ids:
                    self.protocol.send_prepare_deallocate(stmt_id)
                self._stmt_counter = 0
            # Send session close
            self.protocol.send_close()
            self.protocol.read_ok()
        except (InterfaceError, OperationalError, OSError) as err:
            logger.warning(
                "Warning: An error occurred while attempting to close the "
                "connection: %s",
                err,
            )
        finally:
            # The remote connection with the server has been lost,
            # close the connection locally.
            self.stream.close()

    def reset_session(self) -> None:
        """Reset a sucessfully authenticated session."""
        if not self.is_open():
            return
        if self._active_result is not None:
            self._active_result.fetch_all()
        try:
            self.keep_open = self.protocol.send_reset(self.keep_open)
        except (InterfaceError, OperationalError) as err:
            logger.warning(
                "Warning: An error occurred while attempting to reset the "
                "session: %s",
                err,
            )

    def close_connection(self) -> None:
        """Announce to the server that the client wants to close the
        connection. Discards any session state of the server.
        """
        if not self.is_open():
            return
        if self._active_result is not None:
            self._active_result.fetch_all()
        self.protocol.send_connection_close()
        self.protocol.read_ok()
        self.stream.close()


class PooledConnection(Connection):
    """Class to hold :class:`Connection` instances in a pool.

    PooledConnection is used by :class:`ConnectionPool` to facilitate the
    connection to return to the pool once is not required, more specifically
    once the close_session() method is invoked. It works like a normal
    Connection except for methods like close() and sql().

    The close_session() method will add the connection back to the pool rather
    than disconnecting from the MySQL server.

    The sql() method is used to execute sql statements.

    Args:
        pool (ConnectionPool): The pool where this connection must return.

    .. versionadded:: 8.0.13
    """

    def __init__(self, pool: ConnectionPool) -> None:
        if not isinstance(pool, ConnectionPool):
            raise AttributeError("pool should be a ConnectionPool object")
        super().__init__(pool.cnx_config)
        self.pool: ConnectionPool = pool
        self.host: str = pool.cnx_config["host"]
        self.port: int = pool.cnx_config["port"]

    def close_connection(self) -> None:
        """Closes the connection.

        This method closes the socket.
        """
        super().close_session()

    def close_session(self) -> None:
        """Do not close, but add connection back to pool.

        The close_session() method does not close the connection with the
        MySQL server. The connection is added back to the pool so it
        can be reused.

        When the pool is configured to reset the session, the session
        state will be cleared by re-authenticating the user once the connection
        is get from the pool.
        """
        self.pool.add_connection(self)

    def reconnect(self) -> None:
        """Reconnect this connection."""
        if self._active_result is not None:
            self._active_result.fetch_all()
        self._authenticate()

    def reset(self) -> None:
        """Reset the connection.

        Resets the connection by re-authenticate.
        """
        self.reconnect()

    def sql(self, sql: str) -> SqlStatement:
        """Creates a :class:`mysqlx.SqlStatement` object to allow running the
        SQL statement on the target MySQL Server.

        Args:
            sql (string): The SQL statement to be executed.

        Returns:
            mysqlx.SqlStatement: SqlStatement object.
        """
        return SqlStatement(self, sql)


class ConnectionPool(queue.Queue):
    """This class represents a pool of connections.

    Initializes the Pool with the given name and settings.

    Args:
        name (str): The name of the pool, used to track a single pool per
                    combination of host and user.
        **kwargs:
            max_size (int): The maximun number of connections to hold in
                            the pool.
            reset_session (bool): If the connection should be reseted when
                                  is taken from the pool.
            max_idle_time (int): The maximum number of milliseconds to allow
                                 a connection to be idle in the queue before
                                 being closed. Zero value means infinite.
            queue_timeout (int): The maximum number of milliseconds a
                                 request will wait for a connection to
                                 become available. A zero value means
                                 infinite.
            priority (int): The router priority, to choose this pool over
                            other with lower priority.

    Raises:
        :class:`mysqlx.PoolError` on errors.

    .. versionadded:: 8.0.13
    """

    def __init__(self, name: str, **kwargs: Any) -> None:
        self._set_pool_name(name)
        self._open_sessions: int = 0
        self._connections_openned: List[PooledConnection] = []
        self._available: bool = True
        self._timeout: int = 0
        self._timeout_stamp: datetime = datetime.now()
        self.pool_max_size: int = kwargs.get("max_size", 25)
        # Can't invoke super due to Queue not is a new-style class
        queue.Queue.__init__(self, self.pool_max_size)
        self.reset_session: bool = kwargs.get("reset_session", True)
        self.max_idle_time: int = kwargs.get("max_idle_time", 25)
        self.settings: Dict[str, Any] = kwargs
        self.queue_timeout: int = kwargs.get("queue_timeout", 25)
        self.priority: int = kwargs.get("priority", 0)
        self.cnx_config: Dict[str, Any] = kwargs
        self.host: str = kwargs["host"]
        self.port: int = kwargs["port"]

    def _set_pool_name(self, pool_name: str) -> None:
        r"""Set the name of the pool.

        This method checks the validity and sets the name of the pool.

        Args:
            pool_name (str): The pool name.

        Raises:
            AttributeError: If the pool_name contains illegal characters
                            ([^a-zA-Z0-9._\-*$#]) or is longer than
                            connection._CNX_POOL_MAX_NAME_SIZE.
        """
        if _CNX_POOL_NAME_REGEX.search(pool_name):
            raise AttributeError(f"Pool name '{pool_name}' contains illegal characters")
        if len(pool_name) > _CNX_POOL_MAX_NAME_SIZE:
            raise AttributeError(f"Pool name '{pool_name}' is too long")
        self.name = pool_name

    @property
    def open_connections(self) -> int:
        """Returns the number of open connections that can return to this pool."""
        return len(self._connections_openned)

    def remove_connection(self, cnx: Optional[PooledConnection] = None) -> None:
        """Removes a connection from this pool.

        Args:
            cnx (PooledConnection): The connection object.
        """
        self._connections_openned.remove(cnx)

    def remove_connections(self) -> None:
        """Removes all the connections from the pool."""
        while self.qsize() > 0:
            try:
                cnx = self.get(block=True, timeout=self.queue_timeout)
            except queue.Empty:
                pass
            else:
                try:
                    cnx.close_connection()
                except (RuntimeError, OSError, InterfaceError):
                    pass
                finally:
                    self.remove_connection(cnx)

    def add_connection(self, cnx: Optional[PooledConnection] = None) -> None:
        """Adds a connection to this pool.

        This method instantiates a Connection using the configuration passed
        when initializing the ConnectionPool instance or using the set_config()
        method.
        If cnx is a Connection instance, it will be added to the queue.

        Args:
            cnx (PooledConnection): The connection object.

        Raises:
            PoolError: If no configuration is set, if no more connection can
                       be added (maximum reached) or if the connection can not
                       be instantiated.
        """
        if not self.cnx_config:
            raise PoolError("Connection configuration not available")

        if self.full():
            raise PoolError("Failed adding connection; queue is full")

        if not cnx:
            cnx = PooledConnection(self)
            # mysqlx_wait_timeout is only available on MySQL 8
            ver = cnx.sql(_SELECT_VERSION_QUERY).execute().fetch_all()[0][0]
            if tuple(int(n) for n in ver.split("-")[0].split(".")) > (
                8,
                0,
                10,
            ):
                cnx.sql(f"set mysqlx_wait_timeout = {self.max_idle_time}").execute()
            self._connections_openned.append(cnx)
        else:
            if not isinstance(cnx, PooledConnection):
                raise PoolError("Connection instance not subclass of PooledSession")
            if cnx.is_server_disconnected():
                self.remove_connections()
                cnx.close()  # type: ignore[attr-defined]

        self.queue_connection(cnx)

    def queue_connection(self, cnx: PooledConnection) -> None:
        """Put connection back in the queue:

        This method is putting a connection back in the queue.
        It will not acquire a lock as the methods using _queue_connection() will
        have it set.

        Args:
            PooledConnection: The connection object.

        Raises:
            PoolError: On errors.
        """
        if not isinstance(cnx, PooledConnection):
            raise PoolError("Connection instance not subclass of PooledSession.")

        # Reset the connection
        if self.reset_session:
            cnx.reset_session()
        try:
            self.put(cnx, block=False)
        except queue.Full as err:
            raise PoolError("Failed adding connection; queue is full") from err

    def track_connection(self, connection: PooledConnection) -> None:
        """Tracks connection in order of close it when client.close() is invoke."""
        self._connections_openned.append(connection)

    def __str__(self) -> str:
        return self.name

    def available(self) -> bool:
        """Returns if this pool is available for pool connections from it.

        Returns:
            bool: True if this pool is available else False.
        .. versionadded:: 8.0.20
        """
        return self._available

    def set_unavailable(self, time_out: int = -1) -> None:
        """Sets this pool unavailable for a period of time (in seconds).

        .. versionadded:: 8.0.20
        """
        if self._available:
            logger.warning(
                "ConnectionPool.set_unavailable pool: %s time_out: %s",
                self,
                time_out,
            )
            self._available = False
            self._timeout_stamp = datetime.now()
            self._timeout = time_out

    def set_available(self) -> None:
        """Sets this pool available for pool connections from it.

        .. versionadded:: 8.0.20
        """
        self._available = True
        self._timeout_stamp = datetime.now()

    def get_timeout_stamp(self) -> Tuple[int, datetime]:
        """Returns the penalized time (timeout) and the time at the penalty.

        Returns:
            tuple: penalty seconds (int), timestamp at penalty (datetime object)
        .. versionadded:: 8.0.20
        """
        return (self._timeout, self._timeout_stamp)

    def close(self) -> None:
        """Empty this ConnectionPool."""
        for cnx in self._connections_openned:
            cnx.close_connection()


class PoolsManager:
    """Manages a pool of connections for a host or hosts in routers.

    This class handles all the pools of Connections.

    .. versionadded:: 8.0.13
    """

    __instance: PoolsManager = None
    __pools: Dict[str, Any] = {}

    def __new__(cls) -> PoolsManager:
        if PoolsManager.__instance is None:
            PoolsManager.__instance = object.__new__(cls)
            PoolsManager.__pools = {}
        return PoolsManager.__instance

    def _pool_exists(self, client_id: str, pool_name: str) -> bool:
        """Verifies if a pool exists with the given name.

        Args:
            client_id (str): The client id.
            pool_name (str): The name of the pool.

        Returns:
            bool: Returns `True` if the pool exists otherwise `False`.
        """
        pools = self.__pools.get(client_id, [])
        for pool in pools:
            if pool.name == pool_name:
                return True
        return False

    def _get_pools(self, settings: Dict[str, Any]) -> List:
        """Retrieves a list of pools that shares the given settings.

        Args:
            settings (dict): the configuration of the pool.

        Returns:
            list: A list of pools that shares the given settings.
        """
        available_pools = []
        pool_names = []
        connections_settings = self._get_connections_settings(settings)

        # Generate the names of the pools this settings can connect to
        for router_name, _ in connections_settings:
            pool_names.append(router_name)

        # Generate the names of the pools this settings can connect to
        for pool in self.__pools.get(settings.get("client_id", "No id"), []):
            if pool.name in pool_names:
                available_pools.append(pool)
        return available_pools

    @staticmethod
    def _get_connections_settings(
        settings: Dict[str, Any]
    ) -> List[Tuple[str, Dict[str, Any]]]:
        """Generates a list of separated connection settings for each host.

        Gets a list of connection settings for each host or router found in the
        given settings.

        Args:
            settings (dict): The configuration for the connections.

        Returns:
            list: A list of connections settings
        """
        pool_settings = settings.copy()
        routers = pool_settings.get("routers", [])
        connections_settings = []
        if "routers" in pool_settings:
            pool_settings.pop("routers")
        if "host" in pool_settings and "port" in pool_settings:
            routers.append(
                {
                    "priority": 100,
                    "weight": 0,
                    "host": pool_settings["host"],
                    "port": pool_settings["port"],
                }
            )
        # Order routers
        routers.sort(key=lambda x: (x["priority"], -x.get("weight", 0)))
        for router in routers:
            connection_settings = pool_settings.copy()
            connection_settings["host"] = router["host"]
            connection_settings["port"] = router["port"]
            connection_settings["priority"] = router["priority"]
            connection_settings["weight"] = router.get("weight", 0)
            connections_settings.append(
                (
                    generate_pool_name(**connection_settings),
                    connection_settings,
                )
            )
        return connections_settings

    def create_pool(self, cnx_settings: Dict[str, Any]) -> None:
        """Creates a `ConnectionPool` instance to hold the connections.

        Creates a `ConnectionPool` instance to hold the connections only if
        no other pool exists with the same configuration.

        Args:
            cnx_settings (dict): The configuration for the connections.
        """
        connections_settings = self._get_connections_settings(cnx_settings)

        # Subscribe client if it does not exists
        if cnx_settings.get("client_id", "No id") not in self.__pools:
            self.__pools[cnx_settings.get("client_id", "No id")] = []

        # Create a pool for each router
        for router_name, settings in connections_settings:
            if self._pool_exists(cnx_settings.get("client_id", "No id"), router_name):
                continue
            pool = self.__pools.get(cnx_settings.get("client_id", "No id"), [])
            pool.append(ConnectionPool(router_name, **settings))

    @staticmethod
    def _get_random_pool(pool_list: List[ConnectionPool]) -> ConnectionPool:
        """Get a random router from the group with the given priority.

        Returns:
            Router: a random router.

        .. versionadded:: 8.0.20
        """
        if not pool_list:
            return None
        if len(pool_list) == 1:
            return pool_list[0]

        last = len(pool_list) - 1
        index = random.randint(0, last)
        return pool_list[index]

    @staticmethod
    def _get_sublist(
        pools: List[ConnectionPool], index: int, cur_priority: int
    ) -> List[ConnectionPool]:
        sublist = []
        next_priority = None
        while index < len(pools):
            next_priority = pools[index].priority
            if cur_priority == next_priority and pools[index].available():
                sublist.append(pools[index])
            elif cur_priority != next_priority:
                break
            index += 1
        return sublist

    def _get_next_pool(
        self, pools: List[ConnectionPool], cur_priority: int
    ) -> ConnectionPool:
        index = 0
        for pool in pools:
            if pool.available() and cur_priority == pool.priority:
                break
            index += 1
        subpool: List = []
        while not subpool and index < len(pools):
            subpool = self._get_sublist(pools, index, cur_priority)
            index += 1
        return self._get_random_pool(subpool)

    @staticmethod
    def _get_next_priority(
        pools: List[ConnectionPool], cur_priority: Optional[int] = None
    ) -> int:
        if cur_priority is None and pools:
            return pools[0].priority
        # find the first pool that does not share the same priority
        for t_pool in pools:
            if t_pool.available():
                cur_priority = t_pool.priority
                return cur_priority
        return pools[0].priority

    def _check_unavailable_pools(
        self, settings: Dict[str, Any], revive: Optional[bool] = None
    ) -> None:
        pools = self._get_pools(settings)
        for pool in pools:
            if pool.available():
                continue
            timeout, timeout_stamp = pool.get_timeout_stamp()
            if revive:
                timeout = revive
            if datetime.now() > (timeout_stamp + timedelta(seconds=timeout)):
                pool.set_available()

    def get_connection(self, settings: Dict[str, Any]) -> PooledConnection:
        """Get a connection from the pool.

        This method returns an `PooledConnection` instance which has a reference
        to the pool that created it, and can be used as a normal Connection.

        When the MySQL connection is not connected, a reconnect is attempted.

        Raises:
            :class:`PoolError`: On errors.

        Returns:
            PooledConnection: A pooled connection object.
        """

        def set_mysqlx_wait_timeout(cnx: PooledConnection) -> None:
            ver = cnx.sql(_SELECT_VERSION_QUERY).execute().fetch_all()[0][0]
            # mysqlx_wait_timeout is only available on MySQL 8
            if tuple(int(n) for n in ver.split("-")[0].split(".")) > (
                8,
                0,
                10,
            ):
                cnx.sql(f"set mysqlx_wait_timeout = {pool.max_idle_time}").execute()

        pools = self._get_pools(settings)
        cur_priority = settings.get("cur_priority", None)
        error_list = []
        self._check_unavailable_pools(settings)
        cur_priority = self._get_next_priority(pools, cur_priority)
        if cur_priority is None:
            raise PoolError(
                "Unable to connect to any of the target hosts. No pool is available"
            )
        settings["cur_priority"] = cur_priority
        pool = self._get_next_pool(pools, cur_priority)
        lock = threading.RLock()
        while pool is not None:
            try:
                # Check connections aviability in this pool
                if pool.qsize() > 0:
                    # We have connections in pool, try to return a working one
                    with lock:
                        try:
                            cnx = pool.get(block=True, timeout=pool.queue_timeout)
                        except queue.Empty:
                            raise PoolError(
                                "Failed getting connection; pool exhausted"
                            ) from None
                        try:
                            if cnx.is_server_disconnected():
                                pool.remove_connections()
                            # Only reset the connection by re-authentification
                            # if the connection was unable to keep open by the
                            # server
                            if not cnx.keep_open:
                                cnx.reset()
                            set_mysqlx_wait_timeout(cnx)
                        except (RuntimeError, OSError, InterfaceError):
                            # Unable to reset connection, close and remove
                            try:
                                cnx.close_connection()
                            except (RuntimeError, OSError, InterfaceError):
                                pass
                            finally:
                                pool.remove_connection(cnx)
                            # By WL#13222 all idle sessions that connect to the
                            # same endpoint should be removed from the pool.
                            while pool.qsize() > 0:
                                try:
                                    cnx = pool.get(
                                        block=True, timeout=pool.queue_timeout
                                    )
                                except queue.Empty:
                                    pass
                                else:
                                    try:
                                        cnx.close_connection()
                                    except (RuntimeError, OSError, InterfaceError):
                                        pass
                                    finally:
                                        pool.remove_connection(cnx)
                            # Connection was closed by the server, create new
                            try:
                                cnx = PooledConnection(pool)
                                pool.track_connection(cnx)
                                cnx.connect()
                                set_mysqlx_wait_timeout(cnx)
                            except (RuntimeError, OSError, InterfaceError):
                                pass
                            finally:
                                # Server must be down, take down idle
                                # connections from this pool
                                while pool.qsize() > 0:
                                    try:
                                        cnx = pool.get(
                                            block=True,
                                            timeout=pool.queue_timeout,
                                        )
                                        cnx.close_connection()
                                        pool.remove_connection(cnx)
                                    except (RuntimeError, OSError, InterfaceError):
                                        pass
                        return cnx
                elif pool.open_connections < pool.pool_max_size:
                    # No connections in pool, but we can open a new one
                    cnx = PooledConnection(pool)
                    pool.track_connection(cnx)
                    cnx.connect()
                    set_mysqlx_wait_timeout(cnx)
                    return cnx
                else:
                    # Pool is exaust so the client needs to wait
                    with lock:
                        try:
                            cnx = pool.get(block=True, timeout=pool.queue_timeout)
                            cnx.reset()
                            set_mysqlx_wait_timeout(cnx)
                            return cnx
                        except queue.Empty:
                            raise PoolError("pool max size has been reached") from None
            except (InterfaceError, TimeoutError, PoolError) as err:
                error_list.append(f"pool: {pool} error: {err}")
                if isinstance(err, PoolError):
                    # Pool can be exhaust now but can be ready again in no time,
                    # e.g a connection is returned to the pool.
                    pool.set_unavailable(2)
                else:
                    self.set_pool_unavailable(pool, err)

                self._check_unavailable_pools(settings)
                # Try next pool with the same priority
                pool = self._get_next_pool(pools, cur_priority)

                if pool is None:
                    cur_priority = self._get_next_priority(pools, cur_priority)
                    settings["cur_priority"] = cur_priority
                    pool = self._get_next_pool(pools, cur_priority)
                    if pool is None:
                        msg = "\n  ".join(error_list)
                        raise PoolError(
                            "Unable to connect to any of the target hosts: "
                            f"[\n  {msg}\n]"
                        ) from err
                continue

        raise PoolError("Unable to connect to any of the target hosts")

    def close_pool(self, cnx_settings: Dict[str, Any]) -> int:
        """Closes the connections in the pools

        Returns:
            int: The number of closed pools
        """
        pools = self._get_pools(cnx_settings)
        for pool in pools:
            pool.close()
            # Remove the pool
            if cnx_settings.get("client_id", None) is not None:
                client_pools = self.__pools.get(cnx_settings.get("client_id"))
                if pool in client_pools:
                    client_pools.remove(pool)
        return len(pools)

    @staticmethod
    def set_pool_unavailable(
        pool: ConnectionPool, err: Union[InterfaceError, TimeoutError]
    ) -> None:
        """Sets a pool as unavailable.

        The time a pool is set unavailable depends on the given error message
        or the error number.

        Args:
            pool (ConnectionPool): The pool to set unavailable.
            err (Exception): The raised exception raised by a connection belonging
                             to the pool.
        """
        penalty = None
        try:
            err_no = err.errno
            penalty = _TIMEOUT_PENALTIES_BY_ERR_NO[err_no]
        except (AttributeError, KeyError):
            pass
        if not penalty:
            err_msg = err.msg
            for key, value in _TIMEOUT_PENALTIES.items():
                if key in err_msg:
                    penalty = value
        if penalty:
            pool.set_unavailable(penalty)
        else:
            # Other errors are severe punished
            pool.set_unavailable(100000)


class Session:
    """Enables interaction with a X Protocol enabled MySQL Product.

    The functionality includes:

    - Accessing available schemas.
    - Schema management operations.
    - Retrieval of connection information.

    Args:
        settings (dict): Connection data used to connect to the database.
    """

    def __init__(self, settings: Dict[str, Any]) -> None:
        self.use_pure: bool = settings.get("use-pure", Protobuf.use_pure)
        self._settings: Dict[str, Any] = settings

        # Check for DNS SRV
        if settings.get("host") and settings.get("dns-srv"):
            if not HAVE_DNSPYTHON:
                raise InterfaceError(
                    "MySQL host configuration requested DNS "
                    "SRV. This requires the Python dnspython "
                    "module. Please refer to documentation"
                )
            try:
                srv_records = dns.resolver.query(settings["host"], "SRV")
            except dns.exception.DNSException as err:
                raise InterfaceError(
                    f"Unable to locate any hosts for '{settings['host']}'"
                ) from err
            self._settings["routers"] = []
            for srv in srv_records:
                self._settings["routers"].append(
                    {
                        "host": srv.target.to_text(omit_final_dot=True),
                        "port": srv.port,
                        "priority": srv.priority,
                        "weight": srv.weight,
                    }
                )

        if (
            "connection-attributes" not in self._settings
            or self._settings["connection-attributes"] is not False
        ):
            self._settings["attributes"] = {}
            self._init_attributes()

        if "pooling" in settings and settings["pooling"]:
            # Create pool and retrieve a Connection instance
            PoolsManager().create_pool(settings)
            self._connection: Connection = PoolsManager().get_connection(settings)
            if self._connection is None:
                raise PoolError("Connection could not be retrieved from pool")
        else:
            self._connection = Connection(self._settings)
            self._connection.connect()
        # Set default schema
        schema = self._settings.get("schema")
        if schema:
            try:
                self.sql(f"USE {quote_identifier(schema)}").execute()
            except OperationalError as err:
                # Access denied for user will raise err.errno = 1044
                errmsg = (
                    err.msg
                    if err.errno == 1044
                    else f"Default schema '{schema}' does not exists"
                )
                raise InterfaceError(errmsg, err.errno) from err

    def __enter__(self) -> Session:
        return self

    def __exit__(
        self,
        exc_type: Type[BaseException],
        exc_value: BaseException,
        traceback: TracebackType,
    ) -> None:
        self.close()

    def _init_attributes(self) -> None:
        """Setup default and user defined connection-attributes."""
        if os.name == "nt":
            if "64" in platform.architecture()[0]:
                platform_arch: Union[str, Tuple[str, str]] = "x86_64"
            elif "32" in platform.architecture()[0]:
                platform_arch = "i386"
            else:
                platform_arch = platform.architecture()
            os_ver = f"Windows-{platform.win32_ver()[1]}"
        else:
            platform_arch = platform.machine()
            if platform.system() == "Darwin":
                os_ver = f"macOS-{platform.mac_ver()[0]}"
            else:
                os_ver = "-".join(linux_distribution()[0:2])

        license_chunks = LICENSE.split(" ")
        if license_chunks[0] == "GPLv2":
            client_license = "GPL-2.0"
        else:
            client_license = "Commercial"

        default_attributes = {
            # Process id
            "_pid": str(os.getpid()),
            # Platform architecture
            "_platform": platform_arch,
            # OS version
            "_os": os_ver,
            # Hostname of the local machine
            "_source_host": socket.gethostname(),
            # Client's name
            "_client_name": "mysql-connector-python",
            # Client's version
            "_client_version": ".".join([str(x) for x in VERSION[0:3]]),
            # Client's License identifier
            "_client_license": client_license,
        }
        self._settings["attributes"].update(default_attributes)

        if "connection-attributes" in self._settings:
            for attr_name in self._settings["connection-attributes"]:
                attr_value = self._settings["connection-attributes"][attr_name]
                # Validate name type
                if not isinstance(attr_name, str):
                    raise InterfaceError(
                        f"Attribute name '{attr_name}' must be a string type"
                    )
                # Validate attribute name limit 32 characters
                if len(attr_name) > 32:
                    raise InterfaceError(
                        f"Attribute name '{attr_name}' exceeds 32 characters "
                        "limit size"
                    )
                # Validate names in connection-attributes cannot start with "_"
                if attr_name.startswith("_"):
                    raise InterfaceError(
                        "Key names in 'session-connect-attributes' cannot "
                        f"start with '_', found: {attr_name}"
                    )
                # Validate value type
                if not isinstance(attr_value, str):
                    raise InterfaceError(
                        f"Attribute name '{attr_name}' value '{attr_value}' "
                        " must be a string type"
                    )

                # Validate attribute value limit 1024 characters
                if len(attr_value) > 1024:
                    raise InterfaceError(
                        f"Attribute name '{attr_name}' value: '{attr_value}' "
                        "exceeds 1024 characters limit size"
                    )

                self._settings["attributes"][attr_name] = attr_value

    @property
    def use_pure(self) -> bool:
        """bool: `True` to use pure Python Protobuf implementation."""
        return Protobuf.use_pure

    @use_pure.setter
    def use_pure(self, value: bool) -> None:
        if not isinstance(value, bool):
            raise ProgrammingError("'use_pure' option should be True or False")
        Protobuf.set_use_pure(value)

    def is_open(self) -> bool:
        """Returns `True` if the session is open.

        Returns:
            bool: Returns `True` if the session is open.
        """
        return self._connection.stream.is_open()

    def sql(self, sql: str) -> SqlStatement:
        """Creates a :class:`mysqlx.SqlStatement` object to allow running the
        SQL statement on the target MySQL Server.

        Args:
            sql (string): The SQL statement to be executed.

        Returns:
            mysqlx.SqlStatement: SqlStatement object.
        """
        return SqlStatement(self._connection, sql)

    def get_connection(self) -> Connection:
        """Returns the underlying connection.

        Returns:
            mysqlx.connection.Connection: The connection object.
        """
        return self._connection

    def get_schemas(self) -> List[str]:
        """Returns the list of schemas in the current session.

        Returns:
            `list`: The list of schemas in the current session.

        .. versionadded:: 8.0.12
        """
        result = self.sql("SHOW DATABASES").execute()
        return [row[0] for row in result.fetch_all()]

    def get_schema(self, name: str) -> Schema:
        """Retrieves a Schema object from the current session by it's name.

        Args:
            name (string): The name of the Schema object to be retrieved.

        Returns:
            mysqlx.Schema: The Schema object with the given name.
        """
        return Schema(self, name)

    def get_default_schema(self) -> Optional[Schema]:
        """Retrieves a Schema object from the current session by the schema
        name configured in the connection settings.

        Returns:
            mysqlx.Schema: The Schema object with the given name at connect
                           time.
            None: In case the default schema was not provided with the
                  initialization data.

        Raises:
            :class:`mysqlx.ProgrammingError`: If the provided default schema
                                              does not exists.
        """
        schema = self._connection.settings.get("schema")
        if schema:
            res = (
                self.sql(_SELECT_SCHEMA_NAME_QUERY.format(escape(schema)))
                .execute()
                .fetch_all()
            )
            try:
                if res[0][0] == schema:  # type: ignore[index]
                    return Schema(self, schema)
            except IndexError:
                raise ProgrammingError(
                    f"Default schema '{schema}' does not exists"
                ) from None
        return None

    def drop_schema(self, name: str) -> None:
        """Drops the schema with the specified name.

        Args:
            name (string): The name of the Schema object to be retrieved.
        """
        self._connection.execute_nonquery(
            "sql", _DROP_DATABASE_QUERY.format(quote_identifier(name)), True
        )

    def create_schema(self, name: str) -> Schema:
        """Creates a schema on the database and returns the corresponding
        object.

        Args:
            name (string): A string value indicating the schema name.
        """
        self._connection.execute_nonquery(
            "sql", _CREATE_DATABASE_QUERY.format(quote_identifier(name)), True
        )
        return Schema(self, name)

    def start_transaction(self) -> None:
        """Starts a transaction context on the server."""
        self._connection.execute_nonquery("sql", "START TRANSACTION", True)

    def commit(self) -> None:
        """Commits all the operations executed after a call to
        startTransaction().
        """
        self._connection.execute_nonquery("sql", "COMMIT", True)

    def rollback(self) -> None:
        """Discards all the operations executed after a call to
        startTransaction().
        """
        self._connection.execute_nonquery("sql", "ROLLBACK", True)

    def set_savepoint(self, name: Optional[str] = None) -> str:
        """Creates a transaction savepoint.

        If a name is not provided, one will be generated using the uuid.uuid1()
        function.

        Args:
            name (Optional[string]): The savepoint name.

        Returns:
            string: The savepoint name.
        """
        if name is None:
            name = f"{uuid.uuid1()}"
        elif not isinstance(name, str) or len(name.strip()) == 0:
            raise ProgrammingError("Invalid SAVEPOINT name")
        self._connection.execute_nonquery(
            "sql", f"SAVEPOINT {quote_identifier(name)}", True
        )
        return name

    def rollback_to(self, name: str) -> None:
        """Rollback to a transaction savepoint with the given name.

        Args:
            name (string): The savepoint name.
        """
        if not isinstance(name, str) or len(name.strip()) == 0:
            raise ProgrammingError("Invalid SAVEPOINT name")
        self._connection.execute_nonquery(
            "sql",
            f"ROLLBACK TO SAVEPOINT {quote_identifier(name)}",
            True,
        )

    def release_savepoint(self, name: str) -> None:
        """Release a transaction savepoint with the given name.

        Args:
            name (string): The savepoint name.
        """
        if not isinstance(name, str) or len(name.strip()) == 0:
            raise ProgrammingError("Invalid SAVEPOINT name")
        self._connection.execute_nonquery(
            "sql",
            f"RELEASE SAVEPOINT {quote_identifier(name)}",
            True,
        )

    def close(self) -> None:
        """Closes the session."""
        self._connection.close_session()
        # Set an unconnected connection
        self._connection = Connection(self._settings)

    def close_connections(self) -> None:
        """Closes all underliying connections as pooled connections"""
        self._connection.close_connection()


class Client:
    """Class defining a client, it stores a connection configuration.

    Args:
        connection_dict (dict): The connection information to connect to a
                                MySQL server.
        options_dict (dict): The options to configure this client.

    .. versionadded:: 8.0.13
    """

    def __init__(
        self,
        connection_dict: Dict[str, Any],
        options_dict: Optional[Dict[str, Any]] = None,
    ) -> None:
        self.settings: Dict[str, Any] = connection_dict
        if options_dict is None:
            options_dict = {}

        self.sessions: List[Session] = []
        self.client_id: uuid.UUID = uuid.uuid4()

        self._set_pool_size(options_dict.get("max_size", 25))
        self._set_max_idle_time(options_dict.get("max_idle_time", 0))
        self._set_queue_timeout(options_dict.get("queue_timeout", 0))
        self._set_pool_enabled(options_dict.get("enabled", True))

        self.settings["pooling"] = self.pooling_enabled
        self.settings["max_size"] = self.max_size
        self.settings["client_id"] = self.client_id

    def __enter__(self) -> Client:
        return self

    def __exit__(
        self,
        exc_type: Type[BaseException],
        exc_value: BaseException,
        traceback: TracebackType,
    ) -> None:
        self.close()

    def _set_pool_size(self, pool_size: int) -> None:
        """Set the size of the pool.

        This method sets the size of the pool but it will not resize the pool.

        Args:
            pool_size (int): An integer equal or greater than 0 indicating
                             the pool size.

        Raises:
            :class:`AttributeError`: If the pool_size value is not an integer
                                     greater or equal to 0.
        """
        if (
            isinstance(pool_size, bool)
            or not isinstance(pool_size, int)
            or not pool_size > 0
        ):
            raise AttributeError(
                "Pool max_size value must be an integer greater than 0, the "
                f"given value {pool_size} is not valid"
            )

        self.max_size = _CNX_POOL_MAXSIZE if pool_size == 0 else pool_size

    def _set_max_idle_time(self, max_idle_time: int) -> None:
        """Set the max idle time.

        This method sets the max idle time.

        Args:
            max_idle_time (int): An integer equal or greater than 0 indicating
                                 the max idle time.

        Raises:
            :class:`AttributeError`: If the max_idle_time value is not an
                                     integer greater or equal to 0.
        """
        if (
            isinstance(max_idle_time, bool)
            or not isinstance(max_idle_time, int)
            or not max_idle_time > -1
        ):
            raise AttributeError(
                "Connection max_idle_time value must be an integer greater or "
                f"equal to 0, the given value {max_idle_time} is not valid"
            )

        self.max_idle_time = max_idle_time
        self.settings["max_idle_time"] = (
            _CNX_POOL_MAX_IDLE_TIME if max_idle_time == 0 else int(max_idle_time / 1000)
        )

    def _set_pool_enabled(self, enabled: bool) -> None:
        """Set if the pool is enabled.

        This method sets if the pool is enabled.

        Args:
            enabled (bool): True if to enabling the pool.

        Raises:
            :class:`AttributeError`: If the value of enabled is not a bool type.
        """
        if not isinstance(enabled, bool):
            raise AttributeError("The enabled value should be True or False.")
        self.pooling_enabled = enabled

    def _set_queue_timeout(self, queue_timeout: int) -> None:
        """Set the queue timeout.

        This method sets the queue timeout.

        Args:
            queue_timeout (int): An integer equal or greater than 0 indicating
                                 the queue timeout.

        Raises:
            :class:`AttributeError`: If the queue_timeout value is not an
                                     integer greater or equal to 0.
        """
        if (
            isinstance(queue_timeout, bool)
            or not isinstance(queue_timeout, int)
            or not queue_timeout > -1
        ):
            raise AttributeError(
                "Connection queue_timeout value must be an integer greater or "
                f"equal to 0, the given value {queue_timeout} is not valid"
            )

        self.queue_timeout = queue_timeout
        self.settings["queue_timeout"] = (
            _CNX_POOL_QUEUE_TIMEOUT if queue_timeout == 0 else int(queue_timeout / 1000)
        )
        # To avoid a connection stall waiting for the server, if the
        # connect-timeout is not given, use the queue_timeout
        if "connect-timeout" not in self.settings:
            self.settings["connect-timeout"] = self.queue_timeout

    def get_session(self) -> Session:
        """Creates a Session instance using the provided connection data.

        Returns:
            Session: Session object.
        """
        session = Session(self.settings)
        self.sessions.append(session)
        return session

    def close(self) -> None:
        """Closes the sessions opened by this client."""
        PoolsManager().close_pool(self.settings)
        for session in self.sessions:
            session.close_connections()


def _parse_address_list(path: str) -> Union[Dict[str, List[Dict]], Dict]:
    """Parses a list of host, port pairs.

    Args:
        path: String containing a list of routers or just router

    Returns:
        Returns a dict with parsed values of host, port and priority if
        specified.
    """
    path = path.replace(" ", "")
    array = (
        not ("," not in path and path.count(":") > 1 and path.count("[") == 1)
        and path.startswith("[")
        and path.endswith("]")
    )

    routers = []
    address_list = _SPLIT_RE.split(path[1:-1] if array else path)
    priority_count = 0
    for address in address_list:
        router: Dict = {}

        match = _PRIORITY_RE.match(address)
        if match:
            address = match.group(1)
            router["priority"] = int(match.group(2))
            priority_count += 1
        else:
            match = _ROUTER_RE.match(address)
            if match:
                address = match.group(1)
                router["priority"] = 100

        match = urlparse(f"//{address}")
        if not match.hostname:
            raise InterfaceError(f"Invalid address: {address}")

        try:
            router.update(host=match.hostname, port=match.port)
        except ValueError as err:
            raise ProgrammingError(f"Invalid URI: {err}", 4002) from err

        routers.append(router)

    if 0 < priority_count < len(address_list):
        raise ProgrammingError(
            "You must either assign no priority to any of the routers or give "
            "a priority for every router",
            4000,
        )

    return {"routers": routers} if array else routers[0]


def _parse_connection_uri(uri: str) -> Dict[str, Any]:
    """Parses the connection string and returns a dictionary with the
    connection settings.

    Args:
        uri: mysqlx URI scheme to connect to a MySQL server/farm.

    Returns:
        Returns a dict with parsed values of credentials and address of the
        MySQL server/farm.

    Raises:
        :class:`mysqlx.InterfaceError`: If contains a invalid option.
    """
    settings: Dict[str, Any] = {"schema": ""}

    match = _URI_SCHEME_RE.match(uri)
    scheme, uri = match.groups() if match else ("mysqlx", uri)

    if scheme not in ("mysqlx", "mysqlx+srv"):
        raise InterfaceError(f"Scheme '{scheme}' is not valid")

    if scheme == "mysqlx+srv":
        settings["dns-srv"] = True

    userinfo, tmp = uri.partition("@")[::2]
    host, query_str = tmp.partition("?")[::2]

    pos = host.rfind("/")
    if host[pos:].find(")") == -1 and pos > 0:
        host, settings["schema"] = host.rsplit("/", 1)
    host = host.strip("()")

    if not host or not userinfo or ":" not in userinfo:
        raise InterfaceError(f"Malformed URI '{uri}'")
    user, password = userinfo.split(":", 1)
    settings["user"], settings["password"] = unquote(user), unquote(password)

    if host.startswith(("/", "..", ".")):
        settings["socket"] = unquote(host)
    elif host.startswith("\\."):
        raise InterfaceError("Windows Pipe is not supported")
    else:
        settings.update(_parse_address_list(host))

    invalid_options = ("user", "password", "dns-srv")
    for key, val in parse_qsl(query_str, True):
        opt = key.replace("_", "-").lower()
        if opt in invalid_options:
            raise InterfaceError(f"Invalid option: '{key}'")
        if opt in _SSL_OPTS:
            settings[opt] = unquote(val.strip("()"))
        else:
            val_str = val.lower()
            if val_str in ("1", "true"):
                settings[opt] = True
            elif val_str in ("0", "false"):
                settings[opt] = False
            else:
                settings[opt] = val_str
    return settings


def _validate_settings(settings: Dict[str, Any]) -> None:
    """Validates the settings to be passed to a Session object
    the port values are converted to int if specified or set to 33060
    otherwise. The priority values for each router is converted to int
    if specified.

    Args:
        settings: dict containing connection settings.

    Raises:
        :class:`mysqlx.InterfaceError`: On any configuration issue.
    """
    invalid_opts = set(settings.keys()).difference(_SESS_OPTS)
    if invalid_opts:
        invalid_opts_list = "', '".join(invalid_opts)
        raise InterfaceError(f"Invalid option(s): '{invalid_opts_list}'")

    if "routers" in settings:
        for router in settings["routers"]:
            _validate_hosts(router, 33060)
    elif "host" in settings:
        _validate_hosts(settings)

    if "ssl-mode" in settings:
        ssl_mode = settings["ssl-mode"]
        try:
            settings["ssl-mode"] = SSLMode(
                ssl_mode.lower().strip() if isinstance(ssl_mode, str) else ssl_mode
            )
        except (AttributeError, ValueError) as err:
            raise InterfaceError(f"Invalid SSL Mode '{settings['ssl-mode']}'") from err
        if "ssl-ca" not in settings and settings["ssl-mode"] in [
            SSLMode.VERIFY_IDENTITY,
            SSLMode.VERIFY_CA,
        ]:
            raise InterfaceError("Cannot verify Server without CA")

    if "ssl-crl" in settings and "ssl-ca" not in settings:
        raise InterfaceError("CA Certificate not provided")

    if "ssl-key" in settings and "ssl-cert" not in settings:
        raise InterfaceError("Client Certificate not provided")

    if "ssl-ca" in settings and settings.get("ssl-mode") not in [
        SSLMode.VERIFY_IDENTITY,
        SSLMode.VERIFY_CA,
        SSLMode.DISABLED,
    ]:
        raise InterfaceError("Must verify Server if CA is provided")

    if "auth" in settings:
        auth = settings["auth"]
        try:
            settings["auth"] = Auth(
                auth.lower().strip() if isinstance(auth, str) else auth
            )
        except (AttributeError, ValueError) as err:
            raise InterfaceError(f"Invalid Auth '{settings['auth']}'") from err

    if "compression" in settings:
        compression = settings["compression"]
        try:
            settings["compression"] = Compression(
                compression.lower().strip()
                if isinstance(compression, str)
                else compression
            )
        except (AttributeError, ValueError) as err:
            raise InterfaceError(
                "The connection property 'compression' acceptable values are: "
                "'preferred', 'required', or 'disabled'. The value "
                f"'{settings['compression']}' is not acceptable"
            ) from err

    if "compression-algorithms" in settings:
        if isinstance(settings["compression-algorithms"], str):
            compression_algorithms = (
                settings["compression-algorithms"].strip().strip("[]")
            )
            if compression_algorithms:
                settings["compression-algorithms"] = compression_algorithms.split(",")
            else:
                settings["compression-algorithms"] = None
        elif not isinstance(settings["compression-algorithms"], (list, tuple)):
            raise InterfaceError(
                "Invalid type of the connection property 'compression-algorithms'"
            )
        if settings.get("compression") == Compression.DISABLED:
            settings["compression-algorithms"] = None

    if "connection-attributes" in settings:
        _validate_connection_attributes(settings)

    if "connect-timeout" in settings:
        try:
            if isinstance(settings["connect-timeout"], str):
                settings["connect-timeout"] = int(settings["connect-timeout"])
            if (
                not isinstance(settings["connect-timeout"], int)
                or settings["connect-timeout"] < 0
            ):
                raise ValueError
        except ValueError:
            raise TypeError(
                "The connection timeout value must be a positive "
                "integer (including 0)"
            ) from None

    if "dns-srv" in settings:
        if not isinstance(settings["dns-srv"], bool):
            raise InterfaceError("The value of 'dns-srv' must be a boolean")
        if settings.get("socket"):
            raise InterfaceError(
                "Using Unix domain sockets with DNS SRV lookup is not allowed"
            )
        if settings.get("port"):
            raise InterfaceError(
                "Specifying a port number with DNS SRV lookup is not allowed"
            )
        if settings.get("routers"):
            raise InterfaceError(
                "Specifying multiple hostnames with DNS SRV look up is not allowed"
            )
    elif "host" in settings and not settings.get("port"):
        settings["port"] = 33060

    if "tls-versions" in settings:
        _validate_tls_versions(settings)

    if "tls-ciphersuites" in settings:
        _validate_tls_ciphersuites(settings)


def _validate_hosts(
    settings: Dict[str, Any], default_port: Optional[int] = None
) -> None:
    """Validate hosts.

    Args:
        settings (dict): Settings dictionary.
        default_port (int): Default connection port.

    Raises:
        :class:`mysqlx.InterfaceError`: If priority or port are invalid.
    """
    if "priority" in settings and settings["priority"]:
        try:
            settings["priority"] = int(settings["priority"])
            if settings["priority"] < 0 or settings["priority"] > 100:
                raise ProgrammingError(
                    "Invalid priority value, must be between 0 and 100",
                    4007,
                )
        except NameError:
            raise ProgrammingError("Invalid priority", 4007) from None
        except ValueError:
            raise ProgrammingError(
                f"Invalid priority: {settings['priority']}", 4007
            ) from None

    if "port" in settings and settings["port"]:
        try:
            settings["port"] = int(settings["port"])
        except NameError:
            raise InterfaceError("Invalid port") from None
    elif "host" in settings and default_port:
        settings["port"] = default_port


def _validate_connection_attributes(settings: Dict[str, Any]) -> None:
    """Validate connection-attributes.

    Args:
        settings (dict): Settings dictionary.

    Raises:
        :class:`mysqlx.InterfaceError`: If attribute name or value exceeds size.
    """
    attributes = {}
    if "connection-attributes" not in settings:
        return

    conn_attrs = settings["connection-attributes"]

    if isinstance(conn_attrs, str):
        if conn_attrs == "":
            settings["connection-attributes"] = {}
            return
        if not (
            conn_attrs.startswith("[") and conn_attrs.endswith("]")
        ) and conn_attrs not in ["False", "false", "True", "true"]:
            raise InterfaceError(
                "The value of 'connection-attributes' must be a boolean or a "
                f"list of key-value pairs, found: '{conn_attrs}'"
            )
        if conn_attrs in ["False", "false", "True", "true"]:
            if conn_attrs in ["False", "false"]:
                settings["connection-attributes"] = False
            else:
                settings["connection-attributes"] = {}
            return
        conn_attributes = conn_attrs[1:-1].split(",")
        for attr in conn_attributes:
            if attr == "":
                continue
            attr_name_val = attr.split("=")
            attr_name = attr_name_val[0]
            attr_val = attr_name_val[1] if len(attr_name_val) > 1 else ""
            if attr_name in attributes:
                raise InterfaceError(
                    f"Duplicate key '{attr_name}' used in connection-attributes"
                )
            attributes[attr_name] = attr_val
    elif isinstance(conn_attrs, dict):
        for attr_name in conn_attrs:
            attr_value = conn_attrs[attr_name]
            if not isinstance(attr_value, str):
                attr_value = repr(attr_value)
            attributes[attr_name] = attr_value
    elif isinstance(conn_attrs, bool) or conn_attrs in [0, 1]:
        if conn_attrs:
            settings["connection-attributes"] = {}
        else:
            settings["connection-attributes"] = False
        return
    elif isinstance(conn_attrs, set):
        for attr_name in conn_attrs:
            attributes[attr_name] = ""
    elif isinstance(conn_attrs, list):
        for attr in conn_attrs:
            if attr == "":
                continue
            attr_name_val = attr.split("=")
            attr_name = attr_name_val[0]
            attr_val = attr_name_val[1] if len(attr_name_val) > 1 else ""
            if attr_name in attributes:
                raise InterfaceError(
                    f"Duplicate key '{attr_name}' used in connection-attributes"
                )
            attributes[attr_name] = attr_val
    elif not isinstance(conn_attrs, bool):
        raise InterfaceError(
            "connection-attributes must be Boolean or a list of key-value "
            f"pairs, found: '{conn_attrs}'"
        )

    if attributes:
        for attr_name, attr_value in attributes.items():
            # Validate name type
            if not isinstance(attr_name, str):
                raise InterfaceError(
                    f"Attribute name '{attr_name}' must be a string type"
                )
            # Validate attribute name limit 32 characters
            if len(attr_name) > 32:
                raise InterfaceError(
                    f"Attribute name '{attr_name}' exceeds 32 characters limit size"
                )
            # Validate names in connection-attributes cannot start with "_"
            if attr_name.startswith("_"):
                raise InterfaceError(
                    "Key names in connection-attributes cannot start with "
                    f"'_', found: '{attr_name}'"
                )

            # Validate value type
            if not isinstance(attr_value, str):
                raise InterfaceError(
                    f"Attribute '{attr_name}' value: '{attr_value}' must be "
                    "a string type"
                )
            # Validate attribute value limit 1024 characters
            if len(attr_value) > 1024:
                raise InterfaceError(
                    f"Attribute '{attr_name}' value: '{attr_value}' exceeds "
                    "1024 characters limit size"
                )

    settings["connection-attributes"] = attributes


def _validate_tls_versions(settings: Dict[str, Any]) -> None:
    """Validate tls-versions.

    Args:
        settings (dict): Settings dictionary.

    Raises:
        :class:`mysqlx.InterfaceError`: If tls-versions name is not valid.
    """
    tls_versions = []
    if "tls-versions" not in settings:
        return

    tls_versions_settings = settings["tls-versions"]

    if isinstance(tls_versions_settings, str):
        if not (
            tls_versions_settings.startswith("[")
            and tls_versions_settings.endswith("]")
        ):
            raise InterfaceError(
                f"tls-versions must be a list, found: '{tls_versions_settings}'"
            )
        tls_vers = tls_versions_settings[1:-1].split(",")
        for tls_ver in tls_vers:
            tls_version = tls_ver.strip()
            if tls_version == "":
                continue
            if tls_version in tls_versions:
                raise InterfaceError(
                    DUPLICATED_IN_LIST_ERROR.format(
                        list="tls_versions", value=tls_version
                    )
                )
            tls_versions.append(tls_version)
    elif isinstance(tls_versions_settings, list):
        if not tls_versions_settings:
            raise InterfaceError(
                "At least one TLS protocol version must be "
                "specified in 'tls-versions' list."
            )
        for tls_ver in tls_versions_settings:
            if tls_ver in tls_versions:
                raise InterfaceError(
                    DUPLICATED_IN_LIST_ERROR.format(list="tls_versions", value=tls_ver)
                )
            tls_versions.append(tls_ver)

    elif isinstance(tls_versions_settings, set):
        for tls_ver in tls_versions_settings:
            tls_versions.append(tls_ver)
    else:
        raise InterfaceError(
            "tls-versions should be a list with one or more of versions in "
            f"{', '.join(SUPPORTED_TLS_VERSIONS)}. found: '{tls_versions}'"
        )

    if not tls_versions:
        raise InterfaceError(
            "At least one TLS protocol version must be specified in "
            "'tls-versions' list."
        )

    use_tls_versions = []
    deprecated_tls_versions = []
    not_tls_versions = []
    for tls_ver in tls_versions:
        if tls_ver in SUPPORTED_TLS_VERSIONS:
            use_tls_versions.append(tls_ver)
        if tls_ver in DEPRECATED_TLS_VERSIONS:
            deprecated_tls_versions.append(tls_ver)
        else:
            not_tls_versions.append(tls_ver)

    if use_tls_versions:
        if use_tls_versions == ["TLSv1.3"] and not TLS_V1_3_SUPPORTED:
            raise NotSupportedError(
                TLS_VER_NO_SUPPORTED.format(tls_versions, SUPPORTED_TLS_VERSIONS)
            )
        use_tls_versions.sort()
        settings["tls-versions"] = use_tls_versions
    elif deprecated_tls_versions:
        raise NotSupportedError(
            TLS_VERSION_DEPRECATED_ERROR.format(
                deprecated_tls_versions, SUPPORTED_TLS_VERSIONS
            )
        )
    elif not_tls_versions:
        raise InterfaceError(TLS_VERSION_ERROR.format(tls_ver, SUPPORTED_TLS_VERSIONS))


def _validate_tls_ciphersuites(settings: Dict[str, Any]) -> None:
    """Validate tls-ciphersuites.

    Args:
        settings (dict): Settings dictionary.

    Raises:
        :class:`mysqlx.InterfaceError`: If tls-ciphersuites name is not valid.
    """
    tls_ciphersuites = []
    if "tls-ciphersuites" not in settings:
        return

    tls_ciphersuites_settings = settings["tls-ciphersuites"]

    if isinstance(tls_ciphersuites_settings, str):
        if not (
            tls_ciphersuites_settings.startswith("[")
            and tls_ciphersuites_settings.endswith("]")
        ):
            raise InterfaceError(
                "tls-ciphersuites must be a list, found: "
                f"'{tls_ciphersuites_settings}'"
            )
        tls_css = tls_ciphersuites_settings[1:-1].split(",")
        if not tls_css:
            raise InterfaceError(
                "No valid cipher suite found in the 'tls-ciphersuites' list"
            )
        for tls_cs in tls_css:
            tls_cs = tls_cs.strip().upper()
            if tls_cs:
                tls_ciphersuites.append(tls_cs)
    elif isinstance(tls_ciphersuites_settings, (list, set)):
        tls_ciphersuites = [tls_cs for tls_cs in tls_ciphersuites_settings if tls_cs]
    else:
        raise InterfaceError(
            "tls-ciphersuites should be a list with one or more ciphersuites. "
            f"Found: '{tls_ciphersuites_settings}'"
        )

    tls_versions = (
        SUPPORTED_TLS_VERSIONS[:]
        if settings.get("tls-versions", None) is None
        else settings["tls-versions"][:]
    )

    # A newer TLS version can use a cipher introduced on
    # an older version.
    tls_versions.sort(reverse=True)
    newer_tls_ver = tls_versions[0]

    translated_names = []
    iani_cipher_suites_names = {}
    ossl_cipher_suites_names: List[str] = []

    # Old ciphers can work with new TLS versions.
    # Find all the ciphers introduced on previous TLS versions
    for tls_ver in SUPPORTED_TLS_VERSIONS[
        : SUPPORTED_TLS_VERSIONS.index(newer_tls_ver) + 1
    ]:
        iani_cipher_suites_names.update(TLS_CIPHER_SUITES[tls_ver])
        ossl_cipher_suites_names.extend(OPENSSL_CS_NAMES[tls_ver])

    for name in tls_ciphersuites:
        if "-" in name and name in ossl_cipher_suites_names:
            translated_names.append(name)
        elif name in iani_cipher_suites_names:
            translated_name = iani_cipher_suites_names[name]
            if translated_name in translated_names:
                raise AttributeError(
                    DUPLICATED_IN_LIST_ERROR.format(
                        list="tls_ciphersuites", value=translated_name
                    )
                )
            translated_names.append(translated_name)
        else:
            raise InterfaceError(
                f"The value '{name}' in cipher suites is not a valid cipher suite"
            )

    if not translated_names:
        raise InterfaceError(
            "No valid cipher suite found in the 'tls-ciphersuites' list"
        )

    settings["tls-ciphersuites"] = translated_names


def _get_connection_settings(*args: Any, **kwargs: Any) -> Dict[str, Any]:
    """Parses the connection string and returns a dictionary with the
    connection settings.

    Args:
        *args: Variable length argument list with the connection data used
               to connect to the database. It can be a dictionary or a
               connection string.
        **kwargs: Arbitrary keyword arguments with connection data used to
                  connect to the database.

    Returns:
        mysqlx.Session: Session object.

    Raises:
        TypeError: If connection timeout is not a positive integer.
        :class:`mysqlx.InterfaceError`: If settings not provided.
    """
    settings = {}
    if args:
        if isinstance(args[0], str):
            settings = _parse_connection_uri(args[0])
        elif isinstance(args[0], dict):
            for key, val in args[0].items():
                settings[key.replace("_", "-")] = val
    elif kwargs:
        for key, val in kwargs.items():
            settings[key.replace("_", "-")] = val

    if not settings:
        raise InterfaceError("Settings not provided")

    _validate_settings(settings)
    return settings


def get_session(*args: Any, **kwargs: Any) -> Session:
    """Creates a Session instance using the provided connection data.

    Args:
        *args: Variable length argument list with the connection data used
               to connect to a MySQL server. It can be a dictionary or a
               connection string.
        **kwargs: Arbitrary keyword arguments with connection data used to
                  connect to the database.

    Returns:
        mysqlx.Session: Session object.
    """
    settings = _get_connection_settings(*args, **kwargs)
    return Session(settings)


def get_client(
    connection_string: Union[str, Dict[str, Any]],
    options_string: Union[str, Dict[str, Any]],
) -> Client:
    """Creates a Client instance with the provided connection data and settings.

    Args:
        connection_string: A string or a dict type object to indicate the \
            connection data used to connect to a MySQL server.

            The string must have the following uri format::

                cnx_str = 'mysqlx://{user}:{pwd}@{host}:{port}'
                cnx_str = ('mysqlx://{user}:{pwd}@['
                           '    (address={host}:{port}, priority=n),'
                           '    (address={host}:{port}, priority=n), ...]'
                           '       ?[option=value]')

            And the dictionary::

                cnx_dict = {
                    'host': 'The host where the MySQL product is running',
                    'port': '(int) the port number configured for X protocol',
                    'user': 'The user name account',
                    'password': 'The password for the given user account',
                    'ssl-mode': 'The flags for ssl mode in mysqlx.SSLMode.FLAG',
                    'ssl-ca': 'The path to the ca.cert'
                    "connect-timeout": '(int) milliseconds to wait on timeout'
                }

        options_string: A string in the form of a document or a dictionary \
            type with configuration for the client.

            Current options include::

                options = {
                    'pooling': {
                        'enabled': (bool), # [True | False], True by default
                        'max_size': (int), # Maximum connections per pool
                        "max_idle_time": (int), # milliseconds that a
                            # connection will remain active while not in use.
                            # By default 0, means infinite.
                        "queue_timeout": (int), # milliseconds a request will
                            # wait for a connection to become available.
                            # By default 0, means infinite.
                    }
                }

    Returns:
        mysqlx.Client: Client object.

    .. versionadded:: 8.0.13
    """
    if not isinstance(connection_string, (str, dict)):
        raise InterfaceError("connection_data must be a string or dict")

    settings_dict = _get_connection_settings(connection_string)

    if not isinstance(options_string, (str, dict)):
        raise InterfaceError("connection_options must be a string or dict")

    if isinstance(options_string, str):
        try:
            options_dict = json.loads(options_string)
        except JSONDecodeError as err:
            raise InterfaceError(
                "'pooling' options must be given in the form of a document or dict"
            ) from err
    else:
        options_dict = {}
        for key, value in options_string.items():
            options_dict[key.replace("-", "_")] = value

    if not isinstance(options_dict, dict):
        raise InterfaceError(
            "'pooling' options must be given in the form of a document or dict"
        )
    pooling_options_dict = {}
    if "pooling" in options_dict:
        pooling_options = options_dict.pop("pooling")
        if not isinstance(pooling_options, (dict)):
            raise InterfaceError(
                "'pooling' options must be given in the form document or dict"
            )
        # Fill default pooling settings
        pooling_options_dict["enabled"] = pooling_options.pop("enabled", True)
        pooling_options_dict["max_size"] = pooling_options.pop("max_size", 25)
        pooling_options_dict["max_idle_time"] = pooling_options.pop("max_idle_time", 0)
        pooling_options_dict["queue_timeout"] = pooling_options.pop("queue_timeout", 0)

        # No other options besides pooling are supported
        if len(pooling_options) > 0:
            raise InterfaceError(f"Unrecognized pooling options: {pooling_options}")
        # No other options besides pooling are supported
        if len(options_dict) > 0:
            raise InterfaceError(
                f"Unrecognized connection options: {options_dict.keys()}"
            )

    return Client(settings_dict, pooling_options_dict)

virt/lib/python3.9/site-packages/mysqlx/connection 2.py
PrinceDisant/LostInNL
# Copyright (c) 2016, 2023, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

# mypy: disable-error-code="index,attr-defined"

"""Implementation of communication for MySQL X servers."""

from __future__ import annotations

from types import TracebackType

try:
    import ssl

    SSL_AVAILABLE = True
    TLS_VERSIONS = {
        "TLSv1": ssl.PROTOCOL_TLSv1,
        "TLSv1.1": ssl.PROTOCOL_TLSv1_1,
        "TLSv1.2": ssl.PROTOCOL_TLSv1_2,
    }
    # TLSv1.3 included in PROTOCOL_TLS, but PROTOCOL_TLS is not included on 3.4
    TLS_VERSIONS["TLSv1.3"] = (
        ssl.PROTOCOL_TLS
        if hasattr(ssl, "PROTOCOL_TLS")
        else ssl.PROTOCOL_SSLv23  # Alias of PROTOCOL_TLS
    )
    TLS_V1_3_SUPPORTED = hasattr(ssl, "HAS_TLSv1_3") and ssl.HAS_TLSv1_3
except ImportError:
    SSL_AVAILABLE = False
    TLS_V1_3_SUPPORTED = False
    TLS_VERSIONS = {}

import json
import os
import platform
import queue
import random
import re
import socket
import sys
import threading
import uuid
import warnings

from typing import Any, Callable, Dict, List, Mapping, Optional, Tuple, Type, Union

try:
    import dns.exception
    import dns.resolver
except ImportError:
    HAVE_DNSPYTHON = False
else:
    HAVE_DNSPYTHON = True

from datetime import datetime, timedelta
from functools import wraps
from json.decoder import JSONDecodeError
from urllib.parse import parse_qsl, unquote, urlparse

from .authentication import MySQL41AuthPlugin, PlainAuthPlugin, Sha256MemoryAuthPlugin
from .constants import (
    COMPRESSION_ALGORITHMS,
    DEPRECATED_TLS_VERSIONS,
    OPENSSL_CS_NAMES,
    SUPPORTED_TLS_VERSIONS,
    TLS_CIPHER_SUITES,
    Auth,
    Compression,
    SSLMode,
)
from .crud import Schema

# pylint: disable=redefined-builtin
from .errors import (
    InterfaceError,
    NotSupportedError,
    OperationalError,
    PoolError,
    ProgrammingError,
    TimeoutError,
)
from .helpers import escape, get_item_or_attr, iani_to_openssl_cs_name
from .logger import logger
from .protobuf import Protobuf
from .protocol import HAVE_LZ4, HAVE_ZSTD, MessageReader, MessageWriter, Protocol
from .result import BaseResult, DocResult, Result, RowResult, SqlResult
from .statement import (
    AddStatement,
    DeleteStatement,
    FindStatement,
    InsertStatement,
    ModifyStatement,
    ReadStatement,
    RemoveStatement,
    SelectStatement,
    SqlStatement,
    UpdateStatement,
    quote_identifier,
)
from .types import ColumnType, MessageType, ResultBaseType, StatementType

sys.path.append("..")

from mysql.connector.abstracts import (
    DUPLICATED_IN_LIST_ERROR,
    TLS_VER_NO_SUPPORTED,
    TLS_VERSION_DEPRECATED_ERROR,
    TLS_VERSION_ERROR,
)
from mysql.connector.utils import linux_distribution
from mysql.connector.version import LICENSE, VERSION

CONNECTION_CLOSED_ERROR = {
    1810: "This session was closed because the connection has been idle for "
    "too long. Use 'mysqlx.getSession()' or 'mysqlx.getClient()' to create a "
    "new one.",
    1053: "This session was closed because the server is shutting down.",
    3169: "This session was closed because the connection has been killed in "
    "a different session. Use 'mysqlx.getSession()' or 'mysqlx.getClient()' "
    "to create a new one.",
}

_CONNECT_TIMEOUT = 10000  # Default connect timeout in milliseconds
_DROP_DATABASE_QUERY = "DROP DATABASE IF EXISTS {}"
_CREATE_DATABASE_QUERY = "CREATE DATABASE IF NOT EXISTS {}"
_SELECT_SCHEMA_NAME_QUERY = (
    "SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = '{}'"
)
_SELECT_VERSION_QUERY = "SELECT @@version"

_CNX_POOL_MAXSIZE = 99
_CNX_POOL_MAX_NAME_SIZE = 120
_CNX_POOL_NAME_REGEX = re.compile(r"[^a-zA-Z0-9._:\-*$#]")
_CNX_POOL_MAX_IDLE_TIME = 2147483
_CNX_POOL_QUEUE_TIMEOUT = 2147483

# Time is on seconds
_PENALTY_SERVER_OFFLINE = 1000000
_PENALTY_MAXED_OUT = 60
_PENALTY_NO_ADD_INFO = 60 * 60
_PENALTY_CONN_TIMEOUT = 60 * 60
_PENALTY_WRONG_PASSW = 60 * 60 * 24
_PENALTY_RESTARTING = 60
_TIMEOUT_PENALTIES = {
    # Server denays service e.g Max connections reached
    "[WinError 10053]": _PENALTY_MAXED_OUT,  # Established connection was aborted
    "[Errno 32]": _PENALTY_MAXED_OUT,  # Broken pipe
    # Server is Offline
    "[WinError 10061]": _PENALTY_SERVER_OFFLINE,  # Target machine actively refused it
    "[Errno 111]": _PENALTY_SERVER_OFFLINE,  # Connection refused
    # Host is offline:
    "[WinError 10060]": _PENALTY_CONN_TIMEOUT,  # Not respond after a period of time
    # No route to Host:
    "[Errno 11001]": _PENALTY_NO_ADD_INFO,  # getaddrinfo failed
    "[Errno -2]": _PENALTY_NO_ADD_INFO,  # Name or service not known
    # Wrong Password
    "Access denied": _PENALTY_WRONG_PASSW,
}
_TIMEOUT_PENALTIES_BY_ERR_NO = {1053: _PENALTY_RESTARTING}
_SPLIT_RE = re.compile(r",(?![^\(\)]*\))")
_PRIORITY_RE = re.compile(r"^\(address=(.+),priority=(\d+)\)$", re.VERBOSE)
_ROUTER_RE = re.compile(r"^\(address=(.+)[,]*\)$", re.VERBOSE)
_URI_SCHEME_RE = re.compile(r"^([a-zA-Z][a-zA-Z0-9+\-.]+)://(.*)")
_SSL_OPTS = [
    "ssl-cert",
    "ssl-ca",
    "ssl-key",
    "ssl-crl",
    "tls-versions",
    "tls-ciphersuites",
]
_SESS_OPTS = _SSL_OPTS + [
    "user",
    "password",
    "schema",
    "host",
    "port",
    "routers",
    "socket",
    "ssl-mode",
    "auth",
    "use-pure",
    "connect-timeout",
    "connection-attributes",
    "compression",
    "compression-algorithms",
    "dns-srv",
]


def generate_pool_name(**kwargs: Any) -> str:
    """Generate a pool name.

    This function takes keyword arguments, usually the connection arguments and
    tries to generate a name for the pool.

    Args:
        **kwargs: Arbitrary keyword arguments with the connection arguments.

    Raises:
        PoolError: If the name can't be generated.

    Returns:
        str: The generated pool name.
    """
    parts = []
    for key in ("host", "port", "user", "database", "client_id"):
        try:
            parts.append(str(kwargs[key]))
        except KeyError:
            pass

    if not parts:
        raise PoolError("Failed generating pool name; specify pool_name")

    return "_".join(parts)


def update_timeout_penalties_by_error(penalty_dict: Mapping[str, Any]) -> None:
    """Update the timeout penalties directory.

    Update the timeout penalties by error dictionary used to deactivate a pool.
    Args:
        penalty_dict (dict): The dictionary with the new timeouts.
    """
    if penalty_dict and isinstance(penalty_dict, dict):
        _TIMEOUT_PENALTIES_BY_ERR_NO.update(penalty_dict)


class SocketStream:
    """Implements a socket stream."""

    def __init__(self) -> None:
        self._socket: Optional[socket.socket] = None
        self._is_ssl: bool = False
        self._is_socket: bool = False
        self._host: Optional[str] = None

    def connect(self, params: Tuple, connect_timeout: float = _CONNECT_TIMEOUT) -> None:
        """Connects to a TCP service.

        Args:
            params (tuple): The connection parameters.

        Raises:
            :class:`mysqlx.InterfaceError`: If Unix socket is not supported.
        """
        if connect_timeout is not None:
            connect_timeout = connect_timeout / 1000  # Convert to seconds
        try:
            self._socket = socket.create_connection(
                params, connect_timeout  # type: ignore[arg-type]
            )
            self._host = params[0]
        except ValueError:
            try:
                self._socket = socket.socket(socket.AF_UNIX)
                self._socket.settimeout(connect_timeout)
                self._socket.connect(params)
                self._is_socket = True
            except AttributeError:
                raise InterfaceError("Unix socket unsupported") from None
        self._socket.settimeout(None)

    def read(self, count: int) -> bytes:
        """Receive data from the socket.

        Args:
            count (int): Buffer size.

        Returns:
            bytes: The data received.
        """
        if self._socket is None:
            raise OperationalError("MySQLx Connection not available")
        buf = []
        while count > 0:
            data = self._socket.recv(count)
            if data == b"":
                raise RuntimeError("Unexpected connection close")
            buf.append(data)
            count -= len(data)
        return b"".join(buf)

    def sendall(self, data: bytes) -> None:
        """Send data to the socket.

        Args:
            data (bytes): The data to be sent.
        """
        if self._socket is None:
            raise OperationalError("MySQLx Connection not available")
        try:
            self._socket.sendall(data)
        except OSError as err:
            raise OperationalError(f"Unexpected socket error: {err}") from err

    def close(self) -> None:
        """Close the socket."""
        if not self._socket:
            return
        try:
            self._socket.shutdown(socket.SHUT_RDWR)
            self._socket.close()
        except OSError:
            # On [Errno 107] Transport endpoint is not connected
            pass
        self._socket = None

    def __del__(self) -> None:
        self.close()

    def set_ssl(
        self,
        ssl_protos: List[str],
        ssl_mode: str,
        ssl_ca: str,
        ssl_crl: str,
        ssl_cert: str,
        ssl_key: str,
        ssl_ciphers: List[str],
    ) -> None:
        """Set SSL parameters.

        Args:
            ssl_protos (list): SSL protocol to use.
            ssl_mode (str): SSL mode.
            ssl_ca (str): The certification authority certificate.
            ssl_crl (str): The certification revocation lists.
            ssl_cert (str): The certificate.
            ssl_key (str): The certificate key.
            ssl_ciphers (list): SSL ciphersuites to use.

        Raises:
            :class:`mysqlx.RuntimeError`: If Python installation has no SSL
                                          support.
            :class:`mysqlx.InterfaceError`: If the parameters are invalid.
        """
        if not SSL_AVAILABLE:
            self.close()
            raise RuntimeError("Python installation has no SSL support")

        if ssl_protos is None or not ssl_protos:
            context = ssl.create_default_context()
            if ssl_mode != SSLMode.VERIFY_IDENTITY:
                context.check_hostname = False
            if ssl_mode == SSLMode.REQUIRED:
                context.verify_mode = ssl.CERT_NONE
        else:
            ssl_protos.sort(reverse=True)
            tls_version = ssl_protos[0]
            if (
                not TLS_V1_3_SUPPORTED
                and tls_version == "TLSv1.3"
                and len(ssl_protos) > 1
            ):
                tls_version = ssl_protos[1]
            ssl_protocol = TLS_VERSIONS[tls_version]
            context = ssl.SSLContext(ssl_protocol)

            if tls_version == "TLSv1.3":
                if "TLSv1.2" not in ssl_protos:
                    context.options |= ssl.OP_NO_TLSv1_2
                if "TLSv1.1" not in ssl_protos:
                    context.options |= ssl.OP_NO_TLSv1_1
                if "TLSv1" not in ssl_protos:
                    context.options |= ssl.OP_NO_TLSv1

        if ssl_ca:
            try:
                context.load_verify_locations(ssl_ca)
                context.verify_mode = ssl.CERT_REQUIRED
            except (IOError, ssl.SSLError) as err:
                self.close()
                raise InterfaceError(f"Invalid CA Certificate: {err}") from err

        if ssl_crl:
            try:
                context.load_verify_locations(ssl_crl)
                context.verify_flags = ssl.VERIFY_CRL_CHECK_LEAF
            except (IOError, ssl.SSLError) as err:
                self.close()
                raise InterfaceError(f"Invalid CRL: {err}") from err

        if ssl_cert:
            try:
                context.load_cert_chain(ssl_cert, ssl_key)
            except (IOError, ssl.SSLError) as err:
                self.close()
                raise InterfaceError(f"Invalid Certificate/Key: {err}") from err

        if ssl_ciphers:
            context.set_ciphers(
                ":".join(iani_to_openssl_cs_name(ssl_protos[0], ssl_ciphers))
            )
        try:
            self._socket = context.wrap_socket(self._socket, server_hostname=self._host)
        except ssl.CertificateError as err:
            raise InterfaceError(f"{err}") from err
        if ssl_mode == SSLMode.VERIFY_IDENTITY:
            context.check_hostname = True
            hostnames = []
            # Windows does not return loopback aliases on gethostbyaddr
            if os.name == "nt" and self._host in ("localhost", "127.0.0.1"):
                hostnames = ["localhost", "127.0.0.1"]
            aliases = socket.gethostbyaddr(self._host)
            hostnames.extend([aliases[0]] + aliases[1])
            match_found = False
            errs = []
            for hostname in hostnames:
                try:
                    # Deprecated in Python 3.7 without a replacement and
                    # should be removed in the future, since OpenSSL now
                    # performs hostname matching
                    # pylint: disable=deprecated-method
                    ssl.match_hostname(self._socket.getpeercert(), hostname)
                    # pylint: enable=deprecated-method
                except ssl.CertificateError as err:
                    errs.append(str(err))
                else:
                    match_found = True
                    break
            if not match_found:
                self.close()
                raise InterfaceError(
                    f"Unable to verify server identity: {', '.join(errs)}"
                )

        self._is_ssl = True

        # Raise a deprecation warning if TLSv1 or TLSv1.1 is being used
        tls_version = self._socket.version()
        if tls_version in ("TLSv1", "TLSv1.1"):
            warn_msg = (
                f"This connection is using {tls_version} which is now "
                "deprecated and will be removed in a future release of "
                "MySQL Connector/Python"
            )
            warnings.warn(warn_msg, DeprecationWarning)

    def is_ssl(self) -> bool:
        """Verifies if SSL is being used.

        Returns:
            bool: Returns `True` if SSL is being used.
        """
        return self._is_ssl

    def is_socket(self) -> bool:
        """Verifies if socket connection is being used.

        Returns:
            bool: Returns `True` if socket connection is being used.
        """
        return self._is_socket

    def is_secure(self) -> bool:
        """Verifies if connection is secure.

        Returns:
            bool: Returns `True` if connection is secure.
        """
        return self._is_ssl or self._is_socket

    def is_open(self) -> bool:
        """Verifies if connection is open.

        Returns:
            bool: Returns `True` if connection is open.
        """
        return self._socket is not None


def catch_network_exception(func: Callable) -> Callable:
    """Decorator used to catch OSError or RuntimeError.

    Raises:
        :class:`mysqlx.InterfaceError`: If `OSError` or `RuntimeError`
                                        is raised.
    """

    @wraps(func)
    def wrapper(self: Any, *args: Any, **kwargs: Any) -> Any:
        """Wrapper function."""
        try:
            if (
                isinstance(self, (Connection, PooledConnection))
                and self.is_server_disconnected()
            ):
                raise InterfaceError(*self.get_disconnected_reason())
            result = func(self, *args, **kwargs)
            if isinstance(result, BaseResult):
                warns: Any = result.get_warnings()
                for warn in warns:
                    if warn["code"] in CONNECTION_CLOSED_ERROR:
                        error_msg = CONNECTION_CLOSED_ERROR[warn["code"]]
                        reason = (
                            f"Connection close: {warn['msg']}: {error_msg}",
                            warn["code"],
                        )
                        if isinstance(self, (Connection, PooledConnection)):
                            self.set_server_disconnected(reason)
                        break
            return result
        except (InterfaceError, OSError, RuntimeError, TimeoutError) as err:
            if (
                func.__name__ == "get_column_metadata"
                and args
                and isinstance(args[0], SqlResult)
            ):
                warns = args[0].get_warnings()
                if warns:
                    warn = warns[0]
                    error_msg = CONNECTION_CLOSED_ERROR[warn["code"]]
                    reason = (
                        f"Connection close: {warn['msg']}: {error_msg}",
                        warn["code"],
                    )
                    if isinstance(self, PooledConnection):
                        self.pool.remove_connections()
                        # pool must be listed as faulty if server is shutting down
                        if warn["code"] == 1053:
                            PoolsManager().set_pool_unavailable(
                                self.pool, InterfaceError(*reason)
                            )
                    if isinstance(self, (Connection, PooledConnection)):
                        self.set_server_disconnected(reason)
                    self.disconnect()
                    raise InterfaceError(*reason) from err
            self.disconnect()
            raise

    return wrapper


class Router(dict):
    """Represents a set of connection parameters.

    Args:
       settings (dict): Dictionary with connection settings
    .. versionadded:: 8.0.20
    """

    def __init__(self, connection_params: Mapping[str, Any]) -> None:
        super().__init__()
        self.update(connection_params)
        self["available"] = self.get("available", True)

    def available(self) -> bool:
        """Verifies if the Router is available to open connections.

        Returns:
            bool: True if this Router is available else False.
        """
        return self["available"]

    def set_unavailable(self) -> None:
        """Sets this Router unavailable to open connections."""
        self["available"] = False

    def get_connection_params(self) -> Union[str, Tuple[str, Optional[int]]]:
        """Verifies if the Router is available to open connections.

        Returns:
            tuple: host and port or socket information tuple.
        """
        if "socket" in self:
            return self["socket"]
        return (self["host"], self["port"])


class RouterManager:
    """Manages the connection parameters of all the routers.

    Args:
        Routers (list): A list of Router objects.
        settings (dict): Dictionary with connection settings.
    .. versionadded:: 8.0.20
    """

    def __init__(self, routers: List[Router], settings: Dict[str, Any]) -> None:
        self._routers = routers
        self._settings = settings
        self._cur_priority_idx: int = 0
        self._can_failover: bool = True
        # Reuters status
        self._routers_directory: Dict[int, List[Router]] = {}
        self.routers_priority_list: List[int] = []
        self._ensure_priorities()

    def _ensure_priorities(self) -> None:
        """Ensure priorities.

        Raises:
            :class:`mysqlx.ProgrammingError`: If priorities are invalid.
        """
        priority_count = 0

        for router in self._routers:
            priority = router.get("priority", None)
            if priority is None:
                priority_count += 1
                router["priority"] = 100
            elif priority > 100:
                raise ProgrammingError("The priorities must be between 0 and 100", 4007)

        if 0 < priority_count < len(self._routers):
            raise ProgrammingError(
                "You must either assign no priority to any "
                "of the routers or give a priority for "
                "every router",
                4000,
            )

        self._routers.sort(key=lambda x: x["priority"], reverse=True)

        # Group servers with the same priority
        for router in self._routers:
            priority = router["priority"]
            if priority not in self._routers_directory:
                self._routers_directory[priority] = [Router(router)]
                self.routers_priority_list.append(priority)
            else:
                self._routers_directory[priority].append(Router(router))

    def _get_available_routers(self, priority: int) -> List[Router]:
        """Get a list of the current available routers that shares the given priority.

        Returns:
            list: A list of the current available routers.
        """
        router_list = self._routers_directory[priority]
        router_list = [router for router in router_list if router.available()]
        return router_list

    def _get_random_connection_params(self, priority: int) -> Router:
        """Get a random router from the group with the given priority.

        Returns:
            Router: A random router.
        """
        router_list = self._get_available_routers(priority)
        if not router_list:
            return None
        if len(router_list) == 1:
            return router_list[0]

        last = len(router_list) - 1
        index = random.randint(0, last)
        return router_list[index]

    def can_failover(self) -> bool:
        """Returns the next connection parameters.

        Returns:
            bool: True if there is more server to failover to else False.
        """
        return self._can_failover

    def get_next_router(self) -> Router:
        """Returns the next connection parameters.

        Returns:
            Router: with the connection parameters.
        """
        if not self._routers:
            self._can_failover = False
            router_settings = self._settings.copy()
            router_settings["host"] = self._settings.get("host", "localhost")
            router_settings["port"] = self._settings.get("port", 33060)
            return Router(router_settings)

        cur_priority = self.routers_priority_list[self._cur_priority_idx]
        routers_priority_len = len(self.routers_priority_list)

        search = True
        while search:
            router = self._get_random_connection_params(cur_priority)

            if router is not None or self._cur_priority_idx >= routers_priority_len:
                if (
                    self._cur_priority_idx == routers_priority_len - 1
                    and len(self._get_available_routers(cur_priority)) < 2
                ):
                    self._can_failover = False
                break

            # Search on next group
            self._cur_priority_idx += 1
            if self._cur_priority_idx < routers_priority_len:
                cur_priority = self.routers_priority_list[self._cur_priority_idx]

        return router

    def get_routers_directory(self) -> Dict[int, List[Router]]:
        """Returns the directory containing all the routers managed.

        Returns:
            dict: Dictionary with priorities as connection settings.
        """
        return self._routers_directory


class Connection:
    """Connection to a MySQL Server.

    Args:
        settings (dict): Dictionary with connection settings.
    """

    def __init__(self, settings: Dict[str, Any]) -> None:
        self.settings: Dict[str, Any] = settings
        self.stream: SocketStream = SocketStream()
        self.protocol: Optional[Protocol] = None
        self.keep_open: Optional[bool] = None
        self._user: Optional[str] = settings.get("user")
        self._password: Optional[str] = settings.get("password")
        self._schema: Optional[str] = settings.get("schema")
        self._active_result: Optional[ResultBaseType] = None
        self._routers: List[Router] = settings.get("routers", [])

        if "host" in settings and settings["host"]:
            self._routers.append(
                {  # type: ignore[arg-type]
                    "host": settings.get("host"),
                    "port": settings.get("port", None),
                }
            )

        self.router_manager: RouterManager = RouterManager(self._routers, settings)
        self._connect_timeout: Optional[int] = settings.get(
            "connect-timeout", _CONNECT_TIMEOUT
        )
        if self._connect_timeout == 0:
            # None is assigned if connect timeout is 0, which disables timeouts
            # on socket operations
            self._connect_timeout = None

        self._stmt_counter: int = 0
        self._prepared_stmt_ids: List[int] = []
        self._prepared_stmt_supported: bool = True
        self._server_disconnected: bool = False
        self._server_disconnected_reason: Optional[Union[str, Tuple[str, int]]] = None

    def fetch_active_result(self) -> None:
        """Fetch active result."""
        if self._active_result is not None:
            self._active_result.fetch_all()
            self._active_result = None

    def set_active_result(self, result: ResultBaseType) -> None:
        """Set active result.

        Args:
            `Result`: It can be :class:`mysqlx.Result`,
                      :class:`mysqlx.BufferingResult`,
                      :class:`mysqlx.RowResult`, :class:`mysqlx.SqlResult` or
                      :class:`mysqlx.DocResult`.
        """
        self._active_result = result

    def connect(self) -> None:
        """Attempt to connect to the MySQL server.

        Raises:
            :class:`mysqlx.InterfaceError`: If fails to connect to the MySQL
                                            server.
            :class:`mysqlx.TimeoutError`: If connect timeout was exceeded.
        """
        # Loop and check
        error = None
        while self.router_manager.can_failover():
            try:
                router = self.router_manager.get_next_router()
                self.stream.connect(
                    router.get_connection_params(), self._connect_timeout  # type: ignore[arg-type]
                )
                reader = MessageReader(self.stream)
                writer = MessageWriter(self.stream)
                self.protocol = Protocol(reader, writer)

                caps_data = self.protocol.get_capabilites().capabilities
                caps = (
                    {get_item_or_attr(cap, "name").lower(): cap for cap in caps_data}
                    if caps_data
                    else {}
                )

                # Set TLS capabilities
                self._set_tls_capabilities(caps)

                # Set connection attributes capabilities
                if "attributes" in self.settings:
                    conn_attrs = self.settings["attributes"]
                    self.protocol.set_capabilities(session_connect_attrs=conn_attrs)

                # Set compression capabilities
                compression = self.settings.get("compression", Compression.PREFERRED)
                algorithms = self.settings.get("compression-algorithms")
                algorithm = (
                    None
                    if compression == Compression.DISABLED
                    else self._set_compression_capabilities(
                        caps, compression, algorithms
                    )
                )
                self._authenticate()
                self.protocol.set_compression(algorithm)
                return
            except (OSError, RuntimeError) as err:
                error = err
                router.set_unavailable()

        # Python 2.7 does not raise a socket.timeout exception when using
        # settimeout(), but it raises a socket.error with errno.EAGAIN (11)
        # or errno.EINPROGRESS (115) if connect-timeout value is too low
        if error is not None and isinstance(error, socket.timeout):
            if len(self._routers) <= 1:
                raise TimeoutError(
                    "Connection attempt to the server was aborted. "
                    f"Timeout of {self._connect_timeout} ms was exceeded"
                )
            raise TimeoutError(
                "All server connection attempts were aborted. "
                f"Timeout of {self._connect_timeout} ms was exceeded for each "
                "selected server"
            )
        if len(self._routers) <= 1:
            raise InterfaceError(f"Cannot connect to host: {error}")
        raise InterfaceError("Unable to connect to any of the target hosts", 4001)

    def _set_tls_capabilities(self, caps: Dict[str, Any]) -> None:
        """Set the TLS capabilities.

        Args:
            caps (dict): Dictionary with the server capabilities.

        Raises:
            :class:`mysqlx.OperationalError`: If SSL is not enabled at the
                                             server.
            :class:`mysqlx.RuntimeError`: If support for SSL is not available
                                          in Python.

        .. versionadded:: 8.0.21
        """
        if self.settings.get("ssl-mode") == SSLMode.DISABLED:
            return

        if self.stream.is_socket():
            if self.settings.get("ssl-mode"):
                logger.warning("SSL not required when using Unix socket.")
            return

        if "tls" not in caps:
            self.close_connection()
            raise OperationalError("SSL not enabled at server")

        is_ol7 = False
        if platform.system() == "Linux":
            distname, version, _ = linux_distribution()
            try:
                is_ol7 = "Oracle Linux" in distname and version.split(".")[0] == "7"
            except IndexError:
                is_ol7 = False

        if sys.version_info < (2, 7, 9) and not is_ol7:
            self.close_connection()
            raise RuntimeError(
                "The support for SSL is not available for this Python version"
            )

        self.protocol.set_capabilities(tls=True)
        self.stream.set_ssl(
            self.settings.get("tls-versions", None),
            self.settings.get("ssl-mode", SSLMode.REQUIRED),
            self.settings.get("ssl-ca"),
            self.settings.get("ssl-crl"),
            self.settings.get("ssl-cert"),
            self.settings.get("ssl-key"),
            self.settings.get("tls-ciphersuites"),
        )
        if "attributes" in self.settings:
            conn_attrs = self.settings["attributes"]
            self.protocol.set_capabilities(session_connect_attrs=conn_attrs)

    def _set_compression_capabilities(
        self,
        caps: Dict[str, Any],
        compression: str,
        algorithms: Optional[List[str]] = None,
    ) -> Optional[str]:
        """Set the compression capabilities.

        If compression is available, negociates client and server algorithms.
        By trying to find an algorithm from the requested compression
        algorithms list, which is supported by the server.

        If no compression algorithms list is provided, the following priority
        is used:

        1) zstd_stream
        2) lz4_message
        3) deflate_stream

        Args:
            caps (dict): Dictionary with the server capabilities.
            compression (str): The compression connection setting.
            algorithms (list): List of requested compression algorithms.

        Returns:
            str: The compression algorithm.

        .. versionadded:: 8.0.21
        .. versionchanged:: 8.0.22
        """
        compression_data = caps.get("compression")
        if compression_data is None:
            msg = "Compression requested but the server does not support it"
            if compression == Compression.REQUIRED:
                raise NotSupportedError(msg)
            logger.warning(msg)
            return None

        compression_dict = {}
        if isinstance(compression_data, dict):  # C extension is being used
            for fld in compression_data["value"]["obj"]["fld"]:
                compression_dict[fld["key"]] = [
                    value["scalar"]["v_string"]["value"].decode("utf-8")
                    for value in fld["value"]["array"]["value"]
                ]
        else:
            for fld in compression_data.value.obj.fld:
                compression_dict[fld.key] = [
                    value.scalar.v_string.value.decode("utf-8")
                    for value in fld.value.array.value
                ]

        server_algorithms = compression_dict.get("algorithm", [])
        algorithm = None

        # Try to find an algorithm from the requested compression algorithms
        # list, which is supported by the server
        if algorithms:
            # Resolve compression algorithms aliases and ignore unsupported
            client_algorithms = [
                COMPRESSION_ALGORITHMS[item]
                for item in algorithms
                if item in COMPRESSION_ALGORITHMS
            ]
            matched = [item for item in client_algorithms if item in server_algorithms]
            if matched:
                algorithm = COMPRESSION_ALGORITHMS.get(matched[0])
            elif compression == Compression.REQUIRED:
                raise InterfaceError(
                    "The connection compression is set as "
                    "required, but none of the provided "
                    "compression algorithms are supported."
                )
            else:
                return None  # Disable compression

        # No compression algorithms list was provided or couldn't found one
        # supported by the server
        if algorithm is None:
            if HAVE_ZSTD and "zstd_stream" in server_algorithms:
                algorithm = "zstd_stream"
            elif HAVE_LZ4 and "lz4_message" in server_algorithms:
                algorithm = "lz4_message"
            else:
                algorithm = "deflate_stream"

        if algorithm not in server_algorithms:
            msg = (
                "Compression requested but the compression algorithm "
                "negotiation failed"
            )
            if compression == Compression.REQUIRED:
                raise InterfaceError(msg)
            logger.warning(msg)
            return None

        self.protocol.set_capabilities(compression={"algorithm": algorithm})
        return algorithm

    def _authenticate(self) -> None:
        """Authenticate with the MySQL server."""
        auth = self.settings.get("auth")
        if auth:
            if auth == Auth.PLAIN:
                self._authenticate_plain()
            elif auth == Auth.SHA256_MEMORY:
                self._authenticate_sha256_memory()
            elif auth == Auth.MYSQL41:
                self._authenticate_mysql41()
        elif self.stream.is_secure():
            # Use PLAIN if no auth provided and connection is secure
            self._authenticate_plain()
        else:
            # Use MYSQL41 if connection is not secure
            try:
                self._authenticate_mysql41()
            except InterfaceError:
                pass
            else:
                return
            # Try SHA256_MEMORY if MYSQL41 fails
            try:
                self._authenticate_sha256_memory()
            except InterfaceError as err:
                raise InterfaceError(
                    "Authentication failed using MYSQL41 and "
                    "SHA256_MEMORY, check username and "
                    f"password or try a secure connection err:{err}"
                ) from err

    def _authenticate_mysql41(self) -> None:
        """Authenticate with the MySQL server using `MySQL41AuthPlugin`."""
        plugin = MySQL41AuthPlugin(self._user, self._password)
        self.protocol.send_auth_start(plugin.auth_name())
        extra_data = self.protocol.read_auth_continue()
        self.protocol.send_auth_continue(plugin.auth_data(extra_data))
        self.protocol.read_auth_ok()

    def _authenticate_plain(self) -> None:
        """Authenticate with the MySQL server using `PlainAuthPlugin`."""
        if not self.stream.is_secure():
            raise InterfaceError(
                "PLAIN authentication is not allowed via unencrypted connection"
            )
        plugin = PlainAuthPlugin(self._user, self._password)
        self.protocol.send_auth_start(plugin.auth_name(), auth_data=plugin.auth_data())
        self.protocol.read_auth_ok()

    def _authenticate_sha256_memory(self) -> None:
        """Authenticate with the MySQL server using `Sha256MemoryAuthPlugin`."""
        plugin = Sha256MemoryAuthPlugin(self._user, self._password)
        self.protocol.send_auth_start(plugin.auth_name())
        extra_data = self.protocol.read_auth_continue()
        self.protocol.send_auth_continue(plugin.auth_data(extra_data))
        self.protocol.read_auth_ok()

    def _deallocate_statement(self, statement: StatementType) -> None:
        """Deallocates statement.

        Args:
            statement (Statement): A `Statement` based type object.
        """
        if statement.prepared:
            self.protocol.send_prepare_deallocate(statement.stmt_id)
            self._prepared_stmt_ids.remove(statement.stmt_id)
            statement.prepared = False

    def _prepare_statement(
        self,
        msg_type: str,
        msg: MessageType,
        statement: Union[
            FindStatement,
            DeleteStatement,
            ModifyStatement,
            ReadStatement,
            RemoveStatement,
            UpdateStatement,
        ],
    ) -> None:
        """Prepares a statement.

        Args:
            msg_type (str): Message ID string.
            msg (mysqlx.protobuf.Message): MySQL X Protobuf Message.
            statement (Statement): A `Statement` based type object.
        """
        try:
            self.fetch_active_result()
            self.protocol.send_prepare_prepare(msg_type, msg, statement)
        except NotSupportedError:
            self._prepared_stmt_supported = False
            return
        self._prepared_stmt_ids.append(statement.stmt_id)
        statement.prepared = True

    def _execute_prepared_pipeline(
        self,
        msg_type: str,
        msg: MessageType,
        statement: Union[
            FindStatement,
            DeleteStatement,
            ModifyStatement,
            ReadStatement,
            RemoveStatement,
            UpdateStatement,
        ],
    ) -> None:
        """Executes the prepared statement pipeline.

        Args:
            msg_type (str): Message ID string.
            msg (mysqlx.protobuf.Message): MySQL X Protobuf Message.
            statement (Statement): A `Statement` based type object.
        """
        # For old servers without prepared statement support
        if not self._prepared_stmt_supported:
            # Crud::<Operation>
            self.protocol.send_msg_without_ps(msg_type, msg, statement)
            return

        if statement.deallocate_prepare_execute:
            # Prepare::Deallocate + Prepare::Prepare + Prepare::Execute
            self._deallocate_statement(statement)
            self._prepare_statement(msg_type, msg, statement)
            if not self._prepared_stmt_supported:
                self.protocol.send_msg_without_ps(msg_type, msg, statement)
                return
            self.protocol.send_prepare_execute(msg_type, msg, statement)
            statement.deallocate_prepare_execute = False
            statement.reset_exec_counter()
        elif statement.prepared and not statement.changed:
            # Prepare::Execute
            self.protocol.send_prepare_execute(msg_type, msg, statement)
        elif statement.changed and not statement.repeated:
            # Crud::<Operation>
            self._deallocate_statement(statement)
            self.protocol.send_msg_without_ps(msg_type, msg, statement)
            statement.changed = False
            statement.reset_exec_counter()
        elif not statement.changed and not statement.repeated:
            # Prepare::Prepare + Prepare::Execute
            if not statement.prepared:
                self._prepare_statement(msg_type, msg, statement)
            if not self._prepared_stmt_supported:
                self.protocol.send_msg_without_ps(msg_type, msg, statement)
                return
            self.protocol.send_prepare_execute(msg_type, msg, statement)
        elif statement.changed and statement.repeated:
            # Prepare::Deallocate + Crud::<Operation>
            self._deallocate_statement(statement)
            self.protocol.send_msg_without_ps(msg_type, msg, statement)
            statement.changed = False
            statement.reset_exec_counter()

        statement.increment_exec_counter()

    @catch_network_exception
    def send_sql(self, statement: SqlStatement) -> SqlResult:
        """Execute a SQL statement.

        Args:
            sql (str): The SQL statement.

        Raises:
            :class:`mysqlx.ProgrammingError`: If the SQL statement is not a
                                              valid string.
        """
        sql = statement.sql
        if self.protocol is None:
            raise OperationalError("MySQLx Connection not available")
        if not isinstance(sql, str):
            raise ProgrammingError("The SQL statement is not a valid string")
        msg_type, msg = self.protocol.build_execute_statement("sql", sql)
        self.protocol.send_msg_without_ps(msg_type, msg, statement)
        return SqlResult(self)

    @catch_network_exception
    def send_insert(self, statement: Union[AddStatement, InsertStatement]) -> Result:
        """Send an insert statement.

        Args:
            statement (`Statement`): It can be :class:`mysqlx.InsertStatement`
                                     or :class:`mysqlx.AddStatement`.

        Returns:
            :class:`mysqlx.Result`: A result object.
        """
        if self.protocol is None:
            raise OperationalError("MySQLx Connection not available")
        msg_type, msg = self.protocol.build_insert(statement)
        self.protocol.send_msg(msg_type, msg)
        ids = None
        if isinstance(statement, AddStatement):
            ids = statement.ids
        return Result(self, ids)

    @catch_network_exception
    def send_find(
        self, statement: Union[FindStatement, SelectStatement]
    ) -> Union[DocResult, RowResult]:
        """Send an find statement.

        Args:
            statement (`Statement`): It can be :class:`mysqlx.SelectStatement`
                                     or :class:`mysqlx.FindStatement`.

        Returns:
            `Result`: It can be class:`mysqlx.DocResult` or
                      :class:`mysqlx.RowResult`.
        """
        msg_type, msg = self.protocol.build_find(statement)
        self._execute_prepared_pipeline(msg_type, msg, statement)
        return DocResult(self) if statement.is_doc_based() else RowResult(self)

    @catch_network_exception
    def send_delete(self, statement: Union[DeleteStatement, RemoveStatement]) -> Result:
        """Send an delete statement.

        Args:
            statement (`Statement`): It can be :class:`mysqlx.RemoveStatement`
                                     or :class:`mysqlx.DeleteStatement`.

        Returns:
            :class:`mysqlx.Result`: The result object.
        """
        msg_type, msg = self.protocol.build_delete(statement)
        self._execute_prepared_pipeline(msg_type, msg, statement)
        return Result(self)

    @catch_network_exception
    def send_update(self, statement: Union[ModifyStatement, UpdateStatement]) -> Result:
        """Send an delete statement.

        Args:
            statement (`Statement`): It can be :class:`mysqlx.ModifyStatement`
                                     or :class:`mysqlx.UpdateStatement`.

        Returns:
            :class:`mysqlx.Result`: The result object.
        """
        msg_type, msg = self.protocol.build_update(statement)
        self._execute_prepared_pipeline(msg_type, msg, statement)
        return Result(self)

    @catch_network_exception
    def execute_nonquery(
        self,
        namespace: str,
        cmd: str,
        raise_on_fail: bool,
        fields: Optional[Dict[str, Any]] = None,
    ) -> Optional[Result]:
        """Execute a non query command.

        Args:
            namespace (str): The namespace.
            cmd (str): The command.
            raise_on_fail (bool): `True` to raise on fail.
            fields (Optional[dict]): The message fields.

        Raises:
            :class:`mysqlx.OperationalError`: On errors.

        Returns:
            :class:`mysqlx.Result`: The result object.
        """
        try:
            msg_type, msg = self.protocol.build_execute_statement(
                namespace, cmd, fields
            )
            self.protocol.send_msg(msg_type, msg)
            return Result(self)
        except OperationalError:
            if raise_on_fail:
                raise
        return None

    @catch_network_exception
    def execute_sql_scalar(self, sql: StatementType) -> int:
        """Execute a SQL scalar.

        Args:
            sql (str): The SQL statement.

        Raises:
            :class:`mysqlx.InterfaceError`: If no data found.

        Returns:
            :class:`mysqlx.Result`: The result.
        """
        msg_type, msg = self.protocol.build_execute_statement("sql", sql)
        self.protocol.send_msg(msg_type, msg)
        result = RowResult(self)
        result.fetch_all()
        if result.count == 0:
            raise InterfaceError("No data found")
        return result[0][0]  # type: ignore[index]

    @catch_network_exception
    def get_row_result(self, cmd: str, fields: Dict[str, Any]) -> RowResult:
        """Returns the row result.

        Args:
            cmd (str): The command.
            fields (dict): The message fields.

        Returns:
            :class:`mysqlx.RowResult`: The result object.
        """
        msg_type, msg = self.protocol.build_execute_statement("mysqlx", cmd, fields)
        self.protocol.send_msg(msg_type, msg)
        return RowResult(self)

    @catch_network_exception
    def read_row(self, result: RowResult) -> Optional[MessageType]:
        """Read row.

        Args:
            result (:class:`mysqlx.RowResult`): The result object.
        """
        return self.protocol.read_row(result)

    @catch_network_exception
    def close_result(self, result: Result) -> None:
        """Close result.

        Args:
            result (:class:`mysqlx.Result`): The result object.
        """
        self.protocol.close_result(result)

    @catch_network_exception
    def get_column_metadata(self, result: Result) -> List[ColumnType]:
        """Get column metadata.

        Args:
            result (:class:`mysqlx.Result`): The result object.
        """
        return self.protocol.get_column_metadata(result)

    def get_next_statement_id(self) -> int:
        """Returns the next statement ID.

        Returns:
            int: A statement ID.

        .. versionadded:: 8.0.16
        """
        self._stmt_counter += 1
        return self._stmt_counter

    def is_open(self) -> bool:
        """Check if connection is open.

        Returns:
            bool: `True` if connection is open.
        """
        return self.stream.is_open()

    def set_server_disconnected(self, reason: Union[str, Tuple[str, int]]) -> None:
        """Set the disconnection message from the server.

        Args:
            reason (str): disconnection reason from the server.
        """
        self._server_disconnected = True
        self._server_disconnected_reason = reason

    def is_server_disconnected(self) -> bool:
        """Verify if the session has been disconnect from the server.

        Returns:
            bool: `True` if the connection has been closed from the server
                  otherwise `False`.
        """
        return self._server_disconnected

    def get_disconnected_reason(self) -> Optional[Union[str, Tuple[str, int]]]:
        """Get the disconnection message sent by the server.

        Returns:
            string: disconnection reason from the server.
        """
        return self._server_disconnected_reason

    def disconnect(self) -> None:
        """Disconnect from server."""
        if not self.is_open():
            return
        self.stream.close()

    def close_session(self) -> None:
        """Close a sucessfully authenticated session."""
        if not self.is_open():
            return

        try:
            # Fetch any active result
            self.fetch_active_result()
            # Deallocate all prepared statements
            if self._prepared_stmt_supported:
                for stmt_id in self._prepared_stmt_ids:
                    self.protocol.send_prepare_deallocate(stmt_id)
                self._stmt_counter = 0
            # Send session close
            self.protocol.send_close()
            self.protocol.read_ok()
        except (InterfaceError, OperationalError, OSError) as err:
            logger.warning(
                "Warning: An error occurred while attempting to close the "
                "connection: %s",
                err,
            )
        finally:
            # The remote connection with the server has been lost,
            # close the connection locally.
            self.stream.close()

    def reset_session(self) -> None:
        """Reset a sucessfully authenticated session."""
        if not self.is_open():
            return
        if self._active_result is not None:
            self._active_result.fetch_all()
        try:
            self.keep_open = self.protocol.send_reset(self.keep_open)
        except (InterfaceError, OperationalError) as err:
            logger.warning(
                "Warning: An error occurred while attempting to reset the "
                "session: %s",
                err,
            )

    def close_connection(self) -> None:
        """Announce to the server that the client wants to close the
        connection. Discards any session state of the server.
        """
        if not self.is_open():
            return
        if self._active_result is not None:
            self._active_result.fetch_all()
        self.protocol.send_connection_close()
        self.protocol.read_ok()
        self.stream.close()


class PooledConnection(Connection):
    """Class to hold :class:`Connection` instances in a pool.

    PooledConnection is used by :class:`ConnectionPool` to facilitate the
    connection to return to the pool once is not required, more specifically
    once the close_session() method is invoked. It works like a normal
    Connection except for methods like close() and sql().

    The close_session() method will add the connection back to the pool rather
    than disconnecting from the MySQL server.

    The sql() method is used to execute sql statements.

    Args:
        pool (ConnectionPool): The pool where this connection must return.

    .. versionadded:: 8.0.13
    """

    def __init__(self, pool: ConnectionPool) -> None:
        if not isinstance(pool, ConnectionPool):
            raise AttributeError("pool should be a ConnectionPool object")
        super().__init__(pool.cnx_config)
        self.pool: ConnectionPool = pool
        self.host: str = pool.cnx_config["host"]
        self.port: int = pool.cnx_config["port"]

    def close_connection(self) -> None:
        """Closes the connection.

        This method closes the socket.
        """
        super().close_session()

    def close_session(self) -> None:
        """Do not close, but add connection back to pool.

        The close_session() method does not close the connection with the
        MySQL server. The connection is added back to the pool so it
        can be reused.

        When the pool is configured to reset the session, the session
        state will be cleared by re-authenticating the user once the connection
        is get from the pool.
        """
        self.pool.add_connection(self)

    def reconnect(self) -> None:
        """Reconnect this connection."""
        if self._active_result is not None:
            self._active_result.fetch_all()
        self._authenticate()

    def reset(self) -> None:
        """Reset the connection.

        Resets the connection by re-authenticate.
        """
        self.reconnect()

    def sql(self, sql: str) -> SqlStatement:
        """Creates a :class:`mysqlx.SqlStatement` object to allow running the
        SQL statement on the target MySQL Server.

        Args:
            sql (string): The SQL statement to be executed.

        Returns:
            mysqlx.SqlStatement: SqlStatement object.
        """
        return SqlStatement(self, sql)


class ConnectionPool(queue.Queue):
    """This class represents a pool of connections.

    Initializes the Pool with the given name and settings.

    Args:
        name (str): The name of the pool, used to track a single pool per
                    combination of host and user.
        **kwargs:
            max_size (int): The maximun number of connections to hold in
                            the pool.
            reset_session (bool): If the connection should be reseted when
                                  is taken from the pool.
            max_idle_time (int): The maximum number of milliseconds to allow
                                 a connection to be idle in the queue before
                                 being closed. Zero value means infinite.
            queue_timeout (int): The maximum number of milliseconds a
                                 request will wait for a connection to
                                 become available. A zero value means
                                 infinite.
            priority (int): The router priority, to choose this pool over
                            other with lower priority.

    Raises:
        :class:`mysqlx.PoolError` on errors.

    .. versionadded:: 8.0.13
    """

    def __init__(self, name: str, **kwargs: Any) -> None:
        self._set_pool_name(name)
        self._open_sessions: int = 0
        self._connections_openned: List[PooledConnection] = []
        self._available: bool = True
        self._timeout: int = 0
        self._timeout_stamp: datetime = datetime.now()
        self.pool_max_size: int = kwargs.get("max_size", 25)
        # Can't invoke super due to Queue not is a new-style class
        queue.Queue.__init__(self, self.pool_max_size)
        self.reset_session: bool = kwargs.get("reset_session", True)
        self.max_idle_time: int = kwargs.get("max_idle_time", 25)
        self.settings: Dict[str, Any] = kwargs
        self.queue_timeout: int = kwargs.get("queue_timeout", 25)
        self.priority: int = kwargs.get("priority", 0)
        self.cnx_config: Dict[str, Any] = kwargs
        self.host: str = kwargs["host"]
        self.port: int = kwargs["port"]

    def _set_pool_name(self, pool_name: str) -> None:
        r"""Set the name of the pool.

        This method checks the validity and sets the name of the pool.

        Args:
            pool_name (str): The pool name.

        Raises:
            AttributeError: If the pool_name contains illegal characters
                            ([^a-zA-Z0-9._\-*$#]) or is longer than
                            connection._CNX_POOL_MAX_NAME_SIZE.
        """
        if _CNX_POOL_NAME_REGEX.search(pool_name):
            raise AttributeError(f"Pool name '{pool_name}' contains illegal characters")
        if len(pool_name) > _CNX_POOL_MAX_NAME_SIZE:
            raise AttributeError(f"Pool name '{pool_name}' is too long")
        self.name = pool_name

    @property
    def open_connections(self) -> int:
        """Returns the number of open connections that can return to this pool."""
        return len(self._connections_openned)

    def remove_connection(self, cnx: Optional[PooledConnection] = None) -> None:
        """Removes a connection from this pool.

        Args:
            cnx (PooledConnection): The connection object.
        """
        self._connections_openned.remove(cnx)

    def remove_connections(self) -> None:
        """Removes all the connections from the pool."""
        while self.qsize() > 0:
            try:
                cnx = self.get(block=True, timeout=self.queue_timeout)
            except queue.Empty:
                pass
            else:
                try:
                    cnx.close_connection()
                except (RuntimeError, OSError, InterfaceError):
                    pass
                finally:
                    self.remove_connection(cnx)

    def add_connection(self, cnx: Optional[PooledConnection] = None) -> None:
        """Adds a connection to this pool.

        This method instantiates a Connection using the configuration passed
        when initializing the ConnectionPool instance or using the set_config()
        method.
        If cnx is a Connection instance, it will be added to the queue.

        Args:
            cnx (PooledConnection): The connection object.

        Raises:
            PoolError: If no configuration is set, if no more connection can
                       be added (maximum reached) or if the connection can not
                       be instantiated.
        """
        if not self.cnx_config:
            raise PoolError("Connection configuration not available")

        if self.full():
            raise PoolError("Failed adding connection; queue is full")

        if not cnx:
            cnx = PooledConnection(self)
            # mysqlx_wait_timeout is only available on MySQL 8
            ver = cnx.sql(_SELECT_VERSION_QUERY).execute().fetch_all()[0][0]
            if tuple(int(n) for n in ver.split("-")[0].split(".")) > (
                8,
                0,
                10,
            ):
                cnx.sql(f"set mysqlx_wait_timeout = {self.max_idle_time}").execute()
            self._connections_openned.append(cnx)
        else:
            if not isinstance(cnx, PooledConnection):
                raise PoolError("Connection instance not subclass of PooledSession")
            if cnx.is_server_disconnected():
                self.remove_connections()
                cnx.close()  # type: ignore[attr-defined]

        self.queue_connection(cnx)

    def queue_connection(self, cnx: PooledConnection) -> None:
        """Put connection back in the queue:

        This method is putting a connection back in the queue.
        It will not acquire a lock as the methods using _queue_connection() will
        have it set.

        Args:
            PooledConnection: The connection object.

        Raises:
            PoolError: On errors.
        """
        if not isinstance(cnx, PooledConnection):
            raise PoolError("Connection instance not subclass of PooledSession.")

        # Reset the connection
        if self.reset_session:
            cnx.reset_session()
        try:
            self.put(cnx, block=False)
        except queue.Full as err:
            raise PoolError("Failed adding connection; queue is full") from err

    def track_connection(self, connection: PooledConnection) -> None:
        """Tracks connection in order of close it when client.close() is invoke."""
        self._connections_openned.append(connection)

    def __str__(self) -> str:
        return self.name

    def available(self) -> bool:
        """Returns if this pool is available for pool connections from it.

        Returns:
            bool: True if this pool is available else False.
        .. versionadded:: 8.0.20
        """
        return self._available

    def set_unavailable(self, time_out: int = -1) -> None:
        """Sets this pool unavailable for a period of time (in seconds).

        .. versionadded:: 8.0.20
        """
        if self._available:
            logger.warning(
                "ConnectionPool.set_unavailable pool: %s time_out: %s",
                self,
                time_out,
            )
            self._available = False
            self._timeout_stamp = datetime.now()
            self._timeout = time_out

    def set_available(self) -> None:
        """Sets this pool available for pool connections from it.

        .. versionadded:: 8.0.20
        """
        self._available = True
        self._timeout_stamp = datetime.now()

    def get_timeout_stamp(self) -> Tuple[int, datetime]:
        """Returns the penalized time (timeout) and the time at the penalty.

        Returns:
            tuple: penalty seconds (int), timestamp at penalty (datetime object)
        .. versionadded:: 8.0.20
        """
        return (self._timeout, self._timeout_stamp)

    def close(self) -> None:
        """Empty this ConnectionPool."""
        for cnx in self._connections_openned:
            cnx.close_connection()


class PoolsManager:
    """Manages a pool of connections for a host or hosts in routers.

    This class handles all the pools of Connections.

    .. versionadded:: 8.0.13
    """

    __instance: PoolsManager = None
    __pools: Dict[str, Any] = {}

    def __new__(cls) -> PoolsManager:
        if PoolsManager.__instance is None:
            PoolsManager.__instance = object.__new__(cls)
            PoolsManager.__pools = {}
        return PoolsManager.__instance

    def _pool_exists(self, client_id: str, pool_name: str) -> bool:
        """Verifies if a pool exists with the given name.

        Args:
            client_id (str): The client id.
            pool_name (str): The name of the pool.

        Returns:
            bool: Returns `True` if the pool exists otherwise `False`.
        """
        pools = self.__pools.get(client_id, [])
        for pool in pools:
            if pool.name == pool_name:
                return True
        return False

    def _get_pools(self, settings: Dict[str, Any]) -> List:
        """Retrieves a list of pools that shares the given settings.

        Args:
            settings (dict): the configuration of the pool.

        Returns:
            list: A list of pools that shares the given settings.
        """
        available_pools = []
        pool_names = []
        connections_settings = self._get_connections_settings(settings)

        # Generate the names of the pools this settings can connect to
        for router_name, _ in connections_settings:
            pool_names.append(router_name)

        # Generate the names of the pools this settings can connect to
        for pool in self.__pools.get(settings.get("client_id", "No id"), []):
            if pool.name in pool_names:
                available_pools.append(pool)
        return available_pools

    @staticmethod
    def _get_connections_settings(
        settings: Dict[str, Any]
    ) -> List[Tuple[str, Dict[str, Any]]]:
        """Generates a list of separated connection settings for each host.

        Gets a list of connection settings for each host or router found in the
        given settings.

        Args:
            settings (dict): The configuration for the connections.

        Returns:
            list: A list of connections settings
        """
        pool_settings = settings.copy()
        routers = pool_settings.get("routers", [])
        connections_settings = []
        if "routers" in pool_settings:
            pool_settings.pop("routers")
        if "host" in pool_settings and "port" in pool_settings:
            routers.append(
                {
                    "priority": 100,
                    "weight": 0,
                    "host": pool_settings["host"],
                    "port": pool_settings["port"],
                }
            )
        # Order routers
        routers.sort(key=lambda x: (x["priority"], -x.get("weight", 0)))
        for router in routers:
            connection_settings = pool_settings.copy()
            connection_settings["host"] = router["host"]
            connection_settings["port"] = router["port"]
            connection_settings["priority"] = router["priority"]
            connection_settings["weight"] = router.get("weight", 0)
            connections_settings.append(
                (
                    generate_pool_name(**connection_settings),
                    connection_settings,
                )
            )
        return connections_settings

    def create_pool(self, cnx_settings: Dict[str, Any]) -> None:
        """Creates a `ConnectionPool` instance to hold the connections.

        Creates a `ConnectionPool` instance to hold the connections only if
        no other pool exists with the same configuration.

        Args:
            cnx_settings (dict): The configuration for the connections.
        """
        connections_settings = self._get_connections_settings(cnx_settings)

        # Subscribe client if it does not exists
        if cnx_settings.get("client_id", "No id") not in self.__pools:
            self.__pools[cnx_settings.get("client_id", "No id")] = []

        # Create a pool for each router
        for router_name, settings in connections_settings:
            if self._pool_exists(cnx_settings.get("client_id", "No id"), router_name):
                continue
            pool = self.__pools.get(cnx_settings.get("client_id", "No id"), [])
            pool.append(ConnectionPool(router_name, **settings))

    @staticmethod
    def _get_random_pool(pool_list: List[ConnectionPool]) -> ConnectionPool:
        """Get a random router from the group with the given priority.

        Returns:
            Router: a random router.

        .. versionadded:: 8.0.20
        """
        if not pool_list:
            return None
        if len(pool_list) == 1:
            return pool_list[0]

        last = len(pool_list) - 1
        index = random.randint(0, last)
        return pool_list[index]

    @staticmethod
    def _get_sublist(
        pools: List[ConnectionPool], index: int, cur_priority: int
    ) -> List[ConnectionPool]:
        sublist = []
        next_priority = None
        while index < len(pools):
            next_priority = pools[index].priority
            if cur_priority == next_priority and pools[index].available():
                sublist.append(pools[index])
            elif cur_priority != next_priority:
                break
            index += 1
        return sublist

    def _get_next_pool(
        self, pools: List[ConnectionPool], cur_priority: int
    ) -> ConnectionPool:
        index = 0
        for pool in pools:
            if pool.available() and cur_priority == pool.priority:
                break
            index += 1
        subpool: List = []
        while not subpool and index < len(pools):
            subpool = self._get_sublist(pools, index, cur_priority)
            index += 1
        return self._get_random_pool(subpool)

    @staticmethod
    def _get_next_priority(
        pools: List[ConnectionPool], cur_priority: Optional[int] = None
    ) -> int:
        if cur_priority is None and pools:
            return pools[0].priority
        # find the first pool that does not share the same priority
        for t_pool in pools:
            if t_pool.available():
                cur_priority = t_pool.priority
                return cur_priority
        return pools[0].priority

    def _check_unavailable_pools(
        self, settings: Dict[str, Any], revive: Optional[bool] = None
    ) -> None:
        pools = self._get_pools(settings)
        for pool in pools:
            if pool.available():
                continue
            timeout, timeout_stamp = pool.get_timeout_stamp()
            if revive:
                timeout = revive
            if datetime.now() > (timeout_stamp + timedelta(seconds=timeout)):
                pool.set_available()

    def get_connection(self, settings: Dict[str, Any]) -> PooledConnection:
        """Get a connection from the pool.

        This method returns an `PooledConnection` instance which has a reference
        to the pool that created it, and can be used as a normal Connection.

        When the MySQL connection is not connected, a reconnect is attempted.

        Raises:
            :class:`PoolError`: On errors.

        Returns:
            PooledConnection: A pooled connection object.
        """

        def set_mysqlx_wait_timeout(cnx: PooledConnection) -> None:
            ver = cnx.sql(_SELECT_VERSION_QUERY).execute().fetch_all()[0][0]
            # mysqlx_wait_timeout is only available on MySQL 8
            if tuple(int(n) for n in ver.split("-")[0].split(".")) > (
                8,
                0,
                10,
            ):
                cnx.sql(f"set mysqlx_wait_timeout = {pool.max_idle_time}").execute()

        pools = self._get_pools(settings)
        cur_priority = settings.get("cur_priority", None)
        error_list = []
        self._check_unavailable_pools(settings)
        cur_priority = self._get_next_priority(pools, cur_priority)
        if cur_priority is None:
            raise PoolError(
                "Unable to connect to any of the target hosts. No pool is available"
            )
        settings["cur_priority"] = cur_priority
        pool = self._get_next_pool(pools, cur_priority)
        lock = threading.RLock()
        while pool is not None:
            try:
                # Check connections aviability in this pool
                if pool.qsize() > 0:
                    # We have connections in pool, try to return a working one
                    with lock:
                        try:
                            cnx = pool.get(block=True, timeout=pool.queue_timeout)
                        except queue.Empty:
                            raise PoolError(
                                "Failed getting connection; pool exhausted"
                            ) from None
                        try:
                            if cnx.is_server_disconnected():
                                pool.remove_connections()
                            # Only reset the connection by re-authentification
                            # if the connection was unable to keep open by the
                            # server
                            if not cnx.keep_open:
                                cnx.reset()
                            set_mysqlx_wait_timeout(cnx)
                        except (RuntimeError, OSError, InterfaceError):
                            # Unable to reset connection, close and remove
                            try:
                                cnx.close_connection()
                            except (RuntimeError, OSError, InterfaceError):
                                pass
                            finally:
                                pool.remove_connection(cnx)
                            # By WL#13222 all idle sessions that connect to the
                            # same endpoint should be removed from the pool.
                            while pool.qsize() > 0:
                                try:
                                    cnx = pool.get(
                                        block=True, timeout=pool.queue_timeout
                                    )
                                except queue.Empty:
                                    pass
                                else:
                                    try:
                                        cnx.close_connection()
                                    except (RuntimeError, OSError, InterfaceError):
                                        pass
                                    finally:
                                        pool.remove_connection(cnx)
                            # Connection was closed by the server, create new
                            try:
                                cnx = PooledConnection(pool)
                                pool.track_connection(cnx)
                                cnx.connect()
                                set_mysqlx_wait_timeout(cnx)
                            except (RuntimeError, OSError, InterfaceError):
                                pass
                            finally:
                                # Server must be down, take down idle
                                # connections from this pool
                                while pool.qsize() > 0:
                                    try:
                                        cnx = pool.get(
                                            block=True,
                                            timeout=pool.queue_timeout,
                                        )
                                        cnx.close_connection()
                                        pool.remove_connection(cnx)
                                    except (RuntimeError, OSError, InterfaceError):
                                        pass
                        return cnx
                elif pool.open_connections < pool.pool_max_size:
                    # No connections in pool, but we can open a new one
                    cnx = PooledConnection(pool)
                    pool.track_connection(cnx)
                    cnx.connect()
                    set_mysqlx_wait_timeout(cnx)
                    return cnx
                else:
                    # Pool is exaust so the client needs to wait
                    with lock:
                        try:
                            cnx = pool.get(block=True, timeout=pool.queue_timeout)
                            cnx.reset()
                            set_mysqlx_wait_timeout(cnx)
                            return cnx
                        except queue.Empty:
                            raise PoolError("pool max size has been reached") from None
            except (InterfaceError, TimeoutError, PoolError) as err:
                error_list.append(f"pool: {pool} error: {err}")
                if isinstance(err, PoolError):
                    # Pool can be exhaust now but can be ready again in no time,
                    # e.g a connection is returned to the pool.
                    pool.set_unavailable(2)
                else:
                    self.set_pool_unavailable(pool, err)

                self._check_unavailable_pools(settings)
                # Try next pool with the same priority
                pool = self._get_next_pool(pools, cur_priority)

                if pool is None:
                    cur_priority = self._get_next_priority(pools, cur_priority)
                    settings["cur_priority"] = cur_priority
                    pool = self._get_next_pool(pools, cur_priority)
                    if pool is None:
                        msg = "\n  ".join(error_list)
                        raise PoolError(
                            "Unable to connect to any of the target hosts: "
                            f"[\n  {msg}\n]"
                        ) from err
                continue

        raise PoolError("Unable to connect to any of the target hosts")

    def close_pool(self, cnx_settings: Dict[str, Any]) -> int:
        """Closes the connections in the pools

        Returns:
            int: The number of closed pools
        """
        pools = self._get_pools(cnx_settings)
        for pool in pools:
            pool.close()
            # Remove the pool
            if cnx_settings.get("client_id", None) is not None:
                client_pools = self.__pools.get(cnx_settings.get("client_id"))
                if pool in client_pools:
                    client_pools.remove(pool)
        return len(pools)

    @staticmethod
    def set_pool_unavailable(
        pool: ConnectionPool, err: Union[InterfaceError, TimeoutError]
    ) -> None:
        """Sets a pool as unavailable.

        The time a pool is set unavailable depends on the given error message
        or the error number.

        Args:
            pool (ConnectionPool): The pool to set unavailable.
            err (Exception): The raised exception raised by a connection belonging
                             to the pool.
        """
        penalty = None
        try:
            err_no = err.errno
            penalty = _TIMEOUT_PENALTIES_BY_ERR_NO[err_no]
        except (AttributeError, KeyError):
            pass
        if not penalty:
            err_msg = err.msg
            for key, value in _TIMEOUT_PENALTIES.items():
                if key in err_msg:
                    penalty = value
        if penalty:
            pool.set_unavailable(penalty)
        else:
            # Other errors are severe punished
            pool.set_unavailable(100000)


class Session:
    """Enables interaction with a X Protocol enabled MySQL Product.

    The functionality includes:

    - Accessing available schemas.
    - Schema management operations.
    - Retrieval of connection information.

    Args:
        settings (dict): Connection data used to connect to the database.
    """

    def __init__(self, settings: Dict[str, Any]) -> None:
        self.use_pure: bool = settings.get("use-pure", Protobuf.use_pure)
        self._settings: Dict[str, Any] = settings

        # Check for DNS SRV
        if settings.get("host") and settings.get("dns-srv"):
            if not HAVE_DNSPYTHON:
                raise InterfaceError(
                    "MySQL host configuration requested DNS "
                    "SRV. This requires the Python dnspython "
                    "module. Please refer to documentation"
                )
            try:
                srv_records = dns.resolver.query(settings["host"], "SRV")
            except dns.exception.DNSException as err:
                raise InterfaceError(
                    f"Unable to locate any hosts for '{settings['host']}'"
                ) from err
            self._settings["routers"] = []
            for srv in srv_records:
                self._settings["routers"].append(
                    {
                        "host": srv.target.to_text(omit_final_dot=True),
                        "port": srv.port,
                        "priority": srv.priority,
                        "weight": srv.weight,
                    }
                )

        if (
            "connection-attributes" not in self._settings
            or self._settings["connection-attributes"] is not False
        ):
            self._settings["attributes"] = {}
            self._init_attributes()

        if "pooling" in settings and settings["pooling"]:
            # Create pool and retrieve a Connection instance
            PoolsManager().create_pool(settings)
            self._connection: Connection = PoolsManager().get_connection(settings)
            if self._connection is None:
                raise PoolError("Connection could not be retrieved from pool")
        else:
            self._connection = Connection(self._settings)
            self._connection.connect()
        # Set default schema
        schema = self._settings.get("schema")
        if schema:
            try:
                self.sql(f"USE {quote_identifier(schema)}").execute()
            except OperationalError as err:
                # Access denied for user will raise err.errno = 1044
                errmsg = (
                    err.msg
                    if err.errno == 1044
                    else f"Default schema '{schema}' does not exists"
                )
                raise InterfaceError(errmsg, err.errno) from err

    def __enter__(self) -> Session:
        return self

    def __exit__(
        self,
        exc_type: Type[BaseException],
        exc_value: BaseException,
        traceback: TracebackType,
    ) -> None:
        self.close()

    def _init_attributes(self) -> None:
        """Setup default and user defined connection-attributes."""
        if os.name == "nt":
            if "64" in platform.architecture()[0]:
                platform_arch: Union[str, Tuple[str, str]] = "x86_64"
            elif "32" in platform.architecture()[0]:
                platform_arch = "i386"
            else:
                platform_arch = platform.architecture()
            os_ver = f"Windows-{platform.win32_ver()[1]}"
        else:
            platform_arch = platform.machine()
            if platform.system() == "Darwin":
                os_ver = f"macOS-{platform.mac_ver()[0]}"
            else:
                os_ver = "-".join(linux_distribution()[0:2])

        license_chunks = LICENSE.split(" ")
        if license_chunks[0] == "GPLv2":
            client_license = "GPL-2.0"
        else:
            client_license = "Commercial"

        default_attributes = {
            # Process id
            "_pid": str(os.getpid()),
            # Platform architecture
            "_platform": platform_arch,
            # OS version
            "_os": os_ver,
            # Hostname of the local machine
            "_source_host": socket.gethostname(),
            # Client's name
            "_client_name": "mysql-connector-python",
            # Client's version
            "_client_version": ".".join([str(x) for x in VERSION[0:3]]),
            # Client's License identifier
            "_client_license": client_license,
        }
        self._settings["attributes"].update(default_attributes)

        if "connection-attributes" in self._settings:
            for attr_name in self._settings["connection-attributes"]:
                attr_value = self._settings["connection-attributes"][attr_name]
                # Validate name type
                if not isinstance(attr_name, str):
                    raise InterfaceError(
                        f"Attribute name '{attr_name}' must be a string type"
                    )
                # Validate attribute name limit 32 characters
                if len(attr_name) > 32:
                    raise InterfaceError(
                        f"Attribute name '{attr_name}' exceeds 32 characters "
                        "limit size"
                    )
                # Validate names in connection-attributes cannot start with "_"
                if attr_name.startswith("_"):
                    raise InterfaceError(
                        "Key names in 'session-connect-attributes' cannot "
                        f"start with '_', found: {attr_name}"
                    )
                # Validate value type
                if not isinstance(attr_value, str):
                    raise InterfaceError(
                        f"Attribute name '{attr_name}' value '{attr_value}' "
                        " must be a string type"
                    )

                # Validate attribute value limit 1024 characters
                if len(attr_value) > 1024:
                    raise InterfaceError(
                        f"Attribute name '{attr_name}' value: '{attr_value}' "
                        "exceeds 1024 characters limit size"
                    )

                self._settings["attributes"][attr_name] = attr_value

    @property
    def use_pure(self) -> bool:
        """bool: `True` to use pure Python Protobuf implementation."""
        return Protobuf.use_pure

    @use_pure.setter
    def use_pure(self, value: bool) -> None:
        if not isinstance(value, bool):
            raise ProgrammingError("'use_pure' option should be True or False")
        Protobuf.set_use_pure(value)

    def is_open(self) -> bool:
        """Returns `True` if the session is open.

        Returns:
            bool: Returns `True` if the session is open.
        """
        return self._connection.stream.is_open()

    def sql(self, sql: str) -> SqlStatement:
        """Creates a :class:`mysqlx.SqlStatement` object to allow running the
        SQL statement on the target MySQL Server.

        Args:
            sql (string): The SQL statement to be executed.

        Returns:
            mysqlx.SqlStatement: SqlStatement object.
        """
        return SqlStatement(self._connection, sql)

    def get_connection(self) -> Connection:
        """Returns the underlying connection.

        Returns:
            mysqlx.connection.Connection: The connection object.
        """
        return self._connection

    def get_schemas(self) -> List[str]:
        """Returns the list of schemas in the current session.

        Returns:
            `list`: The list of schemas in the current session.

        .. versionadded:: 8.0.12
        """
        result = self.sql("SHOW DATABASES").execute()
        return [row[0] for row in result.fetch_all()]

    def get_schema(self, name: str) -> Schema:
        """Retrieves a Schema object from the current session by it's name.

        Args:
            name (string): The name of the Schema object to be retrieved.

        Returns:
            mysqlx.Schema: The Schema object with the given name.
        """
        return Schema(self, name)

    def get_default_schema(self) -> Optional[Schema]:
        """Retrieves a Schema object from the current session by the schema
        name configured in the connection settings.

        Returns:
            mysqlx.Schema: The Schema object with the given name at connect
                           time.
            None: In case the default schema was not provided with the
                  initialization data.

        Raises:
            :class:`mysqlx.ProgrammingError`: If the provided default schema
                                              does not exists.
        """
        schema = self._connection.settings.get("schema")
        if schema:
            res = (
                self.sql(_SELECT_SCHEMA_NAME_QUERY.format(escape(schema)))
                .execute()
                .fetch_all()
            )
            try:
                if res[0][0] == schema:  # type: ignore[index]
                    return Schema(self, schema)
            except IndexError:
                raise ProgrammingError(
                    f"Default schema '{schema}' does not exists"
                ) from None
        return None

    def drop_schema(self, name: str) -> None:
        """Drops the schema with the specified name.

        Args:
            name (string): The name of the Schema object to be retrieved.
        """
        self._connection.execute_nonquery(
            "sql", _DROP_DATABASE_QUERY.format(quote_identifier(name)), True
        )

    def create_schema(self, name: str) -> Schema:
        """Creates a schema on the database and returns the corresponding
        object.

        Args:
            name (string): A string value indicating the schema name.
        """
        self._connection.execute_nonquery(
            "sql", _CREATE_DATABASE_QUERY.format(quote_identifier(name)), True
        )
        return Schema(self, name)

    def start_transaction(self) -> None:
        """Starts a transaction context on the server."""
        self._connection.execute_nonquery("sql", "START TRANSACTION", True)

    def commit(self) -> None:
        """Commits all the operations executed after a call to
        startTransaction().
        """
        self._connection.execute_nonquery("sql", "COMMIT", True)

    def rollback(self) -> None:
        """Discards all the operations executed after a call to
        startTransaction().
        """
        self._connection.execute_nonquery("sql", "ROLLBACK", True)

    def set_savepoint(self, name: Optional[str] = None) -> str:
        """Creates a transaction savepoint.

        If a name is not provided, one will be generated using the uuid.uuid1()
        function.

        Args:
            name (Optional[string]): The savepoint name.

        Returns:
            string: The savepoint name.
        """
        if name is None:
            name = f"{uuid.uuid1()}"
        elif not isinstance(name, str) or len(name.strip()) == 0:
            raise ProgrammingError("Invalid SAVEPOINT name")
        self._connection.execute_nonquery(
            "sql", f"SAVEPOINT {quote_identifier(name)}", True
        )
        return name

    def rollback_to(self, name: str) -> None:
        """Rollback to a transaction savepoint with the given name.

        Args:
            name (string): The savepoint name.
        """
        if not isinstance(name, str) or len(name.strip()) == 0:
            raise ProgrammingError("Invalid SAVEPOINT name")
        self._connection.execute_nonquery(
            "sql",
            f"ROLLBACK TO SAVEPOINT {quote_identifier(name)}",
            True,
        )

    def release_savepoint(self, name: str) -> None:
        """Release a transaction savepoint with the given name.

        Args:
            name (string): The savepoint name.
        """
        if not isinstance(name, str) or len(name.strip()) == 0:
            raise ProgrammingError("Invalid SAVEPOINT name")
        self._connection.execute_nonquery(
            "sql",
            f"RELEASE SAVEPOINT {quote_identifier(name)}",
            True,
        )

    def close(self) -> None:
        """Closes the session."""
        self._connection.close_session()
        # Set an unconnected connection
        self._connection = Connection(self._settings)

    def close_connections(self) -> None:
        """Closes all underliying connections as pooled connections"""
        self._connection.close_connection()


class Client:
    """Class defining a client, it stores a connection configuration.

    Args:
        connection_dict (dict): The connection information to connect to a
                                MySQL server.
        options_dict (dict): The options to configure this client.

    .. versionadded:: 8.0.13
    """

    def __init__(
        self,
        connection_dict: Dict[str, Any],
        options_dict: Optional[Dict[str, Any]] = None,
    ) -> None:
        self.settings: Dict[str, Any] = connection_dict
        if options_dict is None:
            options_dict = {}

        self.sessions: List[Session] = []
        self.client_id: uuid.UUID = uuid.uuid4()

        self._set_pool_size(options_dict.get("max_size", 25))
        self._set_max_idle_time(options_dict.get("max_idle_time", 0))
        self._set_queue_timeout(options_dict.get("queue_timeout", 0))
        self._set_pool_enabled(options_dict.get("enabled", True))

        self.settings["pooling"] = self.pooling_enabled
        self.settings["max_size"] = self.max_size
        self.settings["client_id"] = self.client_id

    def __enter__(self) -> Client:
        return self

    def __exit__(
        self,
        exc_type: Type[BaseException],
        exc_value: BaseException,
        traceback: TracebackType,
    ) -> None:
        self.close()

    def _set_pool_size(self, pool_size: int) -> None:
        """Set the size of the pool.

        This method sets the size of the pool but it will not resize the pool.

        Args:
            pool_size (int): An integer equal or greater than 0 indicating
                             the pool size.

        Raises:
            :class:`AttributeError`: If the pool_size value is not an integer
                                     greater or equal to 0.
        """
        if (
            isinstance(pool_size, bool)
            or not isinstance(pool_size, int)
            or not pool_size > 0
        ):
            raise AttributeError(
                "Pool max_size value must be an integer greater than 0, the "
                f"given value {pool_size} is not valid"
            )

        self.max_size = _CNX_POOL_MAXSIZE if pool_size == 0 else pool_size

    def _set_max_idle_time(self, max_idle_time: int) -> None:
        """Set the max idle time.

        This method sets the max idle time.

        Args:
            max_idle_time (int): An integer equal or greater than 0 indicating
                                 the max idle time.

        Raises:
            :class:`AttributeError`: If the max_idle_time value is not an
                                     integer greater or equal to 0.
        """
        if (
            isinstance(max_idle_time, bool)
            or not isinstance(max_idle_time, int)
            or not max_idle_time > -1
        ):
            raise AttributeError(
                "Connection max_idle_time value must be an integer greater or "
                f"equal to 0, the given value {max_idle_time} is not valid"
            )

        self.max_idle_time = max_idle_time
        self.settings["max_idle_time"] = (
            _CNX_POOL_MAX_IDLE_TIME if max_idle_time == 0 else int(max_idle_time / 1000)
        )

    def _set_pool_enabled(self, enabled: bool) -> None:
        """Set if the pool is enabled.

        This method sets if the pool is enabled.

        Args:
            enabled (bool): True if to enabling the pool.

        Raises:
            :class:`AttributeError`: If the value of enabled is not a bool type.
        """
        if not isinstance(enabled, bool):
            raise AttributeError("The enabled value should be True or False.")
        self.pooling_enabled = enabled

    def _set_queue_timeout(self, queue_timeout: int) -> None:
        """Set the queue timeout.

        This method sets the queue timeout.

        Args:
            queue_timeout (int): An integer equal or greater than 0 indicating
                                 the queue timeout.

        Raises:
            :class:`AttributeError`: If the queue_timeout value is not an
                                     integer greater or equal to 0.
        """
        if (
            isinstance(queue_timeout, bool)
            or not isinstance(queue_timeout, int)
            or not queue_timeout > -1
        ):
            raise AttributeError(
                "Connection queue_timeout value must be an integer greater or "
                f"equal to 0, the given value {queue_timeout} is not valid"
            )

        self.queue_timeout = queue_timeout
        self.settings["queue_timeout"] = (
            _CNX_POOL_QUEUE_TIMEOUT if queue_timeout == 0 else int(queue_timeout / 1000)
        )
        # To avoid a connection stall waiting for the server, if the
        # connect-timeout is not given, use the queue_timeout
        if "connect-timeout" not in self.settings:
            self.settings["connect-timeout"] = self.queue_timeout

    def get_session(self) -> Session:
        """Creates a Session instance using the provided connection data.

        Returns:
            Session: Session object.
        """
        session = Session(self.settings)
        self.sessions.append(session)
        return session

    def close(self) -> None:
        """Closes the sessions opened by this client."""
        PoolsManager().close_pool(self.settings)
        for session in self.sessions:
            session.close_connections()


def _parse_address_list(path: str) -> Union[Dict[str, List[Dict]], Dict]:
    """Parses a list of host, port pairs.

    Args:
        path: String containing a list of routers or just router

    Returns:
        Returns a dict with parsed values of host, port and priority if
        specified.
    """
    path = path.replace(" ", "")
    array = (
        not ("," not in path and path.count(":") > 1 and path.count("[") == 1)
        and path.startswith("[")
        and path.endswith("]")
    )

    routers = []
    address_list = _SPLIT_RE.split(path[1:-1] if array else path)
    priority_count = 0
    for address in address_list:
        router: Dict = {}

        match = _PRIORITY_RE.match(address)
        if match:
            address = match.group(1)
            router["priority"] = int(match.group(2))
            priority_count += 1
        else:
            match = _ROUTER_RE.match(address)
            if match:
                address = match.group(1)
                router["priority"] = 100

        match = urlparse(f"//{address}")
        if not match.hostname:
            raise InterfaceError(f"Invalid address: {address}")

        try:
            router.update(host=match.hostname, port=match.port)
        except ValueError as err:
            raise ProgrammingError(f"Invalid URI: {err}", 4002) from err

        routers.append(router)

    if 0 < priority_count < len(address_list):
        raise ProgrammingError(
            "You must either assign no priority to any of the routers or give "
            "a priority for every router",
            4000,
        )

    return {"routers": routers} if array else routers[0]


def _parse_connection_uri(uri: str) -> Dict[str, Any]:
    """Parses the connection string and returns a dictionary with the
    connection settings.

    Args:
        uri: mysqlx URI scheme to connect to a MySQL server/farm.

    Returns:
        Returns a dict with parsed values of credentials and address of the
        MySQL server/farm.

    Raises:
        :class:`mysqlx.InterfaceError`: If contains a invalid option.
    """
    settings: Dict[str, Any] = {"schema": ""}

    match = _URI_SCHEME_RE.match(uri)
    scheme, uri = match.groups() if match else ("mysqlx", uri)

    if scheme not in ("mysqlx", "mysqlx+srv"):
        raise InterfaceError(f"Scheme '{scheme}' is not valid")

    if scheme == "mysqlx+srv":
        settings["dns-srv"] = True

    userinfo, tmp = uri.partition("@")[::2]
    host, query_str = tmp.partition("?")[::2]

    pos = host.rfind("/")
    if host[pos:].find(")") == -1 and pos > 0:
        host, settings["schema"] = host.rsplit("/", 1)
    host = host.strip("()")

    if not host or not userinfo or ":" not in userinfo:
        raise InterfaceError(f"Malformed URI '{uri}'")
    user, password = userinfo.split(":", 1)
    settings["user"], settings["password"] = unquote(user), unquote(password)

    if host.startswith(("/", "..", ".")):
        settings["socket"] = unquote(host)
    elif host.startswith("\\."):
        raise InterfaceError("Windows Pipe is not supported")
    else:
        settings.update(_parse_address_list(host))

    invalid_options = ("user", "password", "dns-srv")
    for key, val in parse_qsl(query_str, True):
        opt = key.replace("_", "-").lower()
        if opt in invalid_options:
            raise InterfaceError(f"Invalid option: '{key}'")
        if opt in _SSL_OPTS:
            settings[opt] = unquote(val.strip("()"))
        else:
            val_str = val.lower()
            if val_str in ("1", "true"):
                settings[opt] = True
            elif val_str in ("0", "false"):
                settings[opt] = False
            else:
                settings[opt] = val_str
    return settings


def _validate_settings(settings: Dict[str, Any]) -> None:
    """Validates the settings to be passed to a Session object
    the port values are converted to int if specified or set to 33060
    otherwise. The priority values for each router is converted to int
    if specified.

    Args:
        settings: dict containing connection settings.

    Raises:
        :class:`mysqlx.InterfaceError`: On any configuration issue.
    """
    invalid_opts = set(settings.keys()).difference(_SESS_OPTS)
    if invalid_opts:
        invalid_opts_list = "', '".join(invalid_opts)
        raise InterfaceError(f"Invalid option(s): '{invalid_opts_list}'")

    if "routers" in settings:
        for router in settings["routers"]:
            _validate_hosts(router, 33060)
    elif "host" in settings:
        _validate_hosts(settings)

    if "ssl-mode" in settings:
        ssl_mode = settings["ssl-mode"]
        try:
            settings["ssl-mode"] = SSLMode(
                ssl_mode.lower().strip() if isinstance(ssl_mode, str) else ssl_mode
            )
        except (AttributeError, ValueError) as err:
            raise InterfaceError(f"Invalid SSL Mode '{settings['ssl-mode']}'") from err
        if "ssl-ca" not in settings and settings["ssl-mode"] in [
            SSLMode.VERIFY_IDENTITY,
            SSLMode.VERIFY_CA,
        ]:
            raise InterfaceError("Cannot verify Server without CA")

    if "ssl-crl" in settings and "ssl-ca" not in settings:
        raise InterfaceError("CA Certificate not provided")

    if "ssl-key" in settings and "ssl-cert" not in settings:
        raise InterfaceError("Client Certificate not provided")

    if "ssl-ca" in settings and settings.get("ssl-mode") not in [
        SSLMode.VERIFY_IDENTITY,
        SSLMode.VERIFY_CA,
        SSLMode.DISABLED,
    ]:
        raise InterfaceError("Must verify Server if CA is provided")

    if "auth" in settings:
        auth = settings["auth"]
        try:
            settings["auth"] = Auth(
                auth.lower().strip() if isinstance(auth, str) else auth
            )
        except (AttributeError, ValueError) as err:
            raise InterfaceError(f"Invalid Auth '{settings['auth']}'") from err

    if "compression" in settings:
        compression = settings["compression"]
        try:
            settings["compression"] = Compression(
                compression.lower().strip()
                if isinstance(compression, str)
                else compression
            )
        except (AttributeError, ValueError) as err:
            raise InterfaceError(
                "The connection property 'compression' acceptable values are: "
                "'preferred', 'required', or 'disabled'. The value "
                f"'{settings['compression']}' is not acceptable"
            ) from err

    if "compression-algorithms" in settings:
        if isinstance(settings["compression-algorithms"], str):
            compression_algorithms = (
                settings["compression-algorithms"].strip().strip("[]")
            )
            if compression_algorithms:
                settings["compression-algorithms"] = compression_algorithms.split(",")
            else:
                settings["compression-algorithms"] = None
        elif not isinstance(settings["compression-algorithms"], (list, tuple)):
            raise InterfaceError(
                "Invalid type of the connection property 'compression-algorithms'"
            )
        if settings.get("compression") == Compression.DISABLED:
            settings["compression-algorithms"] = None

    if "connection-attributes" in settings:
        _validate_connection_attributes(settings)

    if "connect-timeout" in settings:
        try:
            if isinstance(settings["connect-timeout"], str):
                settings["connect-timeout"] = int(settings["connect-timeout"])
            if (
                not isinstance(settings["connect-timeout"], int)
                or settings["connect-timeout"] < 0
            ):
                raise ValueError
        except ValueError:
            raise TypeError(
                "The connection timeout value must be a positive "
                "integer (including 0)"
            ) from None

    if "dns-srv" in settings:
        if not isinstance(settings["dns-srv"], bool):
            raise InterfaceError("The value of 'dns-srv' must be a boolean")
        if settings.get("socket"):
            raise InterfaceError(
                "Using Unix domain sockets with DNS SRV lookup is not allowed"
            )
        if settings.get("port"):
            raise InterfaceError(
                "Specifying a port number with DNS SRV lookup is not allowed"
            )
        if settings.get("routers"):
            raise InterfaceError(
                "Specifying multiple hostnames with DNS SRV look up is not allowed"
            )
    elif "host" in settings and not settings.get("port"):
        settings["port"] = 33060

    if "tls-versions" in settings:
        _validate_tls_versions(settings)

    if "tls-ciphersuites" in settings:
        _validate_tls_ciphersuites(settings)


def _validate_hosts(
    settings: Dict[str, Any], default_port: Optional[int] = None
) -> None:
    """Validate hosts.

    Args:
        settings (dict): Settings dictionary.
        default_port (int): Default connection port.

    Raises:
        :class:`mysqlx.InterfaceError`: If priority or port are invalid.
    """
    if "priority" in settings and settings["priority"]:
        try:
            settings["priority"] = int(settings["priority"])
            if settings["priority"] < 0 or settings["priority"] > 100:
                raise ProgrammingError(
                    "Invalid priority value, must be between 0 and 100",
                    4007,
                )
        except NameError:
            raise ProgrammingError("Invalid priority", 4007) from None
        except ValueError:
            raise ProgrammingError(
                f"Invalid priority: {settings['priority']}", 4007
            ) from None

    if "port" in settings and settings["port"]:
        try:
            settings["port"] = int(settings["port"])
        except NameError:
            raise InterfaceError("Invalid port") from None
    elif "host" in settings and default_port:
        settings["port"] = default_port


def _validate_connection_attributes(settings: Dict[str, Any]) -> None:
    """Validate connection-attributes.

    Args:
        settings (dict): Settings dictionary.

    Raises:
        :class:`mysqlx.InterfaceError`: If attribute name or value exceeds size.
    """
    attributes = {}
    if "connection-attributes" not in settings:
        return

    conn_attrs = settings["connection-attributes"]

    if isinstance(conn_attrs, str):
        if conn_attrs == "":
            settings["connection-attributes"] = {}
            return
        if not (
            conn_attrs.startswith("[") and conn_attrs.endswith("]")
        ) and conn_attrs not in ["False", "false", "True", "true"]:
            raise InterfaceError(
                "The value of 'connection-attributes' must be a boolean or a "
                f"list of key-value pairs, found: '{conn_attrs}'"
            )
        if conn_attrs in ["False", "false", "True", "true"]:
            if conn_attrs in ["False", "false"]:
                settings["connection-attributes"] = False
            else:
                settings["connection-attributes"] = {}
            return
        conn_attributes = conn_attrs[1:-1].split(",")
        for attr in conn_attributes:
            if attr == "":
                continue
            attr_name_val = attr.split("=")
            attr_name = attr_name_val[0]
            attr_val = attr_name_val[1] if len(attr_name_val) > 1 else ""
            if attr_name in attributes:
                raise InterfaceError(
                    f"Duplicate key '{attr_name}' used in connection-attributes"
                )
            attributes[attr_name] = attr_val
    elif isinstance(conn_attrs, dict):
        for attr_name in conn_attrs:
            attr_value = conn_attrs[attr_name]
            if not isinstance(attr_value, str):
                attr_value = repr(attr_value)
            attributes[attr_name] = attr_value
    elif isinstance(conn_attrs, bool) or conn_attrs in [0, 1]:
        if conn_attrs:
            settings["connection-attributes"] = {}
        else:
            settings["connection-attributes"] = False
        return
    elif isinstance(conn_attrs, set):
        for attr_name in conn_attrs:
            attributes[attr_name] = ""
    elif isinstance(conn_attrs, list):
        for attr in conn_attrs:
            if attr == "":
                continue
            attr_name_val = attr.split("=")
            attr_name = attr_name_val[0]
            attr_val = attr_name_val[1] if len(attr_name_val) > 1 else ""
            if attr_name in attributes:
                raise InterfaceError(
                    f"Duplicate key '{attr_name}' used in connection-attributes"
                )
            attributes[attr_name] = attr_val
    elif not isinstance(conn_attrs, bool):
        raise InterfaceError(
            "connection-attributes must be Boolean or a list of key-value "
            f"pairs, found: '{conn_attrs}'"
        )

    if attributes:
        for attr_name, attr_value in attributes.items():
            # Validate name type
            if not isinstance(attr_name, str):
                raise InterfaceError(
                    f"Attribute name '{attr_name}' must be a string type"
                )
            # Validate attribute name limit 32 characters
            if len(attr_name) > 32:
                raise InterfaceError(
                    f"Attribute name '{attr_name}' exceeds 32 characters limit size"
                )
            # Validate names in connection-attributes cannot start with "_"
            if attr_name.startswith("_"):
                raise InterfaceError(
                    "Key names in connection-attributes cannot start with "
                    f"'_', found: '{attr_name}'"
                )

            # Validate value type
            if not isinstance(attr_value, str):
                raise InterfaceError(
                    f"Attribute '{attr_name}' value: '{attr_value}' must be "
                    "a string type"
                )
            # Validate attribute value limit 1024 characters
            if len(attr_value) > 1024:
                raise InterfaceError(
                    f"Attribute '{attr_name}' value: '{attr_value}' exceeds "
                    "1024 characters limit size"
                )

    settings["connection-attributes"] = attributes


def _validate_tls_versions(settings: Dict[str, Any]) -> None:
    """Validate tls-versions.

    Args:
        settings (dict): Settings dictionary.

    Raises:
        :class:`mysqlx.InterfaceError`: If tls-versions name is not valid.
    """
    tls_versions = []
    if "tls-versions" not in settings:
        return

    tls_versions_settings = settings["tls-versions"]

    if isinstance(tls_versions_settings, str):
        if not (
            tls_versions_settings.startswith("[")
            and tls_versions_settings.endswith("]")
        ):
            raise InterfaceError(
                f"tls-versions must be a list, found: '{tls_versions_settings}'"
            )
        tls_vers = tls_versions_settings[1:-1].split(",")
        for tls_ver in tls_vers:
            tls_version = tls_ver.strip()
            if tls_version == "":
                continue
            if tls_version in tls_versions:
                raise InterfaceError(
                    DUPLICATED_IN_LIST_ERROR.format(
                        list="tls_versions", value=tls_version
                    )
                )
            tls_versions.append(tls_version)
    elif isinstance(tls_versions_settings, list):
        if not tls_versions_settings:
            raise InterfaceError(
                "At least one TLS protocol version must be "
                "specified in 'tls-versions' list."
            )
        for tls_ver in tls_versions_settings:
            if tls_ver in tls_versions:
                raise InterfaceError(
                    DUPLICATED_IN_LIST_ERROR.format(list="tls_versions", value=tls_ver)
                )
            tls_versions.append(tls_ver)

    elif isinstance(tls_versions_settings, set):
        for tls_ver in tls_versions_settings:
            tls_versions.append(tls_ver)
    else:
        raise InterfaceError(
            "tls-versions should be a list with one or more of versions in "
            f"{', '.join(SUPPORTED_TLS_VERSIONS)}. found: '{tls_versions}'"
        )

    if not tls_versions:
        raise InterfaceError(
            "At least one TLS protocol version must be specified in "
            "'tls-versions' list."
        )

    use_tls_versions = []
    deprecated_tls_versions = []
    not_tls_versions = []
    for tls_ver in tls_versions:
        if tls_ver in SUPPORTED_TLS_VERSIONS:
            use_tls_versions.append(tls_ver)
        if tls_ver in DEPRECATED_TLS_VERSIONS:
            deprecated_tls_versions.append(tls_ver)
        else:
            not_tls_versions.append(tls_ver)

    if use_tls_versions:
        if use_tls_versions == ["TLSv1.3"] and not TLS_V1_3_SUPPORTED:
            raise NotSupportedError(
                TLS_VER_NO_SUPPORTED.format(tls_versions, SUPPORTED_TLS_VERSIONS)
            )
        use_tls_versions.sort()
        settings["tls-versions"] = use_tls_versions
    elif deprecated_tls_versions:
        raise NotSupportedError(
            TLS_VERSION_DEPRECATED_ERROR.format(
                deprecated_tls_versions, SUPPORTED_TLS_VERSIONS
            )
        )
    elif not_tls_versions:
        raise InterfaceError(TLS_VERSION_ERROR.format(tls_ver, SUPPORTED_TLS_VERSIONS))


def _validate_tls_ciphersuites(settings: Dict[str, Any]) -> None:
    """Validate tls-ciphersuites.

    Args:
        settings (dict): Settings dictionary.

    Raises:
        :class:`mysqlx.InterfaceError`: If tls-ciphersuites name is not valid.
    """
    tls_ciphersuites = []
    if "tls-ciphersuites" not in settings:
        return

    tls_ciphersuites_settings = settings["tls-ciphersuites"]

    if isinstance(tls_ciphersuites_settings, str):
        if not (
            tls_ciphersuites_settings.startswith("[")
            and tls_ciphersuites_settings.endswith("]")
        ):
            raise InterfaceError(
                "tls-ciphersuites must be a list, found: "
                f"'{tls_ciphersuites_settings}'"
            )
        tls_css = tls_ciphersuites_settings[1:-1].split(",")
        if not tls_css:
            raise InterfaceError(
                "No valid cipher suite found in the 'tls-ciphersuites' list"
            )
        for tls_cs in tls_css:
            tls_cs = tls_cs.strip().upper()
            if tls_cs:
                tls_ciphersuites.append(tls_cs)
    elif isinstance(tls_ciphersuites_settings, (list, set)):
        tls_ciphersuites = [tls_cs for tls_cs in tls_ciphersuites_settings if tls_cs]
    else:
        raise InterfaceError(
            "tls-ciphersuites should be a list with one or more ciphersuites. "
            f"Found: '{tls_ciphersuites_settings}'"
        )

    tls_versions = (
        SUPPORTED_TLS_VERSIONS[:]
        if settings.get("tls-versions", None) is None
        else settings["tls-versions"][:]
    )

    # A newer TLS version can use a cipher introduced on
    # an older version.
    tls_versions.sort(reverse=True)
    newer_tls_ver = tls_versions[0]

    translated_names = []
    iani_cipher_suites_names = {}
    ossl_cipher_suites_names: List[str] = []

    # Old ciphers can work with new TLS versions.
    # Find all the ciphers introduced on previous TLS versions
    for tls_ver in SUPPORTED_TLS_VERSIONS[
        : SUPPORTED_TLS_VERSIONS.index(newer_tls_ver) + 1
    ]:
        iani_cipher_suites_names.update(TLS_CIPHER_SUITES[tls_ver])
        ossl_cipher_suites_names.extend(OPENSSL_CS_NAMES[tls_ver])

    for name in tls_ciphersuites:
        if "-" in name and name in ossl_cipher_suites_names:
            translated_names.append(name)
        elif name in iani_cipher_suites_names:
            translated_name = iani_cipher_suites_names[name]
            if translated_name in translated_names:
                raise AttributeError(
                    DUPLICATED_IN_LIST_ERROR.format(
                        list="tls_ciphersuites", value=translated_name
                    )
                )
            translated_names.append(translated_name)
        else:
            raise InterfaceError(
                f"The value '{name}' in cipher suites is not a valid cipher suite"
            )

    if not translated_names:
        raise InterfaceError(
            "No valid cipher suite found in the 'tls-ciphersuites' list"
        )

    settings["tls-ciphersuites"] = translated_names


def _get_connection_settings(*args: Any, **kwargs: Any) -> Dict[str, Any]:
    """Parses the connection string and returns a dictionary with the
    connection settings.

    Args:
        *args: Variable length argument list with the connection data used
               to connect to the database. It can be a dictionary or a
               connection string.
        **kwargs: Arbitrary keyword arguments with connection data used to
                  connect to the database.

    Returns:
        mysqlx.Session: Session object.

    Raises:
        TypeError: If connection timeout is not a positive integer.
        :class:`mysqlx.InterfaceError`: If settings not provided.
    """
    settings = {}
    if args:
        if isinstance(args[0], str):
            settings = _parse_connection_uri(args[0])
        elif isinstance(args[0], dict):
            for key, val in args[0].items():
                settings[key.replace("_", "-")] = val
    elif kwargs:
        for key, val in kwargs.items():
            settings[key.replace("_", "-")] = val

    if not settings:
        raise InterfaceError("Settings not provided")

    _validate_settings(settings)
    return settings


def get_session(*args: Any, **kwargs: Any) -> Session:
    """Creates a Session instance using the provided connection data.

    Args:
        *args: Variable length argument list with the connection data used
               to connect to a MySQL server. It can be a dictionary or a
               connection string.
        **kwargs: Arbitrary keyword arguments with connection data used to
                  connect to the database.

    Returns:
        mysqlx.Session: Session object.
    """
    settings = _get_connection_settings(*args, **kwargs)
    return Session(settings)


def get_client(
    connection_string: Union[str, Dict[str, Any]],
    options_string: Union[str, Dict[str, Any]],
) -> Client:
    """Creates a Client instance with the provided connection data and settings.

    Args:
        connection_string: A string or a dict type object to indicate the \
            connection data used to connect to a MySQL server.

            The string must have the following uri format::

                cnx_str = 'mysqlx://{user}:{pwd}@{host}:{port}'
                cnx_str = ('mysqlx://{user}:{pwd}@['
                           '    (address={host}:{port}, priority=n),'
                           '    (address={host}:{port}, priority=n), ...]'
                           '       ?[option=value]')

            And the dictionary::

                cnx_dict = {
                    'host': 'The host where the MySQL product is running',
                    'port': '(int) the port number configured for X protocol',
                    'user': 'The user name account',
                    'password': 'The password for the given user account',
                    'ssl-mode': 'The flags for ssl mode in mysqlx.SSLMode.FLAG',
                    'ssl-ca': 'The path to the ca.cert'
                    "connect-timeout": '(int) milliseconds to wait on timeout'
                }

        options_string: A string in the form of a document or a dictionary \
            type with configuration for the client.

            Current options include::

                options = {
                    'pooling': {
                        'enabled': (bool), # [True | False], True by default
                        'max_size': (int), # Maximum connections per pool
                        "max_idle_time": (int), # milliseconds that a
                            # connection will remain active while not in use.
                            # By default 0, means infinite.
                        "queue_timeout": (int), # milliseconds a request will
                            # wait for a connection to become available.
                            # By default 0, means infinite.
                    }
                }

    Returns:
        mysqlx.Client: Client object.

    .. versionadded:: 8.0.13
    """
    if not isinstance(connection_string, (str, dict)):
        raise InterfaceError("connection_data must be a string or dict")

    settings_dict = _get_connection_settings(connection_string)

    if not isinstance(options_string, (str, dict)):
        raise InterfaceError("connection_options must be a string or dict")

    if isinstance(options_string, str):
        try:
            options_dict = json.loads(options_string)
        except JSONDecodeError as err:
            raise InterfaceError(
                "'pooling' options must be given in the form of a document or dict"
            ) from err
    else:
        options_dict = {}
        for key, value in options_string.items():
            options_dict[key.replace("-", "_")] = value

    if not isinstance(options_dict, dict):
        raise InterfaceError(
            "'pooling' options must be given in the form of a document or dict"
        )
    pooling_options_dict = {}
    if "pooling" in options_dict:
        pooling_options = options_dict.pop("pooling")
        if not isinstance(pooling_options, (dict)):
            raise InterfaceError(
                "'pooling' options must be given in the form document or dict"
            )
        # Fill default pooling settings
        pooling_options_dict["enabled"] = pooling_options.pop("enabled", True)
        pooling_options_dict["max_size"] = pooling_options.pop("max_size", 25)
        pooling_options_dict["max_idle_time"] = pooling_options.pop("max_idle_time", 0)
        pooling_options_dict["queue_timeout"] = pooling_options.pop("queue_timeout", 0)

        # No other options besides pooling are supported
        if len(pooling_options) > 0:
            raise InterfaceError(f"Unrecognized pooling options: {pooling_options}")
        # No other options besides pooling are supported
        if len(options_dict) > 0:
            raise InterfaceError(
                f"Unrecognized connection options: {options_dict.keys()}"
            )

    return Client(settings_dict, pooling_options_dict)

virt/lib/python3.9/site-packages/mysqlx/connection 3.py
PrinceDisant/LostInNL
# Copyright (c) 2016, 2023, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

# mypy: disable-error-code="index,attr-defined"

"""Implementation of communication for MySQL X servers."""

from __future__ import annotations

from types import TracebackType

try:
    import ssl

    SSL_AVAILABLE = True
    TLS_VERSIONS = {
        "TLSv1": ssl.PROTOCOL_TLSv1,
        "TLSv1.1": ssl.PROTOCOL_TLSv1_1,
        "TLSv1.2": ssl.PROTOCOL_TLSv1_2,
    }
    # TLSv1.3 included in PROTOCOL_TLS, but PROTOCOL_TLS is not included on 3.4
    TLS_VERSIONS["TLSv1.3"] = (
        ssl.PROTOCOL_TLS
        if hasattr(ssl, "PROTOCOL_TLS")
        else ssl.PROTOCOL_SSLv23  # Alias of PROTOCOL_TLS
    )
    TLS_V1_3_SUPPORTED = hasattr(ssl, "HAS_TLSv1_3") and ssl.HAS_TLSv1_3
except ImportError:
    SSL_AVAILABLE = False
    TLS_V1_3_SUPPORTED = False
    TLS_VERSIONS = {}

import json
import os
import platform
import queue
import random
import re
import socket
import sys
import threading
import uuid
import warnings

from typing import Any, Callable, Dict, List, Mapping, Optional, Tuple, Type, Union

try:
    import dns.exception
    import dns.resolver
except ImportError:
    HAVE_DNSPYTHON = False
else:
    HAVE_DNSPYTHON = True

from datetime import datetime, timedelta
from functools import wraps
from json.decoder import JSONDecodeError
from urllib.parse import parse_qsl, unquote, urlparse

from .authentication import MySQL41AuthPlugin, PlainAuthPlugin, Sha256MemoryAuthPlugin
from .constants import (
    COMPRESSION_ALGORITHMS,
    DEPRECATED_TLS_VERSIONS,
    OPENSSL_CS_NAMES,
    SUPPORTED_TLS_VERSIONS,
    TLS_CIPHER_SUITES,
    Auth,
    Compression,
    SSLMode,
)
from .crud import Schema

# pylint: disable=redefined-builtin
from .errors import (
    InterfaceError,
    NotSupportedError,
    OperationalError,
    PoolError,
    ProgrammingError,
    TimeoutError,
)
from .helpers import escape, get_item_or_attr, iani_to_openssl_cs_name
from .logger import logger
from .protobuf import Protobuf
from .protocol import HAVE_LZ4, HAVE_ZSTD, MessageReader, MessageWriter, Protocol
from .result import BaseResult, DocResult, Result, RowResult, SqlResult
from .statement import (
    AddStatement,
    DeleteStatement,
    FindStatement,
    InsertStatement,
    ModifyStatement,
    ReadStatement,
    RemoveStatement,
    SelectStatement,
    SqlStatement,
    UpdateStatement,
    quote_identifier,
)
from .types import ColumnType, MessageType, ResultBaseType, StatementType

sys.path.append("..")

from mysql.connector.abstracts import (
    DUPLICATED_IN_LIST_ERROR,
    TLS_VER_NO_SUPPORTED,
    TLS_VERSION_DEPRECATED_ERROR,
    TLS_VERSION_ERROR,
)
from mysql.connector.utils import linux_distribution
from mysql.connector.version import LICENSE, VERSION

CONNECTION_CLOSED_ERROR = {
    1810: "This session was closed because the connection has been idle for "
    "too long. Use 'mysqlx.getSession()' or 'mysqlx.getClient()' to create a "
    "new one.",
    1053: "This session was closed because the server is shutting down.",
    3169: "This session was closed because the connection has been killed in "
    "a different session. Use 'mysqlx.getSession()' or 'mysqlx.getClient()' "
    "to create a new one.",
}

_CONNECT_TIMEOUT = 10000  # Default connect timeout in milliseconds
_DROP_DATABASE_QUERY = "DROP DATABASE IF EXISTS {}"
_CREATE_DATABASE_QUERY = "CREATE DATABASE IF NOT EXISTS {}"
_SELECT_SCHEMA_NAME_QUERY = (
    "SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = '{}'"
)
_SELECT_VERSION_QUERY = "SELECT @@version"

_CNX_POOL_MAXSIZE = 99
_CNX_POOL_MAX_NAME_SIZE = 120
_CNX_POOL_NAME_REGEX = re.compile(r"[^a-zA-Z0-9._:\-*$#]")
_CNX_POOL_MAX_IDLE_TIME = 2147483
_CNX_POOL_QUEUE_TIMEOUT = 2147483

# Time is on seconds
_PENALTY_SERVER_OFFLINE = 1000000
_PENALTY_MAXED_OUT = 60
_PENALTY_NO_ADD_INFO = 60 * 60
_PENALTY_CONN_TIMEOUT = 60 * 60
_PENALTY_WRONG_PASSW = 60 * 60 * 24
_PENALTY_RESTARTING = 60
_TIMEOUT_PENALTIES = {
    # Server denays service e.g Max connections reached
    "[WinError 10053]": _PENALTY_MAXED_OUT,  # Established connection was aborted
    "[Errno 32]": _PENALTY_MAXED_OUT,  # Broken pipe
    # Server is Offline
    "[WinError 10061]": _PENALTY_SERVER_OFFLINE,  # Target machine actively refused it
    "[Errno 111]": _PENALTY_SERVER_OFFLINE,  # Connection refused
    # Host is offline:
    "[WinError 10060]": _PENALTY_CONN_TIMEOUT,  # Not respond after a period of time
    # No route to Host:
    "[Errno 11001]": _PENALTY_NO_ADD_INFO,  # getaddrinfo failed
    "[Errno -2]": _PENALTY_NO_ADD_INFO,  # Name or service not known
    # Wrong Password
    "Access denied": _PENALTY_WRONG_PASSW,
}
_TIMEOUT_PENALTIES_BY_ERR_NO = {1053: _PENALTY_RESTARTING}
_SPLIT_RE = re.compile(r",(?![^\(\)]*\))")
_PRIORITY_RE = re.compile(r"^\(address=(.+),priority=(\d+)\)$", re.VERBOSE)
_ROUTER_RE = re.compile(r"^\(address=(.+)[,]*\)$", re.VERBOSE)
_URI_SCHEME_RE = re.compile(r"^([a-zA-Z][a-zA-Z0-9+\-.]+)://(.*)")
_SSL_OPTS = [
    "ssl-cert",
    "ssl-ca",
    "ssl-key",
    "ssl-crl",
    "tls-versions",
    "tls-ciphersuites",
]
_SESS_OPTS = _SSL_OPTS + [
    "user",
    "password",
    "schema",
    "host",
    "port",
    "routers",
    "socket",
    "ssl-mode",
    "auth",
    "use-pure",
    "connect-timeout",
    "connection-attributes",
    "compression",
    "compression-algorithms",
    "dns-srv",
]


def generate_pool_name(**kwargs: Any) -> str:
    """Generate a pool name.

    This function takes keyword arguments, usually the connection arguments and
    tries to generate a name for the pool.

    Args:
        **kwargs: Arbitrary keyword arguments with the connection arguments.

    Raises:
        PoolError: If the name can't be generated.

    Returns:
        str: The generated pool name.
    """
    parts = []
    for key in ("host", "port", "user", "database", "client_id"):
        try:
            parts.append(str(kwargs[key]))
        except KeyError:
            pass

    if not parts:
        raise PoolError("Failed generating pool name; specify pool_name")

    return "_".join(parts)


def update_timeout_penalties_by_error(penalty_dict: Mapping[str, Any]) -> None:
    """Update the timeout penalties directory.

    Update the timeout penalties by error dictionary used to deactivate a pool.
    Args:
        penalty_dict (dict): The dictionary with the new timeouts.
    """
    if penalty_dict and isinstance(penalty_dict, dict):
        _TIMEOUT_PENALTIES_BY_ERR_NO.update(penalty_dict)


class SocketStream:
    """Implements a socket stream."""

    def __init__(self) -> None:
        self._socket: Optional[socket.socket] = None
        self._is_ssl: bool = False
        self._is_socket: bool = False
        self._host: Optional[str] = None

    def connect(self, params: Tuple, connect_timeout: float = _CONNECT_TIMEOUT) -> None:
        """Connects to a TCP service.

        Args:
            params (tuple): The connection parameters.

        Raises:
            :class:`mysqlx.InterfaceError`: If Unix socket is not supported.
        """
        if connect_timeout is not None:
            connect_timeout = connect_timeout / 1000  # Convert to seconds
        try:
            self._socket = socket.create_connection(
                params, connect_timeout  # type: ignore[arg-type]
            )
            self._host = params[0]
        except ValueError:
            try:
                self._socket = socket.socket(socket.AF_UNIX)
                self._socket.settimeout(connect_timeout)
                self._socket.connect(params)
                self._is_socket = True
            except AttributeError:
                raise InterfaceError("Unix socket unsupported") from None
        self._socket.settimeout(None)

    def read(self, count: int) -> bytes:
        """Receive data from the socket.

        Args:
            count (int): Buffer size.

        Returns:
            bytes: The data received.
        """
        if self._socket is None:
            raise OperationalError("MySQLx Connection not available")
        buf = []
        while count > 0:
            data = self._socket.recv(count)
            if data == b"":
                raise RuntimeError("Unexpected connection close")
            buf.append(data)
            count -= len(data)
        return b"".join(buf)

    def sendall(self, data: bytes) -> None:
        """Send data to the socket.

        Args:
            data (bytes): The data to be sent.
        """
        if self._socket is None:
            raise OperationalError("MySQLx Connection not available")
        try:
            self._socket.sendall(data)
        except OSError as err:
            raise OperationalError(f"Unexpected socket error: {err}") from err

    def close(self) -> None:
        """Close the socket."""
        if not self._socket:
            return
        try:
            self._socket.shutdown(socket.SHUT_RDWR)
            self._socket.close()
        except OSError:
            # On [Errno 107] Transport endpoint is not connected
            pass
        self._socket = None

    def __del__(self) -> None:
        self.close()

    def set_ssl(
        self,
        ssl_protos: List[str],
        ssl_mode: str,
        ssl_ca: str,
        ssl_crl: str,
        ssl_cert: str,
        ssl_key: str,
        ssl_ciphers: List[str],
    ) -> None:
        """Set SSL parameters.

        Args:
            ssl_protos (list): SSL protocol to use.
            ssl_mode (str): SSL mode.
            ssl_ca (str): The certification authority certificate.
            ssl_crl (str): The certification revocation lists.
            ssl_cert (str): The certificate.
            ssl_key (str): The certificate key.
            ssl_ciphers (list): SSL ciphersuites to use.

        Raises:
            :class:`mysqlx.RuntimeError`: If Python installation has no SSL
                                          support.
            :class:`mysqlx.InterfaceError`: If the parameters are invalid.
        """
        if not SSL_AVAILABLE:
            self.close()
            raise RuntimeError("Python installation has no SSL support")

        if ssl_protos is None or not ssl_protos:
            context = ssl.create_default_context()
            if ssl_mode != SSLMode.VERIFY_IDENTITY:
                context.check_hostname = False
            if ssl_mode == SSLMode.REQUIRED:
                context.verify_mode = ssl.CERT_NONE
        else:
            ssl_protos.sort(reverse=True)
            tls_version = ssl_protos[0]
            if (
                not TLS_V1_3_SUPPORTED
                and tls_version == "TLSv1.3"
                and len(ssl_protos) > 1
            ):
                tls_version = ssl_protos[1]
            ssl_protocol = TLS_VERSIONS[tls_version]
            context = ssl.SSLContext(ssl_protocol)

            if tls_version == "TLSv1.3":
                if "TLSv1.2" not in ssl_protos:
                    context.options |= ssl.OP_NO_TLSv1_2
                if "TLSv1.1" not in ssl_protos:
                    context.options |= ssl.OP_NO_TLSv1_1
                if "TLSv1" not in ssl_protos:
                    context.options |= ssl.OP_NO_TLSv1

        if ssl_ca:
            try:
                context.load_verify_locations(ssl_ca)
                context.verify_mode = ssl.CERT_REQUIRED
            except (IOError, ssl.SSLError) as err:
                self.close()
                raise InterfaceError(f"Invalid CA Certificate: {err}") from err

        if ssl_crl:
            try:
                context.load_verify_locations(ssl_crl)
                context.verify_flags = ssl.VERIFY_CRL_CHECK_LEAF
            except (IOError, ssl.SSLError) as err:
                self.close()
                raise InterfaceError(f"Invalid CRL: {err}") from err

        if ssl_cert:
            try:
                context.load_cert_chain(ssl_cert, ssl_key)
            except (IOError, ssl.SSLError) as err:
                self.close()
                raise InterfaceError(f"Invalid Certificate/Key: {err}") from err

        if ssl_ciphers:
            context.set_ciphers(
                ":".join(iani_to_openssl_cs_name(ssl_protos[0], ssl_ciphers))
            )
        try:
            self._socket = context.wrap_socket(self._socket, server_hostname=self._host)
        except ssl.CertificateError as err:
            raise InterfaceError(f"{err}") from err
        if ssl_mode == SSLMode.VERIFY_IDENTITY:
            context.check_hostname = True
            hostnames = []
            # Windows does not return loopback aliases on gethostbyaddr
            if os.name == "nt" and self._host in ("localhost", "127.0.0.1"):
                hostnames = ["localhost", "127.0.0.1"]
            aliases = socket.gethostbyaddr(self._host)
            hostnames.extend([aliases[0]] + aliases[1])
            match_found = False
            errs = []
            for hostname in hostnames:
                try:
                    # Deprecated in Python 3.7 without a replacement and
                    # should be removed in the future, since OpenSSL now
                    # performs hostname matching
                    # pylint: disable=deprecated-method
                    ssl.match_hostname(self._socket.getpeercert(), hostname)
                    # pylint: enable=deprecated-method
                except ssl.CertificateError as err:
                    errs.append(str(err))
                else:
                    match_found = True
                    break
            if not match_found:
                self.close()
                raise InterfaceError(
                    f"Unable to verify server identity: {', '.join(errs)}"
                )

        self._is_ssl = True

        # Raise a deprecation warning if TLSv1 or TLSv1.1 is being used
        tls_version = self._socket.version()
        if tls_version in ("TLSv1", "TLSv1.1"):
            warn_msg = (
                f"This connection is using {tls_version} which is now "
                "deprecated and will be removed in a future release of "
                "MySQL Connector/Python"
            )
            warnings.warn(warn_msg, DeprecationWarning)

    def is_ssl(self) -> bool:
        """Verifies if SSL is being used.

        Returns:
            bool: Returns `True` if SSL is being used.
        """
        return self._is_ssl

    def is_socket(self) -> bool:
        """Verifies if socket connection is being used.

        Returns:
            bool: Returns `True` if socket connection is being used.
        """
        return self._is_socket

    def is_secure(self) -> bool:
        """Verifies if connection is secure.

        Returns:
            bool: Returns `True` if connection is secure.
        """
        return self._is_ssl or self._is_socket

    def is_open(self) -> bool:
        """Verifies if connection is open.

        Returns:
            bool: Returns `True` if connection is open.
        """
        return self._socket is not None


def catch_network_exception(func: Callable) -> Callable:
    """Decorator used to catch OSError or RuntimeError.

    Raises:
        :class:`mysqlx.InterfaceError`: If `OSError` or `RuntimeError`
                                        is raised.
    """

    @wraps(func)
    def wrapper(self: Any, *args: Any, **kwargs: Any) -> Any:
        """Wrapper function."""
        try:
            if (
                isinstance(self, (Connection, PooledConnection))
                and self.is_server_disconnected()
            ):
                raise InterfaceError(*self.get_disconnected_reason())
            result = func(self, *args, **kwargs)
            if isinstance(result, BaseResult):
                warns: Any = result.get_warnings()
                for warn in warns:
                    if warn["code"] in CONNECTION_CLOSED_ERROR:
                        error_msg = CONNECTION_CLOSED_ERROR[warn["code"]]
                        reason = (
                            f"Connection close: {warn['msg']}: {error_msg}",
                            warn["code"],
                        )
                        if isinstance(self, (Connection, PooledConnection)):
                            self.set_server_disconnected(reason)
                        break
            return result
        except (InterfaceError, OSError, RuntimeError, TimeoutError) as err:
            if (
                func.__name__ == "get_column_metadata"
                and args
                and isinstance(args[0], SqlResult)
            ):
                warns = args[0].get_warnings()
                if warns:
                    warn = warns[0]
                    error_msg = CONNECTION_CLOSED_ERROR[warn["code"]]
                    reason = (
                        f"Connection close: {warn['msg']}: {error_msg}",
                        warn["code"],
                    )
                    if isinstance(self, PooledConnection):
                        self.pool.remove_connections()
                        # pool must be listed as faulty if server is shutting down
                        if warn["code"] == 1053:
                            PoolsManager().set_pool_unavailable(
                                self.pool, InterfaceError(*reason)
                            )
                    if isinstance(self, (Connection, PooledConnection)):
                        self.set_server_disconnected(reason)
                    self.disconnect()
                    raise InterfaceError(*reason) from err
            self.disconnect()
            raise

    return wrapper


class Router(dict):
    """Represents a set of connection parameters.

    Args:
       settings (dict): Dictionary with connection settings
    .. versionadded:: 8.0.20
    """

    def __init__(self, connection_params: Mapping[str, Any]) -> None:
        super().__init__()
        self.update(connection_params)
        self["available"] = self.get("available", True)

    def available(self) -> bool:
        """Verifies if the Router is available to open connections.

        Returns:
            bool: True if this Router is available else False.
        """
        return self["available"]

    def set_unavailable(self) -> None:
        """Sets this Router unavailable to open connections."""
        self["available"] = False

    def get_connection_params(self) -> Union[str, Tuple[str, Optional[int]]]:
        """Verifies if the Router is available to open connections.

        Returns:
            tuple: host and port or socket information tuple.
        """
        if "socket" in self:
            return self["socket"]
        return (self["host"], self["port"])


class RouterManager:
    """Manages the connection parameters of all the routers.

    Args:
        Routers (list): A list of Router objects.
        settings (dict): Dictionary with connection settings.
    .. versionadded:: 8.0.20
    """

    def __init__(self, routers: List[Router], settings: Dict[str, Any]) -> None:
        self._routers = routers
        self._settings = settings
        self._cur_priority_idx: int = 0
        self._can_failover: bool = True
        # Reuters status
        self._routers_directory: Dict[int, List[Router]] = {}
        self.routers_priority_list: List[int] = []
        self._ensure_priorities()

    def _ensure_priorities(self) -> None:
        """Ensure priorities.

        Raises:
            :class:`mysqlx.ProgrammingError`: If priorities are invalid.
        """
        priority_count = 0

        for router in self._routers:
            priority = router.get("priority", None)
            if priority is None:
                priority_count += 1
                router["priority"] = 100
            elif priority > 100:
                raise ProgrammingError("The priorities must be between 0 and 100", 4007)

        if 0 < priority_count < len(self._routers):
            raise ProgrammingError(
                "You must either assign no priority to any "
                "of the routers or give a priority for "
                "every router",
                4000,
            )

        self._routers.sort(key=lambda x: x["priority"], reverse=True)

        # Group servers with the same priority
        for router in self._routers:
            priority = router["priority"]
            if priority not in self._routers_directory:
                self._routers_directory[priority] = [Router(router)]
                self.routers_priority_list.append(priority)
            else:
                self._routers_directory[priority].append(Router(router))

    def _get_available_routers(self, priority: int) -> List[Router]:
        """Get a list of the current available routers that shares the given priority.

        Returns:
            list: A list of the current available routers.
        """
        router_list = self._routers_directory[priority]
        router_list = [router for router in router_list if router.available()]
        return router_list

    def _get_random_connection_params(self, priority: int) -> Router:
        """Get a random router from the group with the given priority.

        Returns:
            Router: A random router.
        """
        router_list = self._get_available_routers(priority)
        if not router_list:
            return None
        if len(router_list) == 1:
            return router_list[0]

        last = len(router_list) - 1
        index = random.randint(0, last)
        return router_list[index]

    def can_failover(self) -> bool:
        """Returns the next connection parameters.

        Returns:
            bool: True if there is more server to failover to else False.
        """
        return self._can_failover

    def get_next_router(self) -> Router:
        """Returns the next connection parameters.

        Returns:
            Router: with the connection parameters.
        """
        if not self._routers:
            self._can_failover = False
            router_settings = self._settings.copy()
            router_settings["host"] = self._settings.get("host", "localhost")
            router_settings["port"] = self._settings.get("port", 33060)
            return Router(router_settings)

        cur_priority = self.routers_priority_list[self._cur_priority_idx]
        routers_priority_len = len(self.routers_priority_list)

        search = True
        while search:
            router = self._get_random_connection_params(cur_priority)

            if router is not None or self._cur_priority_idx >= routers_priority_len:
                if (
                    self._cur_priority_idx == routers_priority_len - 1
                    and len(self._get_available_routers(cur_priority)) < 2
                ):
                    self._can_failover = False
                break

            # Search on next group
            self._cur_priority_idx += 1
            if self._cur_priority_idx < routers_priority_len:
                cur_priority = self.routers_priority_list[self._cur_priority_idx]

        return router

    def get_routers_directory(self) -> Dict[int, List[Router]]:
        """Returns the directory containing all the routers managed.

        Returns:
            dict: Dictionary with priorities as connection settings.
        """
        return self._routers_directory


class Connection:
    """Connection to a MySQL Server.

    Args:
        settings (dict): Dictionary with connection settings.
    """

    def __init__(self, settings: Dict[str, Any]) -> None:
        self.settings: Dict[str, Any] = settings
        self.stream: SocketStream = SocketStream()
        self.protocol: Optional[Protocol] = None
        self.keep_open: Optional[bool] = None
        self._user: Optional[str] = settings.get("user")
        self._password: Optional[str] = settings.get("password")
        self._schema: Optional[str] = settings.get("schema")
        self._active_result: Optional[ResultBaseType] = None
        self._routers: List[Router] = settings.get("routers", [])

        if "host" in settings and settings["host"]:
            self._routers.append(
                {  # type: ignore[arg-type]
                    "host": settings.get("host"),
                    "port": settings.get("port", None),
                }
            )

        self.router_manager: RouterManager = RouterManager(self._routers, settings)
        self._connect_timeout: Optional[int] = settings.get(
            "connect-timeout", _CONNECT_TIMEOUT
        )
        if self._connect_timeout == 0:
            # None is assigned if connect timeout is 0, which disables timeouts
            # on socket operations
            self._connect_timeout = None

        self._stmt_counter: int = 0
        self._prepared_stmt_ids: List[int] = []
        self._prepared_stmt_supported: bool = True
        self._server_disconnected: bool = False
        self._server_disconnected_reason: Optional[Union[str, Tuple[str, int]]] = None

    def fetch_active_result(self) -> None:
        """Fetch active result."""
        if self._active_result is not None:
            self._active_result.fetch_all()
            self._active_result = None

    def set_active_result(self, result: ResultBaseType) -> None:
        """Set active result.

        Args:
            `Result`: It can be :class:`mysqlx.Result`,
                      :class:`mysqlx.BufferingResult`,
                      :class:`mysqlx.RowResult`, :class:`mysqlx.SqlResult` or
                      :class:`mysqlx.DocResult`.
        """
        self._active_result = result

    def connect(self) -> None:
        """Attempt to connect to the MySQL server.

        Raises:
            :class:`mysqlx.InterfaceError`: If fails to connect to the MySQL
                                            server.
            :class:`mysqlx.TimeoutError`: If connect timeout was exceeded.
        """
        # Loop and check
        error = None
        while self.router_manager.can_failover():
            try:
                router = self.router_manager.get_next_router()
                self.stream.connect(
                    router.get_connection_params(), self._connect_timeout  # type: ignore[arg-type]
                )
                reader = MessageReader(self.stream)
                writer = MessageWriter(self.stream)
                self.protocol = Protocol(reader, writer)

                caps_data = self.protocol.get_capabilites().capabilities
                caps = (
                    {get_item_or_attr(cap, "name").lower(): cap for cap in caps_data}
                    if caps_data
                    else {}
                )

                # Set TLS capabilities
                self._set_tls_capabilities(caps)

                # Set connection attributes capabilities
                if "attributes" in self.settings:
                    conn_attrs = self.settings["attributes"]
                    self.protocol.set_capabilities(session_connect_attrs=conn_attrs)

                # Set compression capabilities
                compression = self.settings.get("compression", Compression.PREFERRED)
                algorithms = self.settings.get("compression-algorithms")
                algorithm = (
                    None
                    if compression == Compression.DISABLED
                    else self._set_compression_capabilities(
                        caps, compression, algorithms
                    )
                )
                self._authenticate()
                self.protocol.set_compression(algorithm)
                return
            except (OSError, RuntimeError) as err:
                error = err
                router.set_unavailable()

        # Python 2.7 does not raise a socket.timeout exception when using
        # settimeout(), but it raises a socket.error with errno.EAGAIN (11)
        # or errno.EINPROGRESS (115) if connect-timeout value is too low
        if error is not None and isinstance(error, socket.timeout):
            if len(self._routers) <= 1:
                raise TimeoutError(
                    "Connection attempt to the server was aborted. "
                    f"Timeout of {self._connect_timeout} ms was exceeded"
                )
            raise TimeoutError(
                "All server connection attempts were aborted. "
                f"Timeout of {self._connect_timeout} ms was exceeded for each "
                "selected server"
            )
        if len(self._routers) <= 1:
            raise InterfaceError(f"Cannot connect to host: {error}")
        raise InterfaceError("Unable to connect to any of the target hosts", 4001)

    def _set_tls_capabilities(self, caps: Dict[str, Any]) -> None:
        """Set the TLS capabilities.

        Args:
            caps (dict): Dictionary with the server capabilities.

        Raises:
            :class:`mysqlx.OperationalError`: If SSL is not enabled at the
                                             server.
            :class:`mysqlx.RuntimeError`: If support for SSL is not available
                                          in Python.

        .. versionadded:: 8.0.21
        """
        if self.settings.get("ssl-mode") == SSLMode.DISABLED:
            return

        if self.stream.is_socket():
            if self.settings.get("ssl-mode"):
                logger.warning("SSL not required when using Unix socket.")
            return

        if "tls" not in caps:
            self.close_connection()
            raise OperationalError("SSL not enabled at server")

        is_ol7 = False
        if platform.system() == "Linux":
            distname, version, _ = linux_distribution()
            try:
                is_ol7 = "Oracle Linux" in distname and version.split(".")[0] == "7"
            except IndexError:
                is_ol7 = False

        if sys.version_info < (2, 7, 9) and not is_ol7:
            self.close_connection()
            raise RuntimeError(
                "The support for SSL is not available for this Python version"
            )

        self.protocol.set_capabilities(tls=True)
        self.stream.set_ssl(
            self.settings.get("tls-versions", None),
            self.settings.get("ssl-mode", SSLMode.REQUIRED),
            self.settings.get("ssl-ca"),
            self.settings.get("ssl-crl"),
            self.settings.get("ssl-cert"),
            self.settings.get("ssl-key"),
            self.settings.get("tls-ciphersuites"),
        )
        if "attributes" in self.settings:
            conn_attrs = self.settings["attributes"]
            self.protocol.set_capabilities(session_connect_attrs=conn_attrs)

    def _set_compression_capabilities(
        self,
        caps: Dict[str, Any],
        compression: str,
        algorithms: Optional[List[str]] = None,
    ) -> Optional[str]:
        """Set the compression capabilities.

        If compression is available, negociates client and server algorithms.
        By trying to find an algorithm from the requested compression
        algorithms list, which is supported by the server.

        If no compression algorithms list is provided, the following priority
        is used:

        1) zstd_stream
        2) lz4_message
        3) deflate_stream

        Args:
            caps (dict): Dictionary with the server capabilities.
            compression (str): The compression connection setting.
            algorithms (list): List of requested compression algorithms.

        Returns:
            str: The compression algorithm.

        .. versionadded:: 8.0.21
        .. versionchanged:: 8.0.22
        """
        compression_data = caps.get("compression")
        if compression_data is None:
            msg = "Compression requested but the server does not support it"
            if compression == Compression.REQUIRED:
                raise NotSupportedError(msg)
            logger.warning(msg)
            return None

        compression_dict = {}
        if isinstance(compression_data, dict):  # C extension is being used
            for fld in compression_data["value"]["obj"]["fld"]:
                compression_dict[fld["key"]] = [
                    value["scalar"]["v_string"]["value"].decode("utf-8")
                    for value in fld["value"]["array"]["value"]
                ]
        else:
            for fld in compression_data.value.obj.fld:
                compression_dict[fld.key] = [
                    value.scalar.v_string.value.decode("utf-8")
                    for value in fld.value.array.value
                ]

        server_algorithms = compression_dict.get("algorithm", [])
        algorithm = None

        # Try to find an algorithm from the requested compression algorithms
        # list, which is supported by the server
        if algorithms:
            # Resolve compression algorithms aliases and ignore unsupported
            client_algorithms = [
                COMPRESSION_ALGORITHMS[item]
                for item in algorithms
                if item in COMPRESSION_ALGORITHMS
            ]
            matched = [item for item in client_algorithms if item in server_algorithms]
            if matched:
                algorithm = COMPRESSION_ALGORITHMS.get(matched[0])
            elif compression == Compression.REQUIRED:
                raise InterfaceError(
                    "The connection compression is set as "
                    "required, but none of the provided "
                    "compression algorithms are supported."
                )
            else:
                return None  # Disable compression

        # No compression algorithms list was provided or couldn't found one
        # supported by the server
        if algorithm is None:
            if HAVE_ZSTD and "zstd_stream" in server_algorithms:
                algorithm = "zstd_stream"
            elif HAVE_LZ4 and "lz4_message" in server_algorithms:
                algorithm = "lz4_message"
            else:
                algorithm = "deflate_stream"

        if algorithm not in server_algorithms:
            msg = (
                "Compression requested but the compression algorithm "
                "negotiation failed"
            )
            if compression == Compression.REQUIRED:
                raise InterfaceError(msg)
            logger.warning(msg)
            return None

        self.protocol.set_capabilities(compression={"algorithm": algorithm})
        return algorithm

    def _authenticate(self) -> None:
        """Authenticate with the MySQL server."""
        auth = self.settings.get("auth")
        if auth:
            if auth == Auth.PLAIN:
                self._authenticate_plain()
            elif auth == Auth.SHA256_MEMORY:
                self._authenticate_sha256_memory()
            elif auth == Auth.MYSQL41:
                self._authenticate_mysql41()
        elif self.stream.is_secure():
            # Use PLAIN if no auth provided and connection is secure
            self._authenticate_plain()
        else:
            # Use MYSQL41 if connection is not secure
            try:
                self._authenticate_mysql41()
            except InterfaceError:
                pass
            else:
                return
            # Try SHA256_MEMORY if MYSQL41 fails
            try:
                self._authenticate_sha256_memory()
            except InterfaceError as err:
                raise InterfaceError(
                    "Authentication failed using MYSQL41 and "
                    "SHA256_MEMORY, check username and "
                    f"password or try a secure connection err:{err}"
                ) from err

    def _authenticate_mysql41(self) -> None:
        """Authenticate with the MySQL server using `MySQL41AuthPlugin`."""
        plugin = MySQL41AuthPlugin(self._user, self._password)
        self.protocol.send_auth_start(plugin.auth_name())
        extra_data = self.protocol.read_auth_continue()
        self.protocol.send_auth_continue(plugin.auth_data(extra_data))
        self.protocol.read_auth_ok()

    def _authenticate_plain(self) -> None:
        """Authenticate with the MySQL server using `PlainAuthPlugin`."""
        if not self.stream.is_secure():
            raise InterfaceError(
                "PLAIN authentication is not allowed via unencrypted connection"
            )
        plugin = PlainAuthPlugin(self._user, self._password)
        self.protocol.send_auth_start(plugin.auth_name(), auth_data=plugin.auth_data())
        self.protocol.read_auth_ok()

    def _authenticate_sha256_memory(self) -> None:
        """Authenticate with the MySQL server using `Sha256MemoryAuthPlugin`."""
        plugin = Sha256MemoryAuthPlugin(self._user, self._password)
        self.protocol.send_auth_start(plugin.auth_name())
        extra_data = self.protocol.read_auth_continue()
        self.protocol.send_auth_continue(plugin.auth_data(extra_data))
        self.protocol.read_auth_ok()

    def _deallocate_statement(self, statement: StatementType) -> None:
        """Deallocates statement.

        Args:
            statement (Statement): A `Statement` based type object.
        """
        if statement.prepared:
            self.protocol.send_prepare_deallocate(statement.stmt_id)
            self._prepared_stmt_ids.remove(statement.stmt_id)
            statement.prepared = False

    def _prepare_statement(
        self,
        msg_type: str,
        msg: MessageType,
        statement: Union[
            FindStatement,
            DeleteStatement,
            ModifyStatement,
            ReadStatement,
            RemoveStatement,
            UpdateStatement,
        ],
    ) -> None:
        """Prepares a statement.

        Args:
            msg_type (str): Message ID string.
            msg (mysqlx.protobuf.Message): MySQL X Protobuf Message.
            statement (Statement): A `Statement` based type object.
        """
        try:
            self.fetch_active_result()
            self.protocol.send_prepare_prepare(msg_type, msg, statement)
        except NotSupportedError:
            self._prepared_stmt_supported = False
            return
        self._prepared_stmt_ids.append(statement.stmt_id)
        statement.prepared = True

    def _execute_prepared_pipeline(
        self,
        msg_type: str,
        msg: MessageType,
        statement: Union[
            FindStatement,
            DeleteStatement,
            ModifyStatement,
            ReadStatement,
            RemoveStatement,
            UpdateStatement,
        ],
    ) -> None:
        """Executes the prepared statement pipeline.

        Args:
            msg_type (str): Message ID string.
            msg (mysqlx.protobuf.Message): MySQL X Protobuf Message.
            statement (Statement): A `Statement` based type object.
        """
        # For old servers without prepared statement support
        if not self._prepared_stmt_supported:
            # Crud::<Operation>
            self.protocol.send_msg_without_ps(msg_type, msg, statement)
            return

        if statement.deallocate_prepare_execute:
            # Prepare::Deallocate + Prepare::Prepare + Prepare::Execute
            self._deallocate_statement(statement)
            self._prepare_statement(msg_type, msg, statement)
            if not self._prepared_stmt_supported:
                self.protocol.send_msg_without_ps(msg_type, msg, statement)
                return
            self.protocol.send_prepare_execute(msg_type, msg, statement)
            statement.deallocate_prepare_execute = False
            statement.reset_exec_counter()
        elif statement.prepared and not statement.changed:
            # Prepare::Execute
            self.protocol.send_prepare_execute(msg_type, msg, statement)
        elif statement.changed and not statement.repeated:
            # Crud::<Operation>
            self._deallocate_statement(statement)
            self.protocol.send_msg_without_ps(msg_type, msg, statement)
            statement.changed = False
            statement.reset_exec_counter()
        elif not statement.changed and not statement.repeated:
            # Prepare::Prepare + Prepare::Execute
            if not statement.prepared:
                self._prepare_statement(msg_type, msg, statement)
            if not self._prepared_stmt_supported:
                self.protocol.send_msg_without_ps(msg_type, msg, statement)
                return
            self.protocol.send_prepare_execute(msg_type, msg, statement)
        elif statement.changed and statement.repeated:
            # Prepare::Deallocate + Crud::<Operation>
            self._deallocate_statement(statement)
            self.protocol.send_msg_without_ps(msg_type, msg, statement)
            statement.changed = False
            statement.reset_exec_counter()

        statement.increment_exec_counter()

    @catch_network_exception
    def send_sql(self, statement: SqlStatement) -> SqlResult:
        """Execute a SQL statement.

        Args:
            sql (str): The SQL statement.

        Raises:
            :class:`mysqlx.ProgrammingError`: If the SQL statement is not a
                                              valid string.
        """
        sql = statement.sql
        if self.protocol is None:
            raise OperationalError("MySQLx Connection not available")
        if not isinstance(sql, str):
            raise ProgrammingError("The SQL statement is not a valid string")
        msg_type, msg = self.protocol.build_execute_statement("sql", sql)
        self.protocol.send_msg_without_ps(msg_type, msg, statement)
        return SqlResult(self)

    @catch_network_exception
    def send_insert(self, statement: Union[AddStatement, InsertStatement]) -> Result:
        """Send an insert statement.

        Args:
            statement (`Statement`): It can be :class:`mysqlx.InsertStatement`
                                     or :class:`mysqlx.AddStatement`.

        Returns:
            :class:`mysqlx.Result`: A result object.
        """
        if self.protocol is None:
            raise OperationalError("MySQLx Connection not available")
        msg_type, msg = self.protocol.build_insert(statement)
        self.protocol.send_msg(msg_type, msg)
        ids = None
        if isinstance(statement, AddStatement):
            ids = statement.ids
        return Result(self, ids)

    @catch_network_exception
    def send_find(
        self, statement: Union[FindStatement, SelectStatement]
    ) -> Union[DocResult, RowResult]:
        """Send an find statement.

        Args:
            statement (`Statement`): It can be :class:`mysqlx.SelectStatement`
                                     or :class:`mysqlx.FindStatement`.

        Returns:
            `Result`: It can be class:`mysqlx.DocResult` or
                      :class:`mysqlx.RowResult`.
        """
        msg_type, msg = self.protocol.build_find(statement)
        self._execute_prepared_pipeline(msg_type, msg, statement)
        return DocResult(self) if statement.is_doc_based() else RowResult(self)

    @catch_network_exception
    def send_delete(self, statement: Union[DeleteStatement, RemoveStatement]) -> Result:
        """Send an delete statement.

        Args:
            statement (`Statement`): It can be :class:`mysqlx.RemoveStatement`
                                     or :class:`mysqlx.DeleteStatement`.

        Returns:
            :class:`mysqlx.Result`: The result object.
        """
        msg_type, msg = self.protocol.build_delete(statement)
        self._execute_prepared_pipeline(msg_type, msg, statement)
        return Result(self)

    @catch_network_exception
    def send_update(self, statement: Union[ModifyStatement, UpdateStatement]) -> Result:
        """Send an delete statement.

        Args:
            statement (`Statement`): It can be :class:`mysqlx.ModifyStatement`
                                     or :class:`mysqlx.UpdateStatement`.

        Returns:
            :class:`mysqlx.Result`: The result object.
        """
        msg_type, msg = self.protocol.build_update(statement)
        self._execute_prepared_pipeline(msg_type, msg, statement)
        return Result(self)

    @catch_network_exception
    def execute_nonquery(
        self,
        namespace: str,
        cmd: str,
        raise_on_fail: bool,
        fields: Optional[Dict[str, Any]] = None,
    ) -> Optional[Result]:
        """Execute a non query command.

        Args:
            namespace (str): The namespace.
            cmd (str): The command.
            raise_on_fail (bool): `True` to raise on fail.
            fields (Optional[dict]): The message fields.

        Raises:
            :class:`mysqlx.OperationalError`: On errors.

        Returns:
            :class:`mysqlx.Result`: The result object.
        """
        try:
            msg_type, msg = self.protocol.build_execute_statement(
                namespace, cmd, fields
            )
            self.protocol.send_msg(msg_type, msg)
            return Result(self)
        except OperationalError:
            if raise_on_fail:
                raise
        return None

    @catch_network_exception
    def execute_sql_scalar(self, sql: StatementType) -> int:
        """Execute a SQL scalar.

        Args:
            sql (str): The SQL statement.

        Raises:
            :class:`mysqlx.InterfaceError`: If no data found.

        Returns:
            :class:`mysqlx.Result`: The result.
        """
        msg_type, msg = self.protocol.build_execute_statement("sql", sql)
        self.protocol.send_msg(msg_type, msg)
        result = RowResult(self)
        result.fetch_all()
        if result.count == 0:
            raise InterfaceError("No data found")
        return result[0][0]  # type: ignore[index]

    @catch_network_exception
    def get_row_result(self, cmd: str, fields: Dict[str, Any]) -> RowResult:
        """Returns the row result.

        Args:
            cmd (str): The command.
            fields (dict): The message fields.

        Returns:
            :class:`mysqlx.RowResult`: The result object.
        """
        msg_type, msg = self.protocol.build_execute_statement("mysqlx", cmd, fields)
        self.protocol.send_msg(msg_type, msg)
        return RowResult(self)

    @catch_network_exception
    def read_row(self, result: RowResult) -> Optional[MessageType]:
        """Read row.

        Args:
            result (:class:`mysqlx.RowResult`): The result object.
        """
        return self.protocol.read_row(result)

    @catch_network_exception
    def close_result(self, result: Result) -> None:
        """Close result.

        Args:
            result (:class:`mysqlx.Result`): The result object.
        """
        self.protocol.close_result(result)

    @catch_network_exception
    def get_column_metadata(self, result: Result) -> List[ColumnType]:
        """Get column metadata.

        Args:
            result (:class:`mysqlx.Result`): The result object.
        """
        return self.protocol.get_column_metadata(result)

    def get_next_statement_id(self) -> int:
        """Returns the next statement ID.

        Returns:
            int: A statement ID.

        .. versionadded:: 8.0.16
        """
        self._stmt_counter += 1
        return self._stmt_counter

    def is_open(self) -> bool:
        """Check if connection is open.

        Returns:
            bool: `True` if connection is open.
        """
        return self.stream.is_open()

    def set_server_disconnected(self, reason: Union[str, Tuple[str, int]]) -> None:
        """Set the disconnection message from the server.

        Args:
            reason (str): disconnection reason from the server.
        """
        self._server_disconnected = True
        self._server_disconnected_reason = reason

    def is_server_disconnected(self) -> bool:
        """Verify if the session has been disconnect from the server.

        Returns:
            bool: `True` if the connection has been closed from the server
                  otherwise `False`.
        """
        return self._server_disconnected

    def get_disconnected_reason(self) -> Optional[Union[str, Tuple[str, int]]]:
        """Get the disconnection message sent by the server.

        Returns:
            string: disconnection reason from the server.
        """
        return self._server_disconnected_reason

    def disconnect(self) -> None:
        """Disconnect from server."""
        if not self.is_open():
            return
        self.stream.close()

    def close_session(self) -> None:
        """Close a sucessfully authenticated session."""
        if not self.is_open():
            return

        try:
            # Fetch any active result
            self.fetch_active_result()
            # Deallocate all prepared statements
            if self._prepared_stmt_supported:
                for stmt_id in self._prepared_stmt_ids:
                    self.protocol.send_prepare_deallocate(stmt_id)
                self._stmt_counter = 0
            # Send session close
            self.protocol.send_close()
            self.protocol.read_ok()
        except (InterfaceError, OperationalError, OSError) as err:
            logger.warning(
                "Warning: An error occurred while attempting to close the "
                "connection: %s",
                err,
            )
        finally:
            # The remote connection with the server has been lost,
            # close the connection locally.
            self.stream.close()

    def reset_session(self) -> None:
        """Reset a sucessfully authenticated session."""
        if not self.is_open():
            return
        if self._active_result is not None:
            self._active_result.fetch_all()
        try:
            self.keep_open = self.protocol.send_reset(self.keep_open)
        except (InterfaceError, OperationalError) as err:
            logger.warning(
                "Warning: An error occurred while attempting to reset the "
                "session: %s",
                err,
            )

    def close_connection(self) -> None:
        """Announce to the server that the client wants to close the
        connection. Discards any session state of the server.
        """
        if not self.is_open():
            return
        if self._active_result is not None:
            self._active_result.fetch_all()
        self.protocol.send_connection_close()
        self.protocol.read_ok()
        self.stream.close()


class PooledConnection(Connection):
    """Class to hold :class:`Connection` instances in a pool.

    PooledConnection is used by :class:`ConnectionPool` to facilitate the
    connection to return to the pool once is not required, more specifically
    once the close_session() method is invoked. It works like a normal
    Connection except for methods like close() and sql().

    The close_session() method will add the connection back to the pool rather
    than disconnecting from the MySQL server.

    The sql() method is used to execute sql statements.

    Args:
        pool (ConnectionPool): The pool where this connection must return.

    .. versionadded:: 8.0.13
    """

    def __init__(self, pool: ConnectionPool) -> None:
        if not isinstance(pool, ConnectionPool):
            raise AttributeError("pool should be a ConnectionPool object")
        super().__init__(pool.cnx_config)
        self.pool: ConnectionPool = pool
        self.host: str = pool.cnx_config["host"]
        self.port: int = pool.cnx_config["port"]

    def close_connection(self) -> None:
        """Closes the connection.

        This method closes the socket.
        """
        super().close_session()

    def close_session(self) -> None:
        """Do not close, but add connection back to pool.

        The close_session() method does not close the connection with the
        MySQL server. The connection is added back to the pool so it
        can be reused.

        When the pool is configured to reset the session, the session
        state will be cleared by re-authenticating the user once the connection
        is get from the pool.
        """
        self.pool.add_connection(self)

    def reconnect(self) -> None:
        """Reconnect this connection."""
        if self._active_result is not None:
            self._active_result.fetch_all()
        self._authenticate()

    def reset(self) -> None:
        """Reset the connection.

        Resets the connection by re-authenticate.
        """
        self.reconnect()

    def sql(self, sql: str) -> SqlStatement:
        """Creates a :class:`mysqlx.SqlStatement` object to allow running the
        SQL statement on the target MySQL Server.

        Args:
            sql (string): The SQL statement to be executed.

        Returns:
            mysqlx.SqlStatement: SqlStatement object.
        """
        return SqlStatement(self, sql)


class ConnectionPool(queue.Queue):
    """This class represents a pool of connections.

    Initializes the Pool with the given name and settings.

    Args:
        name (str): The name of the pool, used to track a single pool per
                    combination of host and user.
        **kwargs:
            max_size (int): The maximun number of connections to hold in
                            the pool.
            reset_session (bool): If the connection should be reseted when
                                  is taken from the pool.
            max_idle_time (int): The maximum number of milliseconds to allow
                                 a connection to be idle in the queue before
                                 being closed. Zero value means infinite.
            queue_timeout (int): The maximum number of milliseconds a
                                 request will wait for a connection to
                                 become available. A zero value means
                                 infinite.
            priority (int): The router priority, to choose this pool over
                            other with lower priority.

    Raises:
        :class:`mysqlx.PoolError` on errors.

    .. versionadded:: 8.0.13
    """

    def __init__(self, name: str, **kwargs: Any) -> None:
        self._set_pool_name(name)
        self._open_sessions: int = 0
        self._connections_openned: List[PooledConnection] = []
        self._available: bool = True
        self._timeout: int = 0
        self._timeout_stamp: datetime = datetime.now()
        self.pool_max_size: int = kwargs.get("max_size", 25)
        # Can't invoke super due to Queue not is a new-style class
        queue.Queue.__init__(self, self.pool_max_size)
        self.reset_session: bool = kwargs.get("reset_session", True)
        self.max_idle_time: int = kwargs.get("max_idle_time", 25)
        self.settings: Dict[str, Any] = kwargs
        self.queue_timeout: int = kwargs.get("queue_timeout", 25)
        self.priority: int = kwargs.get("priority", 0)
        self.cnx_config: Dict[str, Any] = kwargs
        self.host: str = kwargs["host"]
        self.port: int = kwargs["port"]

    def _set_pool_name(self, pool_name: str) -> None:
        r"""Set the name of the pool.

        This method checks the validity and sets the name of the pool.

        Args:
            pool_name (str): The pool name.

        Raises:
            AttributeError: If the pool_name contains illegal characters
                            ([^a-zA-Z0-9._\-*$#]) or is longer than
                            connection._CNX_POOL_MAX_NAME_SIZE.
        """
        if _CNX_POOL_NAME_REGEX.search(pool_name):
            raise AttributeError(f"Pool name '{pool_name}' contains illegal characters")
        if len(pool_name) > _CNX_POOL_MAX_NAME_SIZE:
            raise AttributeError(f"Pool name '{pool_name}' is too long")
        self.name = pool_name

    @property
    def open_connections(self) -> int:
        """Returns the number of open connections that can return to this pool."""
        return len(self._connections_openned)

    def remove_connection(self, cnx: Optional[PooledConnection] = None) -> None:
        """Removes a connection from this pool.

        Args:
            cnx (PooledConnection): The connection object.
        """
        self._connections_openned.remove(cnx)

    def remove_connections(self) -> None:
        """Removes all the connections from the pool."""
        while self.qsize() > 0:
            try:
                cnx = self.get(block=True, timeout=self.queue_timeout)
            except queue.Empty:
                pass
            else:
                try:
                    cnx.close_connection()
                except (RuntimeError, OSError, InterfaceError):
                    pass
                finally:
                    self.remove_connection(cnx)

    def add_connection(self, cnx: Optional[PooledConnection] = None) -> None:
        """Adds a connection to this pool.

        This method instantiates a Connection using the configuration passed
        when initializing the ConnectionPool instance or using the set_config()
        method.
        If cnx is a Connection instance, it will be added to the queue.

        Args:
            cnx (PooledConnection): The connection object.

        Raises:
            PoolError: If no configuration is set, if no more connection can
                       be added (maximum reached) or if the connection can not
                       be instantiated.
        """
        if not self.cnx_config:
            raise PoolError("Connection configuration not available")

        if self.full():
            raise PoolError("Failed adding connection; queue is full")

        if not cnx:
            cnx = PooledConnection(self)
            # mysqlx_wait_timeout is only available on MySQL 8
            ver = cnx.sql(_SELECT_VERSION_QUERY).execute().fetch_all()[0][0]
            if tuple(int(n) for n in ver.split("-")[0].split(".")) > (
                8,
                0,
                10,
            ):
                cnx.sql(f"set mysqlx_wait_timeout = {self.max_idle_time}").execute()
            self._connections_openned.append(cnx)
        else:
            if not isinstance(cnx, PooledConnection):
                raise PoolError("Connection instance not subclass of PooledSession")
            if cnx.is_server_disconnected():
                self.remove_connections()
                cnx.close()  # type: ignore[attr-defined]

        self.queue_connection(cnx)

    def queue_connection(self, cnx: PooledConnection) -> None:
        """Put connection back in the queue:

        This method is putting a connection back in the queue.
        It will not acquire a lock as the methods using _queue_connection() will
        have it set.

        Args:
            PooledConnection: The connection object.

        Raises:
            PoolError: On errors.
        """
        if not isinstance(cnx, PooledConnection):
            raise PoolError("Connection instance not subclass of PooledSession.")

        # Reset the connection
        if self.reset_session:
            cnx.reset_session()
        try:
            self.put(cnx, block=False)
        except queue.Full as err:
            raise PoolError("Failed adding connection; queue is full") from err

    def track_connection(self, connection: PooledConnection) -> None:
        """Tracks connection in order of close it when client.close() is invoke."""
        self._connections_openned.append(connection)

    def __str__(self) -> str:
        return self.name

    def available(self) -> bool:
        """Returns if this pool is available for pool connections from it.

        Returns:
            bool: True if this pool is available else False.
        .. versionadded:: 8.0.20
        """
        return self._available

    def set_unavailable(self, time_out: int = -1) -> None:
        """Sets this pool unavailable for a period of time (in seconds).

        .. versionadded:: 8.0.20
        """
        if self._available:
            logger.warning(
                "ConnectionPool.set_unavailable pool: %s time_out: %s",
                self,
                time_out,
            )
            self._available = False
            self._timeout_stamp = datetime.now()
            self._timeout = time_out

    def set_available(self) -> None:
        """Sets this pool available for pool connections from it.

        .. versionadded:: 8.0.20
        """
        self._available = True
        self._timeout_stamp = datetime.now()

    def get_timeout_stamp(self) -> Tuple[int, datetime]:
        """Returns the penalized time (timeout) and the time at the penalty.

        Returns:
            tuple: penalty seconds (int), timestamp at penalty (datetime object)
        .. versionadded:: 8.0.20
        """
        return (self._timeout, self._timeout_stamp)

    def close(self) -> None:
        """Empty this ConnectionPool."""
        for cnx in self._connections_openned:
            cnx.close_connection()


class PoolsManager:
    """Manages a pool of connections for a host or hosts in routers.

    This class handles all the pools of Connections.

    .. versionadded:: 8.0.13
    """

    __instance: PoolsManager = None
    __pools: Dict[str, Any] = {}

    def __new__(cls) -> PoolsManager:
        if PoolsManager.__instance is None:
            PoolsManager.__instance = object.__new__(cls)
            PoolsManager.__pools = {}
        return PoolsManager.__instance

    def _pool_exists(self, client_id: str, pool_name: str) -> bool:
        """Verifies if a pool exists with the given name.

        Args:
            client_id (str): The client id.
            pool_name (str): The name of the pool.

        Returns:
            bool: Returns `True` if the pool exists otherwise `False`.
        """
        pools = self.__pools.get(client_id, [])
        for pool in pools:
            if pool.name == pool_name:
                return True
        return False

    def _get_pools(self, settings: Dict[str, Any]) -> List:
        """Retrieves a list of pools that shares the given settings.

        Args:
            settings (dict): the configuration of the pool.

        Returns:
            list: A list of pools that shares the given settings.
        """
        available_pools = []
        pool_names = []
        connections_settings = self._get_connections_settings(settings)

        # Generate the names of the pools this settings can connect to
        for router_name, _ in connections_settings:
            pool_names.append(router_name)

        # Generate the names of the pools this settings can connect to
        for pool in self.__pools.get(settings.get("client_id", "No id"), []):
            if pool.name in pool_names:
                available_pools.append(pool)
        return available_pools

    @staticmethod
    def _get_connections_settings(
        settings: Dict[str, Any]
    ) -> List[Tuple[str, Dict[str, Any]]]:
        """Generates a list of separated connection settings for each host.

        Gets a list of connection settings for each host or router found in the
        given settings.

        Args:
            settings (dict): The configuration for the connections.

        Returns:
            list: A list of connections settings
        """
        pool_settings = settings.copy()
        routers = pool_settings.get("routers", [])
        connections_settings = []
        if "routers" in pool_settings:
            pool_settings.pop("routers")
        if "host" in pool_settings and "port" in pool_settings:
            routers.append(
                {
                    "priority": 100,
                    "weight": 0,
                    "host": pool_settings["host"],
                    "port": pool_settings["port"],
                }
            )
        # Order routers
        routers.sort(key=lambda x: (x["priority"], -x.get("weight", 0)))
        for router in routers:
            connection_settings = pool_settings.copy()
            connection_settings["host"] = router["host"]
            connection_settings["port"] = router["port"]
            connection_settings["priority"] = router["priority"]
            connection_settings["weight"] = router.get("weight", 0)
            connections_settings.append(
                (
                    generate_pool_name(**connection_settings),
                    connection_settings,
                )
            )
        return connections_settings

    def create_pool(self, cnx_settings: Dict[str, Any]) -> None:
        """Creates a `ConnectionPool` instance to hold the connections.

        Creates a `ConnectionPool` instance to hold the connections only if
        no other pool exists with the same configuration.

        Args:
            cnx_settings (dict): The configuration for the connections.
        """
        connections_settings = self._get_connections_settings(cnx_settings)

        # Subscribe client if it does not exists
        if cnx_settings.get("client_id", "No id") not in self.__pools:
            self.__pools[cnx_settings.get("client_id", "No id")] = []

        # Create a pool for each router
        for router_name, settings in connections_settings:
            if self._pool_exists(cnx_settings.get("client_id", "No id"), router_name):
                continue
            pool = self.__pools.get(cnx_settings.get("client_id", "No id"), [])
            pool.append(ConnectionPool(router_name, **settings))

    @staticmethod
    def _get_random_pool(pool_list: List[ConnectionPool]) -> ConnectionPool:
        """Get a random router from the group with the given priority.

        Returns:
            Router: a random router.

        .. versionadded:: 8.0.20
        """
        if not pool_list:
            return None
        if len(pool_list) == 1:
            return pool_list[0]

        last = len(pool_list) - 1
        index = random.randint(0, last)
        return pool_list[index]

    @staticmethod
    def _get_sublist(
        pools: List[ConnectionPool], index: int, cur_priority: int
    ) -> List[ConnectionPool]:
        sublist = []
        next_priority = None
        while index < len(pools):
            next_priority = pools[index].priority
            if cur_priority == next_priority and pools[index].available():
                sublist.append(pools[index])
            elif cur_priority != next_priority:
                break
            index += 1
        return sublist

    def _get_next_pool(
        self, pools: List[ConnectionPool], cur_priority: int
    ) -> ConnectionPool:
        index = 0
        for pool in pools:
            if pool.available() and cur_priority == pool.priority:
                break
            index += 1
        subpool: List = []
        while not subpool and index < len(pools):
            subpool = self._get_sublist(pools, index, cur_priority)
            index += 1
        return self._get_random_pool(subpool)

    @staticmethod
    def _get_next_priority(
        pools: List[ConnectionPool], cur_priority: Optional[int] = None
    ) -> int:
        if cur_priority is None and pools:
            return pools[0].priority
        # find the first pool that does not share the same priority
        for t_pool in pools:
            if t_pool.available():
                cur_priority = t_pool.priority
                return cur_priority
        return pools[0].priority

    def _check_unavailable_pools(
        self, settings: Dict[str, Any], revive: Optional[bool] = None
    ) -> None:
        pools = self._get_pools(settings)
        for pool in pools:
            if pool.available():
                continue
            timeout, timeout_stamp = pool.get_timeout_stamp()
            if revive:
                timeout = revive
            if datetime.now() > (timeout_stamp + timedelta(seconds=timeout)):
                pool.set_available()

    def get_connection(self, settings: Dict[str, Any]) -> PooledConnection:
        """Get a connection from the pool.

        This method returns an `PooledConnection` instance which has a reference
        to the pool that created it, and can be used as a normal Connection.

        When the MySQL connection is not connected, a reconnect is attempted.

        Raises:
            :class:`PoolError`: On errors.

        Returns:
            PooledConnection: A pooled connection object.
        """

        def set_mysqlx_wait_timeout(cnx: PooledConnection) -> None:
            ver = cnx.sql(_SELECT_VERSION_QUERY).execute().fetch_all()[0][0]
            # mysqlx_wait_timeout is only available on MySQL 8
            if tuple(int(n) for n in ver.split("-")[0].split(".")) > (
                8,
                0,
                10,
            ):
                cnx.sql(f"set mysqlx_wait_timeout = {pool.max_idle_time}").execute()

        pools = self._get_pools(settings)
        cur_priority = settings.get("cur_priority", None)
        error_list = []
        self._check_unavailable_pools(settings)
        cur_priority = self._get_next_priority(pools, cur_priority)
        if cur_priority is None:
            raise PoolError(
                "Unable to connect to any of the target hosts. No pool is available"
            )
        settings["cur_priority"] = cur_priority
        pool = self._get_next_pool(pools, cur_priority)
        lock = threading.RLock()
        while pool is not None:
            try:
                # Check connections aviability in this pool
                if pool.qsize() > 0:
                    # We have connections in pool, try to return a working one
                    with lock:
                        try:
                            cnx = pool.get(block=True, timeout=pool.queue_timeout)
                        except queue.Empty:
                            raise PoolError(
                                "Failed getting connection; pool exhausted"
                            ) from None
                        try:
                            if cnx.is_server_disconnected():
                                pool.remove_connections()
                            # Only reset the connection by re-authentification
                            # if the connection was unable to keep open by the
                            # server
                            if not cnx.keep_open:
                                cnx.reset()
                            set_mysqlx_wait_timeout(cnx)
                        except (RuntimeError, OSError, InterfaceError):
                            # Unable to reset connection, close and remove
                            try:
                                cnx.close_connection()
                            except (RuntimeError, OSError, InterfaceError):
                                pass
                            finally:
                                pool.remove_connection(cnx)
                            # By WL#13222 all idle sessions that connect to the
                            # same endpoint should be removed from the pool.
                            while pool.qsize() > 0:
                                try:
                                    cnx = pool.get(
                                        block=True, timeout=pool.queue_timeout
                                    )
                                except queue.Empty:
                                    pass
                                else:
                                    try:
                                        cnx.close_connection()
                                    except (RuntimeError, OSError, InterfaceError):
                                        pass
                                    finally:
                                        pool.remove_connection(cnx)
                            # Connection was closed by the server, create new
                            try:
                                cnx = PooledConnection(pool)
                                pool.track_connection(cnx)
                                cnx.connect()
                                set_mysqlx_wait_timeout(cnx)
                            except (RuntimeError, OSError, InterfaceError):
                                pass
                            finally:
                                # Server must be down, take down idle
                                # connections from this pool
                                while pool.qsize() > 0:
                                    try:
                                        cnx = pool.get(
                                            block=True,
                                            timeout=pool.queue_timeout,
                                        )
                                        cnx.close_connection()
                                        pool.remove_connection(cnx)
                                    except (RuntimeError, OSError, InterfaceError):
                                        pass
                        return cnx
                elif pool.open_connections < pool.pool_max_size:
                    # No connections in pool, but we can open a new one
                    cnx = PooledConnection(pool)
                    pool.track_connection(cnx)
                    cnx.connect()
                    set_mysqlx_wait_timeout(cnx)
                    return cnx
                else:
                    # Pool is exaust so the client needs to wait
                    with lock:
                        try:
                            cnx = pool.get(block=True, timeout=pool.queue_timeout)
                            cnx.reset()
                            set_mysqlx_wait_timeout(cnx)
                            return cnx
                        except queue.Empty:
                            raise PoolError("pool max size has been reached") from None
            except (InterfaceError, TimeoutError, PoolError) as err:
                error_list.append(f"pool: {pool} error: {err}")
                if isinstance(err, PoolError):
                    # Pool can be exhaust now but can be ready again in no time,
                    # e.g a connection is returned to the pool.
                    pool.set_unavailable(2)
                else:
                    self.set_pool_unavailable(pool, err)

                self._check_unavailable_pools(settings)
                # Try next pool with the same priority
                pool = self._get_next_pool(pools, cur_priority)

                if pool is None:
                    cur_priority = self._get_next_priority(pools, cur_priority)
                    settings["cur_priority"] = cur_priority
                    pool = self._get_next_pool(pools, cur_priority)
                    if pool is None:
                        msg = "\n  ".join(error_list)
                        raise PoolError(
                            "Unable to connect to any of the target hosts: "
                            f"[\n  {msg}\n]"
                        ) from err
                continue

        raise PoolError("Unable to connect to any of the target hosts")

    def close_pool(self, cnx_settings: Dict[str, Any]) -> int:
        """Closes the connections in the pools

        Returns:
            int: The number of closed pools
        """
        pools = self._get_pools(cnx_settings)
        for pool in pools:
            pool.close()
            # Remove the pool
            if cnx_settings.get("client_id", None) is not None:
                client_pools = self.__pools.get(cnx_settings.get("client_id"))
                if pool in client_pools:
                    client_pools.remove(pool)
        return len(pools)

    @staticmethod
    def set_pool_unavailable(
        pool: ConnectionPool, err: Union[InterfaceError, TimeoutError]
    ) -> None:
        """Sets a pool as unavailable.

        The time a pool is set unavailable depends on the given error message
        or the error number.

        Args:
            pool (ConnectionPool): The pool to set unavailable.
            err (Exception): The raised exception raised by a connection belonging
                             to the pool.
        """
        penalty = None
        try:
            err_no = err.errno
            penalty = _TIMEOUT_PENALTIES_BY_ERR_NO[err_no]
        except (AttributeError, KeyError):
            pass
        if not penalty:
            err_msg = err.msg
            for key, value in _TIMEOUT_PENALTIES.items():
                if key in err_msg:
                    penalty = value
        if penalty:
            pool.set_unavailable(penalty)
        else:
            # Other errors are severe punished
            pool.set_unavailable(100000)


class Session:
    """Enables interaction with a X Protocol enabled MySQL Product.

    The functionality includes:

    - Accessing available schemas.
    - Schema management operations.
    - Retrieval of connection information.

    Args:
        settings (dict): Connection data used to connect to the database.
    """

    def __init__(self, settings: Dict[str, Any]) -> None:
        self.use_pure: bool = settings.get("use-pure", Protobuf.use_pure)
        self._settings: Dict[str, Any] = settings

        # Check for DNS SRV
        if settings.get("host") and settings.get("dns-srv"):
            if not HAVE_DNSPYTHON:
                raise InterfaceError(
                    "MySQL host configuration requested DNS "
                    "SRV. This requires the Python dnspython "
                    "module. Please refer to documentation"
                )
            try:
                srv_records = dns.resolver.query(settings["host"], "SRV")
            except dns.exception.DNSException as err:
                raise InterfaceError(
                    f"Unable to locate any hosts for '{settings['host']}'"
                ) from err
            self._settings["routers"] = []
            for srv in srv_records:
                self._settings["routers"].append(
                    {
                        "host": srv.target.to_text(omit_final_dot=True),
                        "port": srv.port,
                        "priority": srv.priority,
                        "weight": srv.weight,
                    }
                )

        if (
            "connection-attributes" not in self._settings
            or self._settings["connection-attributes"] is not False
        ):
            self._settings["attributes"] = {}
            self._init_attributes()

        if "pooling" in settings and settings["pooling"]:
            # Create pool and retrieve a Connection instance
            PoolsManager().create_pool(settings)
            self._connection: Connection = PoolsManager().get_connection(settings)
            if self._connection is None:
                raise PoolError("Connection could not be retrieved from pool")
        else:
            self._connection = Connection(self._settings)
            self._connection.connect()
        # Set default schema
        schema = self._settings.get("schema")
        if schema:
            try:
                self.sql(f"USE {quote_identifier(schema)}").execute()
            except OperationalError as err:
                # Access denied for user will raise err.errno = 1044
                errmsg = (
                    err.msg
                    if err.errno == 1044
                    else f"Default schema '{schema}' does not exists"
                )
                raise InterfaceError(errmsg, err.errno) from err

    def __enter__(self) -> Session:
        return self

    def __exit__(
        self,
        exc_type: Type[BaseException],
        exc_value: BaseException,
        traceback: TracebackType,
    ) -> None:
        self.close()

    def _init_attributes(self) -> None:
        """Setup default and user defined connection-attributes."""
        if os.name == "nt":
            if "64" in platform.architecture()[0]:
                platform_arch: Union[str, Tuple[str, str]] = "x86_64"
            elif "32" in platform.architecture()[0]:
                platform_arch = "i386"
            else:
                platform_arch = platform.architecture()
            os_ver = f"Windows-{platform.win32_ver()[1]}"
        else:
            platform_arch = platform.machine()
            if platform.system() == "Darwin":
                os_ver = f"macOS-{platform.mac_ver()[0]}"
            else:
                os_ver = "-".join(linux_distribution()[0:2])

        license_chunks = LICENSE.split(" ")
        if license_chunks[0] == "GPLv2":
            client_license = "GPL-2.0"
        else:
            client_license = "Commercial"

        default_attributes = {
            # Process id
            "_pid": str(os.getpid()),
            # Platform architecture
            "_platform": platform_arch,
            # OS version
            "_os": os_ver,
            # Hostname of the local machine
            "_source_host": socket.gethostname(),
            # Client's name
            "_client_name": "mysql-connector-python",
            # Client's version
            "_client_version": ".".join([str(x) for x in VERSION[0:3]]),
            # Client's License identifier
            "_client_license": client_license,
        }
        self._settings["attributes"].update(default_attributes)

        if "connection-attributes" in self._settings:
            for attr_name in self._settings["connection-attributes"]:
                attr_value = self._settings["connection-attributes"][attr_name]
                # Validate name type
                if not isinstance(attr_name, str):
                    raise InterfaceError(
                        f"Attribute name '{attr_name}' must be a string type"
                    )
                # Validate attribute name limit 32 characters
                if len(attr_name) > 32:
                    raise InterfaceError(
                        f"Attribute name '{attr_name}' exceeds 32 characters "
                        "limit size"
                    )
                # Validate names in connection-attributes cannot start with "_"
                if attr_name.startswith("_"):
                    raise InterfaceError(
                        "Key names in 'session-connect-attributes' cannot "
                        f"start with '_', found: {attr_name}"
                    )
                # Validate value type
                if not isinstance(attr_value, str):
                    raise InterfaceError(
                        f"Attribute name '{attr_name}' value '{attr_value}' "
                        " must be a string type"
                    )

                # Validate attribute value limit 1024 characters
                if len(attr_value) > 1024:
                    raise InterfaceError(
                        f"Attribute name '{attr_name}' value: '{attr_value}' "
                        "exceeds 1024 characters limit size"
                    )

                self._settings["attributes"][attr_name] = attr_value

    @property
    def use_pure(self) -> bool:
        """bool: `True` to use pure Python Protobuf implementation."""
        return Protobuf.use_pure

    @use_pure.setter
    def use_pure(self, value: bool) -> None:
        if not isinstance(value, bool):
            raise ProgrammingError("'use_pure' option should be True or False")
        Protobuf.set_use_pure(value)

    def is_open(self) -> bool:
        """Returns `True` if the session is open.

        Returns:
            bool: Returns `True` if the session is open.
        """
        return self._connection.stream.is_open()

    def sql(self, sql: str) -> SqlStatement:
        """Creates a :class:`mysqlx.SqlStatement` object to allow running the
        SQL statement on the target MySQL Server.

        Args:
            sql (string): The SQL statement to be executed.

        Returns:
            mysqlx.SqlStatement: SqlStatement object.
        """
        return SqlStatement(self._connection, sql)

    def get_connection(self) -> Connection:
        """Returns the underlying connection.

        Returns:
            mysqlx.connection.Connection: The connection object.
        """
        return self._connection

    def get_schemas(self) -> List[str]:
        """Returns the list of schemas in the current session.

        Returns:
            `list`: The list of schemas in the current session.

        .. versionadded:: 8.0.12
        """
        result = self.sql("SHOW DATABASES").execute()
        return [row[0] for row in result.fetch_all()]

    def get_schema(self, name: str) -> Schema:
        """Retrieves a Schema object from the current session by it's name.

        Args:
            name (string): The name of the Schema object to be retrieved.

        Returns:
            mysqlx.Schema: The Schema object with the given name.
        """
        return Schema(self, name)

    def get_default_schema(self) -> Optional[Schema]:
        """Retrieves a Schema object from the current session by the schema
        name configured in the connection settings.

        Returns:
            mysqlx.Schema: The Schema object with the given name at connect
                           time.
            None: In case the default schema was not provided with the
                  initialization data.

        Raises:
            :class:`mysqlx.ProgrammingError`: If the provided default schema
                                              does not exists.
        """
        schema = self._connection.settings.get("schema")
        if schema:
            res = (
                self.sql(_SELECT_SCHEMA_NAME_QUERY.format(escape(schema)))
                .execute()
                .fetch_all()
            )
            try:
                if res[0][0] == schema:  # type: ignore[index]
                    return Schema(self, schema)
            except IndexError:
                raise ProgrammingError(
                    f"Default schema '{schema}' does not exists"
                ) from None
        return None

    def drop_schema(self, name: str) -> None:
        """Drops the schema with the specified name.

        Args:
            name (string): The name of the Schema object to be retrieved.
        """
        self._connection.execute_nonquery(
            "sql", _DROP_DATABASE_QUERY.format(quote_identifier(name)), True
        )

    def create_schema(self, name: str) -> Schema:
        """Creates a schema on the database and returns the corresponding
        object.

        Args:
            name (string): A string value indicating the schema name.
        """
        self._connection.execute_nonquery(
            "sql", _CREATE_DATABASE_QUERY.format(quote_identifier(name)), True
        )
        return Schema(self, name)

    def start_transaction(self) -> None:
        """Starts a transaction context on the server."""
        self._connection.execute_nonquery("sql", "START TRANSACTION", True)

    def commit(self) -> None:
        """Commits all the operations executed after a call to
        startTransaction().
        """
        self._connection.execute_nonquery("sql", "COMMIT", True)

    def rollback(self) -> None:
        """Discards all the operations executed after a call to
        startTransaction().
        """
        self._connection.execute_nonquery("sql", "ROLLBACK", True)

    def set_savepoint(self, name: Optional[str] = None) -> str:
        """Creates a transaction savepoint.

        If a name is not provided, one will be generated using the uuid.uuid1()
        function.

        Args:
            name (Optional[string]): The savepoint name.

        Returns:
            string: The savepoint name.
        """
        if name is None:
            name = f"{uuid.uuid1()}"
        elif not isinstance(name, str) or len(name.strip()) == 0:
            raise ProgrammingError("Invalid SAVEPOINT name")
        self._connection.execute_nonquery(
            "sql", f"SAVEPOINT {quote_identifier(name)}", True
        )
        return name

    def rollback_to(self, name: str) -> None:
        """Rollback to a transaction savepoint with the given name.

        Args:
            name (string): The savepoint name.
        """
        if not isinstance(name, str) or len(name.strip()) == 0:
            raise ProgrammingError("Invalid SAVEPOINT name")
        self._connection.execute_nonquery(
            "sql",
            f"ROLLBACK TO SAVEPOINT {quote_identifier(name)}",
            True,
        )

    def release_savepoint(self, name: str) -> None:
        """Release a transaction savepoint with the given name.

        Args:
            name (string): The savepoint name.
        """
        if not isinstance(name, str) or len(name.strip()) == 0:
            raise ProgrammingError("Invalid SAVEPOINT name")
        self._connection.execute_nonquery(
            "sql",
            f"RELEASE SAVEPOINT {quote_identifier(name)}",
            True,
        )

    def close(self) -> None:
        """Closes the session."""
        self._connection.close_session()
        # Set an unconnected connection
        self._connection = Connection(self._settings)

    def close_connections(self) -> None:
        """Closes all underliying connections as pooled connections"""
        self._connection.close_connection()


class Client:
    """Class defining a client, it stores a connection configuration.

    Args:
        connection_dict (dict): The connection information to connect to a
                                MySQL server.
        options_dict (dict): The options to configure this client.

    .. versionadded:: 8.0.13
    """

    def __init__(
        self,
        connection_dict: Dict[str, Any],
        options_dict: Optional[Dict[str, Any]] = None,
    ) -> None:
        self.settings: Dict[str, Any] = connection_dict
        if options_dict is None:
            options_dict = {}

        self.sessions: List[Session] = []
        self.client_id: uuid.UUID = uuid.uuid4()

        self._set_pool_size(options_dict.get("max_size", 25))
        self._set_max_idle_time(options_dict.get("max_idle_time", 0))
        self._set_queue_timeout(options_dict.get("queue_timeout", 0))
        self._set_pool_enabled(options_dict.get("enabled", True))

        self.settings["pooling"] = self.pooling_enabled
        self.settings["max_size"] = self.max_size
        self.settings["client_id"] = self.client_id

    def __enter__(self) -> Client:
        return self

    def __exit__(
        self,
        exc_type: Type[BaseException],
        exc_value: BaseException,
        traceback: TracebackType,
    ) -> None:
        self.close()

    def _set_pool_size(self, pool_size: int) -> None:
        """Set the size of the pool.

        This method sets the size of the pool but it will not resize the pool.

        Args:
            pool_size (int): An integer equal or greater than 0 indicating
                             the pool size.

        Raises:
            :class:`AttributeError`: If the pool_size value is not an integer
                                     greater or equal to 0.
        """
        if (
            isinstance(pool_size, bool)
            or not isinstance(pool_size, int)
            or not pool_size > 0
        ):
            raise AttributeError(
                "Pool max_size value must be an integer greater than 0, the "
                f"given value {pool_size} is not valid"
            )

        self.max_size = _CNX_POOL_MAXSIZE if pool_size == 0 else pool_size

    def _set_max_idle_time(self, max_idle_time: int) -> None:
        """Set the max idle time.

        This method sets the max idle time.

        Args:
            max_idle_time (int): An integer equal or greater than 0 indicating
                                 the max idle time.

        Raises:
            :class:`AttributeError`: If the max_idle_time value is not an
                                     integer greater or equal to 0.
        """
        if (
            isinstance(max_idle_time, bool)
            or not isinstance(max_idle_time, int)
            or not max_idle_time > -1
        ):
            raise AttributeError(
                "Connection max_idle_time value must be an integer greater or "
                f"equal to 0, the given value {max_idle_time} is not valid"
            )

        self.max_idle_time = max_idle_time
        self.settings["max_idle_time"] = (
            _CNX_POOL_MAX_IDLE_TIME if max_idle_time == 0 else int(max_idle_time / 1000)
        )

    def _set_pool_enabled(self, enabled: bool) -> None:
        """Set if the pool is enabled.

        This method sets if the pool is enabled.

        Args:
            enabled (bool): True if to enabling the pool.

        Raises:
            :class:`AttributeError`: If the value of enabled is not a bool type.
        """
        if not isinstance(enabled, bool):
            raise AttributeError("The enabled value should be True or False.")
        self.pooling_enabled = enabled

    def _set_queue_timeout(self, queue_timeout: int) -> None:
        """Set the queue timeout.

        This method sets the queue timeout.

        Args:
            queue_timeout (int): An integer equal or greater than 0 indicating
                                 the queue timeout.

        Raises:
            :class:`AttributeError`: If the queue_timeout value is not an
                                     integer greater or equal to 0.
        """
        if (
            isinstance(queue_timeout, bool)
            or not isinstance(queue_timeout, int)
            or not queue_timeout > -1
        ):
            raise AttributeError(
                "Connection queue_timeout value must be an integer greater or "
                f"equal to 0, the given value {queue_timeout} is not valid"
            )

        self.queue_timeout = queue_timeout
        self.settings["queue_timeout"] = (
            _CNX_POOL_QUEUE_TIMEOUT if queue_timeout == 0 else int(queue_timeout / 1000)
        )
        # To avoid a connection stall waiting for the server, if the
        # connect-timeout is not given, use the queue_timeout
        if "connect-timeout" not in self.settings:
            self.settings["connect-timeout"] = self.queue_timeout

    def get_session(self) -> Session:
        """Creates a Session instance using the provided connection data.

        Returns:
            Session: Session object.
        """
        session = Session(self.settings)
        self.sessions.append(session)
        return session

    def close(self) -> None:
        """Closes the sessions opened by this client."""
        PoolsManager().close_pool(self.settings)
        for session in self.sessions:
            session.close_connections()


def _parse_address_list(path: str) -> Union[Dict[str, List[Dict]], Dict]:
    """Parses a list of host, port pairs.

    Args:
        path: String containing a list of routers or just router

    Returns:
        Returns a dict with parsed values of host, port and priority if
        specified.
    """
    path = path.replace(" ", "")
    array = (
        not ("," not in path and path.count(":") > 1 and path.count("[") == 1)
        and path.startswith("[")
        and path.endswith("]")
    )

    routers = []
    address_list = _SPLIT_RE.split(path[1:-1] if array else path)
    priority_count = 0
    for address in address_list:
        router: Dict = {}

        match = _PRIORITY_RE.match(address)
        if match:
            address = match.group(1)
            router["priority"] = int(match.group(2))
            priority_count += 1
        else:
            match = _ROUTER_RE.match(address)
            if match:
                address = match.group(1)
                router["priority"] = 100

        match = urlparse(f"//{address}")
        if not match.hostname:
            raise InterfaceError(f"Invalid address: {address}")

        try:
            router.update(host=match.hostname, port=match.port)
        except ValueError as err:
            raise ProgrammingError(f"Invalid URI: {err}", 4002) from err

        routers.append(router)

    if 0 < priority_count < len(address_list):
        raise ProgrammingError(
            "You must either assign no priority to any of the routers or give "
            "a priority for every router",
            4000,
        )

    return {"routers": routers} if array else routers[0]


def _parse_connection_uri(uri: str) -> Dict[str, Any]:
    """Parses the connection string and returns a dictionary with the
    connection settings.

    Args:
        uri: mysqlx URI scheme to connect to a MySQL server/farm.

    Returns:
        Returns a dict with parsed values of credentials and address of the
        MySQL server/farm.

    Raises:
        :class:`mysqlx.InterfaceError`: If contains a invalid option.
    """
    settings: Dict[str, Any] = {"schema": ""}

    match = _URI_SCHEME_RE.match(uri)
    scheme, uri = match.groups() if match else ("mysqlx", uri)

    if scheme not in ("mysqlx", "mysqlx+srv"):
        raise InterfaceError(f"Scheme '{scheme}' is not valid")

    if scheme == "mysqlx+srv":
        settings["dns-srv"] = True

    userinfo, tmp = uri.partition("@")[::2]
    host, query_str = tmp.partition("?")[::2]

    pos = host.rfind("/")
    if host[pos:].find(")") == -1 and pos > 0:
        host, settings["schema"] = host.rsplit("/", 1)
    host = host.strip("()")

    if not host or not userinfo or ":" not in userinfo:
        raise InterfaceError(f"Malformed URI '{uri}'")
    user, password = userinfo.split(":", 1)
    settings["user"], settings["password"] = unquote(user), unquote(password)

    if host.startswith(("/", "..", ".")):
        settings["socket"] = unquote(host)
    elif host.startswith("\\."):
        raise InterfaceError("Windows Pipe is not supported")
    else:
        settings.update(_parse_address_list(host))

    invalid_options = ("user", "password", "dns-srv")
    for key, val in parse_qsl(query_str, True):
        opt = key.replace("_", "-").lower()
        if opt in invalid_options:
            raise InterfaceError(f"Invalid option: '{key}'")
        if opt in _SSL_OPTS:
            settings[opt] = unquote(val.strip("()"))
        else:
            val_str = val.lower()
            if val_str in ("1", "true"):
                settings[opt] = True
            elif val_str in ("0", "false"):
                settings[opt] = False
            else:
                settings[opt] = val_str
    return settings


def _validate_settings(settings: Dict[str, Any]) -> None:
    """Validates the settings to be passed to a Session object
    the port values are converted to int if specified or set to 33060
    otherwise. The priority values for each router is converted to int
    if specified.

    Args:
        settings: dict containing connection settings.

    Raises:
        :class:`mysqlx.InterfaceError`: On any configuration issue.
    """
    invalid_opts = set(settings.keys()).difference(_SESS_OPTS)
    if invalid_opts:
        invalid_opts_list = "', '".join(invalid_opts)
        raise InterfaceError(f"Invalid option(s): '{invalid_opts_list}'")

    if "routers" in settings:
        for router in settings["routers"]:
            _validate_hosts(router, 33060)
    elif "host" in settings:
        _validate_hosts(settings)

    if "ssl-mode" in settings:
        ssl_mode = settings["ssl-mode"]
        try:
            settings["ssl-mode"] = SSLMode(
                ssl_mode.lower().strip() if isinstance(ssl_mode, str) else ssl_mode
            )
        except (AttributeError, ValueError) as err:
            raise InterfaceError(f"Invalid SSL Mode '{settings['ssl-mode']}'") from err
        if "ssl-ca" not in settings and settings["ssl-mode"] in [
            SSLMode.VERIFY_IDENTITY,
            SSLMode.VERIFY_CA,
        ]:
            raise InterfaceError("Cannot verify Server without CA")

    if "ssl-crl" in settings and "ssl-ca" not in settings:
        raise InterfaceError("CA Certificate not provided")

    if "ssl-key" in settings and "ssl-cert" not in settings:
        raise InterfaceError("Client Certificate not provided")

    if "ssl-ca" in settings and settings.get("ssl-mode") not in [
        SSLMode.VERIFY_IDENTITY,
        SSLMode.VERIFY_CA,
        SSLMode.DISABLED,
    ]:
        raise InterfaceError("Must verify Server if CA is provided")

    if "auth" in settings:
        auth = settings["auth"]
        try:
            settings["auth"] = Auth(
                auth.lower().strip() if isinstance(auth, str) else auth
            )
        except (AttributeError, ValueError) as err:
            raise InterfaceError(f"Invalid Auth '{settings['auth']}'") from err

    if "compression" in settings:
        compression = settings["compression"]
        try:
            settings["compression"] = Compression(
                compression.lower().strip()
                if isinstance(compression, str)
                else compression
            )
        except (AttributeError, ValueError) as err:
            raise InterfaceError(
                "The connection property 'compression' acceptable values are: "
                "'preferred', 'required', or 'disabled'. The value "
                f"'{settings['compression']}' is not acceptable"
            ) from err

    if "compression-algorithms" in settings:
        if isinstance(settings["compression-algorithms"], str):
            compression_algorithms = (
                settings["compression-algorithms"].strip().strip("[]")
            )
            if compression_algorithms:
                settings["compression-algorithms"] = compression_algorithms.split(",")
            else:
                settings["compression-algorithms"] = None
        elif not isinstance(settings["compression-algorithms"], (list, tuple)):
            raise InterfaceError(
                "Invalid type of the connection property 'compression-algorithms'"
            )
        if settings.get("compression") == Compression.DISABLED:
            settings["compression-algorithms"] = None

    if "connection-attributes" in settings:
        _validate_connection_attributes(settings)

    if "connect-timeout" in settings:
        try:
            if isinstance(settings["connect-timeout"], str):
                settings["connect-timeout"] = int(settings["connect-timeout"])
            if (
                not isinstance(settings["connect-timeout"], int)
                or settings["connect-timeout"] < 0
            ):
                raise ValueError
        except ValueError:
            raise TypeError(
                "The connection timeout value must be a positive "
                "integer (including 0)"
            ) from None

    if "dns-srv" in settings:
        if not isinstance(settings["dns-srv"], bool):
            raise InterfaceError("The value of 'dns-srv' must be a boolean")
        if settings.get("socket"):
            raise InterfaceError(
                "Using Unix domain sockets with DNS SRV lookup is not allowed"
            )
        if settings.get("port"):
            raise InterfaceError(
                "Specifying a port number with DNS SRV lookup is not allowed"
            )
        if settings.get("routers"):
            raise InterfaceError(
                "Specifying multiple hostnames with DNS SRV look up is not allowed"
            )
    elif "host" in settings and not settings.get("port"):
        settings["port"] = 33060

    if "tls-versions" in settings:
        _validate_tls_versions(settings)

    if "tls-ciphersuites" in settings:
        _validate_tls_ciphersuites(settings)


def _validate_hosts(
    settings: Dict[str, Any], default_port: Optional[int] = None
) -> None:
    """Validate hosts.

    Args:
        settings (dict): Settings dictionary.
        default_port (int): Default connection port.

    Raises:
        :class:`mysqlx.InterfaceError`: If priority or port are invalid.
    """
    if "priority" in settings and settings["priority"]:
        try:
            settings["priority"] = int(settings["priority"])
            if settings["priority"] < 0 or settings["priority"] > 100:
                raise ProgrammingError(
                    "Invalid priority value, must be between 0 and 100",
                    4007,
                )
        except NameError:
            raise ProgrammingError("Invalid priority", 4007) from None
        except ValueError:
            raise ProgrammingError(
                f"Invalid priority: {settings['priority']}", 4007
            ) from None

    if "port" in settings and settings["port"]:
        try:
            settings["port"] = int(settings["port"])
        except NameError:
            raise InterfaceError("Invalid port") from None
    elif "host" in settings and default_port:
        settings["port"] = default_port


def _validate_connection_attributes(settings: Dict[str, Any]) -> None:
    """Validate connection-attributes.

    Args:
        settings (dict): Settings dictionary.

    Raises:
        :class:`mysqlx.InterfaceError`: If attribute name or value exceeds size.
    """
    attributes = {}
    if "connection-attributes" not in settings:
        return

    conn_attrs = settings["connection-attributes"]

    if isinstance(conn_attrs, str):
        if conn_attrs == "":
            settings["connection-attributes"] = {}
            return
        if not (
            conn_attrs.startswith("[") and conn_attrs.endswith("]")
        ) and conn_attrs not in ["False", "false", "True", "true"]:
            raise InterfaceError(
                "The value of 'connection-attributes' must be a boolean or a "
                f"list of key-value pairs, found: '{conn_attrs}'"
            )
        if conn_attrs in ["False", "false", "True", "true"]:
            if conn_attrs in ["False", "false"]:
                settings["connection-attributes"] = False
            else:
                settings["connection-attributes"] = {}
            return
        conn_attributes = conn_attrs[1:-1].split(",")
        for attr in conn_attributes:
            if attr == "":
                continue
            attr_name_val = attr.split("=")
            attr_name = attr_name_val[0]
            attr_val = attr_name_val[1] if len(attr_name_val) > 1 else ""
            if attr_name in attributes:
                raise InterfaceError(
                    f"Duplicate key '{attr_name}' used in connection-attributes"
                )
            attributes[attr_name] = attr_val
    elif isinstance(conn_attrs, dict):
        for attr_name in conn_attrs:
            attr_value = conn_attrs[attr_name]
            if not isinstance(attr_value, str):
                attr_value = repr(attr_value)
            attributes[attr_name] = attr_value
    elif isinstance(conn_attrs, bool) or conn_attrs in [0, 1]:
        if conn_attrs:
            settings["connection-attributes"] = {}
        else:
            settings["connection-attributes"] = False
        return
    elif isinstance(conn_attrs, set):
        for attr_name in conn_attrs:
            attributes[attr_name] = ""
    elif isinstance(conn_attrs, list):
        for attr in conn_attrs:
            if attr == "":
                continue
            attr_name_val = attr.split("=")
            attr_name = attr_name_val[0]
            attr_val = attr_name_val[1] if len(attr_name_val) > 1 else ""
            if attr_name in attributes:
                raise InterfaceError(
                    f"Duplicate key '{attr_name}' used in connection-attributes"
                )
            attributes[attr_name] = attr_val
    elif not isinstance(conn_attrs, bool):
        raise InterfaceError(
            "connection-attributes must be Boolean or a list of key-value "
            f"pairs, found: '{conn_attrs}'"
        )

    if attributes:
        for attr_name, attr_value in attributes.items():
            # Validate name type
            if not isinstance(attr_name, str):
                raise InterfaceError(
                    f"Attribute name '{attr_name}' must be a string type"
                )
            # Validate attribute name limit 32 characters
            if len(attr_name) > 32:
                raise InterfaceError(
                    f"Attribute name '{attr_name}' exceeds 32 characters limit size"
                )
            # Validate names in connection-attributes cannot start with "_"
            if attr_name.startswith("_"):
                raise InterfaceError(
                    "Key names in connection-attributes cannot start with "
                    f"'_', found: '{attr_name}'"
                )

            # Validate value type
            if not isinstance(attr_value, str):
                raise InterfaceError(
                    f"Attribute '{attr_name}' value: '{attr_value}' must be "
                    "a string type"
                )
            # Validate attribute value limit 1024 characters
            if len(attr_value) > 1024:
                raise InterfaceError(
                    f"Attribute '{attr_name}' value: '{attr_value}' exceeds "
                    "1024 characters limit size"
                )

    settings["connection-attributes"] = attributes


def _validate_tls_versions(settings: Dict[str, Any]) -> None:
    """Validate tls-versions.

    Args:
        settings (dict): Settings dictionary.

    Raises:
        :class:`mysqlx.InterfaceError`: If tls-versions name is not valid.
    """
    tls_versions = []
    if "tls-versions" not in settings:
        return

    tls_versions_settings = settings["tls-versions"]

    if isinstance(tls_versions_settings, str):
        if not (
            tls_versions_settings.startswith("[")
            and tls_versions_settings.endswith("]")
        ):
            raise InterfaceError(
                f"tls-versions must be a list, found: '{tls_versions_settings}'"
            )
        tls_vers = tls_versions_settings[1:-1].split(",")
        for tls_ver in tls_vers:
            tls_version = tls_ver.strip()
            if tls_version == "":
                continue
            if tls_version in tls_versions:
                raise InterfaceError(
                    DUPLICATED_IN_LIST_ERROR.format(
                        list="tls_versions", value=tls_version
                    )
                )
            tls_versions.append(tls_version)
    elif isinstance(tls_versions_settings, list):
        if not tls_versions_settings:
            raise InterfaceError(
                "At least one TLS protocol version must be "
                "specified in 'tls-versions' list."
            )
        for tls_ver in tls_versions_settings:
            if tls_ver in tls_versions:
                raise InterfaceError(
                    DUPLICATED_IN_LIST_ERROR.format(list="tls_versions", value=tls_ver)
                )
            tls_versions.append(tls_ver)

    elif isinstance(tls_versions_settings, set):
        for tls_ver in tls_versions_settings:
            tls_versions.append(tls_ver)
    else:
        raise InterfaceError(
            "tls-versions should be a list with one or more of versions in "
            f"{', '.join(SUPPORTED_TLS_VERSIONS)}. found: '{tls_versions}'"
        )

    if not tls_versions:
        raise InterfaceError(
            "At least one TLS protocol version must be specified in "
            "'tls-versions' list."
        )

    use_tls_versions = []
    deprecated_tls_versions = []
    not_tls_versions = []
    for tls_ver in tls_versions:
        if tls_ver in SUPPORTED_TLS_VERSIONS:
            use_tls_versions.append(tls_ver)
        if tls_ver in DEPRECATED_TLS_VERSIONS:
            deprecated_tls_versions.append(tls_ver)
        else:
            not_tls_versions.append(tls_ver)

    if use_tls_versions:
        if use_tls_versions == ["TLSv1.3"] and not TLS_V1_3_SUPPORTED:
            raise NotSupportedError(
                TLS_VER_NO_SUPPORTED.format(tls_versions, SUPPORTED_TLS_VERSIONS)
            )
        use_tls_versions.sort()
        settings["tls-versions"] = use_tls_versions
    elif deprecated_tls_versions:
        raise NotSupportedError(
            TLS_VERSION_DEPRECATED_ERROR.format(
                deprecated_tls_versions, SUPPORTED_TLS_VERSIONS
            )
        )
    elif not_tls_versions:
        raise InterfaceError(TLS_VERSION_ERROR.format(tls_ver, SUPPORTED_TLS_VERSIONS))


def _validate_tls_ciphersuites(settings: Dict[str, Any]) -> None:
    """Validate tls-ciphersuites.

    Args:
        settings (dict): Settings dictionary.

    Raises:
        :class:`mysqlx.InterfaceError`: If tls-ciphersuites name is not valid.
    """
    tls_ciphersuites = []
    if "tls-ciphersuites" not in settings:
        return

    tls_ciphersuites_settings = settings["tls-ciphersuites"]

    if isinstance(tls_ciphersuites_settings, str):
        if not (
            tls_ciphersuites_settings.startswith("[")
            and tls_ciphersuites_settings.endswith("]")
        ):
            raise InterfaceError(
                "tls-ciphersuites must be a list, found: "
                f"'{tls_ciphersuites_settings}'"
            )
        tls_css = tls_ciphersuites_settings[1:-1].split(",")
        if not tls_css:
            raise InterfaceError(
                "No valid cipher suite found in the 'tls-ciphersuites' list"
            )
        for tls_cs in tls_css:
            tls_cs = tls_cs.strip().upper()
            if tls_cs:
                tls_ciphersuites.append(tls_cs)
    elif isinstance(tls_ciphersuites_settings, (list, set)):
        tls_ciphersuites = [tls_cs for tls_cs in tls_ciphersuites_settings if tls_cs]
    else:
        raise InterfaceError(
            "tls-ciphersuites should be a list with one or more ciphersuites. "
            f"Found: '{tls_ciphersuites_settings}'"
        )

    tls_versions = (
        SUPPORTED_TLS_VERSIONS[:]
        if settings.get("tls-versions", None) is None
        else settings["tls-versions"][:]
    )

    # A newer TLS version can use a cipher introduced on
    # an older version.
    tls_versions.sort(reverse=True)
    newer_tls_ver = tls_versions[0]

    translated_names = []
    iani_cipher_suites_names = {}
    ossl_cipher_suites_names: List[str] = []

    # Old ciphers can work with new TLS versions.
    # Find all the ciphers introduced on previous TLS versions
    for tls_ver in SUPPORTED_TLS_VERSIONS[
        : SUPPORTED_TLS_VERSIONS.index(newer_tls_ver) + 1
    ]:
        iani_cipher_suites_names.update(TLS_CIPHER_SUITES[tls_ver])
        ossl_cipher_suites_names.extend(OPENSSL_CS_NAMES[tls_ver])

    for name in tls_ciphersuites:
        if "-" in name and name in ossl_cipher_suites_names:
            translated_names.append(name)
        elif name in iani_cipher_suites_names:
            translated_name = iani_cipher_suites_names[name]
            if translated_name in translated_names:
                raise AttributeError(
                    DUPLICATED_IN_LIST_ERROR.format(
                        list="tls_ciphersuites", value=translated_name
                    )
                )
            translated_names.append(translated_name)
        else:
            raise InterfaceError(
                f"The value '{name}' in cipher suites is not a valid cipher suite"
            )

    if not translated_names:
        raise InterfaceError(
            "No valid cipher suite found in the 'tls-ciphersuites' list"
        )

    settings["tls-ciphersuites"] = translated_names


def _get_connection_settings(*args: Any, **kwargs: Any) -> Dict[str, Any]:
    """Parses the connection string and returns a dictionary with the
    connection settings.

    Args:
        *args: Variable length argument list with the connection data used
               to connect to the database. It can be a dictionary or a
               connection string.
        **kwargs: Arbitrary keyword arguments with connection data used to
                  connect to the database.

    Returns:
        mysqlx.Session: Session object.

    Raises:
        TypeError: If connection timeout is not a positive integer.
        :class:`mysqlx.InterfaceError`: If settings not provided.
    """
    settings = {}
    if args:
        if isinstance(args[0], str):
            settings = _parse_connection_uri(args[0])
        elif isinstance(args[0], dict):
            for key, val in args[0].items():
                settings[key.replace("_", "-")] = val
    elif kwargs:
        for key, val in kwargs.items():
            settings[key.replace("_", "-")] = val

    if not settings:
        raise InterfaceError("Settings not provided")

    _validate_settings(settings)
    return settings


def get_session(*args: Any, **kwargs: Any) -> Session:
    """Creates a Session instance using the provided connection data.

    Args:
        *args: Variable length argument list with the connection data used
               to connect to a MySQL server. It can be a dictionary or a
               connection string.
        **kwargs: Arbitrary keyword arguments with connection data used to
                  connect to the database.

    Returns:
        mysqlx.Session: Session object.
    """
    settings = _get_connection_settings(*args, **kwargs)
    return Session(settings)


def get_client(
    connection_string: Union[str, Dict[str, Any]],
    options_string: Union[str, Dict[str, Any]],
) -> Client:
    """Creates a Client instance with the provided connection data and settings.

    Args:
        connection_string: A string or a dict type object to indicate the \
            connection data used to connect to a MySQL server.

            The string must have the following uri format::

                cnx_str = 'mysqlx://{user}:{pwd}@{host}:{port}'
                cnx_str = ('mysqlx://{user}:{pwd}@['
                           '    (address={host}:{port}, priority=n),'
                           '    (address={host}:{port}, priority=n), ...]'
                           '       ?[option=value]')

            And the dictionary::

                cnx_dict = {
                    'host': 'The host where the MySQL product is running',
                    'port': '(int) the port number configured for X protocol',
                    'user': 'The user name account',
                    'password': 'The password for the given user account',
                    'ssl-mode': 'The flags for ssl mode in mysqlx.SSLMode.FLAG',
                    'ssl-ca': 'The path to the ca.cert'
                    "connect-timeout": '(int) milliseconds to wait on timeout'
                }

        options_string: A string in the form of a document or a dictionary \
            type with configuration for the client.

            Current options include::

                options = {
                    'pooling': {
                        'enabled': (bool), # [True | False], True by default
                        'max_size': (int), # Maximum connections per pool
                        "max_idle_time": (int), # milliseconds that a
                            # connection will remain active while not in use.
                            # By default 0, means infinite.
                        "queue_timeout": (int), # milliseconds a request will
                            # wait for a connection to become available.
                            # By default 0, means infinite.
                    }
                }

    Returns:
        mysqlx.Client: Client object.

    .. versionadded:: 8.0.13
    """
    if not isinstance(connection_string, (str, dict)):
        raise InterfaceError("connection_data must be a string or dict")

    settings_dict = _get_connection_settings(connection_string)

    if not isinstance(options_string, (str, dict)):
        raise InterfaceError("connection_options must be a string or dict")

    if isinstance(options_string, str):
        try:
            options_dict = json.loads(options_string)
        except JSONDecodeError as err:
            raise InterfaceError(
                "'pooling' options must be given in the form of a document or dict"
            ) from err
    else:
        options_dict = {}
        for key, value in options_string.items():
            options_dict[key.replace("-", "_")] = value

    if not isinstance(options_dict, dict):
        raise InterfaceError(
            "'pooling' options must be given in the form of a document or dict"
        )
    pooling_options_dict = {}
    if "pooling" in options_dict:
        pooling_options = options_dict.pop("pooling")
        if not isinstance(pooling_options, (dict)):
            raise InterfaceError(
                "'pooling' options must be given in the form document or dict"
            )
        # Fill default pooling settings
        pooling_options_dict["enabled"] = pooling_options.pop("enabled", True)
        pooling_options_dict["max_size"] = pooling_options.pop("max_size", 25)
        pooling_options_dict["max_idle_time"] = pooling_options.pop("max_idle_time", 0)
        pooling_options_dict["queue_timeout"] = pooling_options.pop("queue_timeout", 0)

        # No other options besides pooling are supported
        if len(pooling_options) > 0:
            raise InterfaceError(f"Unrecognized pooling options: {pooling_options}")
        # No other options besides pooling are supported
        if len(options_dict) > 0:
            raise InterfaceError(
                f"Unrecognized connection options: {options_dict.keys()}"
            )

    return Client(settings_dict, pooling_options_dict)

virt/lib/python3.9/site-packages/mysqlx/connection.py
PrinceDisant/LostInNL
# Copyright (c) 2016, 2022, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

"""Constants."""

from enum import Enum


class Auth(Enum):
    """Enum to identify the authentication mechanisms."""

    PLAIN = "plain"
    MYSQL41 = "mysql41"
    SHA256_MEMORY = "sha256_memory"


class Compression(Enum):
    """Enum to identify the compression options."""

    PREFERRED = "preferred"
    REQUIRED = "required"
    DISABLED = "disabled"


class LockContention(Enum):
    """Enum to identify the row locking modes."""

    DEFAULT = 0
    NOWAIT = 1
    SKIP_LOCKED = 2


class SSLMode(Enum):
    """Enum to identify the SSL modes."""

    REQUIRED = "required"
    DISABLED = "disabled"
    VERIFY_CA = "verify_ca"
    VERIFY_IDENTITY = "verify_identity"


# Compression algorithms and aliases
COMPRESSION_ALGORITHMS = {
    "deflate": "deflate_stream",
    "deflate_stream": "deflate_stream",
    "lz4": "lz4_message",
    "lz4_message": "lz4_message",
    "zstd": "zstd_stream",
    "zstd_stream": "zstd_stream",
}

TLS_VERSIONS = ["TLSv1", "TLSv1.1", "TLSv1.2", "TLSv1.3"]
SUPPORTED_TLS_VERSIONS = ["TLSv1.2", "TLSv1.3"]
DEPRECATED_TLS_VERSIONS = ["TLSv1", "TLSv1.1"]

# TLS v1.0 cipher suites IANI to OpenSSL name translation
TLSV1_CIPHER_SUITES = {
    "TLS_RSA_WITH_NULL_MD5": "NULL-MD5",
    "TLS_RSA_WITH_NULL_SHA": "NULL-SHA",
    "TLS_RSA_WITH_RC4_128_MD5": "RC4-MD5",
    "TLS_RSA_WITH_RC4_128_SHA": "RC4-SHA",
    "TLS_RSA_WITH_IDEA_CBC_SHA": "IDEA-CBC-SHA",
    "TLS_RSA_WITH_3DES_EDE_CBC_SHA": "DES-CBC3-SHA",
    "TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA": "Not implemented.",
    "TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA": "Not implemented.",
    "TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA": "DHE-DSS-DES-CBC3-SHA",
    "TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA": "DHE-RSA-DES-CBC3-SHA",
    "TLS_DH_anon_WITH_RC4_128_MD5": "ADH-RC4-MD5",
    "TLS_DH_anon_WITH_3DES_EDE_CBC_SHA": "ADH-DES-CBC3-SHA",
    # AES cipher suites from RFC3268, extending TLS v1.0
    "TLS_RSA_WITH_AES_128_CBC_SHA": "AES128-SHA",
    "TLS_RSA_WITH_AES_256_CBC_SHA": "AES256-SHA",
    "TLS_DH_DSS_WITH_AES_128_CBC_SHA": "DH-DSS-AES128-SHA",
    "TLS_DH_DSS_WITH_AES_256_CBC_SHA": "DH-DSS-AES256-SHA",
    "TLS_DH_RSA_WITH_AES_128_CBC_SHA": "DH-RSA-AES128-SHA",
    "TLS_DH_RSA_WITH_AES_256_CBC_SHA": "DH-RSA-AES256-SHA",
    "TLS_DHE_DSS_WITH_AES_128_CBC_SHA": "DHE-DSS-AES128-SHA",
    "TLS_DHE_DSS_WITH_AES_256_CBC_SHA": "DHE-DSS-AES256-SHA",
    "TLS_DHE_RSA_WITH_AES_128_CBC_SHA": "DHE-RSA-AES128-SHA",
    "TLS_DHE_RSA_WITH_AES_256_CBC_SHA": "DHE-RSA-AES256-SHA",
    "TLS_DH_anon_WITH_AES_128_CBC_SHA": "ADH-AES128-SHA",
    "TLS_DH_anon_WITH_AES_256_CBC_SHA": "ADH-AES256-SHA",
    # Camellia cipher suites from RFC4132, extending TLS v1.0
    "TLS_RSA_WITH_CAMELLIA_128_CBC_SHA": "CAMELLIA128-SHA",
    "TLS_RSA_WITH_CAMELLIA_256_CBC_SHA": "CAMELLIA256-SHA",
    "TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA": "DH-DSS-CAMELLIA128-SHA",
    "TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA": "DH-DSS-CAMELLIA256-SHA",
    "TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA": "DH-RSA-CAMELLIA128-SHA",
    "TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA": "DH-RSA-CAMELLIA256-SHA",
    "TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA": "DHE-DSS-CAMELLIA128-SHA",
    "TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA": "DHE-DSS-CAMELLIA256-SHA",
    "TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA": "DHE-RSA-CAMELLIA128-SHA",
    "TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA": "DHE-RSA-CAMELLIA256-SHA",
    "TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA": "ADH-CAMELLIA128-SHA",
    "TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA": "ADH-CAMELLIA256-SHA",
    # SEED cipher suites from RFC4162, extending TLS v1.0
    "TLS_RSA_WITH_SEED_CBC_SHA": "SEED-SHA",
    "TLS_DH_DSS_WITH_SEED_CBC_SHA": "DH-DSS-SEED-SHA",
    "TLS_DH_RSA_WITH_SEED_CBC_SHA": "DH-RSA-SEED-SHA",
    "TLS_DHE_DSS_WITH_SEED_CBC_SHA": "DHE-DSS-SEED-SHA",
    "TLS_DHE_RSA_WITH_SEED_CBC_SHA": "DHE-RSA-SEED-SHA",
    "TLS_DH_anon_WITH_SEED_CBC_SHA": "ADH-SEED-SHA",
    # GOST cipher suites from draft-chudov-cryptopro-cptls, extending TLS v1.0
    "TLS_GOSTR341094_WITH_28147_CNT_IMIT": "GOST94-GOST89-GOST89",
    "TLS_GOSTR341001_WITH_28147_CNT_IMIT": "GOST2001-GOST89-GOST89",
    "TLS_GOSTR341094_WITH_NULL_GOSTR3411": "GOST94-NULL-GOST94",
    "TLS_GOSTR341001_WITH_NULL_GOSTR3411": "GOST2001-NULL-GOST94",
}

# TLS v1.1 cipher suites IANI to OpenSSL name translation
TLSV1_1_CIPHER_SUITES = TLSV1_CIPHER_SUITES

# TLS v1.2 cipher suites IANI to OpenSSL name translation
TLSV1_2_CIPHER_SUITES = {
    "TLS_RSA_WITH_NULL_SHA256": "NULL-SHA256",
    "TLS_RSA_WITH_AES_128_CBC_SHA256": "AES128-SHA256",
    "TLS_RSA_WITH_AES_256_CBC_SHA256": "AES256-SHA256",
    "TLS_RSA_WITH_AES_128_GCM_SHA256": "AES128-GCM-SHA256",
    "TLS_RSA_WITH_AES_256_GCM_SHA384": "AES256-GCM-SHA384",
    "TLS_DH_RSA_WITH_AES_128_CBC_SHA256": "DH-RSA-AES128-SHA256",
    "TLS_DH_RSA_WITH_AES_256_CBC_SHA256": "DH-RSA-AES256-SHA256",
    "TLS_DH_RSA_WITH_AES_128_GCM_SHA256": "DH-RSA-AES128-GCM-SHA256",
    "TLS_DH_RSA_WITH_AES_256_GCM_SHA384": "DH-RSA-AES256-GCM-SHA384",
    "TLS_DH_DSS_WITH_AES_128_CBC_SHA256": "DH-DSS-AES128-SHA256",
    "TLS_DH_DSS_WITH_AES_256_CBC_SHA256": "DH-DSS-AES256-SHA256",
    "TLS_DH_DSS_WITH_AES_128_GCM_SHA256": "DH-DSS-AES128-GCM-SHA256",
    "TLS_DH_DSS_WITH_AES_256_GCM_SHA384": "DH-DSS-AES256-GCM-SHA384",
    "TLS_DHE_RSA_WITH_AES_128_CBC_SHA256": "DHE-RSA-AES128-SHA256",
    "TLS_DHE_RSA_WITH_AES_256_CBC_SHA256": "DHE-RSA-AES256-SHA256",
    "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256": "DHE-RSA-AES128-GCM-SHA256",
    "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384": "DHE-RSA-AES256-GCM-SHA384",
    "TLS_DHE_DSS_WITH_AES_128_CBC_SHA256": "DHE-DSS-AES128-SHA256",
    "TLS_DHE_DSS_WITH_AES_256_CBC_SHA256": "DHE-DSS-AES256-SHA256",
    "TLS_DHE_DSS_WITH_AES_128_GCM_SHA256": "DHE-DSS-AES128-GCM-SHA256",
    "TLS_DHE_DSS_WITH_AES_256_GCM_SHA384": "DHE-DSS-AES256-GCM-SHA384",
    "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256": "ECDHE-RSA-AES128-SHA256",
    "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384": "ECDHE-RSA-AES256-SHA384",
    "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256": "ECDHE-RSA-AES128-GCM-SHA256",
    "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384": "ECDHE-RSA-AES256-GCM-SHA384",
    "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256": "ECDHE-ECDSA-AES128-SHA256",
    "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384": "ECDHE-ECDSA-AES256-SHA384",
    "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256": "ECDHE-ECDSA-AES128-GCM-SHA256",
    "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384": "ECDHE-ECDSA-AES256-GCM-SHA384",
    "TLS_DH_anon_WITH_AES_128_CBC_SHA256": "ADH-AES128-SHA256",
    "TLS_DH_anon_WITH_AES_256_CBC_SHA256": "ADH-AES256-SHA256",
    "TLS_DH_anon_WITH_AES_128_GCM_SHA256": "ADH-AES128-GCM-SHA256",
    "TLS_DH_anon_WITH_AES_256_GCM_SHA384": "ADH-AES256-GCM-SHA384",
    "RSA_WITH_AES_128_CCM": "AES128-CCM",
    "RSA_WITH_AES_256_CCM": "AES256-CCM",
    "DHE_RSA_WITH_AES_128_CCM": "DHE-RSA-AES128-CCM",
    "DHE_RSA_WITH_AES_256_CCM": "DHE-RSA-AES256-CCM",
    "RSA_WITH_AES_128_CCM_8": "AES128-CCM8",
    "RSA_WITH_AES_256_CCM_8": "AES256-CCM8",
    "DHE_RSA_WITH_AES_128_CCM_8": "DHE-RSA-AES128-CCM8",
    "DHE_RSA_WITH_AES_256_CCM_8": "DHE-RSA-AES256-CCM8",
    "ECDHE_ECDSA_WITH_AES_128_CCM": "ECDHE-ECDSA-AES128-CCM",
    "ECDHE_ECDSA_WITH_AES_256_CCM": "ECDHE-ECDSA-AES256-CCM",
    "ECDHE_ECDSA_WITH_AES_128_CCM_8": "ECDHE-ECDSA-AES128-CCM8",
    "ECDHE_ECDSA_WITH_AES_256_CCM_8": "ECDHE-ECDSA-AES256-CCM8",
    # ARIA cipher suites from RFC6209, extending TLS v1.2
    "TLS_RSA_WITH_ARIA_128_GCM_SHA256": "ARIA128-GCM-SHA256",
    "TLS_RSA_WITH_ARIA_256_GCM_SHA384": "ARIA256-GCM-SHA384",
    "TLS_DHE_RSA_WITH_ARIA_128_GCM_SHA256": "DHE-RSA-ARIA128-GCM-SHA256",
    "TLS_DHE_RSA_WITH_ARIA_256_GCM_SHA384": "DHE-RSA-ARIA256-GCM-SHA384",
    "TLS_DHE_DSS_WITH_ARIA_128_GCM_SHA256": "DHE-DSS-ARIA128-GCM-SHA256",
    "TLS_DHE_DSS_WITH_ARIA_256_GCM_SHA384": "DHE-DSS-ARIA256-GCM-SHA384",
    "TLS_ECDHE_ECDSA_WITH_ARIA_128_GCM_SHA256": "ECDHE-ECDSA-ARIA128-GCM-SHA256",
    "TLS_ECDHE_ECDSA_WITH_ARIA_256_GCM_SHA384": "ECDHE-ECDSA-ARIA256-GCM-SHA384",
    "TLS_ECDHE_RSA_WITH_ARIA_128_GCM_SHA256": "ECDHE-ARIA128-GCM-SHA256",
    "TLS_ECDHE_RSA_WITH_ARIA_256_GCM_SHA384": "ECDHE-ARIA256-GCM-SHA384",
    "TLS_PSK_WITH_ARIA_128_GCM_SHA256": "PSK-ARIA128-GCM-SHA256",
    "TLS_PSK_WITH_ARIA_256_GCM_SHA384": "PSK-ARIA256-GCM-SHA384",
    "TLS_DHE_PSK_WITH_ARIA_128_GCM_SHA256": "DHE-PSK-ARIA128-GCM-SHA256",
    "TLS_DHE_PSK_WITH_ARIA_256_GCM_SHA384": "DHE-PSK-ARIA256-GCM-SHA384",
    "TLS_RSA_PSK_WITH_ARIA_128_GCM_SHA256": "RSA-PSK-ARIA128-GCM-SHA256",
    "TLS_RSA_PSK_WITH_ARIA_256_GCM_SHA384": "RSA-PSK-ARIA256-GCM-SHA384",
    # Camellia HMAC-Based cipher suites from RFC6367, extending TLS v1.2
    "TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256": "ECDHE-ECDSA-CAMELLIA128-SHA256",
    "TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384": "ECDHE-ECDSA-CAMELLIA256-SHA384",
    "TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256": "ECDHE-RSA-CAMELLIA128-SHA256",
    "TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384": "ECDHE-RSA-CAMELLIA256-SHA384",
    # Pre-shared keying (PSK) cipher suites",
    "PSK_WITH_NULL_SHA": "PSK-NULL-SHA",
    "DHE_PSK_WITH_NULL_SHA": "DHE-PSK-NULL-SHA",
    "RSA_PSK_WITH_NULL_SHA": "RSA-PSK-NULL-SHA",
    "PSK_WITH_RC4_128_SHA": "PSK-RC4-SHA",
    "PSK_WITH_3DES_EDE_CBC_SHA": "PSK-3DES-EDE-CBC-SHA",
    "PSK_WITH_AES_128_CBC_SHA": "PSK-AES128-CBC-SHA",
    "PSK_WITH_AES_256_CBC_SHA": "PSK-AES256-CBC-SHA",
    "DHE_PSK_WITH_RC4_128_SHA": "DHE-PSK-RC4-SHA",
    "DHE_PSK_WITH_3DES_EDE_CBC_SHA": "DHE-PSK-3DES-EDE-CBC-SHA",
    "DHE_PSK_WITH_AES_128_CBC_SHA": "DHE-PSK-AES128-CBC-SHA",
    "DHE_PSK_WITH_AES_256_CBC_SHA": "DHE-PSK-AES256-CBC-SHA",
    "RSA_PSK_WITH_RC4_128_SHA": "RSA-PSK-RC4-SHA",
    "RSA_PSK_WITH_3DES_EDE_CBC_SHA": "RSA-PSK-3DES-EDE-CBC-SHA",
    "RSA_PSK_WITH_AES_128_CBC_SHA": "RSA-PSK-AES128-CBC-SHA",
    "RSA_PSK_WITH_AES_256_CBC_SHA": "RSA-PSK-AES256-CBC-SHA",
    "PSK_WITH_AES_128_GCM_SHA256": "PSK-AES128-GCM-SHA256",
    "PSK_WITH_AES_256_GCM_SHA384": "PSK-AES256-GCM-SHA384",
    "DHE_PSK_WITH_AES_128_GCM_SHA256": "DHE-PSK-AES128-GCM-SHA256",
    "DHE_PSK_WITH_AES_256_GCM_SHA384": "DHE-PSK-AES256-GCM-SHA384",
    "RSA_PSK_WITH_AES_128_GCM_SHA256": "RSA-PSK-AES128-GCM-SHA256",
    "RSA_PSK_WITH_AES_256_GCM_SHA384": "RSA-PSK-AES256-GCM-SHA384",
    "PSK_WITH_AES_128_CBC_SHA256": "PSK-AES128-CBC-SHA256",
    "PSK_WITH_AES_256_CBC_SHA384": "PSK-AES256-CBC-SHA384",
    "PSK_WITH_NULL_SHA256": "PSK-NULL-SHA256",
    "PSK_WITH_NULL_SHA384": "PSK-NULL-SHA384",
    "DHE_PSK_WITH_AES_128_CBC_SHA256": "DHE-PSK-AES128-CBC-SHA256",
    "DHE_PSK_WITH_AES_256_CBC_SHA384": "DHE-PSK-AES256-CBC-SHA384",
    "DHE_PSK_WITH_NULL_SHA256": "DHE-PSK-NULL-SHA256",
    "DHE_PSK_WITH_NULL_SHA384": "DHE-PSK-NULL-SHA384",
    "RSA_PSK_WITH_AES_128_CBC_SHA256": "RSA-PSK-AES128-CBC-SHA256",
    "RSA_PSK_WITH_AES_256_CBC_SHA384": "RSA-PSK-AES256-CBC-SHA384",
    "RSA_PSK_WITH_NULL_SHA256": "RSA-PSK-NULL-SHA256",
    "RSA_PSK_WITH_NULL_SHA384": "RSA-PSK-NULL-SHA384",
    "ECDHE_PSK_WITH_RC4_128_SHA": "ECDHE-PSK-RC4-SHA",
    "ECDHE_PSK_WITH_3DES_EDE_CBC_SHA": "ECDHE-PSK-3DES-EDE-CBC-SHA",
    "ECDHE_PSK_WITH_AES_128_CBC_SHA": "ECDHE-PSK-AES128-CBC-SHA",
    "ECDHE_PSK_WITH_AES_256_CBC_SHA": "ECDHE-PSK-AES256-CBC-SHA",
    "ECDHE_PSK_WITH_AES_128_CBC_SHA256": "ECDHE-PSK-AES128-CBC-SHA256",
    "ECDHE_PSK_WITH_AES_256_CBC_SHA384": "ECDHE-PSK-AES256-CBC-SHA384",
    "ECDHE_PSK_WITH_NULL_SHA": "ECDHE-PSK-NULL-SHA",
    "ECDHE_PSK_WITH_NULL_SHA256": "ECDHE-PSK-NULL-SHA256",
    "ECDHE_PSK_WITH_NULL_SHA384": "ECDHE-PSK-NULL-SHA384",
    "PSK_WITH_CAMELLIA_128_CBC_SHA256": "PSK-CAMELLIA128-SHA256",
    "PSK_WITH_CAMELLIA_256_CBC_SHA384": "PSK-CAMELLIA256-SHA384",
    "DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256": "DHE-PSK-CAMELLIA128-SHA256",
    "DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384": "DHE-PSK-CAMELLIA256-SHA384",
    "RSA_PSK_WITH_CAMELLIA_128_CBC_SHA256": "RSA-PSK-CAMELLIA128-SHA256",
    "RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384": "RSA-PSK-CAMELLIA256-SHA384",
    "ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256": "ECDHE-PSK-CAMELLIA128-SHA256",
    "ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384": "ECDHE-PSK-CAMELLIA256-SHA384",
    "PSK_WITH_AES_128_CCM": "PSK-AES128-CCM",
    "PSK_WITH_AES_256_CCM": "PSK-AES256-CCM",
    "DHE_PSK_WITH_AES_128_CCM": "DHE-PSK-AES128-CCM",
    "DHE_PSK_WITH_AES_256_CCM": "DHE-PSK-AES256-CCM",
    "PSK_WITH_AES_128_CCM_8": "PSK-AES128-CCM8",
    "PSK_WITH_AES_256_CCM_8": "PSK-AES256-CCM8",
    "DHE_PSK_WITH_AES_128_CCM_8": "DHE-PSK-AES128-CCM8",
    "DHE_PSK_WITH_AES_256_CCM_8": "DHE-PSK-AES256-CCM8",
    # ChaCha20-Poly1305 cipher suites, extending TLS v1.2
    "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256": "ECDHE-RSA-CHACHA20-POLY1305",
    "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256": "ECDHE-ECDSA-CHACHA20-POLY1305",
    "TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256": "DHE-RSA-CHACHA20-POLY1305",
    "TLS_PSK_WITH_CHACHA20_POLY1305_SHA256": "PSK-CHACHA20-POLY1305",
    "TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256": "ECDHE-PSK-CHACHA20-POLY1305",
    "TLS_DHE_PSK_WITH_CHACHA20_POLY1305_SHA256": "DHE-PSK-CHACHA20-POLY1305",
    "TLS_RSA_PSK_WITH_CHACHA20_POLY1305_SHA256": "RSA-PSK-CHACHA20-POLY1305",
}

# TLS v1.3 cipher suites IANI to OpenSSL name translation
TLSV1_3_CIPHER_SUITES = {
    "TLS_AES_128_GCM_SHA256": "TLS_AES_128_GCM_SHA256",
    "TLS_AES_256_GCM_SHA384": "TLS_AES_256_GCM_SHA384",
    "TLS_CHACHA20_POLY1305_SHA256": "TLS_CHACHA20_POLY1305_SHA256",
    "TLS_AES_128_CCM_SHA256": "TLS_AES_128_CCM_SHA256",
    "TLS_AES_128_CCM_8_SHA256": "TLS_AES_128_CCM_8_SHA256",
}

TLS_CIPHER_SUITES = {
    "TLSv1": TLSV1_CIPHER_SUITES,
    "TLSv1.1": TLSV1_1_CIPHER_SUITES,
    "TLSv1.2": TLSV1_2_CIPHER_SUITES,
    "TLSv1.3": TLSV1_3_CIPHER_SUITES,
}

OPENSSL_CS_NAMES = {
    "TLSv1": TLSV1_CIPHER_SUITES.values(),
    "TLSv1.1": TLSV1_1_CIPHER_SUITES.values(),
    "TLSv1.2": TLSV1_2_CIPHER_SUITES.values(),
    "TLSv1.3": TLSV1_3_CIPHER_SUITES.values(),
}

virt/lib/python3.9/site-packages/mysqlx/constants 2.py
PrinceDisant/LostInNL
# Copyright (c) 2016, 2022, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

"""Constants."""

from enum import Enum


class Auth(Enum):
    """Enum to identify the authentication mechanisms."""

    PLAIN = "plain"
    MYSQL41 = "mysql41"
    SHA256_MEMORY = "sha256_memory"


class Compression(Enum):
    """Enum to identify the compression options."""

    PREFERRED = "preferred"
    REQUIRED = "required"
    DISABLED = "disabled"


class LockContention(Enum):
    """Enum to identify the row locking modes."""

    DEFAULT = 0
    NOWAIT = 1
    SKIP_LOCKED = 2


class SSLMode(Enum):
    """Enum to identify the SSL modes."""

    REQUIRED = "required"
    DISABLED = "disabled"
    VERIFY_CA = "verify_ca"
    VERIFY_IDENTITY = "verify_identity"


# Compression algorithms and aliases
COMPRESSION_ALGORITHMS = {
    "deflate": "deflate_stream",
    "deflate_stream": "deflate_stream",
    "lz4": "lz4_message",
    "lz4_message": "lz4_message",
    "zstd": "zstd_stream",
    "zstd_stream": "zstd_stream",
}

TLS_VERSIONS = ["TLSv1", "TLSv1.1", "TLSv1.2", "TLSv1.3"]
SUPPORTED_TLS_VERSIONS = ["TLSv1.2", "TLSv1.3"]
DEPRECATED_TLS_VERSIONS = ["TLSv1", "TLSv1.1"]

# TLS v1.0 cipher suites IANI to OpenSSL name translation
TLSV1_CIPHER_SUITES = {
    "TLS_RSA_WITH_NULL_MD5": "NULL-MD5",
    "TLS_RSA_WITH_NULL_SHA": "NULL-SHA",
    "TLS_RSA_WITH_RC4_128_MD5": "RC4-MD5",
    "TLS_RSA_WITH_RC4_128_SHA": "RC4-SHA",
    "TLS_RSA_WITH_IDEA_CBC_SHA": "IDEA-CBC-SHA",
    "TLS_RSA_WITH_3DES_EDE_CBC_SHA": "DES-CBC3-SHA",
    "TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA": "Not implemented.",
    "TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA": "Not implemented.",
    "TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA": "DHE-DSS-DES-CBC3-SHA",
    "TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA": "DHE-RSA-DES-CBC3-SHA",
    "TLS_DH_anon_WITH_RC4_128_MD5": "ADH-RC4-MD5",
    "TLS_DH_anon_WITH_3DES_EDE_CBC_SHA": "ADH-DES-CBC3-SHA",
    # AES cipher suites from RFC3268, extending TLS v1.0
    "TLS_RSA_WITH_AES_128_CBC_SHA": "AES128-SHA",
    "TLS_RSA_WITH_AES_256_CBC_SHA": "AES256-SHA",
    "TLS_DH_DSS_WITH_AES_128_CBC_SHA": "DH-DSS-AES128-SHA",
    "TLS_DH_DSS_WITH_AES_256_CBC_SHA": "DH-DSS-AES256-SHA",
    "TLS_DH_RSA_WITH_AES_128_CBC_SHA": "DH-RSA-AES128-SHA",
    "TLS_DH_RSA_WITH_AES_256_CBC_SHA": "DH-RSA-AES256-SHA",
    "TLS_DHE_DSS_WITH_AES_128_CBC_SHA": "DHE-DSS-AES128-SHA",
    "TLS_DHE_DSS_WITH_AES_256_CBC_SHA": "DHE-DSS-AES256-SHA",
    "TLS_DHE_RSA_WITH_AES_128_CBC_SHA": "DHE-RSA-AES128-SHA",
    "TLS_DHE_RSA_WITH_AES_256_CBC_SHA": "DHE-RSA-AES256-SHA",
    "TLS_DH_anon_WITH_AES_128_CBC_SHA": "ADH-AES128-SHA",
    "TLS_DH_anon_WITH_AES_256_CBC_SHA": "ADH-AES256-SHA",
    # Camellia cipher suites from RFC4132, extending TLS v1.0
    "TLS_RSA_WITH_CAMELLIA_128_CBC_SHA": "CAMELLIA128-SHA",
    "TLS_RSA_WITH_CAMELLIA_256_CBC_SHA": "CAMELLIA256-SHA",
    "TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA": "DH-DSS-CAMELLIA128-SHA",
    "TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA": "DH-DSS-CAMELLIA256-SHA",
    "TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA": "DH-RSA-CAMELLIA128-SHA",
    "TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA": "DH-RSA-CAMELLIA256-SHA",
    "TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA": "DHE-DSS-CAMELLIA128-SHA",
    "TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA": "DHE-DSS-CAMELLIA256-SHA",
    "TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA": "DHE-RSA-CAMELLIA128-SHA",
    "TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA": "DHE-RSA-CAMELLIA256-SHA",
    "TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA": "ADH-CAMELLIA128-SHA",
    "TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA": "ADH-CAMELLIA256-SHA",
    # SEED cipher suites from RFC4162, extending TLS v1.0
    "TLS_RSA_WITH_SEED_CBC_SHA": "SEED-SHA",
    "TLS_DH_DSS_WITH_SEED_CBC_SHA": "DH-DSS-SEED-SHA",
    "TLS_DH_RSA_WITH_SEED_CBC_SHA": "DH-RSA-SEED-SHA",
    "TLS_DHE_DSS_WITH_SEED_CBC_SHA": "DHE-DSS-SEED-SHA",
    "TLS_DHE_RSA_WITH_SEED_CBC_SHA": "DHE-RSA-SEED-SHA",
    "TLS_DH_anon_WITH_SEED_CBC_SHA": "ADH-SEED-SHA",
    # GOST cipher suites from draft-chudov-cryptopro-cptls, extending TLS v1.0
    "TLS_GOSTR341094_WITH_28147_CNT_IMIT": "GOST94-GOST89-GOST89",
    "TLS_GOSTR341001_WITH_28147_CNT_IMIT": "GOST2001-GOST89-GOST89",
    "TLS_GOSTR341094_WITH_NULL_GOSTR3411": "GOST94-NULL-GOST94",
    "TLS_GOSTR341001_WITH_NULL_GOSTR3411": "GOST2001-NULL-GOST94",
}

# TLS v1.1 cipher suites IANI to OpenSSL name translation
TLSV1_1_CIPHER_SUITES = TLSV1_CIPHER_SUITES

# TLS v1.2 cipher suites IANI to OpenSSL name translation
TLSV1_2_CIPHER_SUITES = {
    "TLS_RSA_WITH_NULL_SHA256": "NULL-SHA256",
    "TLS_RSA_WITH_AES_128_CBC_SHA256": "AES128-SHA256",
    "TLS_RSA_WITH_AES_256_CBC_SHA256": "AES256-SHA256",
    "TLS_RSA_WITH_AES_128_GCM_SHA256": "AES128-GCM-SHA256",
    "TLS_RSA_WITH_AES_256_GCM_SHA384": "AES256-GCM-SHA384",
    "TLS_DH_RSA_WITH_AES_128_CBC_SHA256": "DH-RSA-AES128-SHA256",
    "TLS_DH_RSA_WITH_AES_256_CBC_SHA256": "DH-RSA-AES256-SHA256",
    "TLS_DH_RSA_WITH_AES_128_GCM_SHA256": "DH-RSA-AES128-GCM-SHA256",
    "TLS_DH_RSA_WITH_AES_256_GCM_SHA384": "DH-RSA-AES256-GCM-SHA384",
    "TLS_DH_DSS_WITH_AES_128_CBC_SHA256": "DH-DSS-AES128-SHA256",
    "TLS_DH_DSS_WITH_AES_256_CBC_SHA256": "DH-DSS-AES256-SHA256",
    "TLS_DH_DSS_WITH_AES_128_GCM_SHA256": "DH-DSS-AES128-GCM-SHA256",
    "TLS_DH_DSS_WITH_AES_256_GCM_SHA384": "DH-DSS-AES256-GCM-SHA384",
    "TLS_DHE_RSA_WITH_AES_128_CBC_SHA256": "DHE-RSA-AES128-SHA256",
    "TLS_DHE_RSA_WITH_AES_256_CBC_SHA256": "DHE-RSA-AES256-SHA256",
    "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256": "DHE-RSA-AES128-GCM-SHA256",
    "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384": "DHE-RSA-AES256-GCM-SHA384",
    "TLS_DHE_DSS_WITH_AES_128_CBC_SHA256": "DHE-DSS-AES128-SHA256",
    "TLS_DHE_DSS_WITH_AES_256_CBC_SHA256": "DHE-DSS-AES256-SHA256",
    "TLS_DHE_DSS_WITH_AES_128_GCM_SHA256": "DHE-DSS-AES128-GCM-SHA256",
    "TLS_DHE_DSS_WITH_AES_256_GCM_SHA384": "DHE-DSS-AES256-GCM-SHA384",
    "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256": "ECDHE-RSA-AES128-SHA256",
    "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384": "ECDHE-RSA-AES256-SHA384",
    "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256": "ECDHE-RSA-AES128-GCM-SHA256",
    "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384": "ECDHE-RSA-AES256-GCM-SHA384",
    "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256": "ECDHE-ECDSA-AES128-SHA256",
    "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384": "ECDHE-ECDSA-AES256-SHA384",
    "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256": "ECDHE-ECDSA-AES128-GCM-SHA256",
    "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384": "ECDHE-ECDSA-AES256-GCM-SHA384",
    "TLS_DH_anon_WITH_AES_128_CBC_SHA256": "ADH-AES128-SHA256",
    "TLS_DH_anon_WITH_AES_256_CBC_SHA256": "ADH-AES256-SHA256",
    "TLS_DH_anon_WITH_AES_128_GCM_SHA256": "ADH-AES128-GCM-SHA256",
    "TLS_DH_anon_WITH_AES_256_GCM_SHA384": "ADH-AES256-GCM-SHA384",
    "RSA_WITH_AES_128_CCM": "AES128-CCM",
    "RSA_WITH_AES_256_CCM": "AES256-CCM",
    "DHE_RSA_WITH_AES_128_CCM": "DHE-RSA-AES128-CCM",
    "DHE_RSA_WITH_AES_256_CCM": "DHE-RSA-AES256-CCM",
    "RSA_WITH_AES_128_CCM_8": "AES128-CCM8",
    "RSA_WITH_AES_256_CCM_8": "AES256-CCM8",
    "DHE_RSA_WITH_AES_128_CCM_8": "DHE-RSA-AES128-CCM8",
    "DHE_RSA_WITH_AES_256_CCM_8": "DHE-RSA-AES256-CCM8",
    "ECDHE_ECDSA_WITH_AES_128_CCM": "ECDHE-ECDSA-AES128-CCM",
    "ECDHE_ECDSA_WITH_AES_256_CCM": "ECDHE-ECDSA-AES256-CCM",
    "ECDHE_ECDSA_WITH_AES_128_CCM_8": "ECDHE-ECDSA-AES128-CCM8",
    "ECDHE_ECDSA_WITH_AES_256_CCM_8": "ECDHE-ECDSA-AES256-CCM8",
    # ARIA cipher suites from RFC6209, extending TLS v1.2
    "TLS_RSA_WITH_ARIA_128_GCM_SHA256": "ARIA128-GCM-SHA256",
    "TLS_RSA_WITH_ARIA_256_GCM_SHA384": "ARIA256-GCM-SHA384",
    "TLS_DHE_RSA_WITH_ARIA_128_GCM_SHA256": "DHE-RSA-ARIA128-GCM-SHA256",
    "TLS_DHE_RSA_WITH_ARIA_256_GCM_SHA384": "DHE-RSA-ARIA256-GCM-SHA384",
    "TLS_DHE_DSS_WITH_ARIA_128_GCM_SHA256": "DHE-DSS-ARIA128-GCM-SHA256",
    "TLS_DHE_DSS_WITH_ARIA_256_GCM_SHA384": "DHE-DSS-ARIA256-GCM-SHA384",
    "TLS_ECDHE_ECDSA_WITH_ARIA_128_GCM_SHA256": "ECDHE-ECDSA-ARIA128-GCM-SHA256",
    "TLS_ECDHE_ECDSA_WITH_ARIA_256_GCM_SHA384": "ECDHE-ECDSA-ARIA256-GCM-SHA384",
    "TLS_ECDHE_RSA_WITH_ARIA_128_GCM_SHA256": "ECDHE-ARIA128-GCM-SHA256",
    "TLS_ECDHE_RSA_WITH_ARIA_256_GCM_SHA384": "ECDHE-ARIA256-GCM-SHA384",
    "TLS_PSK_WITH_ARIA_128_GCM_SHA256": "PSK-ARIA128-GCM-SHA256",
    "TLS_PSK_WITH_ARIA_256_GCM_SHA384": "PSK-ARIA256-GCM-SHA384",
    "TLS_DHE_PSK_WITH_ARIA_128_GCM_SHA256": "DHE-PSK-ARIA128-GCM-SHA256",
    "TLS_DHE_PSK_WITH_ARIA_256_GCM_SHA384": "DHE-PSK-ARIA256-GCM-SHA384",
    "TLS_RSA_PSK_WITH_ARIA_128_GCM_SHA256": "RSA-PSK-ARIA128-GCM-SHA256",
    "TLS_RSA_PSK_WITH_ARIA_256_GCM_SHA384": "RSA-PSK-ARIA256-GCM-SHA384",
    # Camellia HMAC-Based cipher suites from RFC6367, extending TLS v1.2
    "TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256": "ECDHE-ECDSA-CAMELLIA128-SHA256",
    "TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384": "ECDHE-ECDSA-CAMELLIA256-SHA384",
    "TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256": "ECDHE-RSA-CAMELLIA128-SHA256",
    "TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384": "ECDHE-RSA-CAMELLIA256-SHA384",
    # Pre-shared keying (PSK) cipher suites",
    "PSK_WITH_NULL_SHA": "PSK-NULL-SHA",
    "DHE_PSK_WITH_NULL_SHA": "DHE-PSK-NULL-SHA",
    "RSA_PSK_WITH_NULL_SHA": "RSA-PSK-NULL-SHA",
    "PSK_WITH_RC4_128_SHA": "PSK-RC4-SHA",
    "PSK_WITH_3DES_EDE_CBC_SHA": "PSK-3DES-EDE-CBC-SHA",
    "PSK_WITH_AES_128_CBC_SHA": "PSK-AES128-CBC-SHA",
    "PSK_WITH_AES_256_CBC_SHA": "PSK-AES256-CBC-SHA",
    "DHE_PSK_WITH_RC4_128_SHA": "DHE-PSK-RC4-SHA",
    "DHE_PSK_WITH_3DES_EDE_CBC_SHA": "DHE-PSK-3DES-EDE-CBC-SHA",
    "DHE_PSK_WITH_AES_128_CBC_SHA": "DHE-PSK-AES128-CBC-SHA",
    "DHE_PSK_WITH_AES_256_CBC_SHA": "DHE-PSK-AES256-CBC-SHA",
    "RSA_PSK_WITH_RC4_128_SHA": "RSA-PSK-RC4-SHA",
    "RSA_PSK_WITH_3DES_EDE_CBC_SHA": "RSA-PSK-3DES-EDE-CBC-SHA",
    "RSA_PSK_WITH_AES_128_CBC_SHA": "RSA-PSK-AES128-CBC-SHA",
    "RSA_PSK_WITH_AES_256_CBC_SHA": "RSA-PSK-AES256-CBC-SHA",
    "PSK_WITH_AES_128_GCM_SHA256": "PSK-AES128-GCM-SHA256",
    "PSK_WITH_AES_256_GCM_SHA384": "PSK-AES256-GCM-SHA384",
    "DHE_PSK_WITH_AES_128_GCM_SHA256": "DHE-PSK-AES128-GCM-SHA256",
    "DHE_PSK_WITH_AES_256_GCM_SHA384": "DHE-PSK-AES256-GCM-SHA384",
    "RSA_PSK_WITH_AES_128_GCM_SHA256": "RSA-PSK-AES128-GCM-SHA256",
    "RSA_PSK_WITH_AES_256_GCM_SHA384": "RSA-PSK-AES256-GCM-SHA384",
    "PSK_WITH_AES_128_CBC_SHA256": "PSK-AES128-CBC-SHA256",
    "PSK_WITH_AES_256_CBC_SHA384": "PSK-AES256-CBC-SHA384",
    "PSK_WITH_NULL_SHA256": "PSK-NULL-SHA256",
    "PSK_WITH_NULL_SHA384": "PSK-NULL-SHA384",
    "DHE_PSK_WITH_AES_128_CBC_SHA256": "DHE-PSK-AES128-CBC-SHA256",
    "DHE_PSK_WITH_AES_256_CBC_SHA384": "DHE-PSK-AES256-CBC-SHA384",
    "DHE_PSK_WITH_NULL_SHA256": "DHE-PSK-NULL-SHA256",
    "DHE_PSK_WITH_NULL_SHA384": "DHE-PSK-NULL-SHA384",
    "RSA_PSK_WITH_AES_128_CBC_SHA256": "RSA-PSK-AES128-CBC-SHA256",
    "RSA_PSK_WITH_AES_256_CBC_SHA384": "RSA-PSK-AES256-CBC-SHA384",
    "RSA_PSK_WITH_NULL_SHA256": "RSA-PSK-NULL-SHA256",
    "RSA_PSK_WITH_NULL_SHA384": "RSA-PSK-NULL-SHA384",
    "ECDHE_PSK_WITH_RC4_128_SHA": "ECDHE-PSK-RC4-SHA",
    "ECDHE_PSK_WITH_3DES_EDE_CBC_SHA": "ECDHE-PSK-3DES-EDE-CBC-SHA",
    "ECDHE_PSK_WITH_AES_128_CBC_SHA": "ECDHE-PSK-AES128-CBC-SHA",
    "ECDHE_PSK_WITH_AES_256_CBC_SHA": "ECDHE-PSK-AES256-CBC-SHA",
    "ECDHE_PSK_WITH_AES_128_CBC_SHA256": "ECDHE-PSK-AES128-CBC-SHA256",
    "ECDHE_PSK_WITH_AES_256_CBC_SHA384": "ECDHE-PSK-AES256-CBC-SHA384",
    "ECDHE_PSK_WITH_NULL_SHA": "ECDHE-PSK-NULL-SHA",
    "ECDHE_PSK_WITH_NULL_SHA256": "ECDHE-PSK-NULL-SHA256",
    "ECDHE_PSK_WITH_NULL_SHA384": "ECDHE-PSK-NULL-SHA384",
    "PSK_WITH_CAMELLIA_128_CBC_SHA256": "PSK-CAMELLIA128-SHA256",
    "PSK_WITH_CAMELLIA_256_CBC_SHA384": "PSK-CAMELLIA256-SHA384",
    "DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256": "DHE-PSK-CAMELLIA128-SHA256",
    "DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384": "DHE-PSK-CAMELLIA256-SHA384",
    "RSA_PSK_WITH_CAMELLIA_128_CBC_SHA256": "RSA-PSK-CAMELLIA128-SHA256",
    "RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384": "RSA-PSK-CAMELLIA256-SHA384",
    "ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256": "ECDHE-PSK-CAMELLIA128-SHA256",
    "ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384": "ECDHE-PSK-CAMELLIA256-SHA384",
    "PSK_WITH_AES_128_CCM": "PSK-AES128-CCM",
    "PSK_WITH_AES_256_CCM": "PSK-AES256-CCM",
    "DHE_PSK_WITH_AES_128_CCM": "DHE-PSK-AES128-CCM",
    "DHE_PSK_WITH_AES_256_CCM": "DHE-PSK-AES256-CCM",
    "PSK_WITH_AES_128_CCM_8": "PSK-AES128-CCM8",
    "PSK_WITH_AES_256_CCM_8": "PSK-AES256-CCM8",
    "DHE_PSK_WITH_AES_128_CCM_8": "DHE-PSK-AES128-CCM8",
    "DHE_PSK_WITH_AES_256_CCM_8": "DHE-PSK-AES256-CCM8",
    # ChaCha20-Poly1305 cipher suites, extending TLS v1.2
    "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256": "ECDHE-RSA-CHACHA20-POLY1305",
    "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256": "ECDHE-ECDSA-CHACHA20-POLY1305",
    "TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256": "DHE-RSA-CHACHA20-POLY1305",
    "TLS_PSK_WITH_CHACHA20_POLY1305_SHA256": "PSK-CHACHA20-POLY1305",
    "TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256": "ECDHE-PSK-CHACHA20-POLY1305",
    "TLS_DHE_PSK_WITH_CHACHA20_POLY1305_SHA256": "DHE-PSK-CHACHA20-POLY1305",
    "TLS_RSA_PSK_WITH_CHACHA20_POLY1305_SHA256": "RSA-PSK-CHACHA20-POLY1305",
}

# TLS v1.3 cipher suites IANI to OpenSSL name translation
TLSV1_3_CIPHER_SUITES = {
    "TLS_AES_128_GCM_SHA256": "TLS_AES_128_GCM_SHA256",
    "TLS_AES_256_GCM_SHA384": "TLS_AES_256_GCM_SHA384",
    "TLS_CHACHA20_POLY1305_SHA256": "TLS_CHACHA20_POLY1305_SHA256",
    "TLS_AES_128_CCM_SHA256": "TLS_AES_128_CCM_SHA256",
    "TLS_AES_128_CCM_8_SHA256": "TLS_AES_128_CCM_8_SHA256",
}

TLS_CIPHER_SUITES = {
    "TLSv1": TLSV1_CIPHER_SUITES,
    "TLSv1.1": TLSV1_1_CIPHER_SUITES,
    "TLSv1.2": TLSV1_2_CIPHER_SUITES,
    "TLSv1.3": TLSV1_3_CIPHER_SUITES,
}

OPENSSL_CS_NAMES = {
    "TLSv1": TLSV1_CIPHER_SUITES.values(),
    "TLSv1.1": TLSV1_1_CIPHER_SUITES.values(),
    "TLSv1.2": TLSV1_2_CIPHER_SUITES.values(),
    "TLSv1.3": TLSV1_3_CIPHER_SUITES.values(),
}

virt/lib/python3.9/site-packages/mysqlx/constants 3.py
PrinceDisant/LostInNL
# Copyright (c) 2016, 2022, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

"""Constants."""

from enum import Enum


class Auth(Enum):
    """Enum to identify the authentication mechanisms."""

    PLAIN = "plain"
    MYSQL41 = "mysql41"
    SHA256_MEMORY = "sha256_memory"


class Compression(Enum):
    """Enum to identify the compression options."""

    PREFERRED = "preferred"
    REQUIRED = "required"
    DISABLED = "disabled"


class LockContention(Enum):
    """Enum to identify the row locking modes."""

    DEFAULT = 0
    NOWAIT = 1
    SKIP_LOCKED = 2


class SSLMode(Enum):
    """Enum to identify the SSL modes."""

    REQUIRED = "required"
    DISABLED = "disabled"
    VERIFY_CA = "verify_ca"
    VERIFY_IDENTITY = "verify_identity"


# Compression algorithms and aliases
COMPRESSION_ALGORITHMS = {
    "deflate": "deflate_stream",
    "deflate_stream": "deflate_stream",
    "lz4": "lz4_message",
    "lz4_message": "lz4_message",
    "zstd": "zstd_stream",
    "zstd_stream": "zstd_stream",
}

TLS_VERSIONS = ["TLSv1", "TLSv1.1", "TLSv1.2", "TLSv1.3"]
SUPPORTED_TLS_VERSIONS = ["TLSv1.2", "TLSv1.3"]
DEPRECATED_TLS_VERSIONS = ["TLSv1", "TLSv1.1"]

# TLS v1.0 cipher suites IANI to OpenSSL name translation
TLSV1_CIPHER_SUITES = {
    "TLS_RSA_WITH_NULL_MD5": "NULL-MD5",
    "TLS_RSA_WITH_NULL_SHA": "NULL-SHA",
    "TLS_RSA_WITH_RC4_128_MD5": "RC4-MD5",
    "TLS_RSA_WITH_RC4_128_SHA": "RC4-SHA",
    "TLS_RSA_WITH_IDEA_CBC_SHA": "IDEA-CBC-SHA",
    "TLS_RSA_WITH_3DES_EDE_CBC_SHA": "DES-CBC3-SHA",
    "TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA": "Not implemented.",
    "TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA": "Not implemented.",
    "TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA": "DHE-DSS-DES-CBC3-SHA",
    "TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA": "DHE-RSA-DES-CBC3-SHA",
    "TLS_DH_anon_WITH_RC4_128_MD5": "ADH-RC4-MD5",
    "TLS_DH_anon_WITH_3DES_EDE_CBC_SHA": "ADH-DES-CBC3-SHA",
    # AES cipher suites from RFC3268, extending TLS v1.0
    "TLS_RSA_WITH_AES_128_CBC_SHA": "AES128-SHA",
    "TLS_RSA_WITH_AES_256_CBC_SHA": "AES256-SHA",
    "TLS_DH_DSS_WITH_AES_128_CBC_SHA": "DH-DSS-AES128-SHA",
    "TLS_DH_DSS_WITH_AES_256_CBC_SHA": "DH-DSS-AES256-SHA",
    "TLS_DH_RSA_WITH_AES_128_CBC_SHA": "DH-RSA-AES128-SHA",
    "TLS_DH_RSA_WITH_AES_256_CBC_SHA": "DH-RSA-AES256-SHA",
    "TLS_DHE_DSS_WITH_AES_128_CBC_SHA": "DHE-DSS-AES128-SHA",
    "TLS_DHE_DSS_WITH_AES_256_CBC_SHA": "DHE-DSS-AES256-SHA",
    "TLS_DHE_RSA_WITH_AES_128_CBC_SHA": "DHE-RSA-AES128-SHA",
    "TLS_DHE_RSA_WITH_AES_256_CBC_SHA": "DHE-RSA-AES256-SHA",
    "TLS_DH_anon_WITH_AES_128_CBC_SHA": "ADH-AES128-SHA",
    "TLS_DH_anon_WITH_AES_256_CBC_SHA": "ADH-AES256-SHA",
    # Camellia cipher suites from RFC4132, extending TLS v1.0
    "TLS_RSA_WITH_CAMELLIA_128_CBC_SHA": "CAMELLIA128-SHA",
    "TLS_RSA_WITH_CAMELLIA_256_CBC_SHA": "CAMELLIA256-SHA",
    "TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA": "DH-DSS-CAMELLIA128-SHA",
    "TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA": "DH-DSS-CAMELLIA256-SHA",
    "TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA": "DH-RSA-CAMELLIA128-SHA",
    "TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA": "DH-RSA-CAMELLIA256-SHA",
    "TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA": "DHE-DSS-CAMELLIA128-SHA",
    "TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA": "DHE-DSS-CAMELLIA256-SHA",
    "TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA": "DHE-RSA-CAMELLIA128-SHA",
    "TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA": "DHE-RSA-CAMELLIA256-SHA",
    "TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA": "ADH-CAMELLIA128-SHA",
    "TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA": "ADH-CAMELLIA256-SHA",
    # SEED cipher suites from RFC4162, extending TLS v1.0
    "TLS_RSA_WITH_SEED_CBC_SHA": "SEED-SHA",
    "TLS_DH_DSS_WITH_SEED_CBC_SHA": "DH-DSS-SEED-SHA",
    "TLS_DH_RSA_WITH_SEED_CBC_SHA": "DH-RSA-SEED-SHA",
    "TLS_DHE_DSS_WITH_SEED_CBC_SHA": "DHE-DSS-SEED-SHA",
    "TLS_DHE_RSA_WITH_SEED_CBC_SHA": "DHE-RSA-SEED-SHA",
    "TLS_DH_anon_WITH_SEED_CBC_SHA": "ADH-SEED-SHA",
    # GOST cipher suites from draft-chudov-cryptopro-cptls, extending TLS v1.0
    "TLS_GOSTR341094_WITH_28147_CNT_IMIT": "GOST94-GOST89-GOST89",
    "TLS_GOSTR341001_WITH_28147_CNT_IMIT": "GOST2001-GOST89-GOST89",
    "TLS_GOSTR341094_WITH_NULL_GOSTR3411": "GOST94-NULL-GOST94",
    "TLS_GOSTR341001_WITH_NULL_GOSTR3411": "GOST2001-NULL-GOST94",
}

# TLS v1.1 cipher suites IANI to OpenSSL name translation
TLSV1_1_CIPHER_SUITES = TLSV1_CIPHER_SUITES

# TLS v1.2 cipher suites IANI to OpenSSL name translation
TLSV1_2_CIPHER_SUITES = {
    "TLS_RSA_WITH_NULL_SHA256": "NULL-SHA256",
    "TLS_RSA_WITH_AES_128_CBC_SHA256": "AES128-SHA256",
    "TLS_RSA_WITH_AES_256_CBC_SHA256": "AES256-SHA256",
    "TLS_RSA_WITH_AES_128_GCM_SHA256": "AES128-GCM-SHA256",
    "TLS_RSA_WITH_AES_256_GCM_SHA384": "AES256-GCM-SHA384",
    "TLS_DH_RSA_WITH_AES_128_CBC_SHA256": "DH-RSA-AES128-SHA256",
    "TLS_DH_RSA_WITH_AES_256_CBC_SHA256": "DH-RSA-AES256-SHA256",
    "TLS_DH_RSA_WITH_AES_128_GCM_SHA256": "DH-RSA-AES128-GCM-SHA256",
    "TLS_DH_RSA_WITH_AES_256_GCM_SHA384": "DH-RSA-AES256-GCM-SHA384",
    "TLS_DH_DSS_WITH_AES_128_CBC_SHA256": "DH-DSS-AES128-SHA256",
    "TLS_DH_DSS_WITH_AES_256_CBC_SHA256": "DH-DSS-AES256-SHA256",
    "TLS_DH_DSS_WITH_AES_128_GCM_SHA256": "DH-DSS-AES128-GCM-SHA256",
    "TLS_DH_DSS_WITH_AES_256_GCM_SHA384": "DH-DSS-AES256-GCM-SHA384",
    "TLS_DHE_RSA_WITH_AES_128_CBC_SHA256": "DHE-RSA-AES128-SHA256",
    "TLS_DHE_RSA_WITH_AES_256_CBC_SHA256": "DHE-RSA-AES256-SHA256",
    "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256": "DHE-RSA-AES128-GCM-SHA256",
    "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384": "DHE-RSA-AES256-GCM-SHA384",
    "TLS_DHE_DSS_WITH_AES_128_CBC_SHA256": "DHE-DSS-AES128-SHA256",
    "TLS_DHE_DSS_WITH_AES_256_CBC_SHA256": "DHE-DSS-AES256-SHA256",
    "TLS_DHE_DSS_WITH_AES_128_GCM_SHA256": "DHE-DSS-AES128-GCM-SHA256",
    "TLS_DHE_DSS_WITH_AES_256_GCM_SHA384": "DHE-DSS-AES256-GCM-SHA384",
    "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256": "ECDHE-RSA-AES128-SHA256",
    "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384": "ECDHE-RSA-AES256-SHA384",
    "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256": "ECDHE-RSA-AES128-GCM-SHA256",
    "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384": "ECDHE-RSA-AES256-GCM-SHA384",
    "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256": "ECDHE-ECDSA-AES128-SHA256",
    "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384": "ECDHE-ECDSA-AES256-SHA384",
    "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256": "ECDHE-ECDSA-AES128-GCM-SHA256",
    "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384": "ECDHE-ECDSA-AES256-GCM-SHA384",
    "TLS_DH_anon_WITH_AES_128_CBC_SHA256": "ADH-AES128-SHA256",
    "TLS_DH_anon_WITH_AES_256_CBC_SHA256": "ADH-AES256-SHA256",
    "TLS_DH_anon_WITH_AES_128_GCM_SHA256": "ADH-AES128-GCM-SHA256",
    "TLS_DH_anon_WITH_AES_256_GCM_SHA384": "ADH-AES256-GCM-SHA384",
    "RSA_WITH_AES_128_CCM": "AES128-CCM",
    "RSA_WITH_AES_256_CCM": "AES256-CCM",
    "DHE_RSA_WITH_AES_128_CCM": "DHE-RSA-AES128-CCM",
    "DHE_RSA_WITH_AES_256_CCM": "DHE-RSA-AES256-CCM",
    "RSA_WITH_AES_128_CCM_8": "AES128-CCM8",
    "RSA_WITH_AES_256_CCM_8": "AES256-CCM8",
    "DHE_RSA_WITH_AES_128_CCM_8": "DHE-RSA-AES128-CCM8",
    "DHE_RSA_WITH_AES_256_CCM_8": "DHE-RSA-AES256-CCM8",
    "ECDHE_ECDSA_WITH_AES_128_CCM": "ECDHE-ECDSA-AES128-CCM",
    "ECDHE_ECDSA_WITH_AES_256_CCM": "ECDHE-ECDSA-AES256-CCM",
    "ECDHE_ECDSA_WITH_AES_128_CCM_8": "ECDHE-ECDSA-AES128-CCM8",
    "ECDHE_ECDSA_WITH_AES_256_CCM_8": "ECDHE-ECDSA-AES256-CCM8",
    # ARIA cipher suites from RFC6209, extending TLS v1.2
    "TLS_RSA_WITH_ARIA_128_GCM_SHA256": "ARIA128-GCM-SHA256",
    "TLS_RSA_WITH_ARIA_256_GCM_SHA384": "ARIA256-GCM-SHA384",
    "TLS_DHE_RSA_WITH_ARIA_128_GCM_SHA256": "DHE-RSA-ARIA128-GCM-SHA256",
    "TLS_DHE_RSA_WITH_ARIA_256_GCM_SHA384": "DHE-RSA-ARIA256-GCM-SHA384",
    "TLS_DHE_DSS_WITH_ARIA_128_GCM_SHA256": "DHE-DSS-ARIA128-GCM-SHA256",
    "TLS_DHE_DSS_WITH_ARIA_256_GCM_SHA384": "DHE-DSS-ARIA256-GCM-SHA384",
    "TLS_ECDHE_ECDSA_WITH_ARIA_128_GCM_SHA256": "ECDHE-ECDSA-ARIA128-GCM-SHA256",
    "TLS_ECDHE_ECDSA_WITH_ARIA_256_GCM_SHA384": "ECDHE-ECDSA-ARIA256-GCM-SHA384",
    "TLS_ECDHE_RSA_WITH_ARIA_128_GCM_SHA256": "ECDHE-ARIA128-GCM-SHA256",
    "TLS_ECDHE_RSA_WITH_ARIA_256_GCM_SHA384": "ECDHE-ARIA256-GCM-SHA384",
    "TLS_PSK_WITH_ARIA_128_GCM_SHA256": "PSK-ARIA128-GCM-SHA256",
    "TLS_PSK_WITH_ARIA_256_GCM_SHA384": "PSK-ARIA256-GCM-SHA384",
    "TLS_DHE_PSK_WITH_ARIA_128_GCM_SHA256": "DHE-PSK-ARIA128-GCM-SHA256",
    "TLS_DHE_PSK_WITH_ARIA_256_GCM_SHA384": "DHE-PSK-ARIA256-GCM-SHA384",
    "TLS_RSA_PSK_WITH_ARIA_128_GCM_SHA256": "RSA-PSK-ARIA128-GCM-SHA256",
    "TLS_RSA_PSK_WITH_ARIA_256_GCM_SHA384": "RSA-PSK-ARIA256-GCM-SHA384",
    # Camellia HMAC-Based cipher suites from RFC6367, extending TLS v1.2
    "TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256": "ECDHE-ECDSA-CAMELLIA128-SHA256",
    "TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384": "ECDHE-ECDSA-CAMELLIA256-SHA384",
    "TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256": "ECDHE-RSA-CAMELLIA128-SHA256",
    "TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384": "ECDHE-RSA-CAMELLIA256-SHA384",
    # Pre-shared keying (PSK) cipher suites",
    "PSK_WITH_NULL_SHA": "PSK-NULL-SHA",
    "DHE_PSK_WITH_NULL_SHA": "DHE-PSK-NULL-SHA",
    "RSA_PSK_WITH_NULL_SHA": "RSA-PSK-NULL-SHA",
    "PSK_WITH_RC4_128_SHA": "PSK-RC4-SHA",
    "PSK_WITH_3DES_EDE_CBC_SHA": "PSK-3DES-EDE-CBC-SHA",
    "PSK_WITH_AES_128_CBC_SHA": "PSK-AES128-CBC-SHA",
    "PSK_WITH_AES_256_CBC_SHA": "PSK-AES256-CBC-SHA",
    "DHE_PSK_WITH_RC4_128_SHA": "DHE-PSK-RC4-SHA",
    "DHE_PSK_WITH_3DES_EDE_CBC_SHA": "DHE-PSK-3DES-EDE-CBC-SHA",
    "DHE_PSK_WITH_AES_128_CBC_SHA": "DHE-PSK-AES128-CBC-SHA",
    "DHE_PSK_WITH_AES_256_CBC_SHA": "DHE-PSK-AES256-CBC-SHA",
    "RSA_PSK_WITH_RC4_128_SHA": "RSA-PSK-RC4-SHA",
    "RSA_PSK_WITH_3DES_EDE_CBC_SHA": "RSA-PSK-3DES-EDE-CBC-SHA",
    "RSA_PSK_WITH_AES_128_CBC_SHA": "RSA-PSK-AES128-CBC-SHA",
    "RSA_PSK_WITH_AES_256_CBC_SHA": "RSA-PSK-AES256-CBC-SHA",
    "PSK_WITH_AES_128_GCM_SHA256": "PSK-AES128-GCM-SHA256",
    "PSK_WITH_AES_256_GCM_SHA384": "PSK-AES256-GCM-SHA384",
    "DHE_PSK_WITH_AES_128_GCM_SHA256": "DHE-PSK-AES128-GCM-SHA256",
    "DHE_PSK_WITH_AES_256_GCM_SHA384": "DHE-PSK-AES256-GCM-SHA384",
    "RSA_PSK_WITH_AES_128_GCM_SHA256": "RSA-PSK-AES128-GCM-SHA256",
    "RSA_PSK_WITH_AES_256_GCM_SHA384": "RSA-PSK-AES256-GCM-SHA384",
    "PSK_WITH_AES_128_CBC_SHA256": "PSK-AES128-CBC-SHA256",
    "PSK_WITH_AES_256_CBC_SHA384": "PSK-AES256-CBC-SHA384",
    "PSK_WITH_NULL_SHA256": "PSK-NULL-SHA256",
    "PSK_WITH_NULL_SHA384": "PSK-NULL-SHA384",
    "DHE_PSK_WITH_AES_128_CBC_SHA256": "DHE-PSK-AES128-CBC-SHA256",
    "DHE_PSK_WITH_AES_256_CBC_SHA384": "DHE-PSK-AES256-CBC-SHA384",
    "DHE_PSK_WITH_NULL_SHA256": "DHE-PSK-NULL-SHA256",
    "DHE_PSK_WITH_NULL_SHA384": "DHE-PSK-NULL-SHA384",
    "RSA_PSK_WITH_AES_128_CBC_SHA256": "RSA-PSK-AES128-CBC-SHA256",
    "RSA_PSK_WITH_AES_256_CBC_SHA384": "RSA-PSK-AES256-CBC-SHA384",
    "RSA_PSK_WITH_NULL_SHA256": "RSA-PSK-NULL-SHA256",
    "RSA_PSK_WITH_NULL_SHA384": "RSA-PSK-NULL-SHA384",
    "ECDHE_PSK_WITH_RC4_128_SHA": "ECDHE-PSK-RC4-SHA",
    "ECDHE_PSK_WITH_3DES_EDE_CBC_SHA": "ECDHE-PSK-3DES-EDE-CBC-SHA",
    "ECDHE_PSK_WITH_AES_128_CBC_SHA": "ECDHE-PSK-AES128-CBC-SHA",
    "ECDHE_PSK_WITH_AES_256_CBC_SHA": "ECDHE-PSK-AES256-CBC-SHA",
    "ECDHE_PSK_WITH_AES_128_CBC_SHA256": "ECDHE-PSK-AES128-CBC-SHA256",
    "ECDHE_PSK_WITH_AES_256_CBC_SHA384": "ECDHE-PSK-AES256-CBC-SHA384",
    "ECDHE_PSK_WITH_NULL_SHA": "ECDHE-PSK-NULL-SHA",
    "ECDHE_PSK_WITH_NULL_SHA256": "ECDHE-PSK-NULL-SHA256",
    "ECDHE_PSK_WITH_NULL_SHA384": "ECDHE-PSK-NULL-SHA384",
    "PSK_WITH_CAMELLIA_128_CBC_SHA256": "PSK-CAMELLIA128-SHA256",
    "PSK_WITH_CAMELLIA_256_CBC_SHA384": "PSK-CAMELLIA256-SHA384",
    "DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256": "DHE-PSK-CAMELLIA128-SHA256",
    "DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384": "DHE-PSK-CAMELLIA256-SHA384",
    "RSA_PSK_WITH_CAMELLIA_128_CBC_SHA256": "RSA-PSK-CAMELLIA128-SHA256",
    "RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384": "RSA-PSK-CAMELLIA256-SHA384",
    "ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256": "ECDHE-PSK-CAMELLIA128-SHA256",
    "ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384": "ECDHE-PSK-CAMELLIA256-SHA384",
    "PSK_WITH_AES_128_CCM": "PSK-AES128-CCM",
    "PSK_WITH_AES_256_CCM": "PSK-AES256-CCM",
    "DHE_PSK_WITH_AES_128_CCM": "DHE-PSK-AES128-CCM",
    "DHE_PSK_WITH_AES_256_CCM": "DHE-PSK-AES256-CCM",
    "PSK_WITH_AES_128_CCM_8": "PSK-AES128-CCM8",
    "PSK_WITH_AES_256_CCM_8": "PSK-AES256-CCM8",
    "DHE_PSK_WITH_AES_128_CCM_8": "DHE-PSK-AES128-CCM8",
    "DHE_PSK_WITH_AES_256_CCM_8": "DHE-PSK-AES256-CCM8",
    # ChaCha20-Poly1305 cipher suites, extending TLS v1.2
    "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256": "ECDHE-RSA-CHACHA20-POLY1305",
    "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256": "ECDHE-ECDSA-CHACHA20-POLY1305",
    "TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256": "DHE-RSA-CHACHA20-POLY1305",
    "TLS_PSK_WITH_CHACHA20_POLY1305_SHA256": "PSK-CHACHA20-POLY1305",
    "TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256": "ECDHE-PSK-CHACHA20-POLY1305",
    "TLS_DHE_PSK_WITH_CHACHA20_POLY1305_SHA256": "DHE-PSK-CHACHA20-POLY1305",
    "TLS_RSA_PSK_WITH_CHACHA20_POLY1305_SHA256": "RSA-PSK-CHACHA20-POLY1305",
}

# TLS v1.3 cipher suites IANI to OpenSSL name translation
TLSV1_3_CIPHER_SUITES = {
    "TLS_AES_128_GCM_SHA256": "TLS_AES_128_GCM_SHA256",
    "TLS_AES_256_GCM_SHA384": "TLS_AES_256_GCM_SHA384",
    "TLS_CHACHA20_POLY1305_SHA256": "TLS_CHACHA20_POLY1305_SHA256",
    "TLS_AES_128_CCM_SHA256": "TLS_AES_128_CCM_SHA256",
    "TLS_AES_128_CCM_8_SHA256": "TLS_AES_128_CCM_8_SHA256",
}

TLS_CIPHER_SUITES = {
    "TLSv1": TLSV1_CIPHER_SUITES,
    "TLSv1.1": TLSV1_1_CIPHER_SUITES,
    "TLSv1.2": TLSV1_2_CIPHER_SUITES,
    "TLSv1.3": TLSV1_3_CIPHER_SUITES,
}

OPENSSL_CS_NAMES = {
    "TLSv1": TLSV1_CIPHER_SUITES.values(),
    "TLSv1.1": TLSV1_1_CIPHER_SUITES.values(),
    "TLSv1.2": TLSV1_2_CIPHER_SUITES.values(),
    "TLSv1.3": TLSV1_3_CIPHER_SUITES.values(),
}

virt/lib/python3.9/site-packages/mysqlx/constants.py
PrinceDisant/LostInNL
# Copyright (c) 2016, 2022, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

"""Implementation of the CRUD database objects."""

from __future__ import annotations

import json
import warnings

from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union

from .dbdoc import DbDoc
from .errorcode import (
    ER_NO_SUCH_TABLE,
    ER_TABLE_EXISTS_ERROR,
    ER_X_CMD_NUM_ARGUMENTS,
    ER_X_INVALID_ADMIN_COMMAND,
)
from .errors import NotSupportedError, OperationalError, ProgrammingError
from .helpers import deprecated, escape, quote_identifier
from .statement import (
    AddStatement,
    CreateCollectionIndexStatement,
    DeleteStatement,
    FindStatement,
    InsertStatement,
    ModifyStatement,
    RemoveStatement,
    SelectStatement,
    UpdateStatement,
)
from .types import ConnectionType, SchemaType, SessionType, StrOrBytes

if TYPE_CHECKING:
    from .result import Result

_COUNT_VIEWS_QUERY = (
    "SELECT COUNT(*) FROM information_schema.views "
    "WHERE table_schema = '{0}' AND table_name = '{1}'"
)
_COUNT_TABLES_QUERY = (
    "SELECT COUNT(*) FROM information_schema.tables "
    "WHERE table_schema = '{0}' AND table_name = '{1}'"
)
_COUNT_SCHEMAS_QUERY = (
    "SELECT COUNT(*) FROM information_schema.schemata WHERE schema_name = '{0}'"
)
_COUNT_QUERY = "SELECT COUNT(*) FROM {0}.{1}"
_DROP_TABLE_QUERY = "DROP TABLE IF EXISTS {0}.{1}"


class DatabaseObject:
    """Provides base functionality for database objects.

    Args:
        schema (mysqlx.Schema): The Schema object.
        name (str): The database object name.
    """

    def __init__(self, schema: SchemaType, name: StrOrBytes) -> None:
        self._schema: SchemaType = schema
        self._name: str = name.decode() if isinstance(name, bytes) else name
        self._session: SessionType = self._schema.get_session()
        self._connection: ConnectionType = self._session.get_connection()

    @property
    def session(self) -> SessionType:
        """:class:`mysqlx.Session`: The Session object."""
        return self._session

    @property
    def schema(self) -> SchemaType:
        """:class:`mysqlx.Schema`: The Schema object."""
        return self._schema

    @property
    def name(self) -> str:
        """str: The name of this database object."""
        return self._name

    def get_connection(self) -> ConnectionType:
        """Returns the underlying connection.

        Returns:
            mysqlx.connection.Connection: The connection object.
        """
        return self._connection

    def get_session(self) -> SessionType:
        """Returns the session of this database object.

        Returns:
            mysqlx.Session: The Session object.
        """
        return self._session

    def get_schema(self) -> SchemaType:
        """Returns the Schema object of this database object.

        Returns:
            mysqlx.Schema: The Schema object.
        """
        return self._schema

    def get_name(self) -> str:
        """Returns the name of this database object.

        Returns:
            str: The name of this database object.
        """
        return self._name

    def exists_in_database(self) -> Any:
        """Verifies if this object exists in the database.

        Returns:
            bool: `True` if object exists in database.

        Raises:
           NotImplementedError: This method must be implemented.
        """
        raise NotImplementedError

    @deprecated("8.0.12", "Use 'exists_in_database()' method instead")
    def am_i_real(self) -> Any:
        """Verifies if this object exists in the database.

        Returns:
            bool: `True` if object exists in database.

        Raises:
           NotImplementedError: This method must be implemented.

        .. deprecated:: 8.0.12
           Use ``exists_in_database()`` method instead.
        """
        return self.exists_in_database()

    @deprecated("8.0.12", "Use 'get_name()' method instead")
    def who_am_i(self) -> str:
        """Returns the name of this database object.

        Returns:
            str: The name of this database object.

        .. deprecated:: 8.0.12
           Use ``get_name()`` method instead.
        """
        return self.get_name()


class Schema(DatabaseObject):
    """A client-side representation of a database schema. Provides access to
    the schema contents.

    Args:
        session (mysqlx.XSession): Session object.
        name (str): The Schema name.
    """

    def __init__(self, session: SessionType, name: str) -> None:
        self._session: SessionType = session
        super().__init__(self, name)

    def exists_in_database(self) -> bool:
        """Verifies if this object exists in the database.

        Returns:
            bool: `True` if object exists in database.
        """
        sql = _COUNT_SCHEMAS_QUERY.format(escape(self._name))
        return self._connection.execute_sql_scalar(sql) == 1

    def get_collections(self) -> List[Collection]:
        """Returns a list of collections for this schema.

        Returns:
            `list`: List of Collection objects.
        """
        rows = self._connection.get_row_result("list_objects", {"schema": self._name})
        rows.fetch_all()
        collections = []
        for row in rows:
            if row["type"] != "COLLECTION":
                continue
            try:
                collection = Collection(self, row["TABLE_NAME"])
            except ValueError:
                collection = Collection(self, row["name"])
            collections.append(collection)
        return collections

    def get_collection_as_table(
        self, name: str, check_existence: bool = False
    ) -> Table:
        """Returns a a table object for the given collection

        Returns:
            mysqlx.Table: Table object.

        """
        return self.get_table(name, check_existence)

    def get_tables(self) -> List[Table]:
        """Returns a list of tables for this schema.

        Returns:
            `list`: List of Table objects.
        """
        rows = self._connection.get_row_result("list_objects", {"schema": self._name})
        rows.fetch_all()
        tables = []
        object_types = (
            "TABLE",
            "VIEW",
        )
        for row in rows:
            if row["type"] in object_types:
                try:
                    table = Table(self, row["TABLE_NAME"])
                except ValueError:
                    table = Table(self, row["name"])
                tables.append(table)
        return tables

    def get_table(self, name: str, check_existence: bool = False) -> Table:
        """Returns the table of the given name for this schema.

        Returns:
            mysqlx.Table: Table object.
        """
        table = Table(self, name)
        if check_existence:
            if not table.exists_in_database():
                raise ProgrammingError("Table does not exist")
        return table

    def get_view(self, name: str, check_existence: bool = False) -> View:
        """Returns the view of the given name for this schema.

        Returns:
            mysqlx.View: View object.
        """
        view = View(self, name)
        if check_existence:
            if not view.exists_in_database():
                raise ProgrammingError("View does not exist")
        return view

    def get_collection(self, name: str, check_existence: bool = False) -> Collection:
        """Returns the collection of the given name for this schema.

        Returns:
            mysqlx.Collection: Collection object.
        """
        collection = Collection(self, name)
        if check_existence:
            if not collection.exists_in_database():
                raise ProgrammingError("Collection does not exist")
        return collection

    def drop_collection(self, name: str) -> None:
        """Drops a collection.

        Args:
            name (str): The name of the collection to be dropped.
        """
        self._connection.execute_nonquery(
            "sql",
            _DROP_TABLE_QUERY.format(
                quote_identifier(self._name), quote_identifier(name)
            ),
            False,
        )

    def create_collection(
        self,
        name: str,
        reuse_existing: bool = False,
        validation: Optional[Dict[str, Union[str, Dict]]] = None,
        **kwargs: Any,
    ) -> Collection:
        """Creates in the current schema a new collection with the specified
        name and retrieves an object representing the new collection created.

        Args:
            name (str): The name of the collection.
            reuse_existing (bool): `True` to reuse an existing collection.
            validation (Optional[dict]): A dict, containing the keys `level`
                                         with the validation level and `schema`
                                         with a dict or a string representation
                                         of a JSON schema specification.

        Returns:
            mysqlx.Collection: Collection object.

        Raises:
            :class:`mysqlx.ProgrammingError`: If ``reuse_existing`` is False
                                              and collection exists or the
                                              collection name is invalid.
            :class:`mysqlx.NotSupportedError`: If schema validation is not
                                               supported by the server.

        .. versionchanged:: 8.0.21
        """
        if not name:
            raise ProgrammingError("Collection name is invalid")

        if "reuse" in kwargs:
            warnings.warn(
                "'reuse' is deprecated since 8.0.21. "
                "Please use 'reuse_existing' instead",
                DeprecationWarning,
            )
            reuse_existing = kwargs["reuse"]

        collection = Collection(self, name)
        fields: Dict[str, Any] = {"schema": self._name, "name": name}

        if validation is not None:
            if not isinstance(validation, dict) or not validation:
                raise ProgrammingError("Invalid value for 'validation'")

            valid_options = ("level", "schema")
            for option in validation:
                if option not in valid_options:
                    raise ProgrammingError(f"Invalid option in 'validation': {option}")

            options = []

            if "level" in validation:
                level = validation["level"]
                if not isinstance(level, str):
                    raise ProgrammingError("Invalid value for 'level'")
                options.append(("level", level))

            if "schema" in validation:
                schema = validation["schema"]
                if not isinstance(schema, (str, dict)):
                    raise ProgrammingError("Invalid value for 'schema'")
                options.append(
                    (
                        "schema",
                        json.dumps(schema) if isinstance(schema, dict) else schema,
                    )
                )

            fields["options"] = ("validation", options)

        try:
            self._connection.execute_nonquery(
                "mysqlx", "create_collection", True, fields
            )
        except OperationalError as err:
            if err.errno == ER_X_CMD_NUM_ARGUMENTS:
                raise NotSupportedError(
                    "Your MySQL server does not support the requested "
                    "operation. Please update to MySQL 8.0.19 or a later "
                    "version"
                ) from err
            if err.errno == ER_TABLE_EXISTS_ERROR:
                if not reuse_existing:
                    raise ProgrammingError(
                        f"Collection '{name}' already exists"
                    ) from err
            else:
                raise ProgrammingError(err.msg, err.errno) from err

        return collection

    def modify_collection(
        self, name: str, validation: Optional[Dict[str, Union[str, Dict]]] = None
    ) -> None:
        """Modifies a collection using a JSON schema validation.

        Args:
            name (str): The name of the collection.
            validation (Optional[dict]): A dict, containing the keys `level`
                                         with the validation level and `schema`
                                         with a dict or a string representation
                                         of a JSON schema specification.

        Raises:
            :class:`mysqlx.ProgrammingError`: If the collection name or
                                              validation is invalid.
            :class:`mysqlx.NotSupportedError`: If schema validation is not
                                               supported by the server.

        .. versionadded:: 8.0.21
        """
        if not name:
            raise ProgrammingError("Collection name is invalid")

        if not isinstance(validation, dict) or not validation:
            raise ProgrammingError("Invalid value for 'validation'")

        valid_options = ("level", "schema")
        for option in validation:
            if option not in valid_options:
                raise ProgrammingError(f"Invalid option in 'validation': {option}")
        options = []

        if "level" in validation:
            level = validation["level"]
            if not isinstance(level, str):
                raise ProgrammingError("Invalid value for 'level'")
            options.append(("level", level))

        if "schema" in validation:
            schema = validation["schema"]
            if not isinstance(schema, (str, dict)):
                raise ProgrammingError("Invalid value for 'schema'")
            options.append(
                (
                    "schema",
                    json.dumps(schema) if isinstance(schema, dict) else schema,
                )
            )

        fields = {
            "schema": self._name,
            "name": name,
            "options": ("validation", options),
        }

        try:
            self._connection.execute_nonquery(
                "mysqlx", "modify_collection_options", True, fields
            )
        except OperationalError as err:
            if err.errno == ER_X_INVALID_ADMIN_COMMAND:
                raise NotSupportedError(
                    "Your MySQL server does not support the requested "
                    "operation. Please update to MySQL 8.0.19 or a later "
                    "version"
                ) from err
            raise ProgrammingError(err.msg, err.errno) from err


class Collection(DatabaseObject):
    """Represents a collection of documents on a schema.

    Args:
        schema (mysqlx.Schema): The Schema object.
        name (str): The collection name.
    """

    def exists_in_database(self) -> bool:
        """Verifies if this object exists in the database.

        Returns:
            bool: `True` if object exists in database.
        """
        sql = _COUNT_TABLES_QUERY.format(escape(self._schema.name), escape(self._name))
        return self._connection.execute_sql_scalar(sql) == 1

    def find(self, condition: Optional[str] = None) -> FindStatement:
        """Retrieves documents from a collection.

        Args:
            condition (Optional[str]): The string with the filter expression of
                                       the documents to be retrieved.
        """
        stmt = FindStatement(self, condition)
        stmt.stmt_id = self._connection.get_next_statement_id()
        return stmt

    def add(self, *values: DbDoc) -> AddStatement:
        """Adds a list of documents to a collection.

        Args:
            *values: The document list to be added into the collection.

        Returns:
            mysqlx.AddStatement: AddStatement object.
        """
        return AddStatement(self).add(*values)

    def remove(self, condition: str) -> RemoveStatement:
        """Removes documents based on the ``condition``.

        Args:
            condition (str): The string with the filter expression of the
                             documents to be removed.

        Returns:
            mysqlx.RemoveStatement: RemoveStatement object.

        .. versionchanged:: 8.0.12
           The ``condition`` parameter is now mandatory.
        """
        stmt = RemoveStatement(self, condition)
        stmt.stmt_id = self._connection.get_next_statement_id()
        return stmt

    def modify(self, condition: str) -> ModifyStatement:
        """Modifies documents based on the ``condition``.

        Args:
            condition (str): The string with the filter expression of the
                             documents to be modified.

        Returns:
            mysqlx.ModifyStatement: ModifyStatement object.

        .. versionchanged:: 8.0.12
           The ``condition`` parameter is now mandatory.
        """
        stmt = ModifyStatement(self, condition)
        stmt.stmt_id = self._connection.get_next_statement_id()
        return stmt

    def count(self) -> int:
        """Counts the documents in the collection.

        Returns:
            int: The total of documents in the collection.
        """
        sql = _COUNT_QUERY.format(
            quote_identifier(self._schema.name), quote_identifier(self._name)
        )
        try:
            res = self._connection.execute_sql_scalar(sql)
        except OperationalError as err:
            if err.errno == ER_NO_SUCH_TABLE:
                raise OperationalError(
                    f"Collection '{self._name}' does not exist in schema "
                    f"'{self._schema.name}'"
                ) from err
            raise
        return res

    def create_index(
        self, index_name: str, fields_desc: Dict[str, Any]
    ) -> CreateCollectionIndexStatement:
        """Creates a collection index.

        Args:
            index_name (str): Index name.
            fields_desc (dict): A dictionary containing the fields members that
                                constraints the index to be created. It must
                                have the form as shown in the following::

                                   {"fields": [{"field": member_path,
                                                "type": member_type,
                                                "required": member_required,
                                                "array": array,
                                                "collation": collation,
                                                "options": options,
                                                "srid": srid},
                                                # {... more members,
                                                #      repeated as many times
                                                #      as needed}
                                                ],
                                    "type": type}
        """
        return CreateCollectionIndexStatement(self, index_name, fields_desc)

    def drop_index(self, index_name: str) -> None:
        """Drops a collection index.

        Args:
            index_name (str): Index name.
        """
        self._connection.execute_nonquery(
            "mysqlx",
            "drop_collection_index",
            False,
            {
                "schema": self._schema.name,
                "collection": self._name,
                "name": index_name,
            },
        )

    def replace_one(self, doc_id: str, doc: Union[Dict, DbDoc]) -> "Result":
        """Replaces the Document matching the document ID with a new document
        provided.

        Args:
            doc_id (str): Document ID
            doc (:class:`mysqlx.DbDoc` or `dict`): New Document
        """
        if "_id" in doc and doc["_id"] != doc_id:
            raise ProgrammingError(
                "Replacement document has an _id that is different than the "
                "matched document"
            )
        return self.modify("_id = :id").set("$", doc).bind("id", doc_id).execute()

    def add_or_replace_one(self, doc_id: str, doc: Union[Dict, DbDoc]) -> "Result":
        """Upserts the Document matching the document ID with a new document
        provided.

        Args:
            doc_id (str): Document ID
            doc (:class:`mysqlx.DbDoc` or dict): New Document
        """
        if "_id" in doc and doc["_id"] != doc_id:
            raise ProgrammingError(
                "Replacement document has an _id that is different than the "
                "matched document"
            )
        if not isinstance(doc, DbDoc):
            doc = DbDoc(doc)
        return self.add(doc.copy(doc_id)).upsert(True).execute()

    def get_one(self, doc_id: str) -> DbDoc:
        """Returns a Document matching the Document ID.

        Args:
            doc_id (str): Document ID

        Returns:
            mysqlx.DbDoc: The Document matching the Document ID.
        """
        result = self.find("_id = :id").bind("id", doc_id).execute()
        doc = result.fetch_one()
        self._connection.fetch_active_result()
        return doc

    def remove_one(self, doc_id: str) -> "Result":
        """Removes a Document matching the Document ID.

        Args:
            doc_id (str): Document ID

        Returns:
            mysqlx.Result: Result object.
        """
        return self.remove("_id = :id").bind("id", doc_id).execute()


class Table(DatabaseObject):
    """Represents a database table on a schema.

    Provides access to the table through standard INSERT/SELECT/UPDATE/DELETE
    statements.

    Args:
        schema (mysqlx.Schema): The Schema object.
        name (str): The table name.
    """

    def exists_in_database(self) -> bool:
        """Verifies if this object exists in the database.

        Returns:
            bool: `True` if object exists in database.
        """
        sql = _COUNT_TABLES_QUERY.format(escape(self._schema.name), escape(self._name))
        return self._connection.execute_sql_scalar(sql) == 1

    def select(self, *fields: str) -> SelectStatement:
        """Creates a new :class:`mysqlx.SelectStatement` object.

        Args:
            *fields: The fields to be retrieved.

        Returns:
            mysqlx.SelectStatement: SelectStatement object
        """
        stmt = SelectStatement(self, *fields)
        stmt.stmt_id = self._connection.get_next_statement_id()
        return stmt

    def insert(self, *fields: Any) -> InsertStatement:
        """Creates a new :class:`mysqlx.InsertStatement` object.

        Args:
            *fields: The fields to be inserted.

        Returns:
            mysqlx.InsertStatement: InsertStatement object
        """
        stmt = InsertStatement(self, *fields)
        stmt.stmt_id = self._connection.get_next_statement_id()
        return stmt

    def update(self) -> UpdateStatement:
        """Creates a new :class:`mysqlx.UpdateStatement` object.

        Returns:
            mysqlx.UpdateStatement: UpdateStatement object
        """
        stmt = UpdateStatement(self)
        stmt.stmt_id = self._connection.get_next_statement_id()
        return stmt

    def delete(self) -> DeleteStatement:
        """Creates a new :class:`mysqlx.DeleteStatement` object.

        Returns:
            mysqlx.DeleteStatement: DeleteStatement object

        .. versionchanged:: 8.0.12
           The ``condition`` parameter was removed.
        """
        stmt = DeleteStatement(self)
        stmt.stmt_id = self._connection.get_next_statement_id()
        return stmt

    def count(self) -> int:
        """Counts the rows in the table.

        Returns:
            int: The total of rows in the table.
        """
        sql = _COUNT_QUERY.format(
            quote_identifier(self._schema.name), quote_identifier(self._name)
        )
        try:
            res = self._connection.execute_sql_scalar(sql)
        except OperationalError as err:
            if err.errno == ER_NO_SUCH_TABLE:
                raise OperationalError(
                    f"Table '{self._name}' does not exist in schema "
                    f"'{self._schema.name}'"
                ) from err
            raise
        return res

    def is_view(self) -> bool:
        """Determine if the underlying object is a view or not.

        Returns:
            bool: `True` if the underlying object is a view.
        """
        sql = _COUNT_VIEWS_QUERY.format(escape(self._schema.name), escape(self._name))
        return self._connection.execute_sql_scalar(sql) == 1


class View(Table):
    """Represents a database view on a schema.

    Provides a mechanism for creating, alter and drop views.

    Args:
        schema (mysqlx.Schema): The Schema object.
        name (str): The table name.
    """

    def exists_in_database(self) -> bool:
        """Verifies if this object exists in the database.

        Returns:
            bool: `True` if object exists in database.
        """
        sql = _COUNT_VIEWS_QUERY.format(escape(self._schema.name), escape(self._name))
        return self._connection.execute_sql_scalar(sql) == 1

virt/lib/python3.9/site-packages/mysqlx/crud 2.py
PrinceDisant/LostInNL
# Copyright (c) 2016, 2022, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

"""Implementation of the CRUD database objects."""

from __future__ import annotations

import json
import warnings

from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union

from .dbdoc import DbDoc
from .errorcode import (
    ER_NO_SUCH_TABLE,
    ER_TABLE_EXISTS_ERROR,
    ER_X_CMD_NUM_ARGUMENTS,
    ER_X_INVALID_ADMIN_COMMAND,
)
from .errors import NotSupportedError, OperationalError, ProgrammingError
from .helpers import deprecated, escape, quote_identifier
from .statement import (
    AddStatement,
    CreateCollectionIndexStatement,
    DeleteStatement,
    FindStatement,
    InsertStatement,
    ModifyStatement,
    RemoveStatement,
    SelectStatement,
    UpdateStatement,
)
from .types import ConnectionType, SchemaType, SessionType, StrOrBytes

if TYPE_CHECKING:
    from .result import Result

_COUNT_VIEWS_QUERY = (
    "SELECT COUNT(*) FROM information_schema.views "
    "WHERE table_schema = '{0}' AND table_name = '{1}'"
)
_COUNT_TABLES_QUERY = (
    "SELECT COUNT(*) FROM information_schema.tables "
    "WHERE table_schema = '{0}' AND table_name = '{1}'"
)
_COUNT_SCHEMAS_QUERY = (
    "SELECT COUNT(*) FROM information_schema.schemata WHERE schema_name = '{0}'"
)
_COUNT_QUERY = "SELECT COUNT(*) FROM {0}.{1}"
_DROP_TABLE_QUERY = "DROP TABLE IF EXISTS {0}.{1}"


class DatabaseObject:
    """Provides base functionality for database objects.

    Args:
        schema (mysqlx.Schema): The Schema object.
        name (str): The database object name.
    """

    def __init__(self, schema: SchemaType, name: StrOrBytes) -> None:
        self._schema: SchemaType = schema
        self._name: str = name.decode() if isinstance(name, bytes) else name
        self._session: SessionType = self._schema.get_session()
        self._connection: ConnectionType = self._session.get_connection()

    @property
    def session(self) -> SessionType:
        """:class:`mysqlx.Session`: The Session object."""
        return self._session

    @property
    def schema(self) -> SchemaType:
        """:class:`mysqlx.Schema`: The Schema object."""
        return self._schema

    @property
    def name(self) -> str:
        """str: The name of this database object."""
        return self._name

    def get_connection(self) -> ConnectionType:
        """Returns the underlying connection.

        Returns:
            mysqlx.connection.Connection: The connection object.
        """
        return self._connection

    def get_session(self) -> SessionType:
        """Returns the session of this database object.

        Returns:
            mysqlx.Session: The Session object.
        """
        return self._session

    def get_schema(self) -> SchemaType:
        """Returns the Schema object of this database object.

        Returns:
            mysqlx.Schema: The Schema object.
        """
        return self._schema

    def get_name(self) -> str:
        """Returns the name of this database object.

        Returns:
            str: The name of this database object.
        """
        return self._name

    def exists_in_database(self) -> Any:
        """Verifies if this object exists in the database.

        Returns:
            bool: `True` if object exists in database.

        Raises:
           NotImplementedError: This method must be implemented.
        """
        raise NotImplementedError

    @deprecated("8.0.12", "Use 'exists_in_database()' method instead")
    def am_i_real(self) -> Any:
        """Verifies if this object exists in the database.

        Returns:
            bool: `True` if object exists in database.

        Raises:
           NotImplementedError: This method must be implemented.

        .. deprecated:: 8.0.12
           Use ``exists_in_database()`` method instead.
        """
        return self.exists_in_database()

    @deprecated("8.0.12", "Use 'get_name()' method instead")
    def who_am_i(self) -> str:
        """Returns the name of this database object.

        Returns:
            str: The name of this database object.

        .. deprecated:: 8.0.12
           Use ``get_name()`` method instead.
        """
        return self.get_name()


class Schema(DatabaseObject):
    """A client-side representation of a database schema. Provides access to
    the schema contents.

    Args:
        session (mysqlx.XSession): Session object.
        name (str): The Schema name.
    """

    def __init__(self, session: SessionType, name: str) -> None:
        self._session: SessionType = session
        super().__init__(self, name)

    def exists_in_database(self) -> bool:
        """Verifies if this object exists in the database.

        Returns:
            bool: `True` if object exists in database.
        """
        sql = _COUNT_SCHEMAS_QUERY.format(escape(self._name))
        return self._connection.execute_sql_scalar(sql) == 1

    def get_collections(self) -> List[Collection]:
        """Returns a list of collections for this schema.

        Returns:
            `list`: List of Collection objects.
        """
        rows = self._connection.get_row_result("list_objects", {"schema": self._name})
        rows.fetch_all()
        collections = []
        for row in rows:
            if row["type"] != "COLLECTION":
                continue
            try:
                collection = Collection(self, row["TABLE_NAME"])
            except ValueError:
                collection = Collection(self, row["name"])
            collections.append(collection)
        return collections

    def get_collection_as_table(
        self, name: str, check_existence: bool = False
    ) -> Table:
        """Returns a a table object for the given collection

        Returns:
            mysqlx.Table: Table object.

        """
        return self.get_table(name, check_existence)

    def get_tables(self) -> List[Table]:
        """Returns a list of tables for this schema.

        Returns:
            `list`: List of Table objects.
        """
        rows = self._connection.get_row_result("list_objects", {"schema": self._name})
        rows.fetch_all()
        tables = []
        object_types = (
            "TABLE",
            "VIEW",
        )
        for row in rows:
            if row["type"] in object_types:
                try:
                    table = Table(self, row["TABLE_NAME"])
                except ValueError:
                    table = Table(self, row["name"])
                tables.append(table)
        return tables

    def get_table(self, name: str, check_existence: bool = False) -> Table:
        """Returns the table of the given name for this schema.

        Returns:
            mysqlx.Table: Table object.
        """
        table = Table(self, name)
        if check_existence:
            if not table.exists_in_database():
                raise ProgrammingError("Table does not exist")
        return table

    def get_view(self, name: str, check_existence: bool = False) -> View:
        """Returns the view of the given name for this schema.

        Returns:
            mysqlx.View: View object.
        """
        view = View(self, name)
        if check_existence:
            if not view.exists_in_database():
                raise ProgrammingError("View does not exist")
        return view

    def get_collection(self, name: str, check_existence: bool = False) -> Collection:
        """Returns the collection of the given name for this schema.

        Returns:
            mysqlx.Collection: Collection object.
        """
        collection = Collection(self, name)
        if check_existence:
            if not collection.exists_in_database():
                raise ProgrammingError("Collection does not exist")
        return collection

    def drop_collection(self, name: str) -> None:
        """Drops a collection.

        Args:
            name (str): The name of the collection to be dropped.
        """
        self._connection.execute_nonquery(
            "sql",
            _DROP_TABLE_QUERY.format(
                quote_identifier(self._name), quote_identifier(name)
            ),
            False,
        )

    def create_collection(
        self,
        name: str,
        reuse_existing: bool = False,
        validation: Optional[Dict[str, Union[str, Dict]]] = None,
        **kwargs: Any,
    ) -> Collection:
        """Creates in the current schema a new collection with the specified
        name and retrieves an object representing the new collection created.

        Args:
            name (str): The name of the collection.
            reuse_existing (bool): `True` to reuse an existing collection.
            validation (Optional[dict]): A dict, containing the keys `level`
                                         with the validation level and `schema`
                                         with a dict or a string representation
                                         of a JSON schema specification.

        Returns:
            mysqlx.Collection: Collection object.

        Raises:
            :class:`mysqlx.ProgrammingError`: If ``reuse_existing`` is False
                                              and collection exists or the
                                              collection name is invalid.
            :class:`mysqlx.NotSupportedError`: If schema validation is not
                                               supported by the server.

        .. versionchanged:: 8.0.21
        """
        if not name:
            raise ProgrammingError("Collection name is invalid")

        if "reuse" in kwargs:
            warnings.warn(
                "'reuse' is deprecated since 8.0.21. "
                "Please use 'reuse_existing' instead",
                DeprecationWarning,
            )
            reuse_existing = kwargs["reuse"]

        collection = Collection(self, name)
        fields: Dict[str, Any] = {"schema": self._name, "name": name}

        if validation is not None:
            if not isinstance(validation, dict) or not validation:
                raise ProgrammingError("Invalid value for 'validation'")

            valid_options = ("level", "schema")
            for option in validation:
                if option not in valid_options:
                    raise ProgrammingError(f"Invalid option in 'validation': {option}")

            options = []

            if "level" in validation:
                level = validation["level"]
                if not isinstance(level, str):
                    raise ProgrammingError("Invalid value for 'level'")
                options.append(("level", level))

            if "schema" in validation:
                schema = validation["schema"]
                if not isinstance(schema, (str, dict)):
                    raise ProgrammingError("Invalid value for 'schema'")
                options.append(
                    (
                        "schema",
                        json.dumps(schema) if isinstance(schema, dict) else schema,
                    )
                )

            fields["options"] = ("validation", options)

        try:
            self._connection.execute_nonquery(
                "mysqlx", "create_collection", True, fields
            )
        except OperationalError as err:
            if err.errno == ER_X_CMD_NUM_ARGUMENTS:
                raise NotSupportedError(
                    "Your MySQL server does not support the requested "
                    "operation. Please update to MySQL 8.0.19 or a later "
                    "version"
                ) from err
            if err.errno == ER_TABLE_EXISTS_ERROR:
                if not reuse_existing:
                    raise ProgrammingError(
                        f"Collection '{name}' already exists"
                    ) from err
            else:
                raise ProgrammingError(err.msg, err.errno) from err

        return collection

    def modify_collection(
        self, name: str, validation: Optional[Dict[str, Union[str, Dict]]] = None
    ) -> None:
        """Modifies a collection using a JSON schema validation.

        Args:
            name (str): The name of the collection.
            validation (Optional[dict]): A dict, containing the keys `level`
                                         with the validation level and `schema`
                                         with a dict or a string representation
                                         of a JSON schema specification.

        Raises:
            :class:`mysqlx.ProgrammingError`: If the collection name or
                                              validation is invalid.
            :class:`mysqlx.NotSupportedError`: If schema validation is not
                                               supported by the server.

        .. versionadded:: 8.0.21
        """
        if not name:
            raise ProgrammingError("Collection name is invalid")

        if not isinstance(validation, dict) or not validation:
            raise ProgrammingError("Invalid value for 'validation'")

        valid_options = ("level", "schema")
        for option in validation:
            if option not in valid_options:
                raise ProgrammingError(f"Invalid option in 'validation': {option}")
        options = []

        if "level" in validation:
            level = validation["level"]
            if not isinstance(level, str):
                raise ProgrammingError("Invalid value for 'level'")
            options.append(("level", level))

        if "schema" in validation:
            schema = validation["schema"]
            if not isinstance(schema, (str, dict)):
                raise ProgrammingError("Invalid value for 'schema'")
            options.append(
                (
                    "schema",
                    json.dumps(schema) if isinstance(schema, dict) else schema,
                )
            )

        fields = {
            "schema": self._name,
            "name": name,
            "options": ("validation", options),
        }

        try:
            self._connection.execute_nonquery(
                "mysqlx", "modify_collection_options", True, fields
            )
        except OperationalError as err:
            if err.errno == ER_X_INVALID_ADMIN_COMMAND:
                raise NotSupportedError(
                    "Your MySQL server does not support the requested "
                    "operation. Please update to MySQL 8.0.19 or a later "
                    "version"
                ) from err
            raise ProgrammingError(err.msg, err.errno) from err


class Collection(DatabaseObject):
    """Represents a collection of documents on a schema.

    Args:
        schema (mysqlx.Schema): The Schema object.
        name (str): The collection name.
    """

    def exists_in_database(self) -> bool:
        """Verifies if this object exists in the database.

        Returns:
            bool: `True` if object exists in database.
        """
        sql = _COUNT_TABLES_QUERY.format(escape(self._schema.name), escape(self._name))
        return self._connection.execute_sql_scalar(sql) == 1

    def find(self, condition: Optional[str] = None) -> FindStatement:
        """Retrieves documents from a collection.

        Args:
            condition (Optional[str]): The string with the filter expression of
                                       the documents to be retrieved.
        """
        stmt = FindStatement(self, condition)
        stmt.stmt_id = self._connection.get_next_statement_id()
        return stmt

    def add(self, *values: DbDoc) -> AddStatement:
        """Adds a list of documents to a collection.

        Args:
            *values: The document list to be added into the collection.

        Returns:
            mysqlx.AddStatement: AddStatement object.
        """
        return AddStatement(self).add(*values)

    def remove(self, condition: str) -> RemoveStatement:
        """Removes documents based on the ``condition``.

        Args:
            condition (str): The string with the filter expression of the
                             documents to be removed.

        Returns:
            mysqlx.RemoveStatement: RemoveStatement object.

        .. versionchanged:: 8.0.12
           The ``condition`` parameter is now mandatory.
        """
        stmt = RemoveStatement(self, condition)
        stmt.stmt_id = self._connection.get_next_statement_id()
        return stmt

    def modify(self, condition: str) -> ModifyStatement:
        """Modifies documents based on the ``condition``.

        Args:
            condition (str): The string with the filter expression of the
                             documents to be modified.

        Returns:
            mysqlx.ModifyStatement: ModifyStatement object.

        .. versionchanged:: 8.0.12
           The ``condition`` parameter is now mandatory.
        """
        stmt = ModifyStatement(self, condition)
        stmt.stmt_id = self._connection.get_next_statement_id()
        return stmt

    def count(self) -> int:
        """Counts the documents in the collection.

        Returns:
            int: The total of documents in the collection.
        """
        sql = _COUNT_QUERY.format(
            quote_identifier(self._schema.name), quote_identifier(self._name)
        )
        try:
            res = self._connection.execute_sql_scalar(sql)
        except OperationalError as err:
            if err.errno == ER_NO_SUCH_TABLE:
                raise OperationalError(
                    f"Collection '{self._name}' does not exist in schema "
                    f"'{self._schema.name}'"
                ) from err
            raise
        return res

    def create_index(
        self, index_name: str, fields_desc: Dict[str, Any]
    ) -> CreateCollectionIndexStatement:
        """Creates a collection index.

        Args:
            index_name (str): Index name.
            fields_desc (dict): A dictionary containing the fields members that
                                constraints the index to be created. It must
                                have the form as shown in the following::

                                   {"fields": [{"field": member_path,
                                                "type": member_type,
                                                "required": member_required,
                                                "array": array,
                                                "collation": collation,
                                                "options": options,
                                                "srid": srid},
                                                # {... more members,
                                                #      repeated as many times
                                                #      as needed}
                                                ],
                                    "type": type}
        """
        return CreateCollectionIndexStatement(self, index_name, fields_desc)

    def drop_index(self, index_name: str) -> None:
        """Drops a collection index.

        Args:
            index_name (str): Index name.
        """
        self._connection.execute_nonquery(
            "mysqlx",
            "drop_collection_index",
            False,
            {
                "schema": self._schema.name,
                "collection": self._name,
                "name": index_name,
            },
        )

    def replace_one(self, doc_id: str, doc: Union[Dict, DbDoc]) -> "Result":
        """Replaces the Document matching the document ID with a new document
        provided.

        Args:
            doc_id (str): Document ID
            doc (:class:`mysqlx.DbDoc` or `dict`): New Document
        """
        if "_id" in doc and doc["_id"] != doc_id:
            raise ProgrammingError(
                "Replacement document has an _id that is different than the "
                "matched document"
            )
        return self.modify("_id = :id").set("$", doc).bind("id", doc_id).execute()

    def add_or_replace_one(self, doc_id: str, doc: Union[Dict, DbDoc]) -> "Result":
        """Upserts the Document matching the document ID with a new document
        provided.

        Args:
            doc_id (str): Document ID
            doc (:class:`mysqlx.DbDoc` or dict): New Document
        """
        if "_id" in doc and doc["_id"] != doc_id:
            raise ProgrammingError(
                "Replacement document has an _id that is different than the "
                "matched document"
            )
        if not isinstance(doc, DbDoc):
            doc = DbDoc(doc)
        return self.add(doc.copy(doc_id)).upsert(True).execute()

    def get_one(self, doc_id: str) -> DbDoc:
        """Returns a Document matching the Document ID.

        Args:
            doc_id (str): Document ID

        Returns:
            mysqlx.DbDoc: The Document matching the Document ID.
        """
        result = self.find("_id = :id").bind("id", doc_id).execute()
        doc = result.fetch_one()
        self._connection.fetch_active_result()
        return doc

    def remove_one(self, doc_id: str) -> "Result":
        """Removes a Document matching the Document ID.

        Args:
            doc_id (str): Document ID

        Returns:
            mysqlx.Result: Result object.
        """
        return self.remove("_id = :id").bind("id", doc_id).execute()


class Table(DatabaseObject):
    """Represents a database table on a schema.

    Provides access to the table through standard INSERT/SELECT/UPDATE/DELETE
    statements.

    Args:
        schema (mysqlx.Schema): The Schema object.
        name (str): The table name.
    """

    def exists_in_database(self) -> bool:
        """Verifies if this object exists in the database.

        Returns:
            bool: `True` if object exists in database.
        """
        sql = _COUNT_TABLES_QUERY.format(escape(self._schema.name), escape(self._name))
        return self._connection.execute_sql_scalar(sql) == 1

    def select(self, *fields: str) -> SelectStatement:
        """Creates a new :class:`mysqlx.SelectStatement` object.

        Args:
            *fields: The fields to be retrieved.

        Returns:
            mysqlx.SelectStatement: SelectStatement object
        """
        stmt = SelectStatement(self, *fields)
        stmt.stmt_id = self._connection.get_next_statement_id()
        return stmt

    def insert(self, *fields: Any) -> InsertStatement:
        """Creates a new :class:`mysqlx.InsertStatement` object.

        Args:
            *fields: The fields to be inserted.

        Returns:
            mysqlx.InsertStatement: InsertStatement object
        """
        stmt = InsertStatement(self, *fields)
        stmt.stmt_id = self._connection.get_next_statement_id()
        return stmt

    def update(self) -> UpdateStatement:
        """Creates a new :class:`mysqlx.UpdateStatement` object.

        Returns:
            mysqlx.UpdateStatement: UpdateStatement object
        """
        stmt = UpdateStatement(self)
        stmt.stmt_id = self._connection.get_next_statement_id()
        return stmt

    def delete(self) -> DeleteStatement:
        """Creates a new :class:`mysqlx.DeleteStatement` object.

        Returns:
            mysqlx.DeleteStatement: DeleteStatement object

        .. versionchanged:: 8.0.12
           The ``condition`` parameter was removed.
        """
        stmt = DeleteStatement(self)
        stmt.stmt_id = self._connection.get_next_statement_id()
        return stmt

    def count(self) -> int:
        """Counts the rows in the table.

        Returns:
            int: The total of rows in the table.
        """
        sql = _COUNT_QUERY.format(
            quote_identifier(self._schema.name), quote_identifier(self._name)
        )
        try:
            res = self._connection.execute_sql_scalar(sql)
        except OperationalError as err:
            if err.errno == ER_NO_SUCH_TABLE:
                raise OperationalError(
                    f"Table '{self._name}' does not exist in schema "
                    f"'{self._schema.name}'"
                ) from err
            raise
        return res

    def is_view(self) -> bool:
        """Determine if the underlying object is a view or not.

        Returns:
            bool: `True` if the underlying object is a view.
        """
        sql = _COUNT_VIEWS_QUERY.format(escape(self._schema.name), escape(self._name))
        return self._connection.execute_sql_scalar(sql) == 1


class View(Table):
    """Represents a database view on a schema.

    Provides a mechanism for creating, alter and drop views.

    Args:
        schema (mysqlx.Schema): The Schema object.
        name (str): The table name.
    """

    def exists_in_database(self) -> bool:
        """Verifies if this object exists in the database.

        Returns:
            bool: `True` if object exists in database.
        """
        sql = _COUNT_VIEWS_QUERY.format(escape(self._schema.name), escape(self._name))
        return self._connection.execute_sql_scalar(sql) == 1

virt/lib/python3.9/site-packages/mysqlx/crud 3.py
PrinceDisant/LostInNL
# Copyright (c) 2016, 2022, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

"""Implementation of the CRUD database objects."""

from __future__ import annotations

import json
import warnings

from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union

from .dbdoc import DbDoc
from .errorcode import (
    ER_NO_SUCH_TABLE,
    ER_TABLE_EXISTS_ERROR,
    ER_X_CMD_NUM_ARGUMENTS,
    ER_X_INVALID_ADMIN_COMMAND,
)
from .errors import NotSupportedError, OperationalError, ProgrammingError
from .helpers import deprecated, escape, quote_identifier
from .statement import (
    AddStatement,
    CreateCollectionIndexStatement,
    DeleteStatement,
    FindStatement,
    InsertStatement,
    ModifyStatement,
    RemoveStatement,
    SelectStatement,
    UpdateStatement,
)
from .types import ConnectionType, SchemaType, SessionType, StrOrBytes

if TYPE_CHECKING:
    from .result import Result

_COUNT_VIEWS_QUERY = (
    "SELECT COUNT(*) FROM information_schema.views "
    "WHERE table_schema = '{0}' AND table_name = '{1}'"
)
_COUNT_TABLES_QUERY = (
    "SELECT COUNT(*) FROM information_schema.tables "
    "WHERE table_schema = '{0}' AND table_name = '{1}'"
)
_COUNT_SCHEMAS_QUERY = (
    "SELECT COUNT(*) FROM information_schema.schemata WHERE schema_name = '{0}'"
)
_COUNT_QUERY = "SELECT COUNT(*) FROM {0}.{1}"
_DROP_TABLE_QUERY = "DROP TABLE IF EXISTS {0}.{1}"


class DatabaseObject:
    """Provides base functionality for database objects.

    Args:
        schema (mysqlx.Schema): The Schema object.
        name (str): The database object name.
    """

    def __init__(self, schema: SchemaType, name: StrOrBytes) -> None:
        self._schema: SchemaType = schema
        self._name: str = name.decode() if isinstance(name, bytes) else name
        self._session: SessionType = self._schema.get_session()
        self._connection: ConnectionType = self._session.get_connection()

    @property
    def session(self) -> SessionType:
        """:class:`mysqlx.Session`: The Session object."""
        return self._session

    @property
    def schema(self) -> SchemaType:
        """:class:`mysqlx.Schema`: The Schema object."""
        return self._schema

    @property
    def name(self) -> str:
        """str: The name of this database object."""
        return self._name

    def get_connection(self) -> ConnectionType:
        """Returns the underlying connection.

        Returns:
            mysqlx.connection.Connection: The connection object.
        """
        return self._connection

    def get_session(self) -> SessionType:
        """Returns the session of this database object.

        Returns:
            mysqlx.Session: The Session object.
        """
        return self._session

    def get_schema(self) -> SchemaType:
        """Returns the Schema object of this database object.

        Returns:
            mysqlx.Schema: The Schema object.
        """
        return self._schema

    def get_name(self) -> str:
        """Returns the name of this database object.

        Returns:
            str: The name of this database object.
        """
        return self._name

    def exists_in_database(self) -> Any:
        """Verifies if this object exists in the database.

        Returns:
            bool: `True` if object exists in database.

        Raises:
           NotImplementedError: This method must be implemented.
        """
        raise NotImplementedError

    @deprecated("8.0.12", "Use 'exists_in_database()' method instead")
    def am_i_real(self) -> Any:
        """Verifies if this object exists in the database.

        Returns:
            bool: `True` if object exists in database.

        Raises:
           NotImplementedError: This method must be implemented.

        .. deprecated:: 8.0.12
           Use ``exists_in_database()`` method instead.
        """
        return self.exists_in_database()

    @deprecated("8.0.12", "Use 'get_name()' method instead")
    def who_am_i(self) -> str:
        """Returns the name of this database object.

        Returns:
            str: The name of this database object.

        .. deprecated:: 8.0.12
           Use ``get_name()`` method instead.
        """
        return self.get_name()


class Schema(DatabaseObject):
    """A client-side representation of a database schema. Provides access to
    the schema contents.

    Args:
        session (mysqlx.XSession): Session object.
        name (str): The Schema name.
    """

    def __init__(self, session: SessionType, name: str) -> None:
        self._session: SessionType = session
        super().__init__(self, name)

    def exists_in_database(self) -> bool:
        """Verifies if this object exists in the database.

        Returns:
            bool: `True` if object exists in database.
        """
        sql = _COUNT_SCHEMAS_QUERY.format(escape(self._name))
        return self._connection.execute_sql_scalar(sql) == 1

    def get_collections(self) -> List[Collection]:
        """Returns a list of collections for this schema.

        Returns:
            `list`: List of Collection objects.
        """
        rows = self._connection.get_row_result("list_objects", {"schema": self._name})
        rows.fetch_all()
        collections = []
        for row in rows:
            if row["type"] != "COLLECTION":
                continue
            try:
                collection = Collection(self, row["TABLE_NAME"])
            except ValueError:
                collection = Collection(self, row["name"])
            collections.append(collection)
        return collections

    def get_collection_as_table(
        self, name: str, check_existence: bool = False
    ) -> Table:
        """Returns a a table object for the given collection

        Returns:
            mysqlx.Table: Table object.

        """
        return self.get_table(name, check_existence)

    def get_tables(self) -> List[Table]:
        """Returns a list of tables for this schema.

        Returns:
            `list`: List of Table objects.
        """
        rows = self._connection.get_row_result("list_objects", {"schema": self._name})
        rows.fetch_all()
        tables = []
        object_types = (
            "TABLE",
            "VIEW",
        )
        for row in rows:
            if row["type"] in object_types:
                try:
                    table = Table(self, row["TABLE_NAME"])
                except ValueError:
                    table = Table(self, row["name"])
                tables.append(table)
        return tables

    def get_table(self, name: str, check_existence: bool = False) -> Table:
        """Returns the table of the given name for this schema.

        Returns:
            mysqlx.Table: Table object.
        """
        table = Table(self, name)
        if check_existence:
            if not table.exists_in_database():
                raise ProgrammingError("Table does not exist")
        return table

    def get_view(self, name: str, check_existence: bool = False) -> View:
        """Returns the view of the given name for this schema.

        Returns:
            mysqlx.View: View object.
        """
        view = View(self, name)
        if check_existence:
            if not view.exists_in_database():
                raise ProgrammingError("View does not exist")
        return view

    def get_collection(self, name: str, check_existence: bool = False) -> Collection:
        """Returns the collection of the given name for this schema.

        Returns:
            mysqlx.Collection: Collection object.
        """
        collection = Collection(self, name)
        if check_existence:
            if not collection.exists_in_database():
                raise ProgrammingError("Collection does not exist")
        return collection

    def drop_collection(self, name: str) -> None:
        """Drops a collection.

        Args:
            name (str): The name of the collection to be dropped.
        """
        self._connection.execute_nonquery(
            "sql",
            _DROP_TABLE_QUERY.format(
                quote_identifier(self._name), quote_identifier(name)
            ),
            False,
        )

    def create_collection(
        self,
        name: str,
        reuse_existing: bool = False,
        validation: Optional[Dict[str, Union[str, Dict]]] = None,
        **kwargs: Any,
    ) -> Collection:
        """Creates in the current schema a new collection with the specified
        name and retrieves an object representing the new collection created.

        Args:
            name (str): The name of the collection.
            reuse_existing (bool): `True` to reuse an existing collection.
            validation (Optional[dict]): A dict, containing the keys `level`
                                         with the validation level and `schema`
                                         with a dict or a string representation
                                         of a JSON schema specification.

        Returns:
            mysqlx.Collection: Collection object.

        Raises:
            :class:`mysqlx.ProgrammingError`: If ``reuse_existing`` is False
                                              and collection exists or the
                                              collection name is invalid.
            :class:`mysqlx.NotSupportedError`: If schema validation is not
                                               supported by the server.

        .. versionchanged:: 8.0.21
        """
        if not name:
            raise ProgrammingError("Collection name is invalid")

        if "reuse" in kwargs:
            warnings.warn(
                "'reuse' is deprecated since 8.0.21. "
                "Please use 'reuse_existing' instead",
                DeprecationWarning,
            )
            reuse_existing = kwargs["reuse"]

        collection = Collection(self, name)
        fields: Dict[str, Any] = {"schema": self._name, "name": name}

        if validation is not None:
            if not isinstance(validation, dict) or not validation:
                raise ProgrammingError("Invalid value for 'validation'")

            valid_options = ("level", "schema")
            for option in validation:
                if option not in valid_options:
                    raise ProgrammingError(f"Invalid option in 'validation': {option}")

            options = []

            if "level" in validation:
                level = validation["level"]
                if not isinstance(level, str):
                    raise ProgrammingError("Invalid value for 'level'")
                options.append(("level", level))

            if "schema" in validation:
                schema = validation["schema"]
                if not isinstance(schema, (str, dict)):
                    raise ProgrammingError("Invalid value for 'schema'")
                options.append(
                    (
                        "schema",
                        json.dumps(schema) if isinstance(schema, dict) else schema,
                    )
                )

            fields["options"] = ("validation", options)

        try:
            self._connection.execute_nonquery(
                "mysqlx", "create_collection", True, fields
            )
        except OperationalError as err:
            if err.errno == ER_X_CMD_NUM_ARGUMENTS:
                raise NotSupportedError(
                    "Your MySQL server does not support the requested "
                    "operation. Please update to MySQL 8.0.19 or a later "
                    "version"
                ) from err
            if err.errno == ER_TABLE_EXISTS_ERROR:
                if not reuse_existing:
                    raise ProgrammingError(
                        f"Collection '{name}' already exists"
                    ) from err
            else:
                raise ProgrammingError(err.msg, err.errno) from err

        return collection

    def modify_collection(
        self, name: str, validation: Optional[Dict[str, Union[str, Dict]]] = None
    ) -> None:
        """Modifies a collection using a JSON schema validation.

        Args:
            name (str): The name of the collection.
            validation (Optional[dict]): A dict, containing the keys `level`
                                         with the validation level and `schema`
                                         with a dict or a string representation
                                         of a JSON schema specification.

        Raises:
            :class:`mysqlx.ProgrammingError`: If the collection name or
                                              validation is invalid.
            :class:`mysqlx.NotSupportedError`: If schema validation is not
                                               supported by the server.

        .. versionadded:: 8.0.21
        """
        if not name:
            raise ProgrammingError("Collection name is invalid")

        if not isinstance(validation, dict) or not validation:
            raise ProgrammingError("Invalid value for 'validation'")

        valid_options = ("level", "schema")
        for option in validation:
            if option not in valid_options:
                raise ProgrammingError(f"Invalid option in 'validation': {option}")
        options = []

        if "level" in validation:
            level = validation["level"]
            if not isinstance(level, str):
                raise ProgrammingError("Invalid value for 'level'")
            options.append(("level", level))

        if "schema" in validation:
            schema = validation["schema"]
            if not isinstance(schema, (str, dict)):
                raise ProgrammingError("Invalid value for 'schema'")
            options.append(
                (
                    "schema",
                    json.dumps(schema) if isinstance(schema, dict) else schema,
                )
            )

        fields = {
            "schema": self._name,
            "name": name,
            "options": ("validation", options),
        }

        try:
            self._connection.execute_nonquery(
                "mysqlx", "modify_collection_options", True, fields
            )
        except OperationalError as err:
            if err.errno == ER_X_INVALID_ADMIN_COMMAND:
                raise NotSupportedError(
                    "Your MySQL server does not support the requested "
                    "operation. Please update to MySQL 8.0.19 or a later "
                    "version"
                ) from err
            raise ProgrammingError(err.msg, err.errno) from err


class Collection(DatabaseObject):
    """Represents a collection of documents on a schema.

    Args:
        schema (mysqlx.Schema): The Schema object.
        name (str): The collection name.
    """

    def exists_in_database(self) -> bool:
        """Verifies if this object exists in the database.

        Returns:
            bool: `True` if object exists in database.
        """
        sql = _COUNT_TABLES_QUERY.format(escape(self._schema.name), escape(self._name))
        return self._connection.execute_sql_scalar(sql) == 1

    def find(self, condition: Optional[str] = None) -> FindStatement:
        """Retrieves documents from a collection.

        Args:
            condition (Optional[str]): The string with the filter expression of
                                       the documents to be retrieved.
        """
        stmt = FindStatement(self, condition)
        stmt.stmt_id = self._connection.get_next_statement_id()
        return stmt

    def add(self, *values: DbDoc) -> AddStatement:
        """Adds a list of documents to a collection.

        Args:
            *values: The document list to be added into the collection.

        Returns:
            mysqlx.AddStatement: AddStatement object.
        """
        return AddStatement(self).add(*values)

    def remove(self, condition: str) -> RemoveStatement:
        """Removes documents based on the ``condition``.

        Args:
            condition (str): The string with the filter expression of the
                             documents to be removed.

        Returns:
            mysqlx.RemoveStatement: RemoveStatement object.

        .. versionchanged:: 8.0.12
           The ``condition`` parameter is now mandatory.
        """
        stmt = RemoveStatement(self, condition)
        stmt.stmt_id = self._connection.get_next_statement_id()
        return stmt

    def modify(self, condition: str) -> ModifyStatement:
        """Modifies documents based on the ``condition``.

        Args:
            condition (str): The string with the filter expression of the
                             documents to be modified.

        Returns:
            mysqlx.ModifyStatement: ModifyStatement object.

        .. versionchanged:: 8.0.12
           The ``condition`` parameter is now mandatory.
        """
        stmt = ModifyStatement(self, condition)
        stmt.stmt_id = self._connection.get_next_statement_id()
        return stmt

    def count(self) -> int:
        """Counts the documents in the collection.

        Returns:
            int: The total of documents in the collection.
        """
        sql = _COUNT_QUERY.format(
            quote_identifier(self._schema.name), quote_identifier(self._name)
        )
        try:
            res = self._connection.execute_sql_scalar(sql)
        except OperationalError as err:
            if err.errno == ER_NO_SUCH_TABLE:
                raise OperationalError(
                    f"Collection '{self._name}' does not exist in schema "
                    f"'{self._schema.name}'"
                ) from err
            raise
        return res

    def create_index(
        self, index_name: str, fields_desc: Dict[str, Any]
    ) -> CreateCollectionIndexStatement:
        """Creates a collection index.

        Args:
            index_name (str): Index name.
            fields_desc (dict): A dictionary containing the fields members that
                                constraints the index to be created. It must
                                have the form as shown in the following::

                                   {"fields": [{"field": member_path,
                                                "type": member_type,
                                                "required": member_required,
                                                "array": array,
                                                "collation": collation,
                                                "options": options,
                                                "srid": srid},
                                                # {... more members,
                                                #      repeated as many times
                                                #      as needed}
                                                ],
                                    "type": type}
        """
        return CreateCollectionIndexStatement(self, index_name, fields_desc)

    def drop_index(self, index_name: str) -> None:
        """Drops a collection index.

        Args:
            index_name (str): Index name.
        """
        self._connection.execute_nonquery(
            "mysqlx",
            "drop_collection_index",
            False,
            {
                "schema": self._schema.name,
                "collection": self._name,
                "name": index_name,
            },
        )

    def replace_one(self, doc_id: str, doc: Union[Dict, DbDoc]) -> "Result":
        """Replaces the Document matching the document ID with a new document
        provided.

        Args:
            doc_id (str): Document ID
            doc (:class:`mysqlx.DbDoc` or `dict`): New Document
        """
        if "_id" in doc and doc["_id"] != doc_id:
            raise ProgrammingError(
                "Replacement document has an _id that is different than the "
                "matched document"
            )
        return self.modify("_id = :id").set("$", doc).bind("id", doc_id).execute()

    def add_or_replace_one(self, doc_id: str, doc: Union[Dict, DbDoc]) -> "Result":
        """Upserts the Document matching the document ID with a new document
        provided.

        Args:
            doc_id (str): Document ID
            doc (:class:`mysqlx.DbDoc` or dict): New Document
        """
        if "_id" in doc and doc["_id"] != doc_id:
            raise ProgrammingError(
                "Replacement document has an _id that is different than the "
                "matched document"
            )
        if not isinstance(doc, DbDoc):
            doc = DbDoc(doc)
        return self.add(doc.copy(doc_id)).upsert(True).execute()

    def get_one(self, doc_id: str) -> DbDoc:
        """Returns a Document matching the Document ID.

        Args:
            doc_id (str): Document ID

        Returns:
            mysqlx.DbDoc: The Document matching the Document ID.
        """
        result = self.find("_id = :id").bind("id", doc_id).execute()
        doc = result.fetch_one()
        self._connection.fetch_active_result()
        return doc

    def remove_one(self, doc_id: str) -> "Result":
        """Removes a Document matching the Document ID.

        Args:
            doc_id (str): Document ID

        Returns:
            mysqlx.Result: Result object.
        """
        return self.remove("_id = :id").bind("id", doc_id).execute()


class Table(DatabaseObject):
    """Represents a database table on a schema.

    Provides access to the table through standard INSERT/SELECT/UPDATE/DELETE
    statements.

    Args:
        schema (mysqlx.Schema): The Schema object.
        name (str): The table name.
    """

    def exists_in_database(self) -> bool:
        """Verifies if this object exists in the database.

        Returns:
            bool: `True` if object exists in database.
        """
        sql = _COUNT_TABLES_QUERY.format(escape(self._schema.name), escape(self._name))
        return self._connection.execute_sql_scalar(sql) == 1

    def select(self, *fields: str) -> SelectStatement:
        """Creates a new :class:`mysqlx.SelectStatement` object.

        Args:
            *fields: The fields to be retrieved.

        Returns:
            mysqlx.SelectStatement: SelectStatement object
        """
        stmt = SelectStatement(self, *fields)
        stmt.stmt_id = self._connection.get_next_statement_id()
        return stmt

    def insert(self, *fields: Any) -> InsertStatement:
        """Creates a new :class:`mysqlx.InsertStatement` object.

        Args:
            *fields: The fields to be inserted.

        Returns:
            mysqlx.InsertStatement: InsertStatement object
        """
        stmt = InsertStatement(self, *fields)
        stmt.stmt_id = self._connection.get_next_statement_id()
        return stmt

    def update(self) -> UpdateStatement:
        """Creates a new :class:`mysqlx.UpdateStatement` object.

        Returns:
            mysqlx.UpdateStatement: UpdateStatement object
        """
        stmt = UpdateStatement(self)
        stmt.stmt_id = self._connection.get_next_statement_id()
        return stmt

    def delete(self) -> DeleteStatement:
        """Creates a new :class:`mysqlx.DeleteStatement` object.

        Returns:
            mysqlx.DeleteStatement: DeleteStatement object

        .. versionchanged:: 8.0.12
           The ``condition`` parameter was removed.
        """
        stmt = DeleteStatement(self)
        stmt.stmt_id = self._connection.get_next_statement_id()
        return stmt

    def count(self) -> int:
        """Counts the rows in the table.

        Returns:
            int: The total of rows in the table.
        """
        sql = _COUNT_QUERY.format(
            quote_identifier(self._schema.name), quote_identifier(self._name)
        )
        try:
            res = self._connection.execute_sql_scalar(sql)
        except OperationalError as err:
            if err.errno == ER_NO_SUCH_TABLE:
                raise OperationalError(
                    f"Table '{self._name}' does not exist in schema "
                    f"'{self._schema.name}'"
                ) from err
            raise
        return res

    def is_view(self) -> bool:
        """Determine if the underlying object is a view or not.

        Returns:
            bool: `True` if the underlying object is a view.
        """
        sql = _COUNT_VIEWS_QUERY.format(escape(self._schema.name), escape(self._name))
        return self._connection.execute_sql_scalar(sql) == 1


class View(Table):
    """Represents a database view on a schema.

    Provides a mechanism for creating, alter and drop views.

    Args:
        schema (mysqlx.Schema): The Schema object.
        name (str): The table name.
    """

    def exists_in_database(self) -> bool:
        """Verifies if this object exists in the database.

        Returns:
            bool: `True` if object exists in database.
        """
        sql = _COUNT_VIEWS_QUERY.format(escape(self._schema.name), escape(self._name))
        return self._connection.execute_sql_scalar(sql) == 1

virt/lib/python3.9/site-packages/mysqlx/crud.py
PrinceDisant/LostInNL
# Copyright (c) 2016, 2022, Oracle and/or its affiliates. All rights reserved.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

"""Implementation of the DbDoc."""

from __future__ import annotations

import json

from typing import Any, Dict, KeysView, Optional, Union

from .errors import ProgrammingError


class ExprJSONEncoder(json.JSONEncoder):
    """A :class:`json.JSONEncoder` subclass, which enables encoding of
    :class:`mysqlx.ExprParser` objects."""

    def default(self, o: object) -> str:
        if hasattr(o, "expr"):
            return f"{o}"
        # Let the base class default method raise the TypeError
        return json.JSONEncoder.default(self, o)


class DbDoc:
    """Represents a generic document in JSON format.

    Args:
        value (object): The value can be a JSON string or a dict.

    Raises:
        ValueError: If ``value`` type is not a basestring or dict.
    """

    def __init__(self, value: Union[str, Dict[str, Any]]) -> None:
        if isinstance(value, dict):
            self.__dict__ = value
        elif isinstance(value, str):
            self.__dict__ = json.loads(value)
        else:
            raise ValueError(f"Unable to handle type: {type(value)}")

    def __str__(self) -> str:
        return self.as_str()

    def __repr__(self) -> str:
        return repr(self.__dict__)

    def __setitem__(self, index: str, value: Any) -> None:
        if index == "_id":
            raise ProgrammingError("Cannot modify _id")
        self.__dict__[index] = value

    def __getitem__(self, index: str) -> Any:
        return self.__dict__[index]

    def __contains__(self, item: str) -> bool:
        return item in self.__dict__

    def copy(self, doc_id: Optional[str] = None) -> DbDoc:
        """Returns a new copy of a :class:`mysqlx.DbDoc` object containing the
        `doc_id` provided. If `doc_id` is not provided, it will be removed from
        new :class:`mysqlx.DbDoc` object.

        Args:
            doc_id (Optional[str]): Document ID

        Returns:
            mysqlx.DbDoc: A new instance of DbDoc containing the _id provided
        """
        new_dict = self.__dict__.copy()
        if doc_id:
            new_dict["_id"] = doc_id
        elif "_id" in new_dict:
            del new_dict["_id"]
        return DbDoc(new_dict)

    def keys(self) -> KeysView[str]:
        """Returns the keys.

        Returns:
            `list`: The keys.
        """
        return self.__dict__.keys()

    def as_str(self) -> str:
        """Serialize :class:`mysqlx.DbDoc` to a JSON formatted ``str``.

        Returns:
            str: A JSON formatted ``str`` representation of the document.

        .. versionadded:: 8.0.16
        """
        return json.dumps(self.__dict__, cls=ExprJSONEncoder)

virt/lib/python3.9/site-packages/mysqlx/dbdoc.py
PrinceDisant/LostInNL
# -*- coding: utf-8 -*-

# Copyright (c) 2013, 2022, Oracle and/or its affiliates. All rights reserved.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

"""This module contains the MySQL Server and Client error codes."""

# This file was auto-generated.
_GENERATED_ON = "2021-08-11"
_MYSQL_VERSION = (8, 0, 27)

# Start MySQL Errors
OBSOLETE_ER_HASHCHK = 1000
OBSOLETE_ER_NISAMCHK = 1001
ER_NO = 1002
ER_YES = 1003
ER_CANT_CREATE_FILE = 1004
ER_CANT_CREATE_TABLE = 1005
ER_CANT_CREATE_DB = 1006
ER_DB_CREATE_EXISTS = 1007
ER_DB_DROP_EXISTS = 1008
OBSOLETE_ER_DB_DROP_DELETE = 1009
ER_DB_DROP_RMDIR = 1010
OBSOLETE_ER_CANT_DELETE_FILE = 1011
ER_CANT_FIND_SYSTEM_REC = 1012
ER_CANT_GET_STAT = 1013
OBSOLETE_ER_CANT_GET_WD = 1014
ER_CANT_LOCK = 1015
ER_CANT_OPEN_FILE = 1016
ER_FILE_NOT_FOUND = 1017
ER_CANT_READ_DIR = 1018
OBSOLETE_ER_CANT_SET_WD = 1019
ER_CHECKREAD = 1020
OBSOLETE_ER_DISK_FULL = 1021
ER_DUP_KEY = 1022
OBSOLETE_ER_ERROR_ON_CLOSE = 1023
ER_ERROR_ON_READ = 1024
ER_ERROR_ON_RENAME = 1025
ER_ERROR_ON_WRITE = 1026
ER_FILE_USED = 1027
OBSOLETE_ER_FILSORT_ABORT = 1028
OBSOLETE_ER_FORM_NOT_FOUND = 1029
ER_GET_ERRNO = 1030
ER_ILLEGAL_HA = 1031
ER_KEY_NOT_FOUND = 1032
ER_NOT_FORM_FILE = 1033
ER_NOT_KEYFILE = 1034
ER_OLD_KEYFILE = 1035
ER_OPEN_AS_READONLY = 1036
ER_OUTOFMEMORY = 1037
ER_OUT_OF_SORTMEMORY = 1038
OBSOLETE_ER_UNEXPECTED_EOF = 1039
ER_CON_COUNT_ERROR = 1040
ER_OUT_OF_RESOURCES = 1041
ER_BAD_HOST_ERROR = 1042
ER_HANDSHAKE_ERROR = 1043
ER_DBACCESS_DENIED_ERROR = 1044
ER_ACCESS_DENIED_ERROR = 1045
ER_NO_DB_ERROR = 1046
ER_UNKNOWN_COM_ERROR = 1047
ER_BAD_NULL_ERROR = 1048
ER_BAD_DB_ERROR = 1049
ER_TABLE_EXISTS_ERROR = 1050
ER_BAD_TABLE_ERROR = 1051
ER_NON_UNIQ_ERROR = 1052
ER_SERVER_SHUTDOWN = 1053
ER_BAD_FIELD_ERROR = 1054
ER_WRONG_FIELD_WITH_GROUP = 1055
ER_WRONG_GROUP_FIELD = 1056
ER_WRONG_SUM_SELECT = 1057
ER_WRONG_VALUE_COUNT = 1058
ER_TOO_LONG_IDENT = 1059
ER_DUP_FIELDNAME = 1060
ER_DUP_KEYNAME = 1061
ER_DUP_ENTRY = 1062
ER_WRONG_FIELD_SPEC = 1063
ER_PARSE_ERROR = 1064
ER_EMPTY_QUERY = 1065
ER_NONUNIQ_TABLE = 1066
ER_INVALID_DEFAULT = 1067
ER_MULTIPLE_PRI_KEY = 1068
ER_TOO_MANY_KEYS = 1069
ER_TOO_MANY_KEY_PARTS = 1070
ER_TOO_LONG_KEY = 1071
ER_KEY_COLUMN_DOES_NOT_EXITS = 1072
ER_BLOB_USED_AS_KEY = 1073
ER_TOO_BIG_FIELDLENGTH = 1074
ER_WRONG_AUTO_KEY = 1075
ER_READY = 1076
OBSOLETE_ER_NORMAL_SHUTDOWN = 1077
OBSOLETE_ER_GOT_SIGNAL = 1078
ER_SHUTDOWN_COMPLETE = 1079
ER_FORCING_CLOSE = 1080
ER_IPSOCK_ERROR = 1081
ER_NO_SUCH_INDEX = 1082
ER_WRONG_FIELD_TERMINATORS = 1083
ER_BLOBS_AND_NO_TERMINATED = 1084
ER_TEXTFILE_NOT_READABLE = 1085
ER_FILE_EXISTS_ERROR = 1086
ER_LOAD_INFO = 1087
ER_ALTER_INFO = 1088
ER_WRONG_SUB_KEY = 1089
ER_CANT_REMOVE_ALL_FIELDS = 1090
ER_CANT_DROP_FIELD_OR_KEY = 1091
ER_INSERT_INFO = 1092
ER_UPDATE_TABLE_USED = 1093
ER_NO_SUCH_THREAD = 1094
ER_KILL_DENIED_ERROR = 1095
ER_NO_TABLES_USED = 1096
ER_TOO_BIG_SET = 1097
ER_NO_UNIQUE_LOGFILE = 1098
ER_TABLE_NOT_LOCKED_FOR_WRITE = 1099
ER_TABLE_NOT_LOCKED = 1100
ER_BLOB_CANT_HAVE_DEFAULT = 1101
ER_WRONG_DB_NAME = 1102
ER_WRONG_TABLE_NAME = 1103
ER_TOO_BIG_SELECT = 1104
ER_UNKNOWN_ERROR = 1105
ER_UNKNOWN_PROCEDURE = 1106
ER_WRONG_PARAMCOUNT_TO_PROCEDURE = 1107
ER_WRONG_PARAMETERS_TO_PROCEDURE = 1108
ER_UNKNOWN_TABLE = 1109
ER_FIELD_SPECIFIED_TWICE = 1110
ER_INVALID_GROUP_FUNC_USE = 1111
ER_UNSUPPORTED_EXTENSION = 1112
ER_TABLE_MUST_HAVE_COLUMNS = 1113
ER_RECORD_FILE_FULL = 1114
ER_UNKNOWN_CHARACTER_SET = 1115
ER_TOO_MANY_TABLES = 1116
ER_TOO_MANY_FIELDS = 1117
ER_TOO_BIG_ROWSIZE = 1118
ER_STACK_OVERRUN = 1119
ER_WRONG_OUTER_JOIN_UNUSED = 1120
ER_NULL_COLUMN_IN_INDEX = 1121
ER_CANT_FIND_UDF = 1122
ER_CANT_INITIALIZE_UDF = 1123
ER_UDF_NO_PATHS = 1124
ER_UDF_EXISTS = 1125
ER_CANT_OPEN_LIBRARY = 1126
ER_CANT_FIND_DL_ENTRY = 1127
ER_FUNCTION_NOT_DEFINED = 1128
ER_HOST_IS_BLOCKED = 1129
ER_HOST_NOT_PRIVILEGED = 1130
ER_PASSWORD_ANONYMOUS_USER = 1131
ER_PASSWORD_NOT_ALLOWED = 1132
ER_PASSWORD_NO_MATCH = 1133
ER_UPDATE_INFO = 1134
ER_CANT_CREATE_THREAD = 1135
ER_WRONG_VALUE_COUNT_ON_ROW = 1136
ER_CANT_REOPEN_TABLE = 1137
ER_INVALID_USE_OF_NULL = 1138
ER_REGEXP_ERROR = 1139
ER_MIX_OF_GROUP_FUNC_AND_FIELDS = 1140
ER_NONEXISTING_GRANT = 1141
ER_TABLEACCESS_DENIED_ERROR = 1142
ER_COLUMNACCESS_DENIED_ERROR = 1143
ER_ILLEGAL_GRANT_FOR_TABLE = 1144
ER_GRANT_WRONG_HOST_OR_USER = 1145
ER_NO_SUCH_TABLE = 1146
ER_NONEXISTING_TABLE_GRANT = 1147
ER_NOT_ALLOWED_COMMAND = 1148
ER_SYNTAX_ERROR = 1149
OBSOLETE_ER_UNUSED1 = 1150
OBSOLETE_ER_UNUSED2 = 1151
ER_ABORTING_CONNECTION = 1152
ER_NET_PACKET_TOO_LARGE = 1153
ER_NET_READ_ERROR_FROM_PIPE = 1154
ER_NET_FCNTL_ERROR = 1155
ER_NET_PACKETS_OUT_OF_ORDER = 1156
ER_NET_UNCOMPRESS_ERROR = 1157
ER_NET_READ_ERROR = 1158
ER_NET_READ_INTERRUPTED = 1159
ER_NET_ERROR_ON_WRITE = 1160
ER_NET_WRITE_INTERRUPTED = 1161
ER_TOO_LONG_STRING = 1162
ER_TABLE_CANT_HANDLE_BLOB = 1163
ER_TABLE_CANT_HANDLE_AUTO_INCREMENT = 1164
OBSOLETE_ER_UNUSED3 = 1165
ER_WRONG_COLUMN_NAME = 1166
ER_WRONG_KEY_COLUMN = 1167
ER_WRONG_MRG_TABLE = 1168
ER_DUP_UNIQUE = 1169
ER_BLOB_KEY_WITHOUT_LENGTH = 1170
ER_PRIMARY_CANT_HAVE_NULL = 1171
ER_TOO_MANY_ROWS = 1172
ER_REQUIRES_PRIMARY_KEY = 1173
OBSOLETE_ER_NO_RAID_COMPILED = 1174
ER_UPDATE_WITHOUT_KEY_IN_SAFE_MODE = 1175
ER_KEY_DOES_NOT_EXITS = 1176
ER_CHECK_NO_SUCH_TABLE = 1177
ER_CHECK_NOT_IMPLEMENTED = 1178
ER_CANT_DO_THIS_DURING_AN_TRANSACTION = 1179
ER_ERROR_DURING_COMMIT = 1180
ER_ERROR_DURING_ROLLBACK = 1181
ER_ERROR_DURING_FLUSH_LOGS = 1182
OBSOLETE_ER_ERROR_DURING_CHECKPOINT = 1183
ER_NEW_ABORTING_CONNECTION = 1184
OBSOLETE_ER_DUMP_NOT_IMPLEMENTED = 1185
OBSOLETE_ER_FLUSH_MASTER_BINLOG_CLOSED = 1186
OBSOLETE_ER_INDEX_REBUILD = 1187
ER_MASTER = 1188
ER_MASTER_NET_READ = 1189
ER_MASTER_NET_WRITE = 1190
ER_FT_MATCHING_KEY_NOT_FOUND = 1191
ER_LOCK_OR_ACTIVE_TRANSACTION = 1192
ER_UNKNOWN_SYSTEM_VARIABLE = 1193
ER_CRASHED_ON_USAGE = 1194
ER_CRASHED_ON_REPAIR = 1195
ER_WARNING_NOT_COMPLETE_ROLLBACK = 1196
ER_TRANS_CACHE_FULL = 1197
OBSOLETE_ER_SLAVE_MUST_STOP = 1198
ER_SLAVE_NOT_RUNNING = 1199
ER_BAD_SLAVE = 1200
ER_MASTER_INFO = 1201
ER_SLAVE_THREAD = 1202
ER_TOO_MANY_USER_CONNECTIONS = 1203
ER_SET_CONSTANTS_ONLY = 1204
ER_LOCK_WAIT_TIMEOUT = 1205
ER_LOCK_TABLE_FULL = 1206
ER_READ_ONLY_TRANSACTION = 1207
OBSOLETE_ER_DROP_DB_WITH_READ_LOCK = 1208
OBSOLETE_ER_CREATE_DB_WITH_READ_LOCK = 1209
ER_WRONG_ARGUMENTS = 1210
ER_NO_PERMISSION_TO_CREATE_USER = 1211
OBSOLETE_ER_UNION_TABLES_IN_DIFFERENT_DIR = 1212
ER_LOCK_DEADLOCK = 1213
ER_TABLE_CANT_HANDLE_FT = 1214
ER_CANNOT_ADD_FOREIGN = 1215
ER_NO_REFERENCED_ROW = 1216
ER_ROW_IS_REFERENCED = 1217
ER_CONNECT_TO_MASTER = 1218
OBSOLETE_ER_QUERY_ON_MASTER = 1219
ER_ERROR_WHEN_EXECUTING_COMMAND = 1220
ER_WRONG_USAGE = 1221
ER_WRONG_NUMBER_OF_COLUMNS_IN_SELECT = 1222
ER_CANT_UPDATE_WITH_READLOCK = 1223
ER_MIXING_NOT_ALLOWED = 1224
ER_DUP_ARGUMENT = 1225
ER_USER_LIMIT_REACHED = 1226
ER_SPECIFIC_ACCESS_DENIED_ERROR = 1227
ER_LOCAL_VARIABLE = 1228
ER_GLOBAL_VARIABLE = 1229
ER_NO_DEFAULT = 1230
ER_WRONG_VALUE_FOR_VAR = 1231
ER_WRONG_TYPE_FOR_VAR = 1232
ER_VAR_CANT_BE_READ = 1233
ER_CANT_USE_OPTION_HERE = 1234
ER_NOT_SUPPORTED_YET = 1235
ER_MASTER_FATAL_ERROR_READING_BINLOG = 1236
ER_SLAVE_IGNORED_TABLE = 1237
ER_INCORRECT_GLOBAL_LOCAL_VAR = 1238
ER_WRONG_FK_DEF = 1239
ER_KEY_REF_DO_NOT_MATCH_TABLE_REF = 1240
ER_OPERAND_COLUMNS = 1241
ER_SUBQUERY_NO_1_ROW = 1242
ER_UNKNOWN_STMT_HANDLER = 1243
ER_CORRUPT_HELP_DB = 1244
OBSOLETE_ER_CYCLIC_REFERENCE = 1245
ER_AUTO_CONVERT = 1246
ER_ILLEGAL_REFERENCE = 1247
ER_DERIVED_MUST_HAVE_ALIAS = 1248
ER_SELECT_REDUCED = 1249
ER_TABLENAME_NOT_ALLOWED_HERE = 1250
ER_NOT_SUPPORTED_AUTH_MODE = 1251
ER_SPATIAL_CANT_HAVE_NULL = 1252
ER_COLLATION_CHARSET_MISMATCH = 1253
OBSOLETE_ER_SLAVE_WAS_RUNNING = 1254
OBSOLETE_ER_SLAVE_WAS_NOT_RUNNING = 1255
ER_TOO_BIG_FOR_UNCOMPRESS = 1256
ER_ZLIB_Z_MEM_ERROR = 1257
ER_ZLIB_Z_BUF_ERROR = 1258
ER_ZLIB_Z_DATA_ERROR = 1259
ER_CUT_VALUE_GROUP_CONCAT = 1260
ER_WARN_TOO_FEW_RECORDS = 1261
ER_WARN_TOO_MANY_RECORDS = 1262
ER_WARN_NULL_TO_NOTNULL = 1263
ER_WARN_DATA_OUT_OF_RANGE = 1264
WARN_DATA_TRUNCATED = 1265
ER_WARN_USING_OTHER_HANDLER = 1266
ER_CANT_AGGREGATE_2COLLATIONS = 1267
OBSOLETE_ER_DROP_USER = 1268
ER_REVOKE_GRANTS = 1269
ER_CANT_AGGREGATE_3COLLATIONS = 1270
ER_CANT_AGGREGATE_NCOLLATIONS = 1271
ER_VARIABLE_IS_NOT_STRUCT = 1272
ER_UNKNOWN_COLLATION = 1273
ER_SLAVE_IGNORED_SSL_PARAMS = 1274
OBSOLETE_ER_SERVER_IS_IN_SECURE_AUTH_MODE = 1275
ER_WARN_FIELD_RESOLVED = 1276
ER_BAD_SLAVE_UNTIL_COND = 1277
ER_MISSING_SKIP_SLAVE = 1278
ER_UNTIL_COND_IGNORED = 1279
ER_WRONG_NAME_FOR_INDEX = 1280
ER_WRONG_NAME_FOR_CATALOG = 1281
OBSOLETE_ER_WARN_QC_RESIZE = 1282
ER_BAD_FT_COLUMN = 1283
ER_UNKNOWN_KEY_CACHE = 1284
ER_WARN_HOSTNAME_WONT_WORK = 1285
ER_UNKNOWN_STORAGE_ENGINE = 1286
ER_WARN_DEPRECATED_SYNTAX = 1287
ER_NON_UPDATABLE_TABLE = 1288
ER_FEATURE_DISABLED = 1289
ER_OPTION_PREVENTS_STATEMENT = 1290
ER_DUPLICATED_VALUE_IN_TYPE = 1291
ER_TRUNCATED_WRONG_VALUE = 1292
OBSOLETE_ER_TOO_MUCH_AUTO_TIMESTAMP_COLS = 1293
ER_INVALID_ON_UPDATE = 1294
ER_UNSUPPORTED_PS = 1295
ER_GET_ERRMSG = 1296
ER_GET_TEMPORARY_ERRMSG = 1297
ER_UNKNOWN_TIME_ZONE = 1298
ER_WARN_INVALID_TIMESTAMP = 1299
ER_INVALID_CHARACTER_STRING = 1300
ER_WARN_ALLOWED_PACKET_OVERFLOWED = 1301
ER_CONFLICTING_DECLARATIONS = 1302
ER_SP_NO_RECURSIVE_CREATE = 1303
ER_SP_ALREADY_EXISTS = 1304
ER_SP_DOES_NOT_EXIST = 1305
ER_SP_DROP_FAILED = 1306
ER_SP_STORE_FAILED = 1307
ER_SP_LILABEL_MISMATCH = 1308
ER_SP_LABEL_REDEFINE = 1309
ER_SP_LABEL_MISMATCH = 1310
ER_SP_UNINIT_VAR = 1311
ER_SP_BADSELECT = 1312
ER_SP_BADRETURN = 1313
ER_SP_BADSTATEMENT = 1314
ER_UPDATE_LOG_DEPRECATED_IGNORED = 1315
ER_UPDATE_LOG_DEPRECATED_TRANSLATED = 1316
ER_QUERY_INTERRUPTED = 1317
ER_SP_WRONG_NO_OF_ARGS = 1318
ER_SP_COND_MISMATCH = 1319
ER_SP_NORETURN = 1320
ER_SP_NORETURNEND = 1321
ER_SP_BAD_CURSOR_QUERY = 1322
ER_SP_BAD_CURSOR_SELECT = 1323
ER_SP_CURSOR_MISMATCH = 1324
ER_SP_CURSOR_ALREADY_OPEN = 1325
ER_SP_CURSOR_NOT_OPEN = 1326
ER_SP_UNDECLARED_VAR = 1327
ER_SP_WRONG_NO_OF_FETCH_ARGS = 1328
ER_SP_FETCH_NO_DATA = 1329
ER_SP_DUP_PARAM = 1330
ER_SP_DUP_VAR = 1331
ER_SP_DUP_COND = 1332
ER_SP_DUP_CURS = 1333
ER_SP_CANT_ALTER = 1334
ER_SP_SUBSELECT_NYI = 1335
ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG = 1336
ER_SP_VARCOND_AFTER_CURSHNDLR = 1337
ER_SP_CURSOR_AFTER_HANDLER = 1338
ER_SP_CASE_NOT_FOUND = 1339
ER_FPARSER_TOO_BIG_FILE = 1340
ER_FPARSER_BAD_HEADER = 1341
ER_FPARSER_EOF_IN_COMMENT = 1342
ER_FPARSER_ERROR_IN_PARAMETER = 1343
ER_FPARSER_EOF_IN_UNKNOWN_PARAMETER = 1344
ER_VIEW_NO_EXPLAIN = 1345
OBSOLETE_ER_FRM_UNKNOWN_TYPE = 1346
ER_WRONG_OBJECT = 1347
ER_NONUPDATEABLE_COLUMN = 1348
OBSOLETE_ER_VIEW_SELECT_DERIVED_UNUSED = 1349
ER_VIEW_SELECT_CLAUSE = 1350
ER_VIEW_SELECT_VARIABLE = 1351
ER_VIEW_SELECT_TMPTABLE = 1352
ER_VIEW_WRONG_LIST = 1353
ER_WARN_VIEW_MERGE = 1354
ER_WARN_VIEW_WITHOUT_KEY = 1355
ER_VIEW_INVALID = 1356
ER_SP_NO_DROP_SP = 1357
OBSOLETE_ER_SP_GOTO_IN_HNDLR = 1358
ER_TRG_ALREADY_EXISTS = 1359
ER_TRG_DOES_NOT_EXIST = 1360
ER_TRG_ON_VIEW_OR_TEMP_TABLE = 1361
ER_TRG_CANT_CHANGE_ROW = 1362
ER_TRG_NO_SUCH_ROW_IN_TRG = 1363
ER_NO_DEFAULT_FOR_FIELD = 1364
ER_DIVISION_BY_ZERO = 1365
ER_TRUNCATED_WRONG_VALUE_FOR_FIELD = 1366
ER_ILLEGAL_VALUE_FOR_TYPE = 1367
ER_VIEW_NONUPD_CHECK = 1368
ER_VIEW_CHECK_FAILED = 1369
ER_PROCACCESS_DENIED_ERROR = 1370
ER_RELAY_LOG_FAIL = 1371
OBSOLETE_ER_PASSWD_LENGTH = 1372
ER_UNKNOWN_TARGET_BINLOG = 1373
ER_IO_ERR_LOG_INDEX_READ = 1374
ER_BINLOG_PURGE_PROHIBITED = 1375
ER_FSEEK_FAIL = 1376
ER_BINLOG_PURGE_FATAL_ERR = 1377
ER_LOG_IN_USE = 1378
ER_LOG_PURGE_UNKNOWN_ERR = 1379
ER_RELAY_LOG_INIT = 1380
ER_NO_BINARY_LOGGING = 1381
ER_RESERVED_SYNTAX = 1382
OBSOLETE_ER_WSAS_FAILED = 1383
OBSOLETE_ER_DIFF_GROUPS_PROC = 1384
OBSOLETE_ER_NO_GROUP_FOR_PROC = 1385
OBSOLETE_ER_ORDER_WITH_PROC = 1386
OBSOLETE_ER_LOGGING_PROHIBIT_CHANGING_OF = 1387
OBSOLETE_ER_NO_FILE_MAPPING = 1388
OBSOLETE_ER_WRONG_MAGIC = 1389
ER_PS_MANY_PARAM = 1390
ER_KEY_PART_0 = 1391
ER_VIEW_CHECKSUM = 1392
ER_VIEW_MULTIUPDATE = 1393
ER_VIEW_NO_INSERT_FIELD_LIST = 1394
ER_VIEW_DELETE_MERGE_VIEW = 1395
ER_CANNOT_USER = 1396
ER_XAER_NOTA = 1397
ER_XAER_INVAL = 1398
ER_XAER_RMFAIL = 1399
ER_XAER_OUTSIDE = 1400
ER_XAER_RMERR = 1401
ER_XA_RBROLLBACK = 1402
ER_NONEXISTING_PROC_GRANT = 1403
ER_PROC_AUTO_GRANT_FAIL = 1404
ER_PROC_AUTO_REVOKE_FAIL = 1405
ER_DATA_TOO_LONG = 1406
ER_SP_BAD_SQLSTATE = 1407
ER_STARTUP = 1408
ER_LOAD_FROM_FIXED_SIZE_ROWS_TO_VAR = 1409
ER_CANT_CREATE_USER_WITH_GRANT = 1410
ER_WRONG_VALUE_FOR_TYPE = 1411
ER_TABLE_DEF_CHANGED = 1412
ER_SP_DUP_HANDLER = 1413
ER_SP_NOT_VAR_ARG = 1414
ER_SP_NO_RETSET = 1415
ER_CANT_CREATE_GEOMETRY_OBJECT = 1416
OBSOLETE_ER_FAILED_ROUTINE_BREAK_BINLOG = 1417
ER_BINLOG_UNSAFE_ROUTINE = 1418
ER_BINLOG_CREATE_ROUTINE_NEED_SUPER = 1419
OBSOLETE_ER_EXEC_STMT_WITH_OPEN_CURSOR = 1420
ER_STMT_HAS_NO_OPEN_CURSOR = 1421
ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG = 1422
ER_NO_DEFAULT_FOR_VIEW_FIELD = 1423
ER_SP_NO_RECURSION = 1424
ER_TOO_BIG_SCALE = 1425
ER_TOO_BIG_PRECISION = 1426
ER_M_BIGGER_THAN_D = 1427
ER_WRONG_LOCK_OF_SYSTEM_TABLE = 1428
ER_CONNECT_TO_FOREIGN_DATA_SOURCE = 1429
ER_QUERY_ON_FOREIGN_DATA_SOURCE = 1430
ER_FOREIGN_DATA_SOURCE_DOESNT_EXIST = 1431
ER_FOREIGN_DATA_STRING_INVALID_CANT_CREATE = 1432
ER_FOREIGN_DATA_STRING_INVALID = 1433
OBSOLETE_ER_CANT_CREATE_FEDERATED_TABLE = 1434
ER_TRG_IN_WRONG_SCHEMA = 1435
ER_STACK_OVERRUN_NEED_MORE = 1436
ER_TOO_LONG_BODY = 1437
ER_WARN_CANT_DROP_DEFAULT_KEYCACHE = 1438
ER_TOO_BIG_DISPLAYWIDTH = 1439
ER_XAER_DUPID = 1440
ER_DATETIME_FUNCTION_OVERFLOW = 1441
ER_CANT_UPDATE_USED_TABLE_IN_SF_OR_TRG = 1442
ER_VIEW_PREVENT_UPDATE = 1443
ER_PS_NO_RECURSION = 1444
ER_SP_CANT_SET_AUTOCOMMIT = 1445
OBSOLETE_ER_MALFORMED_DEFINER = 1446
ER_VIEW_FRM_NO_USER = 1447
ER_VIEW_OTHER_USER = 1448
ER_NO_SUCH_USER = 1449
ER_FORBID_SCHEMA_CHANGE = 1450
ER_ROW_IS_REFERENCED_2 = 1451
ER_NO_REFERENCED_ROW_2 = 1452
ER_SP_BAD_VAR_SHADOW = 1453
ER_TRG_NO_DEFINER = 1454
ER_OLD_FILE_FORMAT = 1455
ER_SP_RECURSION_LIMIT = 1456
OBSOLETE_ER_SP_PROC_TABLE_CORRUPT = 1457
ER_SP_WRONG_NAME = 1458
ER_TABLE_NEEDS_UPGRADE = 1459
ER_SP_NO_AGGREGATE = 1460
ER_MAX_PREPARED_STMT_COUNT_REACHED = 1461
ER_VIEW_RECURSIVE = 1462
ER_NON_GROUPING_FIELD_USED = 1463
ER_TABLE_CANT_HANDLE_SPKEYS = 1464
ER_NO_TRIGGERS_ON_SYSTEM_SCHEMA = 1465
ER_REMOVED_SPACES = 1466
ER_AUTOINC_READ_FAILED = 1467
ER_USERNAME = 1468
ER_HOSTNAME = 1469
ER_WRONG_STRING_LENGTH = 1470
ER_NON_INSERTABLE_TABLE = 1471
ER_ADMIN_WRONG_MRG_TABLE = 1472
ER_TOO_HIGH_LEVEL_OF_NESTING_FOR_SELECT = 1473
ER_NAME_BECOMES_EMPTY = 1474
ER_AMBIGUOUS_FIELD_TERM = 1475
ER_FOREIGN_SERVER_EXISTS = 1476
ER_FOREIGN_SERVER_DOESNT_EXIST = 1477
ER_ILLEGAL_HA_CREATE_OPTION = 1478
ER_PARTITION_REQUIRES_VALUES_ERROR = 1479
ER_PARTITION_WRONG_VALUES_ERROR = 1480
ER_PARTITION_MAXVALUE_ERROR = 1481
OBSOLETE_ER_PARTITION_SUBPARTITION_ERROR = 1482
OBSOLETE_ER_PARTITION_SUBPART_MIX_ERROR = 1483
ER_PARTITION_WRONG_NO_PART_ERROR = 1484
ER_PARTITION_WRONG_NO_SUBPART_ERROR = 1485
ER_WRONG_EXPR_IN_PARTITION_FUNC_ERROR = 1486
OBSOLETE_ER_NO_CONST_EXPR_IN_RANGE_OR_LIST_ERROR = 1487
ER_FIELD_NOT_FOUND_PART_ERROR = 1488
OBSOLETE_ER_LIST_OF_FIELDS_ONLY_IN_HASH_ERROR = 1489
ER_INCONSISTENT_PARTITION_INFO_ERROR = 1490
ER_PARTITION_FUNC_NOT_ALLOWED_ERROR = 1491
ER_PARTITIONS_MUST_BE_DEFINED_ERROR = 1492
ER_RANGE_NOT_INCREASING_ERROR = 1493
ER_INCONSISTENT_TYPE_OF_FUNCTIONS_ERROR = 1494
ER_MULTIPLE_DEF_CONST_IN_LIST_PART_ERROR = 1495
ER_PARTITION_ENTRY_ERROR = 1496
ER_MIX_HANDLER_ERROR = 1497
ER_PARTITION_NOT_DEFINED_ERROR = 1498
ER_TOO_MANY_PARTITIONS_ERROR = 1499
ER_SUBPARTITION_ERROR = 1500
ER_CANT_CREATE_HANDLER_FILE = 1501
ER_BLOB_FIELD_IN_PART_FUNC_ERROR = 1502
ER_UNIQUE_KEY_NEED_ALL_FIELDS_IN_PF = 1503
ER_NO_PARTS_ERROR = 1504
ER_PARTITION_MGMT_ON_NONPARTITIONED = 1505
ER_FOREIGN_KEY_ON_PARTITIONED = 1506
ER_DROP_PARTITION_NON_EXISTENT = 1507
ER_DROP_LAST_PARTITION = 1508
ER_COALESCE_ONLY_ON_HASH_PARTITION = 1509
ER_REORG_HASH_ONLY_ON_SAME_NO = 1510
ER_REORG_NO_PARAM_ERROR = 1511
ER_ONLY_ON_RANGE_LIST_PARTITION = 1512
ER_ADD_PARTITION_SUBPART_ERROR = 1513
ER_ADD_PARTITION_NO_NEW_PARTITION = 1514
ER_COALESCE_PARTITION_NO_PARTITION = 1515
ER_REORG_PARTITION_NOT_EXIST = 1516
ER_SAME_NAME_PARTITION = 1517
ER_NO_BINLOG_ERROR = 1518
ER_CONSECUTIVE_REORG_PARTITIONS = 1519
ER_REORG_OUTSIDE_RANGE = 1520
ER_PARTITION_FUNCTION_FAILURE = 1521
OBSOLETE_ER_PART_STATE_ERROR = 1522
ER_LIMITED_PART_RANGE = 1523
ER_PLUGIN_IS_NOT_LOADED = 1524
ER_WRONG_VALUE = 1525
ER_NO_PARTITION_FOR_GIVEN_VALUE = 1526
ER_FILEGROUP_OPTION_ONLY_ONCE = 1527
ER_CREATE_FILEGROUP_FAILED = 1528
ER_DROP_FILEGROUP_FAILED = 1529
ER_TABLESPACE_AUTO_EXTEND_ERROR = 1530
ER_WRONG_SIZE_NUMBER = 1531
ER_SIZE_OVERFLOW_ERROR = 1532
ER_ALTER_FILEGROUP_FAILED = 1533
ER_BINLOG_ROW_LOGGING_FAILED = 1534
OBSOLETE_ER_BINLOG_ROW_WRONG_TABLE_DEF = 1535
OBSOLETE_ER_BINLOG_ROW_RBR_TO_SBR = 1536
ER_EVENT_ALREADY_EXISTS = 1537
OBSOLETE_ER_EVENT_STORE_FAILED = 1538
ER_EVENT_DOES_NOT_EXIST = 1539
OBSOLETE_ER_EVENT_CANT_ALTER = 1540
OBSOLETE_ER_EVENT_DROP_FAILED = 1541
ER_EVENT_INTERVAL_NOT_POSITIVE_OR_TOO_BIG = 1542
ER_EVENT_ENDS_BEFORE_STARTS = 1543
ER_EVENT_EXEC_TIME_IN_THE_PAST = 1544
OBSOLETE_ER_EVENT_OPEN_TABLE_FAILED = 1545
OBSOLETE_ER_EVENT_NEITHER_M_EXPR_NOR_M_AT = 1546
OBSOLETE_ER_COL_COUNT_DOESNT_MATCH_CORRUPTED = 1547
OBSOLETE_ER_CANNOT_LOAD_FROM_TABLE = 1548
OBSOLETE_ER_EVENT_CANNOT_DELETE = 1549
OBSOLETE_ER_EVENT_COMPILE_ERROR = 1550
ER_EVENT_SAME_NAME = 1551
OBSOLETE_ER_EVENT_DATA_TOO_LONG = 1552
ER_DROP_INDEX_FK = 1553
ER_WARN_DEPRECATED_SYNTAX_WITH_VER = 1554
OBSOLETE_ER_CANT_WRITE_LOCK_LOG_TABLE = 1555
ER_CANT_LOCK_LOG_TABLE = 1556
ER_FOREIGN_DUPLICATE_KEY_OLD_UNUSED = 1557
ER_COL_COUNT_DOESNT_MATCH_PLEASE_UPDATE = 1558
OBSOLETE_ER_TEMP_TABLE_PREVENTS_SWITCH_OUT_OF_RBR = 1559
ER_STORED_FUNCTION_PREVENTS_SWITCH_BINLOG_FORMAT = 1560
OBSOLETE_ER_NDB_CANT_SWITCH_BINLOG_FORMAT = 1561
ER_PARTITION_NO_TEMPORARY = 1562
ER_PARTITION_CONST_DOMAIN_ERROR = 1563
ER_PARTITION_FUNCTION_IS_NOT_ALLOWED = 1564
OBSOLETE_ER_DDL_LOG_ERROR_UNUSED = 1565
ER_NULL_IN_VALUES_LESS_THAN = 1566
ER_WRONG_PARTITION_NAME = 1567
ER_CANT_CHANGE_TX_CHARACTERISTICS = 1568
ER_DUP_ENTRY_AUTOINCREMENT_CASE = 1569
OBSOLETE_ER_EVENT_MODIFY_QUEUE_ERROR = 1570
ER_EVENT_SET_VAR_ERROR = 1571
ER_PARTITION_MERGE_ERROR = 1572
OBSOLETE_ER_CANT_ACTIVATE_LOG = 1573
OBSOLETE_ER_RBR_NOT_AVAILABLE = 1574
ER_BASE64_DECODE_ERROR = 1575
ER_EVENT_RECURSION_FORBIDDEN = 1576
OBSOLETE_ER_EVENTS_DB_ERROR = 1577
ER_ONLY_INTEGERS_ALLOWED = 1578
ER_UNSUPORTED_LOG_ENGINE = 1579
ER_BAD_LOG_STATEMENT = 1580
ER_CANT_RENAME_LOG_TABLE = 1581
ER_WRONG_PARAMCOUNT_TO_NATIVE_FCT = 1582
ER_WRONG_PARAMETERS_TO_NATIVE_FCT = 1583
ER_WRONG_PARAMETERS_TO_STORED_FCT = 1584
ER_NATIVE_FCT_NAME_COLLISION = 1585
ER_DUP_ENTRY_WITH_KEY_NAME = 1586
ER_BINLOG_PURGE_EMFILE = 1587
ER_EVENT_CANNOT_CREATE_IN_THE_PAST = 1588
ER_EVENT_CANNOT_ALTER_IN_THE_PAST = 1589
OBSOLETE_ER_SLAVE_INCIDENT = 1590
ER_NO_PARTITION_FOR_GIVEN_VALUE_SILENT = 1591
ER_BINLOG_UNSAFE_STATEMENT = 1592
ER_BINLOG_FATAL_ERROR = 1593
OBSOLETE_ER_SLAVE_RELAY_LOG_READ_FAILURE = 1594
OBSOLETE_ER_SLAVE_RELAY_LOG_WRITE_FAILURE = 1595
OBSOLETE_ER_SLAVE_CREATE_EVENT_FAILURE = 1596
OBSOLETE_ER_SLAVE_MASTER_COM_FAILURE = 1597
ER_BINLOG_LOGGING_IMPOSSIBLE = 1598
ER_VIEW_NO_CREATION_CTX = 1599
ER_VIEW_INVALID_CREATION_CTX = 1600
OBSOLETE_ER_SR_INVALID_CREATION_CTX = 1601
ER_TRG_CORRUPTED_FILE = 1602
ER_TRG_NO_CREATION_CTX = 1603
ER_TRG_INVALID_CREATION_CTX = 1604
ER_EVENT_INVALID_CREATION_CTX = 1605
ER_TRG_CANT_OPEN_TABLE = 1606
OBSOLETE_ER_CANT_CREATE_SROUTINE = 1607
OBSOLETE_ER_NEVER_USED = 1608
ER_NO_FORMAT_DESCRIPTION_EVENT_BEFORE_BINLOG_STATEMENT = 1609
ER_SLAVE_CORRUPT_EVENT = 1610
OBSOLETE_ER_LOAD_DATA_INVALID_COLUMN_UNUSED = 1611
ER_LOG_PURGE_NO_FILE = 1612
ER_XA_RBTIMEOUT = 1613
ER_XA_RBDEADLOCK = 1614
ER_NEED_REPREPARE = 1615
OBSOLETE_ER_DELAYED_NOT_SUPPORTED = 1616
WARN_NO_MASTER_INFO = 1617
WARN_OPTION_IGNORED = 1618
ER_PLUGIN_DELETE_BUILTIN = 1619
WARN_PLUGIN_BUSY = 1620
ER_VARIABLE_IS_READONLY = 1621
ER_WARN_ENGINE_TRANSACTION_ROLLBACK = 1622
OBSOLETE_ER_SLAVE_HEARTBEAT_FAILURE = 1623
ER_SLAVE_HEARTBEAT_VALUE_OUT_OF_RANGE = 1624
ER_NDB_REPLICATION_SCHEMA_ERROR = 1625
ER_CONFLICT_FN_PARSE_ERROR = 1626
ER_EXCEPTIONS_WRITE_ERROR = 1627
ER_TOO_LONG_TABLE_COMMENT = 1628
ER_TOO_LONG_FIELD_COMMENT = 1629
ER_FUNC_INEXISTENT_NAME_COLLISION = 1630
ER_DATABASE_NAME = 1631
ER_TABLE_NAME = 1632
ER_PARTITION_NAME = 1633
ER_SUBPARTITION_NAME = 1634
ER_TEMPORARY_NAME = 1635
ER_RENAMED_NAME = 1636
ER_TOO_MANY_CONCURRENT_TRXS = 1637
WARN_NON_ASCII_SEPARATOR_NOT_IMPLEMENTED = 1638
ER_DEBUG_SYNC_TIMEOUT = 1639
ER_DEBUG_SYNC_HIT_LIMIT = 1640
ER_DUP_SIGNAL_SET = 1641
ER_SIGNAL_WARN = 1642
ER_SIGNAL_NOT_FOUND = 1643
ER_SIGNAL_EXCEPTION = 1644
ER_RESIGNAL_WITHOUT_ACTIVE_HANDLER = 1645
ER_SIGNAL_BAD_CONDITION_TYPE = 1646
WARN_COND_ITEM_TRUNCATED = 1647
ER_COND_ITEM_TOO_LONG = 1648
ER_UNKNOWN_LOCALE = 1649
ER_SLAVE_IGNORE_SERVER_IDS = 1650
OBSOLETE_ER_QUERY_CACHE_DISABLED = 1651
ER_SAME_NAME_PARTITION_FIELD = 1652
ER_PARTITION_COLUMN_LIST_ERROR = 1653
ER_WRONG_TYPE_COLUMN_VALUE_ERROR = 1654
ER_TOO_MANY_PARTITION_FUNC_FIELDS_ERROR = 1655
ER_MAXVALUE_IN_VALUES_IN = 1656
ER_TOO_MANY_VALUES_ERROR = 1657
ER_ROW_SINGLE_PARTITION_FIELD_ERROR = 1658
ER_FIELD_TYPE_NOT_ALLOWED_AS_PARTITION_FIELD = 1659
ER_PARTITION_FIELDS_TOO_LONG = 1660
ER_BINLOG_ROW_ENGINE_AND_STMT_ENGINE = 1661
ER_BINLOG_ROW_MODE_AND_STMT_ENGINE = 1662
ER_BINLOG_UNSAFE_AND_STMT_ENGINE = 1663
ER_BINLOG_ROW_INJECTION_AND_STMT_ENGINE = 1664
ER_BINLOG_STMT_MODE_AND_ROW_ENGINE = 1665
ER_BINLOG_ROW_INJECTION_AND_STMT_MODE = 1666
ER_BINLOG_MULTIPLE_ENGINES_AND_SELF_LOGGING_ENGINE = 1667
ER_BINLOG_UNSAFE_LIMIT = 1668
OBSOLETE_ER_UNUSED4 = 1669
ER_BINLOG_UNSAFE_SYSTEM_TABLE = 1670
ER_BINLOG_UNSAFE_AUTOINC_COLUMNS = 1671
ER_BINLOG_UNSAFE_UDF = 1672
ER_BINLOG_UNSAFE_SYSTEM_VARIABLE = 1673
ER_BINLOG_UNSAFE_SYSTEM_FUNCTION = 1674
ER_BINLOG_UNSAFE_NONTRANS_AFTER_TRANS = 1675
ER_MESSAGE_AND_STATEMENT = 1676
OBSOLETE_ER_SLAVE_CONVERSION_FAILED = 1677
ER_SLAVE_CANT_CREATE_CONVERSION = 1678
ER_INSIDE_TRANSACTION_PREVENTS_SWITCH_BINLOG_FORMAT = 1679
ER_PATH_LENGTH = 1680
ER_WARN_DEPRECATED_SYNTAX_NO_REPLACEMENT = 1681
ER_WRONG_NATIVE_TABLE_STRUCTURE = 1682
ER_WRONG_PERFSCHEMA_USAGE = 1683
ER_WARN_I_S_SKIPPED_TABLE = 1684
ER_INSIDE_TRANSACTION_PREVENTS_SWITCH_BINLOG_DIRECT = 1685
ER_STORED_FUNCTION_PREVENTS_SWITCH_BINLOG_DIRECT = 1686
ER_SPATIAL_MUST_HAVE_GEOM_COL = 1687
ER_TOO_LONG_INDEX_COMMENT = 1688
ER_LOCK_ABORTED = 1689
ER_DATA_OUT_OF_RANGE = 1690
OBSOLETE_ER_WRONG_SPVAR_TYPE_IN_LIMIT = 1691
ER_BINLOG_UNSAFE_MULTIPLE_ENGINES_AND_SELF_LOGGING_ENGINE = 1692
ER_BINLOG_UNSAFE_MIXED_STATEMENT = 1693
ER_INSIDE_TRANSACTION_PREVENTS_SWITCH_SQL_LOG_BIN = 1694
ER_STORED_FUNCTION_PREVENTS_SWITCH_SQL_LOG_BIN = 1695
ER_FAILED_READ_FROM_PAR_FILE = 1696
ER_VALUES_IS_NOT_INT_TYPE_ERROR = 1697
ER_ACCESS_DENIED_NO_PASSWORD_ERROR = 1698
ER_SET_PASSWORD_AUTH_PLUGIN = 1699
OBSOLETE_ER_GRANT_PLUGIN_USER_EXISTS = 1700
ER_TRUNCATE_ILLEGAL_FK = 1701
ER_PLUGIN_IS_PERMANENT = 1702
ER_SLAVE_HEARTBEAT_VALUE_OUT_OF_RANGE_MIN = 1703
ER_SLAVE_HEARTBEAT_VALUE_OUT_OF_RANGE_MAX = 1704
ER_STMT_CACHE_FULL = 1705
ER_MULTI_UPDATE_KEY_CONFLICT = 1706
ER_TABLE_NEEDS_REBUILD = 1707
WARN_OPTION_BELOW_LIMIT = 1708
ER_INDEX_COLUMN_TOO_LONG = 1709
ER_ERROR_IN_TRIGGER_BODY = 1710
ER_ERROR_IN_UNKNOWN_TRIGGER_BODY = 1711
ER_INDEX_CORRUPT = 1712
ER_UNDO_RECORD_TOO_BIG = 1713
ER_BINLOG_UNSAFE_INSERT_IGNORE_SELECT = 1714
ER_BINLOG_UNSAFE_INSERT_SELECT_UPDATE = 1715
ER_BINLOG_UNSAFE_REPLACE_SELECT = 1716
ER_BINLOG_UNSAFE_CREATE_IGNORE_SELECT = 1717
ER_BINLOG_UNSAFE_CREATE_REPLACE_SELECT = 1718
ER_BINLOG_UNSAFE_UPDATE_IGNORE = 1719
ER_PLUGIN_NO_UNINSTALL = 1720
ER_PLUGIN_NO_INSTALL = 1721
ER_BINLOG_UNSAFE_WRITE_AUTOINC_SELECT = 1722
ER_BINLOG_UNSAFE_CREATE_SELECT_AUTOINC = 1723
ER_BINLOG_UNSAFE_INSERT_TWO_KEYS = 1724
ER_TABLE_IN_FK_CHECK = 1725
ER_UNSUPPORTED_ENGINE = 1726
ER_BINLOG_UNSAFE_AUTOINC_NOT_FIRST = 1727
ER_CANNOT_LOAD_FROM_TABLE_V2 = 1728
ER_MASTER_DELAY_VALUE_OUT_OF_RANGE = 1729
ER_ONLY_FD_AND_RBR_EVENTS_ALLOWED_IN_BINLOG_STATEMENT = 1730
ER_PARTITION_EXCHANGE_DIFFERENT_OPTION = 1731
ER_PARTITION_EXCHANGE_PART_TABLE = 1732
ER_PARTITION_EXCHANGE_TEMP_TABLE = 1733
ER_PARTITION_INSTEAD_OF_SUBPARTITION = 1734
ER_UNKNOWN_PARTITION = 1735
ER_TABLES_DIFFERENT_METADATA = 1736
ER_ROW_DOES_NOT_MATCH_PARTITION = 1737
ER_BINLOG_CACHE_SIZE_GREATER_THAN_MAX = 1738
ER_WARN_INDEX_NOT_APPLICABLE = 1739
ER_PARTITION_EXCHANGE_FOREIGN_KEY = 1740
OBSOLETE_ER_NO_SUCH_KEY_VALUE = 1741
ER_RPL_INFO_DATA_TOO_LONG = 1742
OBSOLETE_ER_NETWORK_READ_EVENT_CHECKSUM_FAILURE = 1743
OBSOLETE_ER_BINLOG_READ_EVENT_CHECKSUM_FAILURE = 1744
ER_BINLOG_STMT_CACHE_SIZE_GREATER_THAN_MAX = 1745
ER_CANT_UPDATE_TABLE_IN_CREATE_TABLE_SELECT = 1746
ER_PARTITION_CLAUSE_ON_NONPARTITIONED = 1747
ER_ROW_DOES_NOT_MATCH_GIVEN_PARTITION_SET = 1748
OBSOLETE_ER_NO_SUCH_PARTITION__UNUSED = 1749
ER_CHANGE_RPL_INFO_REPOSITORY_FAILURE = 1750
ER_WARNING_NOT_COMPLETE_ROLLBACK_WITH_CREATED_TEMP_TABLE = 1751
ER_WARNING_NOT_COMPLETE_ROLLBACK_WITH_DROPPED_TEMP_TABLE = 1752
ER_MTS_FEATURE_IS_NOT_SUPPORTED = 1753
ER_MTS_UPDATED_DBS_GREATER_MAX = 1754
ER_MTS_CANT_PARALLEL = 1755
ER_MTS_INCONSISTENT_DATA = 1756
ER_FULLTEXT_NOT_SUPPORTED_WITH_PARTITIONING = 1757
ER_DA_INVALID_CONDITION_NUMBER = 1758
ER_INSECURE_PLAIN_TEXT = 1759
ER_INSECURE_CHANGE_MASTER = 1760
ER_FOREIGN_DUPLICATE_KEY_WITH_CHILD_INFO = 1761
ER_FOREIGN_DUPLICATE_KEY_WITHOUT_CHILD_INFO = 1762
ER_SQLTHREAD_WITH_SECURE_SLAVE = 1763
ER_TABLE_HAS_NO_FT = 1764
ER_VARIABLE_NOT_SETTABLE_IN_SF_OR_TRIGGER = 1765
ER_VARIABLE_NOT_SETTABLE_IN_TRANSACTION = 1766
OBSOLETE_ER_GTID_NEXT_IS_NOT_IN_GTID_NEXT_LIST = 1767
OBSOLETE_ER_CANT_CHANGE_GTID_NEXT_IN_TRANSACTION = 1768
ER_SET_STATEMENT_CANNOT_INVOKE_FUNCTION = 1769
ER_GTID_NEXT_CANT_BE_AUTOMATIC_IF_GTID_NEXT_LIST_IS_NON_NULL = 1770
OBSOLETE_ER_SKIPPING_LOGGED_TRANSACTION = 1771
ER_MALFORMED_GTID_SET_SPECIFICATION = 1772
ER_MALFORMED_GTID_SET_ENCODING = 1773
ER_MALFORMED_GTID_SPECIFICATION = 1774
ER_GNO_EXHAUSTED = 1775
ER_BAD_SLAVE_AUTO_POSITION = 1776
ER_AUTO_POSITION_REQUIRES_GTID_MODE_NOT_OFF = 1777
ER_CANT_DO_IMPLICIT_COMMIT_IN_TRX_WHEN_GTID_NEXT_IS_SET = 1778
ER_GTID_MODE_ON_REQUIRES_ENFORCE_GTID_CONSISTENCY_ON = 1779
OBSOLETE_ER_GTID_MODE_REQUIRES_BINLOG = 1780
ER_CANT_SET_GTID_NEXT_TO_GTID_WHEN_GTID_MODE_IS_OFF = 1781
ER_CANT_SET_GTID_NEXT_TO_ANONYMOUS_WHEN_GTID_MODE_IS_ON = 1782
ER_CANT_SET_GTID_NEXT_LIST_TO_NON_NULL_WHEN_GTID_MODE_IS_OFF = 1783
OBSOLETE_ER_FOUND_GTID_EVENT_WHEN_GTID_MODE_IS_OFF__UNUSED = 1784
ER_GTID_UNSAFE_NON_TRANSACTIONAL_TABLE = 1785
ER_GTID_UNSAFE_CREATE_SELECT = 1786
OBSOLETE_ER_GTID_UNSAFE_CREATE_DROP_TEMP_TABLE_IN_TRANSACTION = 1787
ER_GTID_MODE_CAN_ONLY_CHANGE_ONE_STEP_AT_A_TIME = 1788
ER_MASTER_HAS_PURGED_REQUIRED_GTIDS = 1789
ER_CANT_SET_GTID_NEXT_WHEN_OWNING_GTID = 1790
ER_UNKNOWN_EXPLAIN_FORMAT = 1791
ER_CANT_EXECUTE_IN_READ_ONLY_TRANSACTION = 1792
ER_TOO_LONG_TABLE_PARTITION_COMMENT = 1793
ER_SLAVE_CONFIGURATION = 1794
ER_INNODB_FT_LIMIT = 1795
ER_INNODB_NO_FT_TEMP_TABLE = 1796
ER_INNODB_FT_WRONG_DOCID_COLUMN = 1797
ER_INNODB_FT_WRONG_DOCID_INDEX = 1798
ER_INNODB_ONLINE_LOG_TOO_BIG = 1799
ER_UNKNOWN_ALTER_ALGORITHM = 1800
ER_UNKNOWN_ALTER_LOCK = 1801
ER_MTS_CHANGE_MASTER_CANT_RUN_WITH_GAPS = 1802
ER_MTS_RECOVERY_FAILURE = 1803
ER_MTS_RESET_WORKERS = 1804
ER_COL_COUNT_DOESNT_MATCH_CORRUPTED_V2 = 1805
ER_SLAVE_SILENT_RETRY_TRANSACTION = 1806
ER_DISCARD_FK_CHECKS_RUNNING = 1807
ER_TABLE_SCHEMA_MISMATCH = 1808
ER_TABLE_IN_SYSTEM_TABLESPACE = 1809
ER_IO_READ_ERROR = 1810
ER_IO_WRITE_ERROR = 1811
ER_TABLESPACE_MISSING = 1812
ER_TABLESPACE_EXISTS = 1813
ER_TABLESPACE_DISCARDED = 1814
ER_INTERNAL_ERROR = 1815
ER_INNODB_IMPORT_ERROR = 1816
ER_INNODB_INDEX_CORRUPT = 1817
ER_INVALID_YEAR_COLUMN_LENGTH = 1818
ER_NOT_VALID_PASSWORD = 1819
ER_MUST_CHANGE_PASSWORD = 1820
ER_FK_NO_INDEX_CHILD = 1821
ER_FK_NO_INDEX_PARENT = 1822
ER_FK_FAIL_ADD_SYSTEM = 1823
ER_FK_CANNOT_OPEN_PARENT = 1824
ER_FK_INCORRECT_OPTION = 1825
ER_FK_DUP_NAME = 1826
ER_PASSWORD_FORMAT = 1827
ER_FK_COLUMN_CANNOT_DROP = 1828
ER_FK_COLUMN_CANNOT_DROP_CHILD = 1829
ER_FK_COLUMN_NOT_NULL = 1830
ER_DUP_INDEX = 1831
ER_FK_COLUMN_CANNOT_CHANGE = 1832
ER_FK_COLUMN_CANNOT_CHANGE_CHILD = 1833
OBSOLETE_ER_UNUSED5 = 1834
ER_MALFORMED_PACKET = 1835
ER_READ_ONLY_MODE = 1836
ER_GTID_NEXT_TYPE_UNDEFINED_GTID = 1837
ER_VARIABLE_NOT_SETTABLE_IN_SP = 1838
OBSOLETE_ER_CANT_SET_GTID_PURGED_WHEN_GTID_MODE_IS_OFF = 1839
ER_CANT_SET_GTID_PURGED_WHEN_GTID_EXECUTED_IS_NOT_EMPTY = 1840
ER_CANT_SET_GTID_PURGED_WHEN_OWNED_GTIDS_IS_NOT_EMPTY = 1841
ER_GTID_PURGED_WAS_CHANGED = 1842
ER_GTID_EXECUTED_WAS_CHANGED = 1843
ER_BINLOG_STMT_MODE_AND_NO_REPL_TABLES = 1844
ER_ALTER_OPERATION_NOT_SUPPORTED = 1845
ER_ALTER_OPERATION_NOT_SUPPORTED_REASON = 1846
ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_COPY = 1847
ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_PARTITION = 1848
ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_FK_RENAME = 1849
ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_COLUMN_TYPE = 1850
ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_FK_CHECK = 1851
OBSOLETE_ER_UNUSED6 = 1852
ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_NOPK = 1853
ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_AUTOINC = 1854
ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_HIDDEN_FTS = 1855
ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_CHANGE_FTS = 1856
ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_FTS = 1857
OBSOLETE_ER_SQL_REPLICA_SKIP_COUNTER_NOT_SETTABLE_IN_GTID_MODE = 1858
ER_DUP_UNKNOWN_IN_INDEX = 1859
ER_IDENT_CAUSES_TOO_LONG_PATH = 1860
ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_NOT_NULL = 1861
ER_MUST_CHANGE_PASSWORD_LOGIN = 1862
ER_ROW_IN_WRONG_PARTITION = 1863
ER_MTS_EVENT_BIGGER_PENDING_JOBS_SIZE_MAX = 1864
OBSOLETE_ER_INNODB_NO_FT_USES_PARSER = 1865
ER_BINLOG_LOGICAL_CORRUPTION = 1866
ER_WARN_PURGE_LOG_IN_USE = 1867
ER_WARN_PURGE_LOG_IS_ACTIVE = 1868
ER_AUTO_INCREMENT_CONFLICT = 1869
WARN_ON_BLOCKHOLE_IN_RBR = 1870
ER_SLAVE_MI_INIT_REPOSITORY = 1871
ER_SLAVE_RLI_INIT_REPOSITORY = 1872
ER_ACCESS_DENIED_CHANGE_USER_ERROR = 1873
ER_INNODB_READ_ONLY = 1874
ER_STOP_SLAVE_SQL_THREAD_TIMEOUT = 1875
ER_STOP_SLAVE_IO_THREAD_TIMEOUT = 1876
ER_TABLE_CORRUPT = 1877
ER_TEMP_FILE_WRITE_FAILURE = 1878
ER_INNODB_FT_AUX_NOT_HEX_ID = 1879
ER_OLD_TEMPORALS_UPGRADED = 1880
ER_INNODB_FORCED_RECOVERY = 1881
ER_AES_INVALID_IV = 1882
ER_PLUGIN_CANNOT_BE_UNINSTALLED = 1883
ER_GTID_UNSAFE_BINLOG_SPLITTABLE_STATEMENT_AND_ASSIGNED_GTID = 1884
ER_SLAVE_HAS_MORE_GTIDS_THAN_MASTER = 1885
ER_MISSING_KEY = 1886
WARN_NAMED_PIPE_ACCESS_EVERYONE = 1887
ER_FILE_CORRUPT = 3000
ER_ERROR_ON_MASTER = 3001
OBSOLETE_ER_INCONSISTENT_ERROR = 3002
ER_STORAGE_ENGINE_NOT_LOADED = 3003
ER_GET_STACKED_DA_WITHOUT_ACTIVE_HANDLER = 3004
ER_WARN_LEGACY_SYNTAX_CONVERTED = 3005
ER_BINLOG_UNSAFE_FULLTEXT_PLUGIN = 3006
ER_CANNOT_DISCARD_TEMPORARY_TABLE = 3007
ER_FK_DEPTH_EXCEEDED = 3008
ER_COL_COUNT_DOESNT_MATCH_PLEASE_UPDATE_V2 = 3009
ER_WARN_TRIGGER_DOESNT_HAVE_CREATED = 3010
ER_REFERENCED_TRG_DOES_NOT_EXIST = 3011
ER_EXPLAIN_NOT_SUPPORTED = 3012
ER_INVALID_FIELD_SIZE = 3013
ER_MISSING_HA_CREATE_OPTION = 3014
ER_ENGINE_OUT_OF_MEMORY = 3015
ER_PASSWORD_EXPIRE_ANONYMOUS_USER = 3016
ER_SLAVE_SQL_THREAD_MUST_STOP = 3017
ER_NO_FT_MATERIALIZED_SUBQUERY = 3018
ER_INNODB_UNDO_LOG_FULL = 3019
ER_INVALID_ARGUMENT_FOR_LOGARITHM = 3020
ER_SLAVE_CHANNEL_IO_THREAD_MUST_STOP = 3021
ER_WARN_OPEN_TEMP_TABLES_MUST_BE_ZERO = 3022
ER_WARN_ONLY_MASTER_LOG_FILE_NO_POS = 3023
ER_QUERY_TIMEOUT = 3024
ER_NON_RO_SELECT_DISABLE_TIMER = 3025
ER_DUP_LIST_ENTRY = 3026
OBSOLETE_ER_SQL_MODE_NO_EFFECT = 3027
ER_AGGREGATE_ORDER_FOR_UNION = 3028
ER_AGGREGATE_ORDER_NON_AGG_QUERY = 3029
ER_SLAVE_WORKER_STOPPED_PREVIOUS_THD_ERROR = 3030
ER_DONT_SUPPORT_REPLICA_PRESERVE_COMMIT_ORDER = 3031
ER_SERVER_OFFLINE_MODE = 3032
ER_GIS_DIFFERENT_SRIDS = 3033
ER_GIS_UNSUPPORTED_ARGUMENT = 3034
ER_GIS_UNKNOWN_ERROR = 3035
ER_GIS_UNKNOWN_EXCEPTION = 3036
ER_GIS_INVALID_DATA = 3037
ER_BOOST_GEOMETRY_EMPTY_INPUT_EXCEPTION = 3038
ER_BOOST_GEOMETRY_CENTROID_EXCEPTION = 3039
ER_BOOST_GEOMETRY_OVERLAY_INVALID_INPUT_EXCEPTION = 3040
ER_BOOST_GEOMETRY_TURN_INFO_EXCEPTION = 3041
ER_BOOST_GEOMETRY_SELF_INTERSECTION_POINT_EXCEPTION = 3042
ER_BOOST_GEOMETRY_UNKNOWN_EXCEPTION = 3043
ER_STD_BAD_ALLOC_ERROR = 3044
ER_STD_DOMAIN_ERROR = 3045
ER_STD_LENGTH_ERROR = 3046
ER_STD_INVALID_ARGUMENT = 3047
ER_STD_OUT_OF_RANGE_ERROR = 3048
ER_STD_OVERFLOW_ERROR = 3049
ER_STD_RANGE_ERROR = 3050
ER_STD_UNDERFLOW_ERROR = 3051
ER_STD_LOGIC_ERROR = 3052
ER_STD_RUNTIME_ERROR = 3053
ER_STD_UNKNOWN_EXCEPTION = 3054
ER_GIS_DATA_WRONG_ENDIANESS = 3055
ER_CHANGE_MASTER_PASSWORD_LENGTH = 3056
ER_USER_LOCK_WRONG_NAME = 3057
ER_USER_LOCK_DEADLOCK = 3058
ER_REPLACE_INACCESSIBLE_ROWS = 3059
ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_GIS = 3060
ER_ILLEGAL_USER_VAR = 3061
ER_GTID_MODE_OFF = 3062
OBSOLETE_ER_UNSUPPORTED_BY_REPLICATION_THREAD = 3063
ER_INCORRECT_TYPE = 3064
ER_FIELD_IN_ORDER_NOT_SELECT = 3065
ER_AGGREGATE_IN_ORDER_NOT_SELECT = 3066
ER_INVALID_RPL_WILD_TABLE_FILTER_PATTERN = 3067
ER_NET_OK_PACKET_TOO_LARGE = 3068
ER_INVALID_JSON_DATA = 3069
ER_INVALID_GEOJSON_MISSING_MEMBER = 3070
ER_INVALID_GEOJSON_WRONG_TYPE = 3071
ER_INVALID_GEOJSON_UNSPECIFIED = 3072
ER_DIMENSION_UNSUPPORTED = 3073
ER_SLAVE_CHANNEL_DOES_NOT_EXIST = 3074
OBSOLETE_ER_SLAVE_MULTIPLE_CHANNELS_HOST_PORT = 3075
ER_SLAVE_CHANNEL_NAME_INVALID_OR_TOO_LONG = 3076
ER_SLAVE_NEW_CHANNEL_WRONG_REPOSITORY = 3077
OBSOLETE_ER_SLAVE_CHANNEL_DELETE = 3078
ER_SLAVE_MULTIPLE_CHANNELS_CMD = 3079
ER_SLAVE_MAX_CHANNELS_EXCEEDED = 3080
ER_SLAVE_CHANNEL_MUST_STOP = 3081
ER_SLAVE_CHANNEL_NOT_RUNNING = 3082
ER_SLAVE_CHANNEL_WAS_RUNNING = 3083
ER_SLAVE_CHANNEL_WAS_NOT_RUNNING = 3084
ER_SLAVE_CHANNEL_SQL_THREAD_MUST_STOP = 3085
ER_SLAVE_CHANNEL_SQL_SKIP_COUNTER = 3086
ER_WRONG_FIELD_WITH_GROUP_V2 = 3087
ER_MIX_OF_GROUP_FUNC_AND_FIELDS_V2 = 3088
ER_WARN_DEPRECATED_SYSVAR_UPDATE = 3089
ER_WARN_DEPRECATED_SQLMODE = 3090
ER_CANNOT_LOG_PARTIAL_DROP_DATABASE_WITH_GTID = 3091
ER_GROUP_REPLICATION_CONFIGURATION = 3092
ER_GROUP_REPLICATION_RUNNING = 3093
ER_GROUP_REPLICATION_APPLIER_INIT_ERROR = 3094
ER_GROUP_REPLICATION_STOP_APPLIER_THREAD_TIMEOUT = 3095
ER_GROUP_REPLICATION_COMMUNICATION_LAYER_SESSION_ERROR = 3096
ER_GROUP_REPLICATION_COMMUNICATION_LAYER_JOIN_ERROR = 3097
ER_BEFORE_DML_VALIDATION_ERROR = 3098
ER_PREVENTS_VARIABLE_WITHOUT_RBR = 3099
ER_RUN_HOOK_ERROR = 3100
ER_TRANSACTION_ROLLBACK_DURING_COMMIT = 3101
ER_GENERATED_COLUMN_FUNCTION_IS_NOT_ALLOWED = 3102
ER_UNSUPPORTED_ALTER_INPLACE_ON_VIRTUAL_COLUMN = 3103
ER_WRONG_FK_OPTION_FOR_GENERATED_COLUMN = 3104
ER_NON_DEFAULT_VALUE_FOR_GENERATED_COLUMN = 3105
ER_UNSUPPORTED_ACTION_ON_GENERATED_COLUMN = 3106
ER_GENERATED_COLUMN_NON_PRIOR = 3107
ER_DEPENDENT_BY_GENERATED_COLUMN = 3108
ER_GENERATED_COLUMN_REF_AUTO_INC = 3109
ER_FEATURE_NOT_AVAILABLE = 3110
ER_CANT_SET_GTID_MODE = 3111
ER_CANT_USE_AUTO_POSITION_WITH_GTID_MODE_OFF = 3112
OBSOLETE_ER_CANT_REPLICATE_ANONYMOUS_WITH_AUTO_POSITION = 3113
OBSOLETE_ER_CANT_REPLICATE_ANONYMOUS_WITH_GTID_MODE_ON = 3114
OBSOLETE_ER_CANT_REPLICATE_GTID_WITH_GTID_MODE_OFF = 3115
ER_CANT_ENFORCE_GTID_CONSISTENCY_WITH_ONGOING_GTID_VIOLATING_TX = 3116
ER_ENFORCE_GTID_CONSISTENCY_WARN_WITH_ONGOING_GTID_VIOLATING_TX = 3117
ER_ACCOUNT_HAS_BEEN_LOCKED = 3118
ER_WRONG_TABLESPACE_NAME = 3119
ER_TABLESPACE_IS_NOT_EMPTY = 3120
ER_WRONG_FILE_NAME = 3121
ER_BOOST_GEOMETRY_INCONSISTENT_TURNS_EXCEPTION = 3122
ER_WARN_OPTIMIZER_HINT_SYNTAX_ERROR = 3123
ER_WARN_BAD_MAX_EXECUTION_TIME = 3124
ER_WARN_UNSUPPORTED_MAX_EXECUTION_TIME = 3125
ER_WARN_CONFLICTING_HINT = 3126
ER_WARN_UNKNOWN_QB_NAME = 3127
ER_UNRESOLVED_HINT_NAME = 3128
ER_WARN_ON_MODIFYING_GTID_EXECUTED_TABLE = 3129
ER_PLUGGABLE_PROTOCOL_COMMAND_NOT_SUPPORTED = 3130
ER_LOCKING_SERVICE_WRONG_NAME = 3131
ER_LOCKING_SERVICE_DEADLOCK = 3132
ER_LOCKING_SERVICE_TIMEOUT = 3133
ER_GIS_MAX_POINTS_IN_GEOMETRY_OVERFLOWED = 3134
ER_SQL_MODE_MERGED = 3135
ER_VTOKEN_PLUGIN_TOKEN_MISMATCH = 3136
ER_VTOKEN_PLUGIN_TOKEN_NOT_FOUND = 3137
ER_CANT_SET_VARIABLE_WHEN_OWNING_GTID = 3138
ER_SLAVE_CHANNEL_OPERATION_NOT_ALLOWED = 3139
ER_INVALID_JSON_TEXT = 3140
ER_INVALID_JSON_TEXT_IN_PARAM = 3141
ER_INVALID_JSON_BINARY_DATA = 3142
ER_INVALID_JSON_PATH = 3143
ER_INVALID_JSON_CHARSET = 3144
ER_INVALID_JSON_CHARSET_IN_FUNCTION = 3145
ER_INVALID_TYPE_FOR_JSON = 3146
ER_INVALID_CAST_TO_JSON = 3147
ER_INVALID_JSON_PATH_CHARSET = 3148
ER_INVALID_JSON_PATH_WILDCARD = 3149
ER_JSON_VALUE_TOO_BIG = 3150
ER_JSON_KEY_TOO_BIG = 3151
ER_JSON_USED_AS_KEY = 3152
ER_JSON_VACUOUS_PATH = 3153
ER_JSON_BAD_ONE_OR_ALL_ARG = 3154
ER_NUMERIC_JSON_VALUE_OUT_OF_RANGE = 3155
ER_INVALID_JSON_VALUE_FOR_CAST = 3156
ER_JSON_DOCUMENT_TOO_DEEP = 3157
ER_JSON_DOCUMENT_NULL_KEY = 3158
ER_SECURE_TRANSPORT_REQUIRED = 3159
ER_NO_SECURE_TRANSPORTS_CONFIGURED = 3160
ER_DISABLED_STORAGE_ENGINE = 3161
ER_USER_DOES_NOT_EXIST = 3162
ER_USER_ALREADY_EXISTS = 3163
ER_AUDIT_API_ABORT = 3164
ER_INVALID_JSON_PATH_ARRAY_CELL = 3165
ER_BUFPOOL_RESIZE_INPROGRESS = 3166
ER_FEATURE_DISABLED_SEE_DOC = 3167
ER_SERVER_ISNT_AVAILABLE = 3168
ER_SESSION_WAS_KILLED = 3169
ER_CAPACITY_EXCEEDED = 3170
ER_CAPACITY_EXCEEDED_IN_RANGE_OPTIMIZER = 3171
OBSOLETE_ER_TABLE_NEEDS_UPG_PART = 3172
ER_CANT_WAIT_FOR_EXECUTED_GTID_SET_WHILE_OWNING_A_GTID = 3173
ER_CANNOT_ADD_FOREIGN_BASE_COL_VIRTUAL = 3174
ER_CANNOT_CREATE_VIRTUAL_INDEX_CONSTRAINT = 3175
ER_ERROR_ON_MODIFYING_GTID_EXECUTED_TABLE = 3176
ER_LOCK_REFUSED_BY_ENGINE = 3177
ER_UNSUPPORTED_ALTER_ONLINE_ON_VIRTUAL_COLUMN = 3178
ER_MASTER_KEY_ROTATION_NOT_SUPPORTED_BY_SE = 3179
OBSOLETE_ER_MASTER_KEY_ROTATION_ERROR_BY_SE = 3180
ER_MASTER_KEY_ROTATION_BINLOG_FAILED = 3181
ER_MASTER_KEY_ROTATION_SE_UNAVAILABLE = 3182
ER_TABLESPACE_CANNOT_ENCRYPT = 3183
ER_INVALID_ENCRYPTION_OPTION = 3184
ER_CANNOT_FIND_KEY_IN_KEYRING = 3185
ER_CAPACITY_EXCEEDED_IN_PARSER = 3186
ER_UNSUPPORTED_ALTER_ENCRYPTION_INPLACE = 3187
ER_KEYRING_UDF_KEYRING_SERVICE_ERROR = 3188
ER_USER_COLUMN_OLD_LENGTH = 3189
ER_CANT_RESET_MASTER = 3190
ER_GROUP_REPLICATION_MAX_GROUP_SIZE = 3191
ER_CANNOT_ADD_FOREIGN_BASE_COL_STORED = 3192
ER_TABLE_REFERENCED = 3193
OBSOLETE_ER_PARTITION_ENGINE_DEPRECATED_FOR_TABLE = 3194
OBSOLETE_ER_WARN_USING_GEOMFROMWKB_TO_SET_SRID_ZERO = 3195
OBSOLETE_ER_WARN_USING_GEOMFROMWKB_TO_SET_SRID = 3196
ER_XA_RETRY = 3197
ER_KEYRING_AWS_UDF_AWS_KMS_ERROR = 3198
ER_BINLOG_UNSAFE_XA = 3199
ER_UDF_ERROR = 3200
ER_KEYRING_MIGRATION_FAILURE = 3201
ER_KEYRING_ACCESS_DENIED_ERROR = 3202
ER_KEYRING_MIGRATION_STATUS = 3203
OBSOLETE_ER_PLUGIN_FAILED_TO_OPEN_TABLES = 3204
OBSOLETE_ER_PLUGIN_FAILED_TO_OPEN_TABLE = 3205
OBSOLETE_ER_AUDIT_LOG_NO_KEYRING_PLUGIN_INSTALLED = 3206
OBSOLETE_ER_AUDIT_LOG_ENCRYPTION_PASSWORD_HAS_NOT_BEEN_SET = 3207
OBSOLETE_ER_AUDIT_LOG_COULD_NOT_CREATE_AES_KEY = 3208
OBSOLETE_ER_AUDIT_LOG_ENCRYPTION_PASSWORD_CANNOT_BE_FETCHED = 3209
OBSOLETE_ER_AUDIT_LOG_JSON_FILTERING_NOT_ENABLED = 3210
OBSOLETE_ER_AUDIT_LOG_UDF_INSUFFICIENT_PRIVILEGE = 3211
OBSOLETE_ER_AUDIT_LOG_SUPER_PRIVILEGE_REQUIRED = 3212
OBSOLETE_ER_COULD_NOT_REINITIALIZE_AUDIT_LOG_FILTERS = 3213
OBSOLETE_ER_AUDIT_LOG_UDF_INVALID_ARGUMENT_TYPE = 3214
OBSOLETE_ER_AUDIT_LOG_UDF_INVALID_ARGUMENT_COUNT = 3215
OBSOLETE_ER_AUDIT_LOG_HAS_NOT_BEEN_INSTALLED = 3216
OBSOLETE_ER_AUDIT_LOG_UDF_READ_INVALID_MAX_ARRAY_LENGTH_ARG_TYPE = 3217
ER_AUDIT_LOG_UDF_READ_INVALID_MAX_ARRAY_LENGTH_ARG_VALUE = 3218
OBSOLETE_ER_AUDIT_LOG_JSON_FILTER_PARSING_ERROR = 3219
OBSOLETE_ER_AUDIT_LOG_JSON_FILTER_NAME_CANNOT_BE_EMPTY = 3220
OBSOLETE_ER_AUDIT_LOG_JSON_USER_NAME_CANNOT_BE_EMPTY = 3221
OBSOLETE_ER_AUDIT_LOG_JSON_FILTER_DOES_NOT_EXISTS = 3222
OBSOLETE_ER_AUDIT_LOG_USER_FIRST_CHARACTER_MUST_BE_ALPHANUMERIC = 3223
OBSOLETE_ER_AUDIT_LOG_USER_NAME_INVALID_CHARACTER = 3224
OBSOLETE_ER_AUDIT_LOG_HOST_NAME_INVALID_CHARACTER = 3225
OBSOLETE_ER_XA_REPLICATION_FILTERS = 3226
OBSOLETE_ER_CANT_OPEN_ERROR_LOG = 3227
OBSOLETE_ER_GROUPING_ON_TIMESTAMP_IN_DST = 3228
OBSOLETE_ER_CANT_START_SERVER_NAMED_PIPE = 3229
ER_WRITE_SET_EXCEEDS_LIMIT = 3230
ER_UNSUPPORT_COMPRESSED_TEMPORARY_TABLE = 3500
ER_ACL_OPERATION_FAILED = 3501
ER_UNSUPPORTED_INDEX_ALGORITHM = 3502
ER_NO_SUCH_DB = 3503
ER_TOO_BIG_ENUM = 3504
ER_TOO_LONG_SET_ENUM_VALUE = 3505
ER_INVALID_DD_OBJECT = 3506
ER_UPDATING_DD_TABLE = 3507
ER_INVALID_DD_OBJECT_ID = 3508
ER_INVALID_DD_OBJECT_NAME = 3509
ER_TABLESPACE_MISSING_WITH_NAME = 3510
ER_TOO_LONG_ROUTINE_COMMENT = 3511
ER_SP_LOAD_FAILED = 3512
ER_INVALID_BITWISE_OPERANDS_SIZE = 3513
ER_INVALID_BITWISE_AGGREGATE_OPERANDS_SIZE = 3514
ER_WARN_UNSUPPORTED_HINT = 3515
ER_UNEXPECTED_GEOMETRY_TYPE = 3516
ER_SRS_PARSE_ERROR = 3517
ER_SRS_PROJ_PARAMETER_MISSING = 3518
ER_WARN_SRS_NOT_FOUND = 3519
ER_SRS_NOT_CARTESIAN = 3520
ER_SRS_NOT_CARTESIAN_UNDEFINED = 3521
ER_PK_INDEX_CANT_BE_INVISIBLE = 3522
ER_UNKNOWN_AUTHID = 3523
ER_FAILED_ROLE_GRANT = 3524
ER_OPEN_ROLE_TABLES = 3525
ER_FAILED_DEFAULT_ROLES = 3526
ER_COMPONENTS_NO_SCHEME = 3527
ER_COMPONENTS_NO_SCHEME_SERVICE = 3528
ER_COMPONENTS_CANT_LOAD = 3529
ER_ROLE_NOT_GRANTED = 3530
ER_FAILED_REVOKE_ROLE = 3531
ER_RENAME_ROLE = 3532
ER_COMPONENTS_CANT_ACQUIRE_SERVICE_IMPLEMENTATION = 3533
ER_COMPONENTS_CANT_SATISFY_DEPENDENCY = 3534
ER_COMPONENTS_LOAD_CANT_REGISTER_SERVICE_IMPLEMENTATION = 3535
ER_COMPONENTS_LOAD_CANT_INITIALIZE = 3536
ER_COMPONENTS_UNLOAD_NOT_LOADED = 3537
ER_COMPONENTS_UNLOAD_CANT_DEINITIALIZE = 3538
ER_COMPONENTS_CANT_RELEASE_SERVICE = 3539
ER_COMPONENTS_UNLOAD_CANT_UNREGISTER_SERVICE = 3540
ER_COMPONENTS_CANT_UNLOAD = 3541
ER_WARN_UNLOAD_THE_NOT_PERSISTED = 3542
ER_COMPONENT_TABLE_INCORRECT = 3543
ER_COMPONENT_MANIPULATE_ROW_FAILED = 3544
ER_COMPONENTS_UNLOAD_DUPLICATE_IN_GROUP = 3545
ER_CANT_SET_GTID_PURGED_DUE_SETS_CONSTRAINTS = 3546
ER_CANNOT_LOCK_USER_MANAGEMENT_CACHES = 3547
ER_SRS_NOT_FOUND = 3548
ER_VARIABLE_NOT_PERSISTED = 3549
ER_IS_QUERY_INVALID_CLAUSE = 3550
ER_UNABLE_TO_STORE_STATISTICS = 3551
ER_NO_SYSTEM_SCHEMA_ACCESS = 3552
ER_NO_SYSTEM_TABLESPACE_ACCESS = 3553
ER_NO_SYSTEM_TABLE_ACCESS = 3554
ER_NO_SYSTEM_TABLE_ACCESS_FOR_DICTIONARY_TABLE = 3555
ER_NO_SYSTEM_TABLE_ACCESS_FOR_SYSTEM_TABLE = 3556
ER_NO_SYSTEM_TABLE_ACCESS_FOR_TABLE = 3557
ER_INVALID_OPTION_KEY = 3558
ER_INVALID_OPTION_VALUE = 3559
ER_INVALID_OPTION_KEY_VALUE_PAIR = 3560
ER_INVALID_OPTION_START_CHARACTER = 3561
ER_INVALID_OPTION_END_CHARACTER = 3562
ER_INVALID_OPTION_CHARACTERS = 3563
ER_DUPLICATE_OPTION_KEY = 3564
ER_WARN_SRS_NOT_FOUND_AXIS_ORDER = 3565
ER_NO_ACCESS_TO_NATIVE_FCT = 3566
ER_RESET_MASTER_TO_VALUE_OUT_OF_RANGE = 3567
ER_UNRESOLVED_TABLE_LOCK = 3568
ER_DUPLICATE_TABLE_LOCK = 3569
ER_BINLOG_UNSAFE_SKIP_LOCKED = 3570
ER_BINLOG_UNSAFE_NOWAIT = 3571
ER_LOCK_NOWAIT = 3572
ER_CTE_RECURSIVE_REQUIRES_UNION = 3573
ER_CTE_RECURSIVE_REQUIRES_NONRECURSIVE_FIRST = 3574
ER_CTE_RECURSIVE_FORBIDS_AGGREGATION = 3575
ER_CTE_RECURSIVE_FORBIDDEN_JOIN_ORDER = 3576
ER_CTE_RECURSIVE_REQUIRES_SINGLE_REFERENCE = 3577
ER_SWITCH_TMP_ENGINE = 3578
ER_WINDOW_NO_SUCH_WINDOW = 3579
ER_WINDOW_CIRCULARITY_IN_WINDOW_GRAPH = 3580
ER_WINDOW_NO_CHILD_PARTITIONING = 3581
ER_WINDOW_NO_INHERIT_FRAME = 3582
ER_WINDOW_NO_REDEFINE_ORDER_BY = 3583
ER_WINDOW_FRAME_START_ILLEGAL = 3584
ER_WINDOW_FRAME_END_ILLEGAL = 3585
ER_WINDOW_FRAME_ILLEGAL = 3586
ER_WINDOW_RANGE_FRAME_ORDER_TYPE = 3587
ER_WINDOW_RANGE_FRAME_TEMPORAL_TYPE = 3588
ER_WINDOW_RANGE_FRAME_NUMERIC_TYPE = 3589
ER_WINDOW_RANGE_BOUND_NOT_CONSTANT = 3590
ER_WINDOW_DUPLICATE_NAME = 3591
ER_WINDOW_ILLEGAL_ORDER_BY = 3592
ER_WINDOW_INVALID_WINDOW_FUNC_USE = 3593
ER_WINDOW_INVALID_WINDOW_FUNC_ALIAS_USE = 3594
ER_WINDOW_NESTED_WINDOW_FUNC_USE_IN_WINDOW_SPEC = 3595
ER_WINDOW_ROWS_INTERVAL_USE = 3596
ER_WINDOW_NO_GROUP_ORDER_UNUSED = 3597
ER_WINDOW_EXPLAIN_JSON = 3598
ER_WINDOW_FUNCTION_IGNORES_FRAME = 3599
ER_WL9236_NOW_UNUSED = 3600
ER_INVALID_NO_OF_ARGS = 3601
ER_FIELD_IN_GROUPING_NOT_GROUP_BY = 3602
ER_TOO_LONG_TABLESPACE_COMMENT = 3603
ER_ENGINE_CANT_DROP_TABLE = 3604
ER_ENGINE_CANT_DROP_MISSING_TABLE = 3605
ER_TABLESPACE_DUP_FILENAME = 3606
ER_DB_DROP_RMDIR2 = 3607
ER_IMP_NO_FILES_MATCHED = 3608
ER_IMP_SCHEMA_DOES_NOT_EXIST = 3609
ER_IMP_TABLE_ALREADY_EXISTS = 3610
ER_IMP_INCOMPATIBLE_MYSQLD_VERSION = 3611
ER_IMP_INCOMPATIBLE_DD_VERSION = 3612
ER_IMP_INCOMPATIBLE_SDI_VERSION = 3613
ER_WARN_INVALID_HINT = 3614
ER_VAR_DOES_NOT_EXIST = 3615
ER_LONGITUDE_OUT_OF_RANGE = 3616
ER_LATITUDE_OUT_OF_RANGE = 3617
ER_NOT_IMPLEMENTED_FOR_GEOGRAPHIC_SRS = 3618
ER_ILLEGAL_PRIVILEGE_LEVEL = 3619
ER_NO_SYSTEM_VIEW_ACCESS = 3620
ER_COMPONENT_FILTER_FLABBERGASTED = 3621
ER_PART_EXPR_TOO_LONG = 3622
ER_UDF_DROP_DYNAMICALLY_REGISTERED = 3623
ER_UNABLE_TO_STORE_COLUMN_STATISTICS = 3624
ER_UNABLE_TO_UPDATE_COLUMN_STATISTICS = 3625
ER_UNABLE_TO_DROP_COLUMN_STATISTICS = 3626
ER_UNABLE_TO_BUILD_HISTOGRAM = 3627
ER_MANDATORY_ROLE = 3628
ER_MISSING_TABLESPACE_FILE = 3629
ER_PERSIST_ONLY_ACCESS_DENIED_ERROR = 3630
ER_CMD_NEED_SUPER = 3631
ER_PATH_IN_DATADIR = 3632
ER_CLONE_DDL_IN_PROGRESS = 3633
ER_CLONE_TOO_MANY_CONCURRENT_CLONES = 3634
ER_APPLIER_LOG_EVENT_VALIDATION_ERROR = 3635
ER_CTE_MAX_RECURSION_DEPTH = 3636
ER_NOT_HINT_UPDATABLE_VARIABLE = 3637
ER_CREDENTIALS_CONTRADICT_TO_HISTORY = 3638
ER_WARNING_PASSWORD_HISTORY_CLAUSES_VOID = 3639
ER_CLIENT_DOES_NOT_SUPPORT = 3640
ER_I_S_SKIPPED_TABLESPACE = 3641
ER_TABLESPACE_ENGINE_MISMATCH = 3642
ER_WRONG_SRID_FOR_COLUMN = 3643
ER_CANNOT_ALTER_SRID_DUE_TO_INDEX = 3644
ER_WARN_BINLOG_PARTIAL_UPDATES_DISABLED = 3645
ER_WARN_BINLOG_V1_ROW_EVENTS_DISABLED = 3646
ER_WARN_BINLOG_PARTIAL_UPDATES_SUGGESTS_PARTIAL_IMAGES = 3647
ER_COULD_NOT_APPLY_JSON_DIFF = 3648
ER_CORRUPTED_JSON_DIFF = 3649
ER_RESOURCE_GROUP_EXISTS = 3650
ER_RESOURCE_GROUP_NOT_EXISTS = 3651
ER_INVALID_VCPU_ID = 3652
ER_INVALID_VCPU_RANGE = 3653
ER_INVALID_THREAD_PRIORITY = 3654
ER_DISALLOWED_OPERATION = 3655
ER_RESOURCE_GROUP_BUSY = 3656
ER_RESOURCE_GROUP_DISABLED = 3657
ER_FEATURE_UNSUPPORTED = 3658
ER_ATTRIBUTE_IGNORED = 3659
ER_INVALID_THREAD_ID = 3660
ER_RESOURCE_GROUP_BIND_FAILED = 3661
ER_INVALID_USE_OF_FORCE_OPTION = 3662
ER_GROUP_REPLICATION_COMMAND_FAILURE = 3663
ER_SDI_OPERATION_FAILED = 3664
ER_MISSING_JSON_TABLE_VALUE = 3665
ER_WRONG_JSON_TABLE_VALUE = 3666
ER_TF_MUST_HAVE_ALIAS = 3667
ER_TF_FORBIDDEN_JOIN_TYPE = 3668
ER_JT_VALUE_OUT_OF_RANGE = 3669
ER_JT_MAX_NESTED_PATH = 3670
ER_PASSWORD_EXPIRATION_NOT_SUPPORTED_BY_AUTH_METHOD = 3671
ER_INVALID_GEOJSON_CRS_NOT_TOP_LEVEL = 3672
ER_BAD_NULL_ERROR_NOT_IGNORED = 3673
WARN_USELESS_SPATIAL_INDEX = 3674
ER_DISK_FULL_NOWAIT = 3675
ER_PARSE_ERROR_IN_DIGEST_FN = 3676
ER_UNDISCLOSED_PARSE_ERROR_IN_DIGEST_FN = 3677
ER_SCHEMA_DIR_EXISTS = 3678
ER_SCHEMA_DIR_MISSING = 3679
ER_SCHEMA_DIR_CREATE_FAILED = 3680
ER_SCHEMA_DIR_UNKNOWN = 3681
ER_ONLY_IMPLEMENTED_FOR_SRID_0_AND_4326 = 3682
ER_BINLOG_EXPIRE_LOG_DAYS_AND_SECS_USED_TOGETHER = 3683
ER_REGEXP_BUFFER_OVERFLOW = 3684
ER_REGEXP_ILLEGAL_ARGUMENT = 3685
ER_REGEXP_INDEX_OUTOFBOUNDS_ERROR = 3686
ER_REGEXP_INTERNAL_ERROR = 3687
ER_REGEXP_RULE_SYNTAX = 3688
ER_REGEXP_BAD_ESCAPE_SEQUENCE = 3689
ER_REGEXP_UNIMPLEMENTED = 3690
ER_REGEXP_MISMATCHED_PAREN = 3691
ER_REGEXP_BAD_INTERVAL = 3692
ER_REGEXP_MAX_LT_MIN = 3693
ER_REGEXP_INVALID_BACK_REF = 3694
ER_REGEXP_LOOK_BEHIND_LIMIT = 3695
ER_REGEXP_MISSING_CLOSE_BRACKET = 3696
ER_REGEXP_INVALID_RANGE = 3697
ER_REGEXP_STACK_OVERFLOW = 3698
ER_REGEXP_TIME_OUT = 3699
ER_REGEXP_PATTERN_TOO_BIG = 3700
ER_CANT_SET_ERROR_LOG_SERVICE = 3701
ER_EMPTY_PIPELINE_FOR_ERROR_LOG_SERVICE = 3702
ER_COMPONENT_FILTER_DIAGNOSTICS = 3703
ER_NOT_IMPLEMENTED_FOR_CARTESIAN_SRS = 3704
ER_NOT_IMPLEMENTED_FOR_PROJECTED_SRS = 3705
ER_NONPOSITIVE_RADIUS = 3706
ER_RESTART_SERVER_FAILED = 3707
ER_SRS_MISSING_MANDATORY_ATTRIBUTE = 3708
ER_SRS_MULTIPLE_ATTRIBUTE_DEFINITIONS = 3709
ER_SRS_NAME_CANT_BE_EMPTY_OR_WHITESPACE = 3710
ER_SRS_ORGANIZATION_CANT_BE_EMPTY_OR_WHITESPACE = 3711
ER_SRS_ID_ALREADY_EXISTS = 3712
ER_WARN_SRS_ID_ALREADY_EXISTS = 3713
ER_CANT_MODIFY_SRID_0 = 3714
ER_WARN_RESERVED_SRID_RANGE = 3715
ER_CANT_MODIFY_SRS_USED_BY_COLUMN = 3716
ER_SRS_INVALID_CHARACTER_IN_ATTRIBUTE = 3717
ER_SRS_ATTRIBUTE_STRING_TOO_LONG = 3718
ER_DEPRECATED_UTF8_ALIAS = 3719
ER_DEPRECATED_NATIONAL = 3720
ER_INVALID_DEFAULT_UTF8MB4_COLLATION = 3721
ER_UNABLE_TO_COLLECT_LOG_STATUS = 3722
ER_RESERVED_TABLESPACE_NAME = 3723
ER_UNABLE_TO_SET_OPTION = 3724
ER_SLAVE_POSSIBLY_DIVERGED_AFTER_DDL = 3725
ER_SRS_NOT_GEOGRAPHIC = 3726
ER_POLYGON_TOO_LARGE = 3727
ER_SPATIAL_UNIQUE_INDEX = 3728
ER_INDEX_TYPE_NOT_SUPPORTED_FOR_SPATIAL_INDEX = 3729
ER_FK_CANNOT_DROP_PARENT = 3730
ER_GEOMETRY_PARAM_LONGITUDE_OUT_OF_RANGE = 3731
ER_GEOMETRY_PARAM_LATITUDE_OUT_OF_RANGE = 3732
ER_FK_CANNOT_USE_VIRTUAL_COLUMN = 3733
ER_FK_NO_COLUMN_PARENT = 3734
ER_CANT_SET_ERROR_SUPPRESSION_LIST = 3735
ER_SRS_GEOGCS_INVALID_AXES = 3736
ER_SRS_INVALID_SEMI_MAJOR_AXIS = 3737
ER_SRS_INVALID_INVERSE_FLATTENING = 3738
ER_SRS_INVALID_ANGULAR_UNIT = 3739
ER_SRS_INVALID_PRIME_MERIDIAN = 3740
ER_TRANSFORM_SOURCE_SRS_NOT_SUPPORTED = 3741
ER_TRANSFORM_TARGET_SRS_NOT_SUPPORTED = 3742
ER_TRANSFORM_SOURCE_SRS_MISSING_TOWGS84 = 3743
ER_TRANSFORM_TARGET_SRS_MISSING_TOWGS84 = 3744
ER_TEMP_TABLE_PREVENTS_SWITCH_SESSION_BINLOG_FORMAT = 3745
ER_TEMP_TABLE_PREVENTS_SWITCH_GLOBAL_BINLOG_FORMAT = 3746
ER_RUNNING_APPLIER_PREVENTS_SWITCH_GLOBAL_BINLOG_FORMAT = 3747
ER_CLIENT_GTID_UNSAFE_CREATE_DROP_TEMP_TABLE_IN_TRX_IN_SBR = 3748
OBSOLETE_ER_XA_CANT_CREATE_MDL_BACKUP = 3749
ER_TABLE_WITHOUT_PK = 3750
ER_WARN_DATA_TRUNCATED_FUNCTIONAL_INDEX = 3751
ER_WARN_DATA_OUT_OF_RANGE_FUNCTIONAL_INDEX = 3752
ER_FUNCTIONAL_INDEX_ON_JSON_OR_GEOMETRY_FUNCTION = 3753
ER_FUNCTIONAL_INDEX_REF_AUTO_INCREMENT = 3754
ER_CANNOT_DROP_COLUMN_FUNCTIONAL_INDEX = 3755
ER_FUNCTIONAL_INDEX_PRIMARY_KEY = 3756
ER_FUNCTIONAL_INDEX_ON_LOB = 3757
ER_FUNCTIONAL_INDEX_FUNCTION_IS_NOT_ALLOWED = 3758
ER_FULLTEXT_FUNCTIONAL_INDEX = 3759
ER_SPATIAL_FUNCTIONAL_INDEX = 3760
ER_WRONG_KEY_COLUMN_FUNCTIONAL_INDEX = 3761
ER_FUNCTIONAL_INDEX_ON_FIELD = 3762
ER_GENERATED_COLUMN_NAMED_FUNCTION_IS_NOT_ALLOWED = 3763
ER_GENERATED_COLUMN_ROW_VALUE = 3764
ER_GENERATED_COLUMN_VARIABLES = 3765
ER_DEPENDENT_BY_DEFAULT_GENERATED_VALUE = 3766
ER_DEFAULT_VAL_GENERATED_NON_PRIOR = 3767
ER_DEFAULT_VAL_GENERATED_REF_AUTO_INC = 3768
ER_DEFAULT_VAL_GENERATED_FUNCTION_IS_NOT_ALLOWED = 3769
ER_DEFAULT_VAL_GENERATED_NAMED_FUNCTION_IS_NOT_ALLOWED = 3770
ER_DEFAULT_VAL_GENERATED_ROW_VALUE = 3771
ER_DEFAULT_VAL_GENERATED_VARIABLES = 3772
ER_DEFAULT_AS_VAL_GENERATED = 3773
ER_UNSUPPORTED_ACTION_ON_DEFAULT_VAL_GENERATED = 3774
ER_GTID_UNSAFE_ALTER_ADD_COL_WITH_DEFAULT_EXPRESSION = 3775
ER_FK_CANNOT_CHANGE_ENGINE = 3776
ER_WARN_DEPRECATED_USER_SET_EXPR = 3777
ER_WARN_DEPRECATED_UTF8MB3_COLLATION = 3778
ER_WARN_DEPRECATED_NESTED_COMMENT_SYNTAX = 3779
ER_FK_INCOMPATIBLE_COLUMNS = 3780
ER_GR_HOLD_WAIT_TIMEOUT = 3781
ER_GR_HOLD_KILLED = 3782
ER_GR_HOLD_MEMBER_STATUS_ERROR = 3783
ER_RPL_ENCRYPTION_FAILED_TO_FETCH_KEY = 3784
ER_RPL_ENCRYPTION_KEY_NOT_FOUND = 3785
ER_RPL_ENCRYPTION_KEYRING_INVALID_KEY = 3786
ER_RPL_ENCRYPTION_HEADER_ERROR = 3787
ER_RPL_ENCRYPTION_FAILED_TO_ROTATE_LOGS = 3788
ER_RPL_ENCRYPTION_KEY_EXISTS_UNEXPECTED = 3789
ER_RPL_ENCRYPTION_FAILED_TO_GENERATE_KEY = 3790
ER_RPL_ENCRYPTION_FAILED_TO_STORE_KEY = 3791
ER_RPL_ENCRYPTION_FAILED_TO_REMOVE_KEY = 3792
ER_RPL_ENCRYPTION_UNABLE_TO_CHANGE_OPTION = 3793
ER_RPL_ENCRYPTION_MASTER_KEY_RECOVERY_FAILED = 3794
ER_SLOW_LOG_MODE_IGNORED_WHEN_NOT_LOGGING_TO_FILE = 3795
ER_GRP_TRX_CONSISTENCY_NOT_ALLOWED = 3796
ER_GRP_TRX_CONSISTENCY_BEFORE = 3797
ER_GRP_TRX_CONSISTENCY_AFTER_ON_TRX_BEGIN = 3798
ER_GRP_TRX_CONSISTENCY_BEGIN_NOT_ALLOWED = 3799
ER_FUNCTIONAL_INDEX_ROW_VALUE_IS_NOT_ALLOWED = 3800
ER_RPL_ENCRYPTION_FAILED_TO_ENCRYPT = 3801
ER_PAGE_TRACKING_NOT_STARTED = 3802
ER_PAGE_TRACKING_RANGE_NOT_TRACKED = 3803
ER_PAGE_TRACKING_CANNOT_PURGE = 3804
ER_RPL_ENCRYPTION_CANNOT_ROTATE_BINLOG_MASTER_KEY = 3805
ER_BINLOG_MASTER_KEY_RECOVERY_OUT_OF_COMBINATION = 3806
ER_BINLOG_MASTER_KEY_ROTATION_FAIL_TO_OPERATE_KEY = 3807
ER_BINLOG_MASTER_KEY_ROTATION_FAIL_TO_ROTATE_LOGS = 3808
ER_BINLOG_MASTER_KEY_ROTATION_FAIL_TO_REENCRYPT_LOG = 3809
ER_BINLOG_MASTER_KEY_ROTATION_FAIL_TO_CLEANUP_UNUSED_KEYS = 3810
ER_BINLOG_MASTER_KEY_ROTATION_FAIL_TO_CLEANUP_AUX_KEY = 3811
ER_NON_BOOLEAN_EXPR_FOR_CHECK_CONSTRAINT = 3812
ER_COLUMN_CHECK_CONSTRAINT_REFERENCES_OTHER_COLUMN = 3813
ER_CHECK_CONSTRAINT_NAMED_FUNCTION_IS_NOT_ALLOWED = 3814
ER_CHECK_CONSTRAINT_FUNCTION_IS_NOT_ALLOWED = 3815
ER_CHECK_CONSTRAINT_VARIABLES = 3816
ER_CHECK_CONSTRAINT_ROW_VALUE = 3817
ER_CHECK_CONSTRAINT_REFERS_AUTO_INCREMENT_COLUMN = 3818
ER_CHECK_CONSTRAINT_VIOLATED = 3819
ER_CHECK_CONSTRAINT_REFERS_UNKNOWN_COLUMN = 3820
ER_CHECK_CONSTRAINT_NOT_FOUND = 3821
ER_CHECK_CONSTRAINT_DUP_NAME = 3822
ER_CHECK_CONSTRAINT_CLAUSE_USING_FK_REFER_ACTION_COLUMN = 3823
WARN_UNENCRYPTED_TABLE_IN_ENCRYPTED_DB = 3824
ER_INVALID_ENCRYPTION_REQUEST = 3825
ER_CANNOT_SET_TABLE_ENCRYPTION = 3826
ER_CANNOT_SET_DATABASE_ENCRYPTION = 3827
ER_CANNOT_SET_TABLESPACE_ENCRYPTION = 3828
ER_TABLESPACE_CANNOT_BE_ENCRYPTED = 3829
ER_TABLESPACE_CANNOT_BE_DECRYPTED = 3830
ER_TABLESPACE_TYPE_UNKNOWN = 3831
ER_TARGET_TABLESPACE_UNENCRYPTED = 3832
ER_CANNOT_USE_ENCRYPTION_CLAUSE = 3833
ER_INVALID_MULTIPLE_CLAUSES = 3834
ER_UNSUPPORTED_USE_OF_GRANT_AS = 3835
ER_UKNOWN_AUTH_ID_OR_ACCESS_DENIED_FOR_GRANT_AS = 3836
ER_DEPENDENT_BY_FUNCTIONAL_INDEX = 3837
ER_PLUGIN_NOT_EARLY = 3838
ER_INNODB_REDO_LOG_ARCHIVE_START_SUBDIR_PATH = 3839
ER_INNODB_REDO_LOG_ARCHIVE_START_TIMEOUT = 3840
ER_INNODB_REDO_LOG_ARCHIVE_DIRS_INVALID = 3841
ER_INNODB_REDO_LOG_ARCHIVE_LABEL_NOT_FOUND = 3842
ER_INNODB_REDO_LOG_ARCHIVE_DIR_EMPTY = 3843
ER_INNODB_REDO_LOG_ARCHIVE_NO_SUCH_DIR = 3844
ER_INNODB_REDO_LOG_ARCHIVE_DIR_CLASH = 3845
ER_INNODB_REDO_LOG_ARCHIVE_DIR_PERMISSIONS = 3846
ER_INNODB_REDO_LOG_ARCHIVE_FILE_CREATE = 3847
ER_INNODB_REDO_LOG_ARCHIVE_ACTIVE = 3848
ER_INNODB_REDO_LOG_ARCHIVE_INACTIVE = 3849
ER_INNODB_REDO_LOG_ARCHIVE_FAILED = 3850
ER_INNODB_REDO_LOG_ARCHIVE_SESSION = 3851
ER_STD_REGEX_ERROR = 3852
ER_INVALID_JSON_TYPE = 3853
ER_CANNOT_CONVERT_STRING = 3854
ER_DEPENDENT_BY_PARTITION_FUNC = 3855
ER_WARN_DEPRECATED_FLOAT_AUTO_INCREMENT = 3856
ER_RPL_CANT_STOP_SLAVE_WHILE_LOCKED_BACKUP = 3857
ER_WARN_DEPRECATED_FLOAT_DIGITS = 3858
ER_WARN_DEPRECATED_FLOAT_UNSIGNED = 3859
ER_WARN_DEPRECATED_INTEGER_DISPLAY_WIDTH = 3860
ER_WARN_DEPRECATED_ZEROFILL = 3861
ER_CLONE_DONOR = 3862
ER_CLONE_PROTOCOL = 3863
ER_CLONE_DONOR_VERSION = 3864
ER_CLONE_OS = 3865
ER_CLONE_PLATFORM = 3866
ER_CLONE_CHARSET = 3867
ER_CLONE_CONFIG = 3868
ER_CLONE_SYS_CONFIG = 3869
ER_CLONE_PLUGIN_MATCH = 3870
ER_CLONE_LOOPBACK = 3871
ER_CLONE_ENCRYPTION = 3872
ER_CLONE_DISK_SPACE = 3873
ER_CLONE_IN_PROGRESS = 3874
ER_CLONE_DISALLOWED = 3875
ER_CANNOT_GRANT_ROLES_TO_ANONYMOUS_USER = 3876
ER_SECONDARY_ENGINE_PLUGIN = 3877
ER_SECOND_PASSWORD_CANNOT_BE_EMPTY = 3878
ER_DB_ACCESS_DENIED = 3879
ER_DA_AUTH_ID_WITH_SYSTEM_USER_PRIV_IN_MANDATORY_ROLES = 3880
ER_DA_RPL_GTID_TABLE_CANNOT_OPEN = 3881
ER_GEOMETRY_IN_UNKNOWN_LENGTH_UNIT = 3882
ER_DA_PLUGIN_INSTALL_ERROR = 3883
ER_NO_SESSION_TEMP = 3884
ER_DA_UNKNOWN_ERROR_NUMBER = 3885
ER_COLUMN_CHANGE_SIZE = 3886
ER_REGEXP_INVALID_CAPTURE_GROUP_NAME = 3887
ER_DA_SSL_LIBRARY_ERROR = 3888
ER_SECONDARY_ENGINE = 3889
ER_SECONDARY_ENGINE_DDL = 3890
ER_INCORRECT_CURRENT_PASSWORD = 3891
ER_MISSING_CURRENT_PASSWORD = 3892
ER_CURRENT_PASSWORD_NOT_REQUIRED = 3893
ER_PASSWORD_CANNOT_BE_RETAINED_ON_PLUGIN_CHANGE = 3894
ER_CURRENT_PASSWORD_CANNOT_BE_RETAINED = 3895
ER_PARTIAL_REVOKES_EXIST = 3896
ER_CANNOT_GRANT_SYSTEM_PRIV_TO_MANDATORY_ROLE = 3897
ER_XA_REPLICATION_FILTERS = 3898
ER_UNSUPPORTED_SQL_MODE = 3899
ER_REGEXP_INVALID_FLAG = 3900
ER_PARTIAL_REVOKE_AND_DB_GRANT_BOTH_EXISTS = 3901
ER_UNIT_NOT_FOUND = 3902
ER_INVALID_JSON_VALUE_FOR_FUNC_INDEX = 3903
ER_JSON_VALUE_OUT_OF_RANGE_FOR_FUNC_INDEX = 3904
ER_EXCEEDED_MV_KEYS_NUM = 3905
ER_EXCEEDED_MV_KEYS_SPACE = 3906
ER_FUNCTIONAL_INDEX_DATA_IS_TOO_LONG = 3907
ER_WRONG_MVI_VALUE = 3908
ER_WARN_FUNC_INDEX_NOT_APPLICABLE = 3909
ER_GRP_RPL_UDF_ERROR = 3910
ER_UPDATE_GTID_PURGED_WITH_GR = 3911
ER_GROUPING_ON_TIMESTAMP_IN_DST = 3912
ER_TABLE_NAME_CAUSES_TOO_LONG_PATH = 3913
ER_AUDIT_LOG_INSUFFICIENT_PRIVILEGE = 3914
OBSOLETE_ER_AUDIT_LOG_PASSWORD_HAS_BEEN_COPIED = 3915
ER_DA_GRP_RPL_STARTED_AUTO_REJOIN = 3916
ER_SYSVAR_CHANGE_DURING_QUERY = 3917
ER_GLOBSTAT_CHANGE_DURING_QUERY = 3918
ER_GRP_RPL_MESSAGE_SERVICE_INIT_FAILURE = 3919
ER_CHANGE_MASTER_WRONG_COMPRESSION_ALGORITHM_CLIENT = 3920
ER_CHANGE_MASTER_WRONG_COMPRESSION_LEVEL_CLIENT = 3921
ER_WRONG_COMPRESSION_ALGORITHM_CLIENT = 3922
ER_WRONG_COMPRESSION_LEVEL_CLIENT = 3923
ER_CHANGE_MASTER_WRONG_COMPRESSION_ALGORITHM_LIST_CLIENT = 3924
ER_CLIENT_PRIVILEGE_CHECKS_USER_CANNOT_BE_ANONYMOUS = 3925
ER_CLIENT_PRIVILEGE_CHECKS_USER_DOES_NOT_EXIST = 3926
ER_CLIENT_PRIVILEGE_CHECKS_USER_CORRUPT = 3927
ER_CLIENT_PRIVILEGE_CHECKS_USER_NEEDS_RPL_APPLIER_PRIV = 3928
ER_WARN_DA_PRIVILEGE_NOT_REGISTERED = 3929
ER_CLIENT_KEYRING_UDF_KEY_INVALID = 3930
ER_CLIENT_KEYRING_UDF_KEY_TYPE_INVALID = 3931
ER_CLIENT_KEYRING_UDF_KEY_TOO_LONG = 3932
ER_CLIENT_KEYRING_UDF_KEY_TYPE_TOO_LONG = 3933
ER_JSON_SCHEMA_VALIDATION_ERROR_WITH_DETAILED_REPORT = 3934
ER_DA_UDF_INVALID_CHARSET_SPECIFIED = 3935
ER_DA_UDF_INVALID_CHARSET = 3936
ER_DA_UDF_INVALID_COLLATION = 3937
ER_DA_UDF_INVALID_EXTENSION_ARGUMENT_TYPE = 3938
ER_MULTIPLE_CONSTRAINTS_WITH_SAME_NAME = 3939
ER_CONSTRAINT_NOT_FOUND = 3940
ER_ALTER_CONSTRAINT_ENFORCEMENT_NOT_SUPPORTED = 3941
ER_TABLE_VALUE_CONSTRUCTOR_MUST_HAVE_COLUMNS = 3942
ER_TABLE_VALUE_CONSTRUCTOR_CANNOT_HAVE_DEFAULT = 3943
ER_CLIENT_QUERY_FAILURE_INVALID_NON_ROW_FORMAT = 3944
ER_REQUIRE_ROW_FORMAT_INVALID_VALUE = 3945
ER_FAILED_TO_DETERMINE_IF_ROLE_IS_MANDATORY = 3946
ER_FAILED_TO_FETCH_MANDATORY_ROLE_LIST = 3947
ER_CLIENT_LOCAL_FILES_DISABLED = 3948
ER_IMP_INCOMPATIBLE_CFG_VERSION = 3949
ER_DA_OOM = 3950
ER_DA_UDF_INVALID_ARGUMENT_TO_SET_CHARSET = 3951
ER_DA_UDF_INVALID_RETURN_TYPE_TO_SET_CHARSET = 3952
ER_MULTIPLE_INTO_CLAUSES = 3953
ER_MISPLACED_INTO = 3954
ER_USER_ACCESS_DENIED_FOR_USER_ACCOUNT_BLOCKED_BY_PASSWORD_LOCK = 3955
ER_WARN_DEPRECATED_YEAR_UNSIGNED = 3956
ER_CLONE_NETWORK_PACKET = 3957
ER_SDI_OPERATION_FAILED_MISSING_RECORD = 3958
ER_DEPENDENT_BY_CHECK_CONSTRAINT = 3959
ER_GRP_OPERATION_NOT_ALLOWED_GR_MUST_STOP = 3960
ER_WARN_DEPRECATED_JSON_TABLE_ON_ERROR_ON_EMPTY = 3961
ER_WARN_DEPRECATED_INNER_INTO = 3962
ER_WARN_DEPRECATED_VALUES_FUNCTION_ALWAYS_NULL = 3963
ER_WARN_DEPRECATED_SQL_CALC_FOUND_ROWS = 3964
ER_WARN_DEPRECATED_FOUND_ROWS = 3965
ER_MISSING_JSON_VALUE = 3966
ER_MULTIPLE_JSON_VALUES = 3967
ER_HOSTNAME_TOO_LONG = 3968
ER_WARN_CLIENT_DEPRECATED_PARTITION_PREFIX_KEY = 3969
ER_GROUP_REPLICATION_USER_EMPTY_MSG = 3970
ER_GROUP_REPLICATION_USER_MANDATORY_MSG = 3971
ER_GROUP_REPLICATION_PASSWORD_LENGTH = 3972
ER_SUBQUERY_TRANSFORM_REJECTED = 3973
ER_DA_GRP_RPL_RECOVERY_ENDPOINT_FORMAT = 3974
ER_DA_GRP_RPL_RECOVERY_ENDPOINT_INVALID = 3975
ER_WRONG_VALUE_FOR_VAR_PLUS_ACTIONABLE_PART = 3976
ER_STATEMENT_NOT_ALLOWED_AFTER_START_TRANSACTION = 3977
ER_FOREIGN_KEY_WITH_ATOMIC_CREATE_SELECT = 3978
ER_NOT_ALLOWED_WITH_START_TRANSACTION = 3979
ER_INVALID_JSON_ATTRIBUTE = 3980
ER_ENGINE_ATTRIBUTE_NOT_SUPPORTED = 3981
ER_INVALID_USER_ATTRIBUTE_JSON = 3982
ER_INNODB_REDO_DISABLED = 3983
ER_INNODB_REDO_ARCHIVING_ENABLED = 3984
ER_MDL_OUT_OF_RESOURCES = 3985
ER_IMPLICIT_COMPARISON_FOR_JSON = 3986
ER_FUNCTION_DOES_NOT_SUPPORT_CHARACTER_SET = 3987
ER_IMPOSSIBLE_STRING_CONVERSION = 3988
ER_SCHEMA_READ_ONLY = 3989
ER_RPL_ASYNC_RECONNECT_GTID_MODE_OFF = 3990
ER_RPL_ASYNC_RECONNECT_AUTO_POSITION_OFF = 3991
ER_DISABLE_GTID_MODE_REQUIRES_ASYNC_RECONNECT_OFF = 3992
ER_DISABLE_AUTO_POSITION_REQUIRES_ASYNC_RECONNECT_OFF = 3993
ER_INVALID_PARAMETER_USE = 3994
ER_CHARACTER_SET_MISMATCH = 3995
ER_WARN_VAR_VALUE_CHANGE_NOT_SUPPORTED = 3996
ER_INVALID_TIME_ZONE_INTERVAL = 3997
ER_INVALID_CAST = 3998
ER_HYPERGRAPH_NOT_SUPPORTED_YET = 3999
ER_WARN_HYPERGRAPH_EXPERIMENTAL = 4000
ER_DA_NO_ERROR_LOG_PARSER_CONFIGURED = 4001
ER_DA_ERROR_LOG_TABLE_DISABLED = 4002
ER_DA_ERROR_LOG_MULTIPLE_FILTERS = 4003
ER_DA_CANT_OPEN_ERROR_LOG = 4004
ER_USER_REFERENCED_AS_DEFINER = 4005
ER_CANNOT_USER_REFERENCED_AS_DEFINER = 4006
ER_REGEX_NUMBER_TOO_BIG = 4007
ER_SPVAR_NONINTEGER_TYPE = 4008
WARN_UNSUPPORTED_ACL_TABLES_READ = 4009
ER_BINLOG_UNSAFE_ACL_TABLE_READ_IN_DML_DDL = 4010
ER_STOP_REPLICA_MONITOR_IO_THREAD_TIMEOUT = 4011
ER_STARTING_REPLICA_MONITOR_IO_THREAD = 4012
ER_CANT_USE_ANONYMOUS_TO_GTID_WITH_GTID_MODE_NOT_ON = 4013
ER_CANT_COMBINE_ANONYMOUS_TO_GTID_AND_AUTOPOSITION = 4014
ER_ASSIGN_GTIDS_TO_ANONYMOUS_TRANSACTIONS_REQUIRES_GTID_MODE_ON = 4015
ER_SQL_REPLICA_SKIP_COUNTER_USED_WITH_GTID_MODE_ON = 4016
ER_USING_ASSIGN_GTIDS_TO_ANONYMOUS_TRANSACTIONS_AS_LOCAL_OR_UUID = 4017
ER_CANT_SET_ANONYMOUS_TO_GTID_AND_WAIT_UNTIL_SQL_THD_AFTER_GTIDS = 4018
ER_CANT_SET_SQL_AFTER_OR_BEFORE_GTIDS_WITH_ANONYMOUS_TO_GTID = 4019
ER_ANONYMOUS_TO_GTID_UUID_SAME_AS_GROUP_NAME = 4020
ER_CANT_USE_SAME_UUID_AS_GROUP_NAME = 4021
ER_GRP_RPL_RECOVERY_CHANNEL_STILL_RUNNING = 4022
ER_INNODB_INVALID_AUTOEXTEND_SIZE_VALUE = 4023
ER_INNODB_INCOMPATIBLE_WITH_TABLESPACE = 4024
ER_INNODB_AUTOEXTEND_SIZE_OUT_OF_RANGE = 4025
ER_CANNOT_USE_AUTOEXTEND_SIZE_CLAUSE = 4026
ER_ROLE_GRANTED_TO_ITSELF = 4027
ER_TABLE_MUST_HAVE_A_VISIBLE_COLUMN = 4028
ER_INNODB_COMPRESSION_FAILURE = 4029
ER_WARN_ASYNC_CONN_FAILOVER_NETWORK_NAMESPACE = 4030
ER_CLIENT_INTERACTION_TIMEOUT = 4031
ER_INVALID_CAST_TO_GEOMETRY = 4032
ER_INVALID_CAST_POLYGON_RING_DIRECTION = 4033
ER_GIS_DIFFERENT_SRIDS_AGGREGATION = 4034
ER_RELOAD_KEYRING_FAILURE = 4035
ER_SDI_GET_KEYS_INVALID_TABLESPACE = 4036
ER_CHANGE_RPL_SRC_WRONG_COMPRESSION_ALGORITHM_SIZE = 4037
ER_WARN_DEPRECATED_TLS_VERSION_FOR_CHANNEL_CLI = 4038
ER_CANT_USE_SAME_UUID_AS_VIEW_CHANGE_UUID = 4039
ER_ANONYMOUS_TO_GTID_UUID_SAME_AS_VIEW_CHANGE_UUID = 4040
ER_GRP_RPL_VIEW_CHANGE_UUID_FAIL_GET_VARIABLE = 4041
ER_WARN_ADUIT_LOG_MAX_SIZE_AND_PRUNE_SECONDS = 4042
ER_WARN_ADUIT_LOG_MAX_SIZE_CLOSE_TO_ROTATE_ON_SIZE = 4043
ER_KERBEROS_CREATE_USER = 4044
ER_INSTALL_PLUGIN_CONFLICT_CLIENT = 4045
ER_DA_ERROR_LOG_COMPONENT_FLUSH_FAILED = 4046
ER_WARN_SQL_AFTER_MTS_GAPS_GAP_NOT_CALCULATED = 4047
ER_INVALID_ASSIGNMENT_TARGET = 4048
ER_OPERATION_NOT_ALLOWED_ON_GR_SECONDARY = 4049
ER_GRP_RPL_FAILOVER_CHANNEL_STATUS_PROPAGATION = 4050
ER_WARN_AUDIT_LOG_FORMAT_UNIX_TIMESTAMP_ONLY_WHEN_JSON = 4051
ER_INVALID_MFA_PLUGIN_SPECIFIED = 4052
ER_IDENTIFIED_BY_UNSUPPORTED = 4053
ER_INVALID_PLUGIN_FOR_REGISTRATION = 4054
ER_PLUGIN_REQUIRES_REGISTRATION = 4055
ER_MFA_METHOD_EXISTS = 4056
ER_MFA_METHOD_NOT_EXISTS = 4057
ER_AUTHENTICATION_POLICY_MISMATCH = 4058
ER_PLUGIN_REGISTRATION_DONE = 4059
ER_INVALID_USER_FOR_REGISTRATION = 4060
ER_USER_REGISTRATION_FAILED = 4061
ER_MFA_METHODS_INVALID_ORDER = 4062
ER_MFA_METHODS_IDENTICAL = 4063
ER_INVALID_MFA_OPERATIONS_FOR_PASSWORDLESS_USER = 4064
ER_CHANGE_REPLICATION_SOURCE_NO_OPTIONS_FOR_GTID_ONLY = 4065
ER_CHANGE_REP_SOURCE_CANT_DISABLE_REQ_ROW_FORMAT_WITH_GTID_ONLY = 4066
ER_CHANGE_REP_SOURCE_CANT_DISABLE_AUTO_POSITION_WITH_GTID_ONLY = 4067
ER_CHANGE_REP_SOURCE_CANT_DISABLE_GTID_ONLY_WITHOUT_POSITIONS = 4068
ER_CHANGE_REP_SOURCE_CANT_DISABLE_AUTO_POS_WITHOUT_POSITIONS = 4069
ER_CHANGE_REP_SOURCE_GR_CHANNEL_WITH_GTID_MODE_NOT_ON = 4070
ER_CANT_USE_GTID_ONLY_WITH_GTID_MODE_NOT_ON = 4071
ER_WARN_C_DISABLE_GTID_ONLY_WITH_SOURCE_AUTO_POS_INVALID_POS = 4072
ER_DA_SSL_FIPS_MODE_ERROR = 4073
CR_UNKNOWN_ERROR = 2000
CR_SOCKET_CREATE_ERROR = 2001
CR_CONNECTION_ERROR = 2002
CR_CONN_HOST_ERROR = 2003
CR_IPSOCK_ERROR = 2004
CR_UNKNOWN_HOST = 2005
CR_SERVER_GONE_ERROR = 2006
CR_VERSION_ERROR = 2007
CR_OUT_OF_MEMORY = 2008
CR_WRONG_HOST_INFO = 2009
CR_LOCALHOST_CONNECTION = 2010
CR_TCP_CONNECTION = 2011
CR_SERVER_HANDSHAKE_ERR = 2012
CR_SERVER_LOST = 2013
CR_COMMANDS_OUT_OF_SYNC = 2014
CR_NAMEDPIPE_CONNECTION = 2015
CR_NAMEDPIPEWAIT_ERROR = 2016
CR_NAMEDPIPEOPEN_ERROR = 2017
CR_NAMEDPIPESETSTATE_ERROR = 2018
CR_CANT_READ_CHARSET = 2019
CR_NET_PACKET_TOO_LARGE = 2020
CR_EMBEDDED_CONNECTION = 2021
CR_PROBE_SLAVE_STATUS = 2022
CR_PROBE_SLAVE_HOSTS = 2023
CR_PROBE_SLAVE_CONNECT = 2024
CR_PROBE_MASTER_CONNECT = 2025
CR_SSL_CONNECTION_ERROR = 2026
CR_MALFORMED_PACKET = 2027
CR_WRONG_LICENSE = 2028
CR_NULL_POINTER = 2029
CR_NO_PREPARE_STMT = 2030
CR_PARAMS_NOT_BOUND = 2031
CR_DATA_TRUNCATED = 2032
CR_NO_PARAMETERS_EXISTS = 2033
CR_INVALID_PARAMETER_NO = 2034
CR_INVALID_BUFFER_USE = 2035
CR_UNSUPPORTED_PARAM_TYPE = 2036
CR_SHARED_MEMORY_CONNECTION = 2037
CR_SHARED_MEMORY_CONNECT_REQUEST_ERROR = 2038
CR_SHARED_MEMORY_CONNECT_ANSWER_ERROR = 2039
CR_SHARED_MEMORY_CONNECT_FILE_MAP_ERROR = 2040
CR_SHARED_MEMORY_CONNECT_MAP_ERROR = 2041
CR_SHARED_MEMORY_FILE_MAP_ERROR = 2042
CR_SHARED_MEMORY_MAP_ERROR = 2043
CR_SHARED_MEMORY_EVENT_ERROR = 2044
CR_SHARED_MEMORY_CONNECT_ABANDONED_ERROR = 2045
CR_SHARED_MEMORY_CONNECT_SET_ERROR = 2046
CR_CONN_UNKNOW_PROTOCOL = 2047
CR_INVALID_CONN_HANDLE = 2048
CR_UNUSED_1 = 2049
CR_FETCH_CANCELED = 2050
CR_NO_DATA = 2051
CR_NO_STMT_METADATA = 2052
CR_NO_RESULT_SET = 2053
CR_NOT_IMPLEMENTED = 2054
CR_SERVER_LOST_EXTENDED = 2055
CR_STMT_CLOSED = 2056
CR_NEW_STMT_METADATA = 2057
CR_ALREADY_CONNECTED = 2058
CR_AUTH_PLUGIN_CANNOT_LOAD = 2059
CR_DUPLICATE_CONNECTION_ATTR = 2060
CR_AUTH_PLUGIN_ERR = 2061
CR_INSECURE_API_ERR = 2062
CR_FILE_NAME_TOO_LONG = 2063
CR_SSL_FIPS_MODE_ERR = 2064
CR_DEPRECATED_COMPRESSION_NOT_SUPPORTED = 2065
CR_COMPRESSION_WRONGLY_CONFIGURED = 2066
CR_KERBEROS_USER_NOT_FOUND = 2067
CR_LOAD_DATA_LOCAL_INFILE_REJECTED = 2068
CR_LOAD_DATA_LOCAL_INFILE_REALPATH_FAIL = 2069
CR_DNS_SRV_LOOKUP_FAILED = 2070
CR_MANDATORY_TRACKER_NOT_FOUND = 2071
CR_INVALID_FACTOR_NO = 2072
# End MySQL Errors

# Start X Plugin Errors
ER_X_BAD_MESSAGE = 5000
ER_X_CAPABILITIES_PREPARE_FAILED = 5001
ER_X_CAPABILITY_NOT_FOUND = 5002
ER_X_INVALID_PROTOCOL_DATA = 5003
ER_X_BAD_CONNECTION_SESSION_ATTRIBUTE_VALUE_LENGTH = 5004
ER_X_BAD_CONNECTION_SESSION_ATTRIBUTE_KEY_LENGTH = 5005
ER_X_BAD_CONNECTION_SESSION_ATTRIBUTE_EMPTY_KEY = 5006
ER_X_BAD_CONNECTION_SESSION_ATTRIBUTE_LENGTH = 5007
ER_X_BAD_CONNECTION_SESSION_ATTRIBUTE_TYPE = 5008
ER_X_CAPABILITY_SET_NOT_ALLOWED = 5009
ER_X_SERVICE_ERROR = 5010
ER_X_SESSION = 5011
ER_X_INVALID_ARGUMENT = 5012
ER_X_MISSING_ARGUMENT = 5013
ER_X_BAD_INSERT_DATA = 5014
ER_X_CMD_NUM_ARGUMENTS = 5015
ER_X_CMD_ARGUMENT_TYPE = 5016
ER_X_CMD_ARGUMENT_VALUE = 5017
ER_X_BAD_UPSERT_DATA = 5018
ER_X_DUPLICATED_CAPABILITIES = 5019
ER_X_CMD_ARGUMENT_OBJECT_EMPTY = 5020
ER_X_CMD_INVALID_ARGUMENT = 5021
ER_X_BAD_UPDATE_DATA = 5050
ER_X_BAD_TYPE_OF_UPDATE = 5051
ER_X_BAD_COLUMN_TO_UPDATE = 5052
ER_X_BAD_MEMBER_TO_UPDATE = 5053
ER_X_BAD_STATEMENT_ID = 5110
ER_X_BAD_CURSOR_ID = 5111
ER_X_BAD_SCHEMA = 5112
ER_X_BAD_TABLE = 5113
ER_X_BAD_PROJECTION = 5114
ER_X_DOC_ID_MISSING = 5115
ER_X_DUPLICATE_ENTRY = 5116
ER_X_DOC_REQUIRED_FIELD_MISSING = 5117
ER_X_PROJ_BAD_KEY_NAME = 5120
ER_X_BAD_DOC_PATH = 5121
ER_X_CURSOR_EXISTS = 5122
ER_X_CURSOR_REACHED_EOF = 5123
ER_X_PREPARED_STATMENT_CAN_HAVE_ONE_CURSOR = 5131
ER_X_PREPARED_EXECUTE_ARGUMENT_NOT_SUPPORTED = 5133
ER_X_PREPARED_EXECUTE_ARGUMENT_CONSISTENCY = 5134
ER_X_EXPR_BAD_OPERATOR = 5150
ER_X_EXPR_BAD_NUM_ARGS = 5151
ER_X_EXPR_MISSING_ARG = 5152
ER_X_EXPR_BAD_TYPE_VALUE = 5153
ER_X_EXPR_BAD_VALUE = 5154
ER_X_INVALID_COLLECTION = 5156
ER_X_INVALID_ADMIN_COMMAND = 5157
ER_X_EXPECT_NOT_OPEN = 5158
ER_X_EXPECT_NO_ERROR_FAILED = 5159
ER_X_EXPECT_BAD_CONDITION = 5160
ER_X_EXPECT_BAD_CONDITION_VALUE = 5161
ER_X_INVALID_NAMESPACE = 5162
ER_X_BAD_NOTICE = 5163
ER_X_CANNOT_DISABLE_NOTICE = 5164
ER_X_BAD_CONFIGURATION = 5165
ER_X_MYSQLX_ACCOUNT_MISSING_PERMISSIONS = 5167
ER_X_EXPECT_FIELD_EXISTS_FAILED = 5168
ER_X_BAD_LOCKING = 5169
ER_X_FRAME_COMPRESSION_DISABLED = 5170
ER_X_DECOMPRESSION_FAILED = 5171
ER_X_BAD_COMPRESSED_FRAME = 5174
ER_X_CAPABILITY_COMPRESSION_INVALID_ALGORITHM = 5175
ER_X_CAPABILITY_COMPRESSION_INVALID_SERVER_STYLE = 5176
ER_X_CAPABILITY_COMPRESSION_INVALID_CLIENT_STYLE = 5177
ER_X_CAPABILITY_COMPRESSION_INVALID_OPTION = 5178
ER_X_CAPABILITY_COMPRESSION_MISSING_REQUIRED_FIELDS = 5179
ER_X_DOCUMENT_DOESNT_MATCH_EXPECTED_SCHEMA = 5180
ER_X_COLLECTION_OPTION_DOESNT_EXISTS = 5181
ER_X_INVALID_VALIDATION_SCHEMA = 5182
# End X Plugin Errors

virt/lib/python3.9/site-packages/mysqlx/errorcode 2.py
PrinceDisant/LostInNL
# -*- coding: utf-8 -*-

# Copyright (c) 2013, 2022, Oracle and/or its affiliates. All rights reserved.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

"""This module contains the MySQL Server and Client error codes."""

# This file was auto-generated.
_GENERATED_ON = "2021-08-11"
_MYSQL_VERSION = (8, 0, 27)

# Start MySQL Errors
OBSOLETE_ER_HASHCHK = 1000
OBSOLETE_ER_NISAMCHK = 1001
ER_NO = 1002
ER_YES = 1003
ER_CANT_CREATE_FILE = 1004
ER_CANT_CREATE_TABLE = 1005
ER_CANT_CREATE_DB = 1006
ER_DB_CREATE_EXISTS = 1007
ER_DB_DROP_EXISTS = 1008
OBSOLETE_ER_DB_DROP_DELETE = 1009
ER_DB_DROP_RMDIR = 1010
OBSOLETE_ER_CANT_DELETE_FILE = 1011
ER_CANT_FIND_SYSTEM_REC = 1012
ER_CANT_GET_STAT = 1013
OBSOLETE_ER_CANT_GET_WD = 1014
ER_CANT_LOCK = 1015
ER_CANT_OPEN_FILE = 1016
ER_FILE_NOT_FOUND = 1017
ER_CANT_READ_DIR = 1018
OBSOLETE_ER_CANT_SET_WD = 1019
ER_CHECKREAD = 1020
OBSOLETE_ER_DISK_FULL = 1021
ER_DUP_KEY = 1022
OBSOLETE_ER_ERROR_ON_CLOSE = 1023
ER_ERROR_ON_READ = 1024
ER_ERROR_ON_RENAME = 1025
ER_ERROR_ON_WRITE = 1026
ER_FILE_USED = 1027
OBSOLETE_ER_FILSORT_ABORT = 1028
OBSOLETE_ER_FORM_NOT_FOUND = 1029
ER_GET_ERRNO = 1030
ER_ILLEGAL_HA = 1031
ER_KEY_NOT_FOUND = 1032
ER_NOT_FORM_FILE = 1033
ER_NOT_KEYFILE = 1034
ER_OLD_KEYFILE = 1035
ER_OPEN_AS_READONLY = 1036
ER_OUTOFMEMORY = 1037
ER_OUT_OF_SORTMEMORY = 1038
OBSOLETE_ER_UNEXPECTED_EOF = 1039
ER_CON_COUNT_ERROR = 1040
ER_OUT_OF_RESOURCES = 1041
ER_BAD_HOST_ERROR = 1042
ER_HANDSHAKE_ERROR = 1043
ER_DBACCESS_DENIED_ERROR = 1044
ER_ACCESS_DENIED_ERROR = 1045
ER_NO_DB_ERROR = 1046
ER_UNKNOWN_COM_ERROR = 1047
ER_BAD_NULL_ERROR = 1048
ER_BAD_DB_ERROR = 1049
ER_TABLE_EXISTS_ERROR = 1050
ER_BAD_TABLE_ERROR = 1051
ER_NON_UNIQ_ERROR = 1052
ER_SERVER_SHUTDOWN = 1053
ER_BAD_FIELD_ERROR = 1054
ER_WRONG_FIELD_WITH_GROUP = 1055
ER_WRONG_GROUP_FIELD = 1056
ER_WRONG_SUM_SELECT = 1057
ER_WRONG_VALUE_COUNT = 1058
ER_TOO_LONG_IDENT = 1059
ER_DUP_FIELDNAME = 1060
ER_DUP_KEYNAME = 1061
ER_DUP_ENTRY = 1062
ER_WRONG_FIELD_SPEC = 1063
ER_PARSE_ERROR = 1064
ER_EMPTY_QUERY = 1065
ER_NONUNIQ_TABLE = 1066
ER_INVALID_DEFAULT = 1067
ER_MULTIPLE_PRI_KEY = 1068
ER_TOO_MANY_KEYS = 1069
ER_TOO_MANY_KEY_PARTS = 1070
ER_TOO_LONG_KEY = 1071
ER_KEY_COLUMN_DOES_NOT_EXITS = 1072
ER_BLOB_USED_AS_KEY = 1073
ER_TOO_BIG_FIELDLENGTH = 1074
ER_WRONG_AUTO_KEY = 1075
ER_READY = 1076
OBSOLETE_ER_NORMAL_SHUTDOWN = 1077
OBSOLETE_ER_GOT_SIGNAL = 1078
ER_SHUTDOWN_COMPLETE = 1079
ER_FORCING_CLOSE = 1080
ER_IPSOCK_ERROR = 1081
ER_NO_SUCH_INDEX = 1082
ER_WRONG_FIELD_TERMINATORS = 1083
ER_BLOBS_AND_NO_TERMINATED = 1084
ER_TEXTFILE_NOT_READABLE = 1085
ER_FILE_EXISTS_ERROR = 1086
ER_LOAD_INFO = 1087
ER_ALTER_INFO = 1088
ER_WRONG_SUB_KEY = 1089
ER_CANT_REMOVE_ALL_FIELDS = 1090
ER_CANT_DROP_FIELD_OR_KEY = 1091
ER_INSERT_INFO = 1092
ER_UPDATE_TABLE_USED = 1093
ER_NO_SUCH_THREAD = 1094
ER_KILL_DENIED_ERROR = 1095
ER_NO_TABLES_USED = 1096
ER_TOO_BIG_SET = 1097
ER_NO_UNIQUE_LOGFILE = 1098
ER_TABLE_NOT_LOCKED_FOR_WRITE = 1099
ER_TABLE_NOT_LOCKED = 1100
ER_BLOB_CANT_HAVE_DEFAULT = 1101
ER_WRONG_DB_NAME = 1102
ER_WRONG_TABLE_NAME = 1103
ER_TOO_BIG_SELECT = 1104
ER_UNKNOWN_ERROR = 1105
ER_UNKNOWN_PROCEDURE = 1106
ER_WRONG_PARAMCOUNT_TO_PROCEDURE = 1107
ER_WRONG_PARAMETERS_TO_PROCEDURE = 1108
ER_UNKNOWN_TABLE = 1109
ER_FIELD_SPECIFIED_TWICE = 1110
ER_INVALID_GROUP_FUNC_USE = 1111
ER_UNSUPPORTED_EXTENSION = 1112
ER_TABLE_MUST_HAVE_COLUMNS = 1113
ER_RECORD_FILE_FULL = 1114
ER_UNKNOWN_CHARACTER_SET = 1115
ER_TOO_MANY_TABLES = 1116
ER_TOO_MANY_FIELDS = 1117
ER_TOO_BIG_ROWSIZE = 1118
ER_STACK_OVERRUN = 1119
ER_WRONG_OUTER_JOIN_UNUSED = 1120
ER_NULL_COLUMN_IN_INDEX = 1121
ER_CANT_FIND_UDF = 1122
ER_CANT_INITIALIZE_UDF = 1123
ER_UDF_NO_PATHS = 1124
ER_UDF_EXISTS = 1125
ER_CANT_OPEN_LIBRARY = 1126
ER_CANT_FIND_DL_ENTRY = 1127
ER_FUNCTION_NOT_DEFINED = 1128
ER_HOST_IS_BLOCKED = 1129
ER_HOST_NOT_PRIVILEGED = 1130
ER_PASSWORD_ANONYMOUS_USER = 1131
ER_PASSWORD_NOT_ALLOWED = 1132
ER_PASSWORD_NO_MATCH = 1133
ER_UPDATE_INFO = 1134
ER_CANT_CREATE_THREAD = 1135
ER_WRONG_VALUE_COUNT_ON_ROW = 1136
ER_CANT_REOPEN_TABLE = 1137
ER_INVALID_USE_OF_NULL = 1138
ER_REGEXP_ERROR = 1139
ER_MIX_OF_GROUP_FUNC_AND_FIELDS = 1140
ER_NONEXISTING_GRANT = 1141
ER_TABLEACCESS_DENIED_ERROR = 1142
ER_COLUMNACCESS_DENIED_ERROR = 1143
ER_ILLEGAL_GRANT_FOR_TABLE = 1144
ER_GRANT_WRONG_HOST_OR_USER = 1145
ER_NO_SUCH_TABLE = 1146
ER_NONEXISTING_TABLE_GRANT = 1147
ER_NOT_ALLOWED_COMMAND = 1148
ER_SYNTAX_ERROR = 1149
OBSOLETE_ER_UNUSED1 = 1150
OBSOLETE_ER_UNUSED2 = 1151
ER_ABORTING_CONNECTION = 1152
ER_NET_PACKET_TOO_LARGE = 1153
ER_NET_READ_ERROR_FROM_PIPE = 1154
ER_NET_FCNTL_ERROR = 1155
ER_NET_PACKETS_OUT_OF_ORDER = 1156
ER_NET_UNCOMPRESS_ERROR = 1157
ER_NET_READ_ERROR = 1158
ER_NET_READ_INTERRUPTED = 1159
ER_NET_ERROR_ON_WRITE = 1160
ER_NET_WRITE_INTERRUPTED = 1161
ER_TOO_LONG_STRING = 1162
ER_TABLE_CANT_HANDLE_BLOB = 1163
ER_TABLE_CANT_HANDLE_AUTO_INCREMENT = 1164
OBSOLETE_ER_UNUSED3 = 1165
ER_WRONG_COLUMN_NAME = 1166
ER_WRONG_KEY_COLUMN = 1167
ER_WRONG_MRG_TABLE = 1168
ER_DUP_UNIQUE = 1169
ER_BLOB_KEY_WITHOUT_LENGTH = 1170
ER_PRIMARY_CANT_HAVE_NULL = 1171
ER_TOO_MANY_ROWS = 1172
ER_REQUIRES_PRIMARY_KEY = 1173
OBSOLETE_ER_NO_RAID_COMPILED = 1174
ER_UPDATE_WITHOUT_KEY_IN_SAFE_MODE = 1175
ER_KEY_DOES_NOT_EXITS = 1176
ER_CHECK_NO_SUCH_TABLE = 1177
ER_CHECK_NOT_IMPLEMENTED = 1178
ER_CANT_DO_THIS_DURING_AN_TRANSACTION = 1179
ER_ERROR_DURING_COMMIT = 1180
ER_ERROR_DURING_ROLLBACK = 1181
ER_ERROR_DURING_FLUSH_LOGS = 1182
OBSOLETE_ER_ERROR_DURING_CHECKPOINT = 1183
ER_NEW_ABORTING_CONNECTION = 1184
OBSOLETE_ER_DUMP_NOT_IMPLEMENTED = 1185
OBSOLETE_ER_FLUSH_MASTER_BINLOG_CLOSED = 1186
OBSOLETE_ER_INDEX_REBUILD = 1187
ER_MASTER = 1188
ER_MASTER_NET_READ = 1189
ER_MASTER_NET_WRITE = 1190
ER_FT_MATCHING_KEY_NOT_FOUND = 1191
ER_LOCK_OR_ACTIVE_TRANSACTION = 1192
ER_UNKNOWN_SYSTEM_VARIABLE = 1193
ER_CRASHED_ON_USAGE = 1194
ER_CRASHED_ON_REPAIR = 1195
ER_WARNING_NOT_COMPLETE_ROLLBACK = 1196
ER_TRANS_CACHE_FULL = 1197
OBSOLETE_ER_SLAVE_MUST_STOP = 1198
ER_SLAVE_NOT_RUNNING = 1199
ER_BAD_SLAVE = 1200
ER_MASTER_INFO = 1201
ER_SLAVE_THREAD = 1202
ER_TOO_MANY_USER_CONNECTIONS = 1203
ER_SET_CONSTANTS_ONLY = 1204
ER_LOCK_WAIT_TIMEOUT = 1205
ER_LOCK_TABLE_FULL = 1206
ER_READ_ONLY_TRANSACTION = 1207
OBSOLETE_ER_DROP_DB_WITH_READ_LOCK = 1208
OBSOLETE_ER_CREATE_DB_WITH_READ_LOCK = 1209
ER_WRONG_ARGUMENTS = 1210
ER_NO_PERMISSION_TO_CREATE_USER = 1211
OBSOLETE_ER_UNION_TABLES_IN_DIFFERENT_DIR = 1212
ER_LOCK_DEADLOCK = 1213
ER_TABLE_CANT_HANDLE_FT = 1214
ER_CANNOT_ADD_FOREIGN = 1215
ER_NO_REFERENCED_ROW = 1216
ER_ROW_IS_REFERENCED = 1217
ER_CONNECT_TO_MASTER = 1218
OBSOLETE_ER_QUERY_ON_MASTER = 1219
ER_ERROR_WHEN_EXECUTING_COMMAND = 1220
ER_WRONG_USAGE = 1221
ER_WRONG_NUMBER_OF_COLUMNS_IN_SELECT = 1222
ER_CANT_UPDATE_WITH_READLOCK = 1223
ER_MIXING_NOT_ALLOWED = 1224
ER_DUP_ARGUMENT = 1225
ER_USER_LIMIT_REACHED = 1226
ER_SPECIFIC_ACCESS_DENIED_ERROR = 1227
ER_LOCAL_VARIABLE = 1228
ER_GLOBAL_VARIABLE = 1229
ER_NO_DEFAULT = 1230
ER_WRONG_VALUE_FOR_VAR = 1231
ER_WRONG_TYPE_FOR_VAR = 1232
ER_VAR_CANT_BE_READ = 1233
ER_CANT_USE_OPTION_HERE = 1234
ER_NOT_SUPPORTED_YET = 1235
ER_MASTER_FATAL_ERROR_READING_BINLOG = 1236
ER_SLAVE_IGNORED_TABLE = 1237
ER_INCORRECT_GLOBAL_LOCAL_VAR = 1238
ER_WRONG_FK_DEF = 1239
ER_KEY_REF_DO_NOT_MATCH_TABLE_REF = 1240
ER_OPERAND_COLUMNS = 1241
ER_SUBQUERY_NO_1_ROW = 1242
ER_UNKNOWN_STMT_HANDLER = 1243
ER_CORRUPT_HELP_DB = 1244
OBSOLETE_ER_CYCLIC_REFERENCE = 1245
ER_AUTO_CONVERT = 1246
ER_ILLEGAL_REFERENCE = 1247
ER_DERIVED_MUST_HAVE_ALIAS = 1248
ER_SELECT_REDUCED = 1249
ER_TABLENAME_NOT_ALLOWED_HERE = 1250
ER_NOT_SUPPORTED_AUTH_MODE = 1251
ER_SPATIAL_CANT_HAVE_NULL = 1252
ER_COLLATION_CHARSET_MISMATCH = 1253
OBSOLETE_ER_SLAVE_WAS_RUNNING = 1254
OBSOLETE_ER_SLAVE_WAS_NOT_RUNNING = 1255
ER_TOO_BIG_FOR_UNCOMPRESS = 1256
ER_ZLIB_Z_MEM_ERROR = 1257
ER_ZLIB_Z_BUF_ERROR = 1258
ER_ZLIB_Z_DATA_ERROR = 1259
ER_CUT_VALUE_GROUP_CONCAT = 1260
ER_WARN_TOO_FEW_RECORDS = 1261
ER_WARN_TOO_MANY_RECORDS = 1262
ER_WARN_NULL_TO_NOTNULL = 1263
ER_WARN_DATA_OUT_OF_RANGE = 1264
WARN_DATA_TRUNCATED = 1265
ER_WARN_USING_OTHER_HANDLER = 1266
ER_CANT_AGGREGATE_2COLLATIONS = 1267
OBSOLETE_ER_DROP_USER = 1268
ER_REVOKE_GRANTS = 1269
ER_CANT_AGGREGATE_3COLLATIONS = 1270
ER_CANT_AGGREGATE_NCOLLATIONS = 1271
ER_VARIABLE_IS_NOT_STRUCT = 1272
ER_UNKNOWN_COLLATION = 1273
ER_SLAVE_IGNORED_SSL_PARAMS = 1274
OBSOLETE_ER_SERVER_IS_IN_SECURE_AUTH_MODE = 1275
ER_WARN_FIELD_RESOLVED = 1276
ER_BAD_SLAVE_UNTIL_COND = 1277
ER_MISSING_SKIP_SLAVE = 1278
ER_UNTIL_COND_IGNORED = 1279
ER_WRONG_NAME_FOR_INDEX = 1280
ER_WRONG_NAME_FOR_CATALOG = 1281
OBSOLETE_ER_WARN_QC_RESIZE = 1282
ER_BAD_FT_COLUMN = 1283
ER_UNKNOWN_KEY_CACHE = 1284
ER_WARN_HOSTNAME_WONT_WORK = 1285
ER_UNKNOWN_STORAGE_ENGINE = 1286
ER_WARN_DEPRECATED_SYNTAX = 1287
ER_NON_UPDATABLE_TABLE = 1288
ER_FEATURE_DISABLED = 1289
ER_OPTION_PREVENTS_STATEMENT = 1290
ER_DUPLICATED_VALUE_IN_TYPE = 1291
ER_TRUNCATED_WRONG_VALUE = 1292
OBSOLETE_ER_TOO_MUCH_AUTO_TIMESTAMP_COLS = 1293
ER_INVALID_ON_UPDATE = 1294
ER_UNSUPPORTED_PS = 1295
ER_GET_ERRMSG = 1296
ER_GET_TEMPORARY_ERRMSG = 1297
ER_UNKNOWN_TIME_ZONE = 1298
ER_WARN_INVALID_TIMESTAMP = 1299
ER_INVALID_CHARACTER_STRING = 1300
ER_WARN_ALLOWED_PACKET_OVERFLOWED = 1301
ER_CONFLICTING_DECLARATIONS = 1302
ER_SP_NO_RECURSIVE_CREATE = 1303
ER_SP_ALREADY_EXISTS = 1304
ER_SP_DOES_NOT_EXIST = 1305
ER_SP_DROP_FAILED = 1306
ER_SP_STORE_FAILED = 1307
ER_SP_LILABEL_MISMATCH = 1308
ER_SP_LABEL_REDEFINE = 1309
ER_SP_LABEL_MISMATCH = 1310
ER_SP_UNINIT_VAR = 1311
ER_SP_BADSELECT = 1312
ER_SP_BADRETURN = 1313
ER_SP_BADSTATEMENT = 1314
ER_UPDATE_LOG_DEPRECATED_IGNORED = 1315
ER_UPDATE_LOG_DEPRECATED_TRANSLATED = 1316
ER_QUERY_INTERRUPTED = 1317
ER_SP_WRONG_NO_OF_ARGS = 1318
ER_SP_COND_MISMATCH = 1319
ER_SP_NORETURN = 1320
ER_SP_NORETURNEND = 1321
ER_SP_BAD_CURSOR_QUERY = 1322
ER_SP_BAD_CURSOR_SELECT = 1323
ER_SP_CURSOR_MISMATCH = 1324
ER_SP_CURSOR_ALREADY_OPEN = 1325
ER_SP_CURSOR_NOT_OPEN = 1326
ER_SP_UNDECLARED_VAR = 1327
ER_SP_WRONG_NO_OF_FETCH_ARGS = 1328
ER_SP_FETCH_NO_DATA = 1329
ER_SP_DUP_PARAM = 1330
ER_SP_DUP_VAR = 1331
ER_SP_DUP_COND = 1332
ER_SP_DUP_CURS = 1333
ER_SP_CANT_ALTER = 1334
ER_SP_SUBSELECT_NYI = 1335
ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG = 1336
ER_SP_VARCOND_AFTER_CURSHNDLR = 1337
ER_SP_CURSOR_AFTER_HANDLER = 1338
ER_SP_CASE_NOT_FOUND = 1339
ER_FPARSER_TOO_BIG_FILE = 1340
ER_FPARSER_BAD_HEADER = 1341
ER_FPARSER_EOF_IN_COMMENT = 1342
ER_FPARSER_ERROR_IN_PARAMETER = 1343
ER_FPARSER_EOF_IN_UNKNOWN_PARAMETER = 1344
ER_VIEW_NO_EXPLAIN = 1345
OBSOLETE_ER_FRM_UNKNOWN_TYPE = 1346
ER_WRONG_OBJECT = 1347
ER_NONUPDATEABLE_COLUMN = 1348
OBSOLETE_ER_VIEW_SELECT_DERIVED_UNUSED = 1349
ER_VIEW_SELECT_CLAUSE = 1350
ER_VIEW_SELECT_VARIABLE = 1351
ER_VIEW_SELECT_TMPTABLE = 1352
ER_VIEW_WRONG_LIST = 1353
ER_WARN_VIEW_MERGE = 1354
ER_WARN_VIEW_WITHOUT_KEY = 1355
ER_VIEW_INVALID = 1356
ER_SP_NO_DROP_SP = 1357
OBSOLETE_ER_SP_GOTO_IN_HNDLR = 1358
ER_TRG_ALREADY_EXISTS = 1359
ER_TRG_DOES_NOT_EXIST = 1360
ER_TRG_ON_VIEW_OR_TEMP_TABLE = 1361
ER_TRG_CANT_CHANGE_ROW = 1362
ER_TRG_NO_SUCH_ROW_IN_TRG = 1363
ER_NO_DEFAULT_FOR_FIELD = 1364
ER_DIVISION_BY_ZERO = 1365
ER_TRUNCATED_WRONG_VALUE_FOR_FIELD = 1366
ER_ILLEGAL_VALUE_FOR_TYPE = 1367
ER_VIEW_NONUPD_CHECK = 1368
ER_VIEW_CHECK_FAILED = 1369
ER_PROCACCESS_DENIED_ERROR = 1370
ER_RELAY_LOG_FAIL = 1371
OBSOLETE_ER_PASSWD_LENGTH = 1372
ER_UNKNOWN_TARGET_BINLOG = 1373
ER_IO_ERR_LOG_INDEX_READ = 1374
ER_BINLOG_PURGE_PROHIBITED = 1375
ER_FSEEK_FAIL = 1376
ER_BINLOG_PURGE_FATAL_ERR = 1377
ER_LOG_IN_USE = 1378
ER_LOG_PURGE_UNKNOWN_ERR = 1379
ER_RELAY_LOG_INIT = 1380
ER_NO_BINARY_LOGGING = 1381
ER_RESERVED_SYNTAX = 1382
OBSOLETE_ER_WSAS_FAILED = 1383
OBSOLETE_ER_DIFF_GROUPS_PROC = 1384
OBSOLETE_ER_NO_GROUP_FOR_PROC = 1385
OBSOLETE_ER_ORDER_WITH_PROC = 1386
OBSOLETE_ER_LOGGING_PROHIBIT_CHANGING_OF = 1387
OBSOLETE_ER_NO_FILE_MAPPING = 1388
OBSOLETE_ER_WRONG_MAGIC = 1389
ER_PS_MANY_PARAM = 1390
ER_KEY_PART_0 = 1391
ER_VIEW_CHECKSUM = 1392
ER_VIEW_MULTIUPDATE = 1393
ER_VIEW_NO_INSERT_FIELD_LIST = 1394
ER_VIEW_DELETE_MERGE_VIEW = 1395
ER_CANNOT_USER = 1396
ER_XAER_NOTA = 1397
ER_XAER_INVAL = 1398
ER_XAER_RMFAIL = 1399
ER_XAER_OUTSIDE = 1400
ER_XAER_RMERR = 1401
ER_XA_RBROLLBACK = 1402
ER_NONEXISTING_PROC_GRANT = 1403
ER_PROC_AUTO_GRANT_FAIL = 1404
ER_PROC_AUTO_REVOKE_FAIL = 1405
ER_DATA_TOO_LONG = 1406
ER_SP_BAD_SQLSTATE = 1407
ER_STARTUP = 1408
ER_LOAD_FROM_FIXED_SIZE_ROWS_TO_VAR = 1409
ER_CANT_CREATE_USER_WITH_GRANT = 1410
ER_WRONG_VALUE_FOR_TYPE = 1411
ER_TABLE_DEF_CHANGED = 1412
ER_SP_DUP_HANDLER = 1413
ER_SP_NOT_VAR_ARG = 1414
ER_SP_NO_RETSET = 1415
ER_CANT_CREATE_GEOMETRY_OBJECT = 1416
OBSOLETE_ER_FAILED_ROUTINE_BREAK_BINLOG = 1417
ER_BINLOG_UNSAFE_ROUTINE = 1418
ER_BINLOG_CREATE_ROUTINE_NEED_SUPER = 1419
OBSOLETE_ER_EXEC_STMT_WITH_OPEN_CURSOR = 1420
ER_STMT_HAS_NO_OPEN_CURSOR = 1421
ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG = 1422
ER_NO_DEFAULT_FOR_VIEW_FIELD = 1423
ER_SP_NO_RECURSION = 1424
ER_TOO_BIG_SCALE = 1425
ER_TOO_BIG_PRECISION = 1426
ER_M_BIGGER_THAN_D = 1427
ER_WRONG_LOCK_OF_SYSTEM_TABLE = 1428
ER_CONNECT_TO_FOREIGN_DATA_SOURCE = 1429
ER_QUERY_ON_FOREIGN_DATA_SOURCE = 1430
ER_FOREIGN_DATA_SOURCE_DOESNT_EXIST = 1431
ER_FOREIGN_DATA_STRING_INVALID_CANT_CREATE = 1432
ER_FOREIGN_DATA_STRING_INVALID = 1433
OBSOLETE_ER_CANT_CREATE_FEDERATED_TABLE = 1434
ER_TRG_IN_WRONG_SCHEMA = 1435
ER_STACK_OVERRUN_NEED_MORE = 1436
ER_TOO_LONG_BODY = 1437
ER_WARN_CANT_DROP_DEFAULT_KEYCACHE = 1438
ER_TOO_BIG_DISPLAYWIDTH = 1439
ER_XAER_DUPID = 1440
ER_DATETIME_FUNCTION_OVERFLOW = 1441
ER_CANT_UPDATE_USED_TABLE_IN_SF_OR_TRG = 1442
ER_VIEW_PREVENT_UPDATE = 1443
ER_PS_NO_RECURSION = 1444
ER_SP_CANT_SET_AUTOCOMMIT = 1445
OBSOLETE_ER_MALFORMED_DEFINER = 1446
ER_VIEW_FRM_NO_USER = 1447
ER_VIEW_OTHER_USER = 1448
ER_NO_SUCH_USER = 1449
ER_FORBID_SCHEMA_CHANGE = 1450
ER_ROW_IS_REFERENCED_2 = 1451
ER_NO_REFERENCED_ROW_2 = 1452
ER_SP_BAD_VAR_SHADOW = 1453
ER_TRG_NO_DEFINER = 1454
ER_OLD_FILE_FORMAT = 1455
ER_SP_RECURSION_LIMIT = 1456
OBSOLETE_ER_SP_PROC_TABLE_CORRUPT = 1457
ER_SP_WRONG_NAME = 1458
ER_TABLE_NEEDS_UPGRADE = 1459
ER_SP_NO_AGGREGATE = 1460
ER_MAX_PREPARED_STMT_COUNT_REACHED = 1461
ER_VIEW_RECURSIVE = 1462
ER_NON_GROUPING_FIELD_USED = 1463
ER_TABLE_CANT_HANDLE_SPKEYS = 1464
ER_NO_TRIGGERS_ON_SYSTEM_SCHEMA = 1465
ER_REMOVED_SPACES = 1466
ER_AUTOINC_READ_FAILED = 1467
ER_USERNAME = 1468
ER_HOSTNAME = 1469
ER_WRONG_STRING_LENGTH = 1470
ER_NON_INSERTABLE_TABLE = 1471
ER_ADMIN_WRONG_MRG_TABLE = 1472
ER_TOO_HIGH_LEVEL_OF_NESTING_FOR_SELECT = 1473
ER_NAME_BECOMES_EMPTY = 1474
ER_AMBIGUOUS_FIELD_TERM = 1475
ER_FOREIGN_SERVER_EXISTS = 1476
ER_FOREIGN_SERVER_DOESNT_EXIST = 1477
ER_ILLEGAL_HA_CREATE_OPTION = 1478
ER_PARTITION_REQUIRES_VALUES_ERROR = 1479
ER_PARTITION_WRONG_VALUES_ERROR = 1480
ER_PARTITION_MAXVALUE_ERROR = 1481
OBSOLETE_ER_PARTITION_SUBPARTITION_ERROR = 1482
OBSOLETE_ER_PARTITION_SUBPART_MIX_ERROR = 1483
ER_PARTITION_WRONG_NO_PART_ERROR = 1484
ER_PARTITION_WRONG_NO_SUBPART_ERROR = 1485
ER_WRONG_EXPR_IN_PARTITION_FUNC_ERROR = 1486
OBSOLETE_ER_NO_CONST_EXPR_IN_RANGE_OR_LIST_ERROR = 1487
ER_FIELD_NOT_FOUND_PART_ERROR = 1488
OBSOLETE_ER_LIST_OF_FIELDS_ONLY_IN_HASH_ERROR = 1489
ER_INCONSISTENT_PARTITION_INFO_ERROR = 1490
ER_PARTITION_FUNC_NOT_ALLOWED_ERROR = 1491
ER_PARTITIONS_MUST_BE_DEFINED_ERROR = 1492
ER_RANGE_NOT_INCREASING_ERROR = 1493
ER_INCONSISTENT_TYPE_OF_FUNCTIONS_ERROR = 1494
ER_MULTIPLE_DEF_CONST_IN_LIST_PART_ERROR = 1495
ER_PARTITION_ENTRY_ERROR = 1496
ER_MIX_HANDLER_ERROR = 1497
ER_PARTITION_NOT_DEFINED_ERROR = 1498
ER_TOO_MANY_PARTITIONS_ERROR = 1499
ER_SUBPARTITION_ERROR = 1500
ER_CANT_CREATE_HANDLER_FILE = 1501
ER_BLOB_FIELD_IN_PART_FUNC_ERROR = 1502
ER_UNIQUE_KEY_NEED_ALL_FIELDS_IN_PF = 1503
ER_NO_PARTS_ERROR = 1504
ER_PARTITION_MGMT_ON_NONPARTITIONED = 1505
ER_FOREIGN_KEY_ON_PARTITIONED = 1506
ER_DROP_PARTITION_NON_EXISTENT = 1507
ER_DROP_LAST_PARTITION = 1508
ER_COALESCE_ONLY_ON_HASH_PARTITION = 1509
ER_REORG_HASH_ONLY_ON_SAME_NO = 1510
ER_REORG_NO_PARAM_ERROR = 1511
ER_ONLY_ON_RANGE_LIST_PARTITION = 1512
ER_ADD_PARTITION_SUBPART_ERROR = 1513
ER_ADD_PARTITION_NO_NEW_PARTITION = 1514
ER_COALESCE_PARTITION_NO_PARTITION = 1515
ER_REORG_PARTITION_NOT_EXIST = 1516
ER_SAME_NAME_PARTITION = 1517
ER_NO_BINLOG_ERROR = 1518
ER_CONSECUTIVE_REORG_PARTITIONS = 1519
ER_REORG_OUTSIDE_RANGE = 1520
ER_PARTITION_FUNCTION_FAILURE = 1521
OBSOLETE_ER_PART_STATE_ERROR = 1522
ER_LIMITED_PART_RANGE = 1523
ER_PLUGIN_IS_NOT_LOADED = 1524
ER_WRONG_VALUE = 1525
ER_NO_PARTITION_FOR_GIVEN_VALUE = 1526
ER_FILEGROUP_OPTION_ONLY_ONCE = 1527
ER_CREATE_FILEGROUP_FAILED = 1528
ER_DROP_FILEGROUP_FAILED = 1529
ER_TABLESPACE_AUTO_EXTEND_ERROR = 1530
ER_WRONG_SIZE_NUMBER = 1531
ER_SIZE_OVERFLOW_ERROR = 1532
ER_ALTER_FILEGROUP_FAILED = 1533
ER_BINLOG_ROW_LOGGING_FAILED = 1534
OBSOLETE_ER_BINLOG_ROW_WRONG_TABLE_DEF = 1535
OBSOLETE_ER_BINLOG_ROW_RBR_TO_SBR = 1536
ER_EVENT_ALREADY_EXISTS = 1537
OBSOLETE_ER_EVENT_STORE_FAILED = 1538
ER_EVENT_DOES_NOT_EXIST = 1539
OBSOLETE_ER_EVENT_CANT_ALTER = 1540
OBSOLETE_ER_EVENT_DROP_FAILED = 1541
ER_EVENT_INTERVAL_NOT_POSITIVE_OR_TOO_BIG = 1542
ER_EVENT_ENDS_BEFORE_STARTS = 1543
ER_EVENT_EXEC_TIME_IN_THE_PAST = 1544
OBSOLETE_ER_EVENT_OPEN_TABLE_FAILED = 1545
OBSOLETE_ER_EVENT_NEITHER_M_EXPR_NOR_M_AT = 1546
OBSOLETE_ER_COL_COUNT_DOESNT_MATCH_CORRUPTED = 1547
OBSOLETE_ER_CANNOT_LOAD_FROM_TABLE = 1548
OBSOLETE_ER_EVENT_CANNOT_DELETE = 1549
OBSOLETE_ER_EVENT_COMPILE_ERROR = 1550
ER_EVENT_SAME_NAME = 1551
OBSOLETE_ER_EVENT_DATA_TOO_LONG = 1552
ER_DROP_INDEX_FK = 1553
ER_WARN_DEPRECATED_SYNTAX_WITH_VER = 1554
OBSOLETE_ER_CANT_WRITE_LOCK_LOG_TABLE = 1555
ER_CANT_LOCK_LOG_TABLE = 1556
ER_FOREIGN_DUPLICATE_KEY_OLD_UNUSED = 1557
ER_COL_COUNT_DOESNT_MATCH_PLEASE_UPDATE = 1558
OBSOLETE_ER_TEMP_TABLE_PREVENTS_SWITCH_OUT_OF_RBR = 1559
ER_STORED_FUNCTION_PREVENTS_SWITCH_BINLOG_FORMAT = 1560
OBSOLETE_ER_NDB_CANT_SWITCH_BINLOG_FORMAT = 1561
ER_PARTITION_NO_TEMPORARY = 1562
ER_PARTITION_CONST_DOMAIN_ERROR = 1563
ER_PARTITION_FUNCTION_IS_NOT_ALLOWED = 1564
OBSOLETE_ER_DDL_LOG_ERROR_UNUSED = 1565
ER_NULL_IN_VALUES_LESS_THAN = 1566
ER_WRONG_PARTITION_NAME = 1567
ER_CANT_CHANGE_TX_CHARACTERISTICS = 1568
ER_DUP_ENTRY_AUTOINCREMENT_CASE = 1569
OBSOLETE_ER_EVENT_MODIFY_QUEUE_ERROR = 1570
ER_EVENT_SET_VAR_ERROR = 1571
ER_PARTITION_MERGE_ERROR = 1572
OBSOLETE_ER_CANT_ACTIVATE_LOG = 1573
OBSOLETE_ER_RBR_NOT_AVAILABLE = 1574
ER_BASE64_DECODE_ERROR = 1575
ER_EVENT_RECURSION_FORBIDDEN = 1576
OBSOLETE_ER_EVENTS_DB_ERROR = 1577
ER_ONLY_INTEGERS_ALLOWED = 1578
ER_UNSUPORTED_LOG_ENGINE = 1579
ER_BAD_LOG_STATEMENT = 1580
ER_CANT_RENAME_LOG_TABLE = 1581
ER_WRONG_PARAMCOUNT_TO_NATIVE_FCT = 1582
ER_WRONG_PARAMETERS_TO_NATIVE_FCT = 1583
ER_WRONG_PARAMETERS_TO_STORED_FCT = 1584
ER_NATIVE_FCT_NAME_COLLISION = 1585
ER_DUP_ENTRY_WITH_KEY_NAME = 1586
ER_BINLOG_PURGE_EMFILE = 1587
ER_EVENT_CANNOT_CREATE_IN_THE_PAST = 1588
ER_EVENT_CANNOT_ALTER_IN_THE_PAST = 1589
OBSOLETE_ER_SLAVE_INCIDENT = 1590
ER_NO_PARTITION_FOR_GIVEN_VALUE_SILENT = 1591
ER_BINLOG_UNSAFE_STATEMENT = 1592
ER_BINLOG_FATAL_ERROR = 1593
OBSOLETE_ER_SLAVE_RELAY_LOG_READ_FAILURE = 1594
OBSOLETE_ER_SLAVE_RELAY_LOG_WRITE_FAILURE = 1595
OBSOLETE_ER_SLAVE_CREATE_EVENT_FAILURE = 1596
OBSOLETE_ER_SLAVE_MASTER_COM_FAILURE = 1597
ER_BINLOG_LOGGING_IMPOSSIBLE = 1598
ER_VIEW_NO_CREATION_CTX = 1599
ER_VIEW_INVALID_CREATION_CTX = 1600
OBSOLETE_ER_SR_INVALID_CREATION_CTX = 1601
ER_TRG_CORRUPTED_FILE = 1602
ER_TRG_NO_CREATION_CTX = 1603
ER_TRG_INVALID_CREATION_CTX = 1604
ER_EVENT_INVALID_CREATION_CTX = 1605
ER_TRG_CANT_OPEN_TABLE = 1606
OBSOLETE_ER_CANT_CREATE_SROUTINE = 1607
OBSOLETE_ER_NEVER_USED = 1608
ER_NO_FORMAT_DESCRIPTION_EVENT_BEFORE_BINLOG_STATEMENT = 1609
ER_SLAVE_CORRUPT_EVENT = 1610
OBSOLETE_ER_LOAD_DATA_INVALID_COLUMN_UNUSED = 1611
ER_LOG_PURGE_NO_FILE = 1612
ER_XA_RBTIMEOUT = 1613
ER_XA_RBDEADLOCK = 1614
ER_NEED_REPREPARE = 1615
OBSOLETE_ER_DELAYED_NOT_SUPPORTED = 1616
WARN_NO_MASTER_INFO = 1617
WARN_OPTION_IGNORED = 1618
ER_PLUGIN_DELETE_BUILTIN = 1619
WARN_PLUGIN_BUSY = 1620
ER_VARIABLE_IS_READONLY = 1621
ER_WARN_ENGINE_TRANSACTION_ROLLBACK = 1622
OBSOLETE_ER_SLAVE_HEARTBEAT_FAILURE = 1623
ER_SLAVE_HEARTBEAT_VALUE_OUT_OF_RANGE = 1624
ER_NDB_REPLICATION_SCHEMA_ERROR = 1625
ER_CONFLICT_FN_PARSE_ERROR = 1626
ER_EXCEPTIONS_WRITE_ERROR = 1627
ER_TOO_LONG_TABLE_COMMENT = 1628
ER_TOO_LONG_FIELD_COMMENT = 1629
ER_FUNC_INEXISTENT_NAME_COLLISION = 1630
ER_DATABASE_NAME = 1631
ER_TABLE_NAME = 1632
ER_PARTITION_NAME = 1633
ER_SUBPARTITION_NAME = 1634
ER_TEMPORARY_NAME = 1635
ER_RENAMED_NAME = 1636
ER_TOO_MANY_CONCURRENT_TRXS = 1637
WARN_NON_ASCII_SEPARATOR_NOT_IMPLEMENTED = 1638
ER_DEBUG_SYNC_TIMEOUT = 1639
ER_DEBUG_SYNC_HIT_LIMIT = 1640
ER_DUP_SIGNAL_SET = 1641
ER_SIGNAL_WARN = 1642
ER_SIGNAL_NOT_FOUND = 1643
ER_SIGNAL_EXCEPTION = 1644
ER_RESIGNAL_WITHOUT_ACTIVE_HANDLER = 1645
ER_SIGNAL_BAD_CONDITION_TYPE = 1646
WARN_COND_ITEM_TRUNCATED = 1647
ER_COND_ITEM_TOO_LONG = 1648
ER_UNKNOWN_LOCALE = 1649
ER_SLAVE_IGNORE_SERVER_IDS = 1650
OBSOLETE_ER_QUERY_CACHE_DISABLED = 1651
ER_SAME_NAME_PARTITION_FIELD = 1652
ER_PARTITION_COLUMN_LIST_ERROR = 1653
ER_WRONG_TYPE_COLUMN_VALUE_ERROR = 1654
ER_TOO_MANY_PARTITION_FUNC_FIELDS_ERROR = 1655
ER_MAXVALUE_IN_VALUES_IN = 1656
ER_TOO_MANY_VALUES_ERROR = 1657
ER_ROW_SINGLE_PARTITION_FIELD_ERROR = 1658
ER_FIELD_TYPE_NOT_ALLOWED_AS_PARTITION_FIELD = 1659
ER_PARTITION_FIELDS_TOO_LONG = 1660
ER_BINLOG_ROW_ENGINE_AND_STMT_ENGINE = 1661
ER_BINLOG_ROW_MODE_AND_STMT_ENGINE = 1662
ER_BINLOG_UNSAFE_AND_STMT_ENGINE = 1663
ER_BINLOG_ROW_INJECTION_AND_STMT_ENGINE = 1664
ER_BINLOG_STMT_MODE_AND_ROW_ENGINE = 1665
ER_BINLOG_ROW_INJECTION_AND_STMT_MODE = 1666
ER_BINLOG_MULTIPLE_ENGINES_AND_SELF_LOGGING_ENGINE = 1667
ER_BINLOG_UNSAFE_LIMIT = 1668
OBSOLETE_ER_UNUSED4 = 1669
ER_BINLOG_UNSAFE_SYSTEM_TABLE = 1670
ER_BINLOG_UNSAFE_AUTOINC_COLUMNS = 1671
ER_BINLOG_UNSAFE_UDF = 1672
ER_BINLOG_UNSAFE_SYSTEM_VARIABLE = 1673
ER_BINLOG_UNSAFE_SYSTEM_FUNCTION = 1674
ER_BINLOG_UNSAFE_NONTRANS_AFTER_TRANS = 1675
ER_MESSAGE_AND_STATEMENT = 1676
OBSOLETE_ER_SLAVE_CONVERSION_FAILED = 1677
ER_SLAVE_CANT_CREATE_CONVERSION = 1678
ER_INSIDE_TRANSACTION_PREVENTS_SWITCH_BINLOG_FORMAT = 1679
ER_PATH_LENGTH = 1680
ER_WARN_DEPRECATED_SYNTAX_NO_REPLACEMENT = 1681
ER_WRONG_NATIVE_TABLE_STRUCTURE = 1682
ER_WRONG_PERFSCHEMA_USAGE = 1683
ER_WARN_I_S_SKIPPED_TABLE = 1684
ER_INSIDE_TRANSACTION_PREVENTS_SWITCH_BINLOG_DIRECT = 1685
ER_STORED_FUNCTION_PREVENTS_SWITCH_BINLOG_DIRECT = 1686
ER_SPATIAL_MUST_HAVE_GEOM_COL = 1687
ER_TOO_LONG_INDEX_COMMENT = 1688
ER_LOCK_ABORTED = 1689
ER_DATA_OUT_OF_RANGE = 1690
OBSOLETE_ER_WRONG_SPVAR_TYPE_IN_LIMIT = 1691
ER_BINLOG_UNSAFE_MULTIPLE_ENGINES_AND_SELF_LOGGING_ENGINE = 1692
ER_BINLOG_UNSAFE_MIXED_STATEMENT = 1693
ER_INSIDE_TRANSACTION_PREVENTS_SWITCH_SQL_LOG_BIN = 1694
ER_STORED_FUNCTION_PREVENTS_SWITCH_SQL_LOG_BIN = 1695
ER_FAILED_READ_FROM_PAR_FILE = 1696
ER_VALUES_IS_NOT_INT_TYPE_ERROR = 1697
ER_ACCESS_DENIED_NO_PASSWORD_ERROR = 1698
ER_SET_PASSWORD_AUTH_PLUGIN = 1699
OBSOLETE_ER_GRANT_PLUGIN_USER_EXISTS = 1700
ER_TRUNCATE_ILLEGAL_FK = 1701
ER_PLUGIN_IS_PERMANENT = 1702
ER_SLAVE_HEARTBEAT_VALUE_OUT_OF_RANGE_MIN = 1703
ER_SLAVE_HEARTBEAT_VALUE_OUT_OF_RANGE_MAX = 1704
ER_STMT_CACHE_FULL = 1705
ER_MULTI_UPDATE_KEY_CONFLICT = 1706
ER_TABLE_NEEDS_REBUILD = 1707
WARN_OPTION_BELOW_LIMIT = 1708
ER_INDEX_COLUMN_TOO_LONG = 1709
ER_ERROR_IN_TRIGGER_BODY = 1710
ER_ERROR_IN_UNKNOWN_TRIGGER_BODY = 1711
ER_INDEX_CORRUPT = 1712
ER_UNDO_RECORD_TOO_BIG = 1713
ER_BINLOG_UNSAFE_INSERT_IGNORE_SELECT = 1714
ER_BINLOG_UNSAFE_INSERT_SELECT_UPDATE = 1715
ER_BINLOG_UNSAFE_REPLACE_SELECT = 1716
ER_BINLOG_UNSAFE_CREATE_IGNORE_SELECT = 1717
ER_BINLOG_UNSAFE_CREATE_REPLACE_SELECT = 1718
ER_BINLOG_UNSAFE_UPDATE_IGNORE = 1719
ER_PLUGIN_NO_UNINSTALL = 1720
ER_PLUGIN_NO_INSTALL = 1721
ER_BINLOG_UNSAFE_WRITE_AUTOINC_SELECT = 1722
ER_BINLOG_UNSAFE_CREATE_SELECT_AUTOINC = 1723
ER_BINLOG_UNSAFE_INSERT_TWO_KEYS = 1724
ER_TABLE_IN_FK_CHECK = 1725
ER_UNSUPPORTED_ENGINE = 1726
ER_BINLOG_UNSAFE_AUTOINC_NOT_FIRST = 1727
ER_CANNOT_LOAD_FROM_TABLE_V2 = 1728
ER_MASTER_DELAY_VALUE_OUT_OF_RANGE = 1729
ER_ONLY_FD_AND_RBR_EVENTS_ALLOWED_IN_BINLOG_STATEMENT = 1730
ER_PARTITION_EXCHANGE_DIFFERENT_OPTION = 1731
ER_PARTITION_EXCHANGE_PART_TABLE = 1732
ER_PARTITION_EXCHANGE_TEMP_TABLE = 1733
ER_PARTITION_INSTEAD_OF_SUBPARTITION = 1734
ER_UNKNOWN_PARTITION = 1735
ER_TABLES_DIFFERENT_METADATA = 1736
ER_ROW_DOES_NOT_MATCH_PARTITION = 1737
ER_BINLOG_CACHE_SIZE_GREATER_THAN_MAX = 1738
ER_WARN_INDEX_NOT_APPLICABLE = 1739
ER_PARTITION_EXCHANGE_FOREIGN_KEY = 1740
OBSOLETE_ER_NO_SUCH_KEY_VALUE = 1741
ER_RPL_INFO_DATA_TOO_LONG = 1742
OBSOLETE_ER_NETWORK_READ_EVENT_CHECKSUM_FAILURE = 1743
OBSOLETE_ER_BINLOG_READ_EVENT_CHECKSUM_FAILURE = 1744
ER_BINLOG_STMT_CACHE_SIZE_GREATER_THAN_MAX = 1745
ER_CANT_UPDATE_TABLE_IN_CREATE_TABLE_SELECT = 1746
ER_PARTITION_CLAUSE_ON_NONPARTITIONED = 1747
ER_ROW_DOES_NOT_MATCH_GIVEN_PARTITION_SET = 1748
OBSOLETE_ER_NO_SUCH_PARTITION__UNUSED = 1749
ER_CHANGE_RPL_INFO_REPOSITORY_FAILURE = 1750
ER_WARNING_NOT_COMPLETE_ROLLBACK_WITH_CREATED_TEMP_TABLE = 1751
ER_WARNING_NOT_COMPLETE_ROLLBACK_WITH_DROPPED_TEMP_TABLE = 1752
ER_MTS_FEATURE_IS_NOT_SUPPORTED = 1753
ER_MTS_UPDATED_DBS_GREATER_MAX = 1754
ER_MTS_CANT_PARALLEL = 1755
ER_MTS_INCONSISTENT_DATA = 1756
ER_FULLTEXT_NOT_SUPPORTED_WITH_PARTITIONING = 1757
ER_DA_INVALID_CONDITION_NUMBER = 1758
ER_INSECURE_PLAIN_TEXT = 1759
ER_INSECURE_CHANGE_MASTER = 1760
ER_FOREIGN_DUPLICATE_KEY_WITH_CHILD_INFO = 1761
ER_FOREIGN_DUPLICATE_KEY_WITHOUT_CHILD_INFO = 1762
ER_SQLTHREAD_WITH_SECURE_SLAVE = 1763
ER_TABLE_HAS_NO_FT = 1764
ER_VARIABLE_NOT_SETTABLE_IN_SF_OR_TRIGGER = 1765
ER_VARIABLE_NOT_SETTABLE_IN_TRANSACTION = 1766
OBSOLETE_ER_GTID_NEXT_IS_NOT_IN_GTID_NEXT_LIST = 1767
OBSOLETE_ER_CANT_CHANGE_GTID_NEXT_IN_TRANSACTION = 1768
ER_SET_STATEMENT_CANNOT_INVOKE_FUNCTION = 1769
ER_GTID_NEXT_CANT_BE_AUTOMATIC_IF_GTID_NEXT_LIST_IS_NON_NULL = 1770
OBSOLETE_ER_SKIPPING_LOGGED_TRANSACTION = 1771
ER_MALFORMED_GTID_SET_SPECIFICATION = 1772
ER_MALFORMED_GTID_SET_ENCODING = 1773
ER_MALFORMED_GTID_SPECIFICATION = 1774
ER_GNO_EXHAUSTED = 1775
ER_BAD_SLAVE_AUTO_POSITION = 1776
ER_AUTO_POSITION_REQUIRES_GTID_MODE_NOT_OFF = 1777
ER_CANT_DO_IMPLICIT_COMMIT_IN_TRX_WHEN_GTID_NEXT_IS_SET = 1778
ER_GTID_MODE_ON_REQUIRES_ENFORCE_GTID_CONSISTENCY_ON = 1779
OBSOLETE_ER_GTID_MODE_REQUIRES_BINLOG = 1780
ER_CANT_SET_GTID_NEXT_TO_GTID_WHEN_GTID_MODE_IS_OFF = 1781
ER_CANT_SET_GTID_NEXT_TO_ANONYMOUS_WHEN_GTID_MODE_IS_ON = 1782
ER_CANT_SET_GTID_NEXT_LIST_TO_NON_NULL_WHEN_GTID_MODE_IS_OFF = 1783
OBSOLETE_ER_FOUND_GTID_EVENT_WHEN_GTID_MODE_IS_OFF__UNUSED = 1784
ER_GTID_UNSAFE_NON_TRANSACTIONAL_TABLE = 1785
ER_GTID_UNSAFE_CREATE_SELECT = 1786
OBSOLETE_ER_GTID_UNSAFE_CREATE_DROP_TEMP_TABLE_IN_TRANSACTION = 1787
ER_GTID_MODE_CAN_ONLY_CHANGE_ONE_STEP_AT_A_TIME = 1788
ER_MASTER_HAS_PURGED_REQUIRED_GTIDS = 1789
ER_CANT_SET_GTID_NEXT_WHEN_OWNING_GTID = 1790
ER_UNKNOWN_EXPLAIN_FORMAT = 1791
ER_CANT_EXECUTE_IN_READ_ONLY_TRANSACTION = 1792
ER_TOO_LONG_TABLE_PARTITION_COMMENT = 1793
ER_SLAVE_CONFIGURATION = 1794
ER_INNODB_FT_LIMIT = 1795
ER_INNODB_NO_FT_TEMP_TABLE = 1796
ER_INNODB_FT_WRONG_DOCID_COLUMN = 1797
ER_INNODB_FT_WRONG_DOCID_INDEX = 1798
ER_INNODB_ONLINE_LOG_TOO_BIG = 1799
ER_UNKNOWN_ALTER_ALGORITHM = 1800
ER_UNKNOWN_ALTER_LOCK = 1801
ER_MTS_CHANGE_MASTER_CANT_RUN_WITH_GAPS = 1802
ER_MTS_RECOVERY_FAILURE = 1803
ER_MTS_RESET_WORKERS = 1804
ER_COL_COUNT_DOESNT_MATCH_CORRUPTED_V2 = 1805
ER_SLAVE_SILENT_RETRY_TRANSACTION = 1806
ER_DISCARD_FK_CHECKS_RUNNING = 1807
ER_TABLE_SCHEMA_MISMATCH = 1808
ER_TABLE_IN_SYSTEM_TABLESPACE = 1809
ER_IO_READ_ERROR = 1810
ER_IO_WRITE_ERROR = 1811
ER_TABLESPACE_MISSING = 1812
ER_TABLESPACE_EXISTS = 1813
ER_TABLESPACE_DISCARDED = 1814
ER_INTERNAL_ERROR = 1815
ER_INNODB_IMPORT_ERROR = 1816
ER_INNODB_INDEX_CORRUPT = 1817
ER_INVALID_YEAR_COLUMN_LENGTH = 1818
ER_NOT_VALID_PASSWORD = 1819
ER_MUST_CHANGE_PASSWORD = 1820
ER_FK_NO_INDEX_CHILD = 1821
ER_FK_NO_INDEX_PARENT = 1822
ER_FK_FAIL_ADD_SYSTEM = 1823
ER_FK_CANNOT_OPEN_PARENT = 1824
ER_FK_INCORRECT_OPTION = 1825
ER_FK_DUP_NAME = 1826
ER_PASSWORD_FORMAT = 1827
ER_FK_COLUMN_CANNOT_DROP = 1828
ER_FK_COLUMN_CANNOT_DROP_CHILD = 1829
ER_FK_COLUMN_NOT_NULL = 1830
ER_DUP_INDEX = 1831
ER_FK_COLUMN_CANNOT_CHANGE = 1832
ER_FK_COLUMN_CANNOT_CHANGE_CHILD = 1833
OBSOLETE_ER_UNUSED5 = 1834
ER_MALFORMED_PACKET = 1835
ER_READ_ONLY_MODE = 1836
ER_GTID_NEXT_TYPE_UNDEFINED_GTID = 1837
ER_VARIABLE_NOT_SETTABLE_IN_SP = 1838
OBSOLETE_ER_CANT_SET_GTID_PURGED_WHEN_GTID_MODE_IS_OFF = 1839
ER_CANT_SET_GTID_PURGED_WHEN_GTID_EXECUTED_IS_NOT_EMPTY = 1840
ER_CANT_SET_GTID_PURGED_WHEN_OWNED_GTIDS_IS_NOT_EMPTY = 1841
ER_GTID_PURGED_WAS_CHANGED = 1842
ER_GTID_EXECUTED_WAS_CHANGED = 1843
ER_BINLOG_STMT_MODE_AND_NO_REPL_TABLES = 1844
ER_ALTER_OPERATION_NOT_SUPPORTED = 1845
ER_ALTER_OPERATION_NOT_SUPPORTED_REASON = 1846
ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_COPY = 1847
ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_PARTITION = 1848
ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_FK_RENAME = 1849
ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_COLUMN_TYPE = 1850
ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_FK_CHECK = 1851
OBSOLETE_ER_UNUSED6 = 1852
ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_NOPK = 1853
ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_AUTOINC = 1854
ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_HIDDEN_FTS = 1855
ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_CHANGE_FTS = 1856
ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_FTS = 1857
OBSOLETE_ER_SQL_REPLICA_SKIP_COUNTER_NOT_SETTABLE_IN_GTID_MODE = 1858
ER_DUP_UNKNOWN_IN_INDEX = 1859
ER_IDENT_CAUSES_TOO_LONG_PATH = 1860
ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_NOT_NULL = 1861
ER_MUST_CHANGE_PASSWORD_LOGIN = 1862
ER_ROW_IN_WRONG_PARTITION = 1863
ER_MTS_EVENT_BIGGER_PENDING_JOBS_SIZE_MAX = 1864
OBSOLETE_ER_INNODB_NO_FT_USES_PARSER = 1865
ER_BINLOG_LOGICAL_CORRUPTION = 1866
ER_WARN_PURGE_LOG_IN_USE = 1867
ER_WARN_PURGE_LOG_IS_ACTIVE = 1868
ER_AUTO_INCREMENT_CONFLICT = 1869
WARN_ON_BLOCKHOLE_IN_RBR = 1870
ER_SLAVE_MI_INIT_REPOSITORY = 1871
ER_SLAVE_RLI_INIT_REPOSITORY = 1872
ER_ACCESS_DENIED_CHANGE_USER_ERROR = 1873
ER_INNODB_READ_ONLY = 1874
ER_STOP_SLAVE_SQL_THREAD_TIMEOUT = 1875
ER_STOP_SLAVE_IO_THREAD_TIMEOUT = 1876
ER_TABLE_CORRUPT = 1877
ER_TEMP_FILE_WRITE_FAILURE = 1878
ER_INNODB_FT_AUX_NOT_HEX_ID = 1879
ER_OLD_TEMPORALS_UPGRADED = 1880
ER_INNODB_FORCED_RECOVERY = 1881
ER_AES_INVALID_IV = 1882
ER_PLUGIN_CANNOT_BE_UNINSTALLED = 1883
ER_GTID_UNSAFE_BINLOG_SPLITTABLE_STATEMENT_AND_ASSIGNED_GTID = 1884
ER_SLAVE_HAS_MORE_GTIDS_THAN_MASTER = 1885
ER_MISSING_KEY = 1886
WARN_NAMED_PIPE_ACCESS_EVERYONE = 1887
ER_FILE_CORRUPT = 3000
ER_ERROR_ON_MASTER = 3001
OBSOLETE_ER_INCONSISTENT_ERROR = 3002
ER_STORAGE_ENGINE_NOT_LOADED = 3003
ER_GET_STACKED_DA_WITHOUT_ACTIVE_HANDLER = 3004
ER_WARN_LEGACY_SYNTAX_CONVERTED = 3005
ER_BINLOG_UNSAFE_FULLTEXT_PLUGIN = 3006
ER_CANNOT_DISCARD_TEMPORARY_TABLE = 3007
ER_FK_DEPTH_EXCEEDED = 3008
ER_COL_COUNT_DOESNT_MATCH_PLEASE_UPDATE_V2 = 3009
ER_WARN_TRIGGER_DOESNT_HAVE_CREATED = 3010
ER_REFERENCED_TRG_DOES_NOT_EXIST = 3011
ER_EXPLAIN_NOT_SUPPORTED = 3012
ER_INVALID_FIELD_SIZE = 3013
ER_MISSING_HA_CREATE_OPTION = 3014
ER_ENGINE_OUT_OF_MEMORY = 3015
ER_PASSWORD_EXPIRE_ANONYMOUS_USER = 3016
ER_SLAVE_SQL_THREAD_MUST_STOP = 3017
ER_NO_FT_MATERIALIZED_SUBQUERY = 3018
ER_INNODB_UNDO_LOG_FULL = 3019
ER_INVALID_ARGUMENT_FOR_LOGARITHM = 3020
ER_SLAVE_CHANNEL_IO_THREAD_MUST_STOP = 3021
ER_WARN_OPEN_TEMP_TABLES_MUST_BE_ZERO = 3022
ER_WARN_ONLY_MASTER_LOG_FILE_NO_POS = 3023
ER_QUERY_TIMEOUT = 3024
ER_NON_RO_SELECT_DISABLE_TIMER = 3025
ER_DUP_LIST_ENTRY = 3026
OBSOLETE_ER_SQL_MODE_NO_EFFECT = 3027
ER_AGGREGATE_ORDER_FOR_UNION = 3028
ER_AGGREGATE_ORDER_NON_AGG_QUERY = 3029
ER_SLAVE_WORKER_STOPPED_PREVIOUS_THD_ERROR = 3030
ER_DONT_SUPPORT_REPLICA_PRESERVE_COMMIT_ORDER = 3031
ER_SERVER_OFFLINE_MODE = 3032
ER_GIS_DIFFERENT_SRIDS = 3033
ER_GIS_UNSUPPORTED_ARGUMENT = 3034
ER_GIS_UNKNOWN_ERROR = 3035
ER_GIS_UNKNOWN_EXCEPTION = 3036
ER_GIS_INVALID_DATA = 3037
ER_BOOST_GEOMETRY_EMPTY_INPUT_EXCEPTION = 3038
ER_BOOST_GEOMETRY_CENTROID_EXCEPTION = 3039
ER_BOOST_GEOMETRY_OVERLAY_INVALID_INPUT_EXCEPTION = 3040
ER_BOOST_GEOMETRY_TURN_INFO_EXCEPTION = 3041
ER_BOOST_GEOMETRY_SELF_INTERSECTION_POINT_EXCEPTION = 3042
ER_BOOST_GEOMETRY_UNKNOWN_EXCEPTION = 3043
ER_STD_BAD_ALLOC_ERROR = 3044
ER_STD_DOMAIN_ERROR = 3045
ER_STD_LENGTH_ERROR = 3046
ER_STD_INVALID_ARGUMENT = 3047
ER_STD_OUT_OF_RANGE_ERROR = 3048
ER_STD_OVERFLOW_ERROR = 3049
ER_STD_RANGE_ERROR = 3050
ER_STD_UNDERFLOW_ERROR = 3051
ER_STD_LOGIC_ERROR = 3052
ER_STD_RUNTIME_ERROR = 3053
ER_STD_UNKNOWN_EXCEPTION = 3054
ER_GIS_DATA_WRONG_ENDIANESS = 3055
ER_CHANGE_MASTER_PASSWORD_LENGTH = 3056
ER_USER_LOCK_WRONG_NAME = 3057
ER_USER_LOCK_DEADLOCK = 3058
ER_REPLACE_INACCESSIBLE_ROWS = 3059
ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_GIS = 3060
ER_ILLEGAL_USER_VAR = 3061
ER_GTID_MODE_OFF = 3062
OBSOLETE_ER_UNSUPPORTED_BY_REPLICATION_THREAD = 3063
ER_INCORRECT_TYPE = 3064
ER_FIELD_IN_ORDER_NOT_SELECT = 3065
ER_AGGREGATE_IN_ORDER_NOT_SELECT = 3066
ER_INVALID_RPL_WILD_TABLE_FILTER_PATTERN = 3067
ER_NET_OK_PACKET_TOO_LARGE = 3068
ER_INVALID_JSON_DATA = 3069
ER_INVALID_GEOJSON_MISSING_MEMBER = 3070
ER_INVALID_GEOJSON_WRONG_TYPE = 3071
ER_INVALID_GEOJSON_UNSPECIFIED = 3072
ER_DIMENSION_UNSUPPORTED = 3073
ER_SLAVE_CHANNEL_DOES_NOT_EXIST = 3074
OBSOLETE_ER_SLAVE_MULTIPLE_CHANNELS_HOST_PORT = 3075
ER_SLAVE_CHANNEL_NAME_INVALID_OR_TOO_LONG = 3076
ER_SLAVE_NEW_CHANNEL_WRONG_REPOSITORY = 3077
OBSOLETE_ER_SLAVE_CHANNEL_DELETE = 3078
ER_SLAVE_MULTIPLE_CHANNELS_CMD = 3079
ER_SLAVE_MAX_CHANNELS_EXCEEDED = 3080
ER_SLAVE_CHANNEL_MUST_STOP = 3081
ER_SLAVE_CHANNEL_NOT_RUNNING = 3082
ER_SLAVE_CHANNEL_WAS_RUNNING = 3083
ER_SLAVE_CHANNEL_WAS_NOT_RUNNING = 3084
ER_SLAVE_CHANNEL_SQL_THREAD_MUST_STOP = 3085
ER_SLAVE_CHANNEL_SQL_SKIP_COUNTER = 3086
ER_WRONG_FIELD_WITH_GROUP_V2 = 3087
ER_MIX_OF_GROUP_FUNC_AND_FIELDS_V2 = 3088
ER_WARN_DEPRECATED_SYSVAR_UPDATE = 3089
ER_WARN_DEPRECATED_SQLMODE = 3090
ER_CANNOT_LOG_PARTIAL_DROP_DATABASE_WITH_GTID = 3091
ER_GROUP_REPLICATION_CONFIGURATION = 3092
ER_GROUP_REPLICATION_RUNNING = 3093
ER_GROUP_REPLICATION_APPLIER_INIT_ERROR = 3094
ER_GROUP_REPLICATION_STOP_APPLIER_THREAD_TIMEOUT = 3095
ER_GROUP_REPLICATION_COMMUNICATION_LAYER_SESSION_ERROR = 3096
ER_GROUP_REPLICATION_COMMUNICATION_LAYER_JOIN_ERROR = 3097
ER_BEFORE_DML_VALIDATION_ERROR = 3098
ER_PREVENTS_VARIABLE_WITHOUT_RBR = 3099
ER_RUN_HOOK_ERROR = 3100
ER_TRANSACTION_ROLLBACK_DURING_COMMIT = 3101
ER_GENERATED_COLUMN_FUNCTION_IS_NOT_ALLOWED = 3102
ER_UNSUPPORTED_ALTER_INPLACE_ON_VIRTUAL_COLUMN = 3103
ER_WRONG_FK_OPTION_FOR_GENERATED_COLUMN = 3104
ER_NON_DEFAULT_VALUE_FOR_GENERATED_COLUMN = 3105
ER_UNSUPPORTED_ACTION_ON_GENERATED_COLUMN = 3106
ER_GENERATED_COLUMN_NON_PRIOR = 3107
ER_DEPENDENT_BY_GENERATED_COLUMN = 3108
ER_GENERATED_COLUMN_REF_AUTO_INC = 3109
ER_FEATURE_NOT_AVAILABLE = 3110
ER_CANT_SET_GTID_MODE = 3111
ER_CANT_USE_AUTO_POSITION_WITH_GTID_MODE_OFF = 3112
OBSOLETE_ER_CANT_REPLICATE_ANONYMOUS_WITH_AUTO_POSITION = 3113
OBSOLETE_ER_CANT_REPLICATE_ANONYMOUS_WITH_GTID_MODE_ON = 3114
OBSOLETE_ER_CANT_REPLICATE_GTID_WITH_GTID_MODE_OFF = 3115
ER_CANT_ENFORCE_GTID_CONSISTENCY_WITH_ONGOING_GTID_VIOLATING_TX = 3116
ER_ENFORCE_GTID_CONSISTENCY_WARN_WITH_ONGOING_GTID_VIOLATING_TX = 3117
ER_ACCOUNT_HAS_BEEN_LOCKED = 3118
ER_WRONG_TABLESPACE_NAME = 3119
ER_TABLESPACE_IS_NOT_EMPTY = 3120
ER_WRONG_FILE_NAME = 3121
ER_BOOST_GEOMETRY_INCONSISTENT_TURNS_EXCEPTION = 3122
ER_WARN_OPTIMIZER_HINT_SYNTAX_ERROR = 3123
ER_WARN_BAD_MAX_EXECUTION_TIME = 3124
ER_WARN_UNSUPPORTED_MAX_EXECUTION_TIME = 3125
ER_WARN_CONFLICTING_HINT = 3126
ER_WARN_UNKNOWN_QB_NAME = 3127
ER_UNRESOLVED_HINT_NAME = 3128
ER_WARN_ON_MODIFYING_GTID_EXECUTED_TABLE = 3129
ER_PLUGGABLE_PROTOCOL_COMMAND_NOT_SUPPORTED = 3130
ER_LOCKING_SERVICE_WRONG_NAME = 3131
ER_LOCKING_SERVICE_DEADLOCK = 3132
ER_LOCKING_SERVICE_TIMEOUT = 3133
ER_GIS_MAX_POINTS_IN_GEOMETRY_OVERFLOWED = 3134
ER_SQL_MODE_MERGED = 3135
ER_VTOKEN_PLUGIN_TOKEN_MISMATCH = 3136
ER_VTOKEN_PLUGIN_TOKEN_NOT_FOUND = 3137
ER_CANT_SET_VARIABLE_WHEN_OWNING_GTID = 3138
ER_SLAVE_CHANNEL_OPERATION_NOT_ALLOWED = 3139
ER_INVALID_JSON_TEXT = 3140
ER_INVALID_JSON_TEXT_IN_PARAM = 3141
ER_INVALID_JSON_BINARY_DATA = 3142
ER_INVALID_JSON_PATH = 3143
ER_INVALID_JSON_CHARSET = 3144
ER_INVALID_JSON_CHARSET_IN_FUNCTION = 3145
ER_INVALID_TYPE_FOR_JSON = 3146
ER_INVALID_CAST_TO_JSON = 3147
ER_INVALID_JSON_PATH_CHARSET = 3148
ER_INVALID_JSON_PATH_WILDCARD = 3149
ER_JSON_VALUE_TOO_BIG = 3150
ER_JSON_KEY_TOO_BIG = 3151
ER_JSON_USED_AS_KEY = 3152
ER_JSON_VACUOUS_PATH = 3153
ER_JSON_BAD_ONE_OR_ALL_ARG = 3154
ER_NUMERIC_JSON_VALUE_OUT_OF_RANGE = 3155
ER_INVALID_JSON_VALUE_FOR_CAST = 3156
ER_JSON_DOCUMENT_TOO_DEEP = 3157
ER_JSON_DOCUMENT_NULL_KEY = 3158
ER_SECURE_TRANSPORT_REQUIRED = 3159
ER_NO_SECURE_TRANSPORTS_CONFIGURED = 3160
ER_DISABLED_STORAGE_ENGINE = 3161
ER_USER_DOES_NOT_EXIST = 3162
ER_USER_ALREADY_EXISTS = 3163
ER_AUDIT_API_ABORT = 3164
ER_INVALID_JSON_PATH_ARRAY_CELL = 3165
ER_BUFPOOL_RESIZE_INPROGRESS = 3166
ER_FEATURE_DISABLED_SEE_DOC = 3167
ER_SERVER_ISNT_AVAILABLE = 3168
ER_SESSION_WAS_KILLED = 3169
ER_CAPACITY_EXCEEDED = 3170
ER_CAPACITY_EXCEEDED_IN_RANGE_OPTIMIZER = 3171
OBSOLETE_ER_TABLE_NEEDS_UPG_PART = 3172
ER_CANT_WAIT_FOR_EXECUTED_GTID_SET_WHILE_OWNING_A_GTID = 3173
ER_CANNOT_ADD_FOREIGN_BASE_COL_VIRTUAL = 3174
ER_CANNOT_CREATE_VIRTUAL_INDEX_CONSTRAINT = 3175
ER_ERROR_ON_MODIFYING_GTID_EXECUTED_TABLE = 3176
ER_LOCK_REFUSED_BY_ENGINE = 3177
ER_UNSUPPORTED_ALTER_ONLINE_ON_VIRTUAL_COLUMN = 3178
ER_MASTER_KEY_ROTATION_NOT_SUPPORTED_BY_SE = 3179
OBSOLETE_ER_MASTER_KEY_ROTATION_ERROR_BY_SE = 3180
ER_MASTER_KEY_ROTATION_BINLOG_FAILED = 3181
ER_MASTER_KEY_ROTATION_SE_UNAVAILABLE = 3182
ER_TABLESPACE_CANNOT_ENCRYPT = 3183
ER_INVALID_ENCRYPTION_OPTION = 3184
ER_CANNOT_FIND_KEY_IN_KEYRING = 3185
ER_CAPACITY_EXCEEDED_IN_PARSER = 3186
ER_UNSUPPORTED_ALTER_ENCRYPTION_INPLACE = 3187
ER_KEYRING_UDF_KEYRING_SERVICE_ERROR = 3188
ER_USER_COLUMN_OLD_LENGTH = 3189
ER_CANT_RESET_MASTER = 3190
ER_GROUP_REPLICATION_MAX_GROUP_SIZE = 3191
ER_CANNOT_ADD_FOREIGN_BASE_COL_STORED = 3192
ER_TABLE_REFERENCED = 3193
OBSOLETE_ER_PARTITION_ENGINE_DEPRECATED_FOR_TABLE = 3194
OBSOLETE_ER_WARN_USING_GEOMFROMWKB_TO_SET_SRID_ZERO = 3195
OBSOLETE_ER_WARN_USING_GEOMFROMWKB_TO_SET_SRID = 3196
ER_XA_RETRY = 3197
ER_KEYRING_AWS_UDF_AWS_KMS_ERROR = 3198
ER_BINLOG_UNSAFE_XA = 3199
ER_UDF_ERROR = 3200
ER_KEYRING_MIGRATION_FAILURE = 3201
ER_KEYRING_ACCESS_DENIED_ERROR = 3202
ER_KEYRING_MIGRATION_STATUS = 3203
OBSOLETE_ER_PLUGIN_FAILED_TO_OPEN_TABLES = 3204
OBSOLETE_ER_PLUGIN_FAILED_TO_OPEN_TABLE = 3205
OBSOLETE_ER_AUDIT_LOG_NO_KEYRING_PLUGIN_INSTALLED = 3206
OBSOLETE_ER_AUDIT_LOG_ENCRYPTION_PASSWORD_HAS_NOT_BEEN_SET = 3207
OBSOLETE_ER_AUDIT_LOG_COULD_NOT_CREATE_AES_KEY = 3208
OBSOLETE_ER_AUDIT_LOG_ENCRYPTION_PASSWORD_CANNOT_BE_FETCHED = 3209
OBSOLETE_ER_AUDIT_LOG_JSON_FILTERING_NOT_ENABLED = 3210
OBSOLETE_ER_AUDIT_LOG_UDF_INSUFFICIENT_PRIVILEGE = 3211
OBSOLETE_ER_AUDIT_LOG_SUPER_PRIVILEGE_REQUIRED = 3212
OBSOLETE_ER_COULD_NOT_REINITIALIZE_AUDIT_LOG_FILTERS = 3213
OBSOLETE_ER_AUDIT_LOG_UDF_INVALID_ARGUMENT_TYPE = 3214
OBSOLETE_ER_AUDIT_LOG_UDF_INVALID_ARGUMENT_COUNT = 3215
OBSOLETE_ER_AUDIT_LOG_HAS_NOT_BEEN_INSTALLED = 3216
OBSOLETE_ER_AUDIT_LOG_UDF_READ_INVALID_MAX_ARRAY_LENGTH_ARG_TYPE = 3217
ER_AUDIT_LOG_UDF_READ_INVALID_MAX_ARRAY_LENGTH_ARG_VALUE = 3218
OBSOLETE_ER_AUDIT_LOG_JSON_FILTER_PARSING_ERROR = 3219
OBSOLETE_ER_AUDIT_LOG_JSON_FILTER_NAME_CANNOT_BE_EMPTY = 3220
OBSOLETE_ER_AUDIT_LOG_JSON_USER_NAME_CANNOT_BE_EMPTY = 3221
OBSOLETE_ER_AUDIT_LOG_JSON_FILTER_DOES_NOT_EXISTS = 3222
OBSOLETE_ER_AUDIT_LOG_USER_FIRST_CHARACTER_MUST_BE_ALPHANUMERIC = 3223
OBSOLETE_ER_AUDIT_LOG_USER_NAME_INVALID_CHARACTER = 3224
OBSOLETE_ER_AUDIT_LOG_HOST_NAME_INVALID_CHARACTER = 3225
OBSOLETE_ER_XA_REPLICATION_FILTERS = 3226
OBSOLETE_ER_CANT_OPEN_ERROR_LOG = 3227
OBSOLETE_ER_GROUPING_ON_TIMESTAMP_IN_DST = 3228
OBSOLETE_ER_CANT_START_SERVER_NAMED_PIPE = 3229
ER_WRITE_SET_EXCEEDS_LIMIT = 3230
ER_UNSUPPORT_COMPRESSED_TEMPORARY_TABLE = 3500
ER_ACL_OPERATION_FAILED = 3501
ER_UNSUPPORTED_INDEX_ALGORITHM = 3502
ER_NO_SUCH_DB = 3503
ER_TOO_BIG_ENUM = 3504
ER_TOO_LONG_SET_ENUM_VALUE = 3505
ER_INVALID_DD_OBJECT = 3506
ER_UPDATING_DD_TABLE = 3507
ER_INVALID_DD_OBJECT_ID = 3508
ER_INVALID_DD_OBJECT_NAME = 3509
ER_TABLESPACE_MISSING_WITH_NAME = 3510
ER_TOO_LONG_ROUTINE_COMMENT = 3511
ER_SP_LOAD_FAILED = 3512
ER_INVALID_BITWISE_OPERANDS_SIZE = 3513
ER_INVALID_BITWISE_AGGREGATE_OPERANDS_SIZE = 3514
ER_WARN_UNSUPPORTED_HINT = 3515
ER_UNEXPECTED_GEOMETRY_TYPE = 3516
ER_SRS_PARSE_ERROR = 3517
ER_SRS_PROJ_PARAMETER_MISSING = 3518
ER_WARN_SRS_NOT_FOUND = 3519
ER_SRS_NOT_CARTESIAN = 3520
ER_SRS_NOT_CARTESIAN_UNDEFINED = 3521
ER_PK_INDEX_CANT_BE_INVISIBLE = 3522
ER_UNKNOWN_AUTHID = 3523
ER_FAILED_ROLE_GRANT = 3524
ER_OPEN_ROLE_TABLES = 3525
ER_FAILED_DEFAULT_ROLES = 3526
ER_COMPONENTS_NO_SCHEME = 3527
ER_COMPONENTS_NO_SCHEME_SERVICE = 3528
ER_COMPONENTS_CANT_LOAD = 3529
ER_ROLE_NOT_GRANTED = 3530
ER_FAILED_REVOKE_ROLE = 3531
ER_RENAME_ROLE = 3532
ER_COMPONENTS_CANT_ACQUIRE_SERVICE_IMPLEMENTATION = 3533
ER_COMPONENTS_CANT_SATISFY_DEPENDENCY = 3534
ER_COMPONENTS_LOAD_CANT_REGISTER_SERVICE_IMPLEMENTATION = 3535
ER_COMPONENTS_LOAD_CANT_INITIALIZE = 3536
ER_COMPONENTS_UNLOAD_NOT_LOADED = 3537
ER_COMPONENTS_UNLOAD_CANT_DEINITIALIZE = 3538
ER_COMPONENTS_CANT_RELEASE_SERVICE = 3539
ER_COMPONENTS_UNLOAD_CANT_UNREGISTER_SERVICE = 3540
ER_COMPONENTS_CANT_UNLOAD = 3541
ER_WARN_UNLOAD_THE_NOT_PERSISTED = 3542
ER_COMPONENT_TABLE_INCORRECT = 3543
ER_COMPONENT_MANIPULATE_ROW_FAILED = 3544
ER_COMPONENTS_UNLOAD_DUPLICATE_IN_GROUP = 3545
ER_CANT_SET_GTID_PURGED_DUE_SETS_CONSTRAINTS = 3546
ER_CANNOT_LOCK_USER_MANAGEMENT_CACHES = 3547
ER_SRS_NOT_FOUND = 3548
ER_VARIABLE_NOT_PERSISTED = 3549
ER_IS_QUERY_INVALID_CLAUSE = 3550
ER_UNABLE_TO_STORE_STATISTICS = 3551
ER_NO_SYSTEM_SCHEMA_ACCESS = 3552
ER_NO_SYSTEM_TABLESPACE_ACCESS = 3553
ER_NO_SYSTEM_TABLE_ACCESS = 3554
ER_NO_SYSTEM_TABLE_ACCESS_FOR_DICTIONARY_TABLE = 3555
ER_NO_SYSTEM_TABLE_ACCESS_FOR_SYSTEM_TABLE = 3556
ER_NO_SYSTEM_TABLE_ACCESS_FOR_TABLE = 3557
ER_INVALID_OPTION_KEY = 3558
ER_INVALID_OPTION_VALUE = 3559
ER_INVALID_OPTION_KEY_VALUE_PAIR = 3560
ER_INVALID_OPTION_START_CHARACTER = 3561
ER_INVALID_OPTION_END_CHARACTER = 3562
ER_INVALID_OPTION_CHARACTERS = 3563
ER_DUPLICATE_OPTION_KEY = 3564
ER_WARN_SRS_NOT_FOUND_AXIS_ORDER = 3565
ER_NO_ACCESS_TO_NATIVE_FCT = 3566
ER_RESET_MASTER_TO_VALUE_OUT_OF_RANGE = 3567
ER_UNRESOLVED_TABLE_LOCK = 3568
ER_DUPLICATE_TABLE_LOCK = 3569
ER_BINLOG_UNSAFE_SKIP_LOCKED = 3570
ER_BINLOG_UNSAFE_NOWAIT = 3571
ER_LOCK_NOWAIT = 3572
ER_CTE_RECURSIVE_REQUIRES_UNION = 3573
ER_CTE_RECURSIVE_REQUIRES_NONRECURSIVE_FIRST = 3574
ER_CTE_RECURSIVE_FORBIDS_AGGREGATION = 3575
ER_CTE_RECURSIVE_FORBIDDEN_JOIN_ORDER = 3576
ER_CTE_RECURSIVE_REQUIRES_SINGLE_REFERENCE = 3577
ER_SWITCH_TMP_ENGINE = 3578
ER_WINDOW_NO_SUCH_WINDOW = 3579
ER_WINDOW_CIRCULARITY_IN_WINDOW_GRAPH = 3580
ER_WINDOW_NO_CHILD_PARTITIONING = 3581
ER_WINDOW_NO_INHERIT_FRAME = 3582
ER_WINDOW_NO_REDEFINE_ORDER_BY = 3583
ER_WINDOW_FRAME_START_ILLEGAL = 3584
ER_WINDOW_FRAME_END_ILLEGAL = 3585
ER_WINDOW_FRAME_ILLEGAL = 3586
ER_WINDOW_RANGE_FRAME_ORDER_TYPE = 3587
ER_WINDOW_RANGE_FRAME_TEMPORAL_TYPE = 3588
ER_WINDOW_RANGE_FRAME_NUMERIC_TYPE = 3589
ER_WINDOW_RANGE_BOUND_NOT_CONSTANT = 3590
ER_WINDOW_DUPLICATE_NAME = 3591
ER_WINDOW_ILLEGAL_ORDER_BY = 3592
ER_WINDOW_INVALID_WINDOW_FUNC_USE = 3593
ER_WINDOW_INVALID_WINDOW_FUNC_ALIAS_USE = 3594
ER_WINDOW_NESTED_WINDOW_FUNC_USE_IN_WINDOW_SPEC = 3595
ER_WINDOW_ROWS_INTERVAL_USE = 3596
ER_WINDOW_NO_GROUP_ORDER_UNUSED = 3597
ER_WINDOW_EXPLAIN_JSON = 3598
ER_WINDOW_FUNCTION_IGNORES_FRAME = 3599
ER_WL9236_NOW_UNUSED = 3600
ER_INVALID_NO_OF_ARGS = 3601
ER_FIELD_IN_GROUPING_NOT_GROUP_BY = 3602
ER_TOO_LONG_TABLESPACE_COMMENT = 3603
ER_ENGINE_CANT_DROP_TABLE = 3604
ER_ENGINE_CANT_DROP_MISSING_TABLE = 3605
ER_TABLESPACE_DUP_FILENAME = 3606
ER_DB_DROP_RMDIR2 = 3607
ER_IMP_NO_FILES_MATCHED = 3608
ER_IMP_SCHEMA_DOES_NOT_EXIST = 3609
ER_IMP_TABLE_ALREADY_EXISTS = 3610
ER_IMP_INCOMPATIBLE_MYSQLD_VERSION = 3611
ER_IMP_INCOMPATIBLE_DD_VERSION = 3612
ER_IMP_INCOMPATIBLE_SDI_VERSION = 3613
ER_WARN_INVALID_HINT = 3614
ER_VAR_DOES_NOT_EXIST = 3615
ER_LONGITUDE_OUT_OF_RANGE = 3616
ER_LATITUDE_OUT_OF_RANGE = 3617
ER_NOT_IMPLEMENTED_FOR_GEOGRAPHIC_SRS = 3618
ER_ILLEGAL_PRIVILEGE_LEVEL = 3619
ER_NO_SYSTEM_VIEW_ACCESS = 3620
ER_COMPONENT_FILTER_FLABBERGASTED = 3621
ER_PART_EXPR_TOO_LONG = 3622
ER_UDF_DROP_DYNAMICALLY_REGISTERED = 3623
ER_UNABLE_TO_STORE_COLUMN_STATISTICS = 3624
ER_UNABLE_TO_UPDATE_COLUMN_STATISTICS = 3625
ER_UNABLE_TO_DROP_COLUMN_STATISTICS = 3626
ER_UNABLE_TO_BUILD_HISTOGRAM = 3627
ER_MANDATORY_ROLE = 3628
ER_MISSING_TABLESPACE_FILE = 3629
ER_PERSIST_ONLY_ACCESS_DENIED_ERROR = 3630
ER_CMD_NEED_SUPER = 3631
ER_PATH_IN_DATADIR = 3632
ER_CLONE_DDL_IN_PROGRESS = 3633
ER_CLONE_TOO_MANY_CONCURRENT_CLONES = 3634
ER_APPLIER_LOG_EVENT_VALIDATION_ERROR = 3635
ER_CTE_MAX_RECURSION_DEPTH = 3636
ER_NOT_HINT_UPDATABLE_VARIABLE = 3637
ER_CREDENTIALS_CONTRADICT_TO_HISTORY = 3638
ER_WARNING_PASSWORD_HISTORY_CLAUSES_VOID = 3639
ER_CLIENT_DOES_NOT_SUPPORT = 3640
ER_I_S_SKIPPED_TABLESPACE = 3641
ER_TABLESPACE_ENGINE_MISMATCH = 3642
ER_WRONG_SRID_FOR_COLUMN = 3643
ER_CANNOT_ALTER_SRID_DUE_TO_INDEX = 3644
ER_WARN_BINLOG_PARTIAL_UPDATES_DISABLED = 3645
ER_WARN_BINLOG_V1_ROW_EVENTS_DISABLED = 3646
ER_WARN_BINLOG_PARTIAL_UPDATES_SUGGESTS_PARTIAL_IMAGES = 3647
ER_COULD_NOT_APPLY_JSON_DIFF = 3648
ER_CORRUPTED_JSON_DIFF = 3649
ER_RESOURCE_GROUP_EXISTS = 3650
ER_RESOURCE_GROUP_NOT_EXISTS = 3651
ER_INVALID_VCPU_ID = 3652
ER_INVALID_VCPU_RANGE = 3653
ER_INVALID_THREAD_PRIORITY = 3654
ER_DISALLOWED_OPERATION = 3655
ER_RESOURCE_GROUP_BUSY = 3656
ER_RESOURCE_GROUP_DISABLED = 3657
ER_FEATURE_UNSUPPORTED = 3658
ER_ATTRIBUTE_IGNORED = 3659
ER_INVALID_THREAD_ID = 3660
ER_RESOURCE_GROUP_BIND_FAILED = 3661
ER_INVALID_USE_OF_FORCE_OPTION = 3662
ER_GROUP_REPLICATION_COMMAND_FAILURE = 3663
ER_SDI_OPERATION_FAILED = 3664
ER_MISSING_JSON_TABLE_VALUE = 3665
ER_WRONG_JSON_TABLE_VALUE = 3666
ER_TF_MUST_HAVE_ALIAS = 3667
ER_TF_FORBIDDEN_JOIN_TYPE = 3668
ER_JT_VALUE_OUT_OF_RANGE = 3669
ER_JT_MAX_NESTED_PATH = 3670
ER_PASSWORD_EXPIRATION_NOT_SUPPORTED_BY_AUTH_METHOD = 3671
ER_INVALID_GEOJSON_CRS_NOT_TOP_LEVEL = 3672
ER_BAD_NULL_ERROR_NOT_IGNORED = 3673
WARN_USELESS_SPATIAL_INDEX = 3674
ER_DISK_FULL_NOWAIT = 3675
ER_PARSE_ERROR_IN_DIGEST_FN = 3676
ER_UNDISCLOSED_PARSE_ERROR_IN_DIGEST_FN = 3677
ER_SCHEMA_DIR_EXISTS = 3678
ER_SCHEMA_DIR_MISSING = 3679
ER_SCHEMA_DIR_CREATE_FAILED = 3680
ER_SCHEMA_DIR_UNKNOWN = 3681
ER_ONLY_IMPLEMENTED_FOR_SRID_0_AND_4326 = 3682
ER_BINLOG_EXPIRE_LOG_DAYS_AND_SECS_USED_TOGETHER = 3683
ER_REGEXP_BUFFER_OVERFLOW = 3684
ER_REGEXP_ILLEGAL_ARGUMENT = 3685
ER_REGEXP_INDEX_OUTOFBOUNDS_ERROR = 3686
ER_REGEXP_INTERNAL_ERROR = 3687
ER_REGEXP_RULE_SYNTAX = 3688
ER_REGEXP_BAD_ESCAPE_SEQUENCE = 3689
ER_REGEXP_UNIMPLEMENTED = 3690
ER_REGEXP_MISMATCHED_PAREN = 3691
ER_REGEXP_BAD_INTERVAL = 3692
ER_REGEXP_MAX_LT_MIN = 3693
ER_REGEXP_INVALID_BACK_REF = 3694
ER_REGEXP_LOOK_BEHIND_LIMIT = 3695
ER_REGEXP_MISSING_CLOSE_BRACKET = 3696
ER_REGEXP_INVALID_RANGE = 3697
ER_REGEXP_STACK_OVERFLOW = 3698
ER_REGEXP_TIME_OUT = 3699
ER_REGEXP_PATTERN_TOO_BIG = 3700
ER_CANT_SET_ERROR_LOG_SERVICE = 3701
ER_EMPTY_PIPELINE_FOR_ERROR_LOG_SERVICE = 3702
ER_COMPONENT_FILTER_DIAGNOSTICS = 3703
ER_NOT_IMPLEMENTED_FOR_CARTESIAN_SRS = 3704
ER_NOT_IMPLEMENTED_FOR_PROJECTED_SRS = 3705
ER_NONPOSITIVE_RADIUS = 3706
ER_RESTART_SERVER_FAILED = 3707
ER_SRS_MISSING_MANDATORY_ATTRIBUTE = 3708
ER_SRS_MULTIPLE_ATTRIBUTE_DEFINITIONS = 3709
ER_SRS_NAME_CANT_BE_EMPTY_OR_WHITESPACE = 3710
ER_SRS_ORGANIZATION_CANT_BE_EMPTY_OR_WHITESPACE = 3711
ER_SRS_ID_ALREADY_EXISTS = 3712
ER_WARN_SRS_ID_ALREADY_EXISTS = 3713
ER_CANT_MODIFY_SRID_0 = 3714
ER_WARN_RESERVED_SRID_RANGE = 3715
ER_CANT_MODIFY_SRS_USED_BY_COLUMN = 3716
ER_SRS_INVALID_CHARACTER_IN_ATTRIBUTE = 3717
ER_SRS_ATTRIBUTE_STRING_TOO_LONG = 3718
ER_DEPRECATED_UTF8_ALIAS = 3719
ER_DEPRECATED_NATIONAL = 3720
ER_INVALID_DEFAULT_UTF8MB4_COLLATION = 3721
ER_UNABLE_TO_COLLECT_LOG_STATUS = 3722
ER_RESERVED_TABLESPACE_NAME = 3723
ER_UNABLE_TO_SET_OPTION = 3724
ER_SLAVE_POSSIBLY_DIVERGED_AFTER_DDL = 3725
ER_SRS_NOT_GEOGRAPHIC = 3726
ER_POLYGON_TOO_LARGE = 3727
ER_SPATIAL_UNIQUE_INDEX = 3728
ER_INDEX_TYPE_NOT_SUPPORTED_FOR_SPATIAL_INDEX = 3729
ER_FK_CANNOT_DROP_PARENT = 3730
ER_GEOMETRY_PARAM_LONGITUDE_OUT_OF_RANGE = 3731
ER_GEOMETRY_PARAM_LATITUDE_OUT_OF_RANGE = 3732
ER_FK_CANNOT_USE_VIRTUAL_COLUMN = 3733
ER_FK_NO_COLUMN_PARENT = 3734
ER_CANT_SET_ERROR_SUPPRESSION_LIST = 3735
ER_SRS_GEOGCS_INVALID_AXES = 3736
ER_SRS_INVALID_SEMI_MAJOR_AXIS = 3737
ER_SRS_INVALID_INVERSE_FLATTENING = 3738
ER_SRS_INVALID_ANGULAR_UNIT = 3739
ER_SRS_INVALID_PRIME_MERIDIAN = 3740
ER_TRANSFORM_SOURCE_SRS_NOT_SUPPORTED = 3741
ER_TRANSFORM_TARGET_SRS_NOT_SUPPORTED = 3742
ER_TRANSFORM_SOURCE_SRS_MISSING_TOWGS84 = 3743
ER_TRANSFORM_TARGET_SRS_MISSING_TOWGS84 = 3744
ER_TEMP_TABLE_PREVENTS_SWITCH_SESSION_BINLOG_FORMAT = 3745
ER_TEMP_TABLE_PREVENTS_SWITCH_GLOBAL_BINLOG_FORMAT = 3746
ER_RUNNING_APPLIER_PREVENTS_SWITCH_GLOBAL_BINLOG_FORMAT = 3747
ER_CLIENT_GTID_UNSAFE_CREATE_DROP_TEMP_TABLE_IN_TRX_IN_SBR = 3748
OBSOLETE_ER_XA_CANT_CREATE_MDL_BACKUP = 3749
ER_TABLE_WITHOUT_PK = 3750
ER_WARN_DATA_TRUNCATED_FUNCTIONAL_INDEX = 3751
ER_WARN_DATA_OUT_OF_RANGE_FUNCTIONAL_INDEX = 3752
ER_FUNCTIONAL_INDEX_ON_JSON_OR_GEOMETRY_FUNCTION = 3753
ER_FUNCTIONAL_INDEX_REF_AUTO_INCREMENT = 3754
ER_CANNOT_DROP_COLUMN_FUNCTIONAL_INDEX = 3755
ER_FUNCTIONAL_INDEX_PRIMARY_KEY = 3756
ER_FUNCTIONAL_INDEX_ON_LOB = 3757
ER_FUNCTIONAL_INDEX_FUNCTION_IS_NOT_ALLOWED = 3758
ER_FULLTEXT_FUNCTIONAL_INDEX = 3759
ER_SPATIAL_FUNCTIONAL_INDEX = 3760
ER_WRONG_KEY_COLUMN_FUNCTIONAL_INDEX = 3761
ER_FUNCTIONAL_INDEX_ON_FIELD = 3762
ER_GENERATED_COLUMN_NAMED_FUNCTION_IS_NOT_ALLOWED = 3763
ER_GENERATED_COLUMN_ROW_VALUE = 3764
ER_GENERATED_COLUMN_VARIABLES = 3765
ER_DEPENDENT_BY_DEFAULT_GENERATED_VALUE = 3766
ER_DEFAULT_VAL_GENERATED_NON_PRIOR = 3767
ER_DEFAULT_VAL_GENERATED_REF_AUTO_INC = 3768
ER_DEFAULT_VAL_GENERATED_FUNCTION_IS_NOT_ALLOWED = 3769
ER_DEFAULT_VAL_GENERATED_NAMED_FUNCTION_IS_NOT_ALLOWED = 3770
ER_DEFAULT_VAL_GENERATED_ROW_VALUE = 3771
ER_DEFAULT_VAL_GENERATED_VARIABLES = 3772
ER_DEFAULT_AS_VAL_GENERATED = 3773
ER_UNSUPPORTED_ACTION_ON_DEFAULT_VAL_GENERATED = 3774
ER_GTID_UNSAFE_ALTER_ADD_COL_WITH_DEFAULT_EXPRESSION = 3775
ER_FK_CANNOT_CHANGE_ENGINE = 3776
ER_WARN_DEPRECATED_USER_SET_EXPR = 3777
ER_WARN_DEPRECATED_UTF8MB3_COLLATION = 3778
ER_WARN_DEPRECATED_NESTED_COMMENT_SYNTAX = 3779
ER_FK_INCOMPATIBLE_COLUMNS = 3780
ER_GR_HOLD_WAIT_TIMEOUT = 3781
ER_GR_HOLD_KILLED = 3782
ER_GR_HOLD_MEMBER_STATUS_ERROR = 3783
ER_RPL_ENCRYPTION_FAILED_TO_FETCH_KEY = 3784
ER_RPL_ENCRYPTION_KEY_NOT_FOUND = 3785
ER_RPL_ENCRYPTION_KEYRING_INVALID_KEY = 3786
ER_RPL_ENCRYPTION_HEADER_ERROR = 3787
ER_RPL_ENCRYPTION_FAILED_TO_ROTATE_LOGS = 3788
ER_RPL_ENCRYPTION_KEY_EXISTS_UNEXPECTED = 3789
ER_RPL_ENCRYPTION_FAILED_TO_GENERATE_KEY = 3790
ER_RPL_ENCRYPTION_FAILED_TO_STORE_KEY = 3791
ER_RPL_ENCRYPTION_FAILED_TO_REMOVE_KEY = 3792
ER_RPL_ENCRYPTION_UNABLE_TO_CHANGE_OPTION = 3793
ER_RPL_ENCRYPTION_MASTER_KEY_RECOVERY_FAILED = 3794
ER_SLOW_LOG_MODE_IGNORED_WHEN_NOT_LOGGING_TO_FILE = 3795
ER_GRP_TRX_CONSISTENCY_NOT_ALLOWED = 3796
ER_GRP_TRX_CONSISTENCY_BEFORE = 3797
ER_GRP_TRX_CONSISTENCY_AFTER_ON_TRX_BEGIN = 3798
ER_GRP_TRX_CONSISTENCY_BEGIN_NOT_ALLOWED = 3799
ER_FUNCTIONAL_INDEX_ROW_VALUE_IS_NOT_ALLOWED = 3800
ER_RPL_ENCRYPTION_FAILED_TO_ENCRYPT = 3801
ER_PAGE_TRACKING_NOT_STARTED = 3802
ER_PAGE_TRACKING_RANGE_NOT_TRACKED = 3803
ER_PAGE_TRACKING_CANNOT_PURGE = 3804
ER_RPL_ENCRYPTION_CANNOT_ROTATE_BINLOG_MASTER_KEY = 3805
ER_BINLOG_MASTER_KEY_RECOVERY_OUT_OF_COMBINATION = 3806
ER_BINLOG_MASTER_KEY_ROTATION_FAIL_TO_OPERATE_KEY = 3807
ER_BINLOG_MASTER_KEY_ROTATION_FAIL_TO_ROTATE_LOGS = 3808
ER_BINLOG_MASTER_KEY_ROTATION_FAIL_TO_REENCRYPT_LOG = 3809
ER_BINLOG_MASTER_KEY_ROTATION_FAIL_TO_CLEANUP_UNUSED_KEYS = 3810
ER_BINLOG_MASTER_KEY_ROTATION_FAIL_TO_CLEANUP_AUX_KEY = 3811
ER_NON_BOOLEAN_EXPR_FOR_CHECK_CONSTRAINT = 3812
ER_COLUMN_CHECK_CONSTRAINT_REFERENCES_OTHER_COLUMN = 3813
ER_CHECK_CONSTRAINT_NAMED_FUNCTION_IS_NOT_ALLOWED = 3814
ER_CHECK_CONSTRAINT_FUNCTION_IS_NOT_ALLOWED = 3815
ER_CHECK_CONSTRAINT_VARIABLES = 3816
ER_CHECK_CONSTRAINT_ROW_VALUE = 3817
ER_CHECK_CONSTRAINT_REFERS_AUTO_INCREMENT_COLUMN = 3818
ER_CHECK_CONSTRAINT_VIOLATED = 3819
ER_CHECK_CONSTRAINT_REFERS_UNKNOWN_COLUMN = 3820
ER_CHECK_CONSTRAINT_NOT_FOUND = 3821
ER_CHECK_CONSTRAINT_DUP_NAME = 3822
ER_CHECK_CONSTRAINT_CLAUSE_USING_FK_REFER_ACTION_COLUMN = 3823
WARN_UNENCRYPTED_TABLE_IN_ENCRYPTED_DB = 3824
ER_INVALID_ENCRYPTION_REQUEST = 3825
ER_CANNOT_SET_TABLE_ENCRYPTION = 3826
ER_CANNOT_SET_DATABASE_ENCRYPTION = 3827
ER_CANNOT_SET_TABLESPACE_ENCRYPTION = 3828
ER_TABLESPACE_CANNOT_BE_ENCRYPTED = 3829
ER_TABLESPACE_CANNOT_BE_DECRYPTED = 3830
ER_TABLESPACE_TYPE_UNKNOWN = 3831
ER_TARGET_TABLESPACE_UNENCRYPTED = 3832
ER_CANNOT_USE_ENCRYPTION_CLAUSE = 3833
ER_INVALID_MULTIPLE_CLAUSES = 3834
ER_UNSUPPORTED_USE_OF_GRANT_AS = 3835
ER_UKNOWN_AUTH_ID_OR_ACCESS_DENIED_FOR_GRANT_AS = 3836
ER_DEPENDENT_BY_FUNCTIONAL_INDEX = 3837
ER_PLUGIN_NOT_EARLY = 3838
ER_INNODB_REDO_LOG_ARCHIVE_START_SUBDIR_PATH = 3839
ER_INNODB_REDO_LOG_ARCHIVE_START_TIMEOUT = 3840
ER_INNODB_REDO_LOG_ARCHIVE_DIRS_INVALID = 3841
ER_INNODB_REDO_LOG_ARCHIVE_LABEL_NOT_FOUND = 3842
ER_INNODB_REDO_LOG_ARCHIVE_DIR_EMPTY = 3843
ER_INNODB_REDO_LOG_ARCHIVE_NO_SUCH_DIR = 3844
ER_INNODB_REDO_LOG_ARCHIVE_DIR_CLASH = 3845
ER_INNODB_REDO_LOG_ARCHIVE_DIR_PERMISSIONS = 3846
ER_INNODB_REDO_LOG_ARCHIVE_FILE_CREATE = 3847
ER_INNODB_REDO_LOG_ARCHIVE_ACTIVE = 3848
ER_INNODB_REDO_LOG_ARCHIVE_INACTIVE = 3849
ER_INNODB_REDO_LOG_ARCHIVE_FAILED = 3850
ER_INNODB_REDO_LOG_ARCHIVE_SESSION = 3851
ER_STD_REGEX_ERROR = 3852
ER_INVALID_JSON_TYPE = 3853
ER_CANNOT_CONVERT_STRING = 3854
ER_DEPENDENT_BY_PARTITION_FUNC = 3855
ER_WARN_DEPRECATED_FLOAT_AUTO_INCREMENT = 3856
ER_RPL_CANT_STOP_SLAVE_WHILE_LOCKED_BACKUP = 3857
ER_WARN_DEPRECATED_FLOAT_DIGITS = 3858
ER_WARN_DEPRECATED_FLOAT_UNSIGNED = 3859
ER_WARN_DEPRECATED_INTEGER_DISPLAY_WIDTH = 3860
ER_WARN_DEPRECATED_ZEROFILL = 3861
ER_CLONE_DONOR = 3862
ER_CLONE_PROTOCOL = 3863
ER_CLONE_DONOR_VERSION = 3864
ER_CLONE_OS = 3865
ER_CLONE_PLATFORM = 3866
ER_CLONE_CHARSET = 3867
ER_CLONE_CONFIG = 3868
ER_CLONE_SYS_CONFIG = 3869
ER_CLONE_PLUGIN_MATCH = 3870
ER_CLONE_LOOPBACK = 3871
ER_CLONE_ENCRYPTION = 3872
ER_CLONE_DISK_SPACE = 3873
ER_CLONE_IN_PROGRESS = 3874
ER_CLONE_DISALLOWED = 3875
ER_CANNOT_GRANT_ROLES_TO_ANONYMOUS_USER = 3876
ER_SECONDARY_ENGINE_PLUGIN = 3877
ER_SECOND_PASSWORD_CANNOT_BE_EMPTY = 3878
ER_DB_ACCESS_DENIED = 3879
ER_DA_AUTH_ID_WITH_SYSTEM_USER_PRIV_IN_MANDATORY_ROLES = 3880
ER_DA_RPL_GTID_TABLE_CANNOT_OPEN = 3881
ER_GEOMETRY_IN_UNKNOWN_LENGTH_UNIT = 3882
ER_DA_PLUGIN_INSTALL_ERROR = 3883
ER_NO_SESSION_TEMP = 3884
ER_DA_UNKNOWN_ERROR_NUMBER = 3885
ER_COLUMN_CHANGE_SIZE = 3886
ER_REGEXP_INVALID_CAPTURE_GROUP_NAME = 3887
ER_DA_SSL_LIBRARY_ERROR = 3888
ER_SECONDARY_ENGINE = 3889
ER_SECONDARY_ENGINE_DDL = 3890
ER_INCORRECT_CURRENT_PASSWORD = 3891
ER_MISSING_CURRENT_PASSWORD = 3892
ER_CURRENT_PASSWORD_NOT_REQUIRED = 3893
ER_PASSWORD_CANNOT_BE_RETAINED_ON_PLUGIN_CHANGE = 3894
ER_CURRENT_PASSWORD_CANNOT_BE_RETAINED = 3895
ER_PARTIAL_REVOKES_EXIST = 3896
ER_CANNOT_GRANT_SYSTEM_PRIV_TO_MANDATORY_ROLE = 3897
ER_XA_REPLICATION_FILTERS = 3898
ER_UNSUPPORTED_SQL_MODE = 3899
ER_REGEXP_INVALID_FLAG = 3900
ER_PARTIAL_REVOKE_AND_DB_GRANT_BOTH_EXISTS = 3901
ER_UNIT_NOT_FOUND = 3902
ER_INVALID_JSON_VALUE_FOR_FUNC_INDEX = 3903
ER_JSON_VALUE_OUT_OF_RANGE_FOR_FUNC_INDEX = 3904
ER_EXCEEDED_MV_KEYS_NUM = 3905
ER_EXCEEDED_MV_KEYS_SPACE = 3906
ER_FUNCTIONAL_INDEX_DATA_IS_TOO_LONG = 3907
ER_WRONG_MVI_VALUE = 3908
ER_WARN_FUNC_INDEX_NOT_APPLICABLE = 3909
ER_GRP_RPL_UDF_ERROR = 3910
ER_UPDATE_GTID_PURGED_WITH_GR = 3911
ER_GROUPING_ON_TIMESTAMP_IN_DST = 3912
ER_TABLE_NAME_CAUSES_TOO_LONG_PATH = 3913
ER_AUDIT_LOG_INSUFFICIENT_PRIVILEGE = 3914
OBSOLETE_ER_AUDIT_LOG_PASSWORD_HAS_BEEN_COPIED = 3915
ER_DA_GRP_RPL_STARTED_AUTO_REJOIN = 3916
ER_SYSVAR_CHANGE_DURING_QUERY = 3917
ER_GLOBSTAT_CHANGE_DURING_QUERY = 3918
ER_GRP_RPL_MESSAGE_SERVICE_INIT_FAILURE = 3919
ER_CHANGE_MASTER_WRONG_COMPRESSION_ALGORITHM_CLIENT = 3920
ER_CHANGE_MASTER_WRONG_COMPRESSION_LEVEL_CLIENT = 3921
ER_WRONG_COMPRESSION_ALGORITHM_CLIENT = 3922
ER_WRONG_COMPRESSION_LEVEL_CLIENT = 3923
ER_CHANGE_MASTER_WRONG_COMPRESSION_ALGORITHM_LIST_CLIENT = 3924
ER_CLIENT_PRIVILEGE_CHECKS_USER_CANNOT_BE_ANONYMOUS = 3925
ER_CLIENT_PRIVILEGE_CHECKS_USER_DOES_NOT_EXIST = 3926
ER_CLIENT_PRIVILEGE_CHECKS_USER_CORRUPT = 3927
ER_CLIENT_PRIVILEGE_CHECKS_USER_NEEDS_RPL_APPLIER_PRIV = 3928
ER_WARN_DA_PRIVILEGE_NOT_REGISTERED = 3929
ER_CLIENT_KEYRING_UDF_KEY_INVALID = 3930
ER_CLIENT_KEYRING_UDF_KEY_TYPE_INVALID = 3931
ER_CLIENT_KEYRING_UDF_KEY_TOO_LONG = 3932
ER_CLIENT_KEYRING_UDF_KEY_TYPE_TOO_LONG = 3933
ER_JSON_SCHEMA_VALIDATION_ERROR_WITH_DETAILED_REPORT = 3934
ER_DA_UDF_INVALID_CHARSET_SPECIFIED = 3935
ER_DA_UDF_INVALID_CHARSET = 3936
ER_DA_UDF_INVALID_COLLATION = 3937
ER_DA_UDF_INVALID_EXTENSION_ARGUMENT_TYPE = 3938
ER_MULTIPLE_CONSTRAINTS_WITH_SAME_NAME = 3939
ER_CONSTRAINT_NOT_FOUND = 3940
ER_ALTER_CONSTRAINT_ENFORCEMENT_NOT_SUPPORTED = 3941
ER_TABLE_VALUE_CONSTRUCTOR_MUST_HAVE_COLUMNS = 3942
ER_TABLE_VALUE_CONSTRUCTOR_CANNOT_HAVE_DEFAULT = 3943
ER_CLIENT_QUERY_FAILURE_INVALID_NON_ROW_FORMAT = 3944
ER_REQUIRE_ROW_FORMAT_INVALID_VALUE = 3945
ER_FAILED_TO_DETERMINE_IF_ROLE_IS_MANDATORY = 3946
ER_FAILED_TO_FETCH_MANDATORY_ROLE_LIST = 3947
ER_CLIENT_LOCAL_FILES_DISABLED = 3948
ER_IMP_INCOMPATIBLE_CFG_VERSION = 3949
ER_DA_OOM = 3950
ER_DA_UDF_INVALID_ARGUMENT_TO_SET_CHARSET = 3951
ER_DA_UDF_INVALID_RETURN_TYPE_TO_SET_CHARSET = 3952
ER_MULTIPLE_INTO_CLAUSES = 3953
ER_MISPLACED_INTO = 3954
ER_USER_ACCESS_DENIED_FOR_USER_ACCOUNT_BLOCKED_BY_PASSWORD_LOCK = 3955
ER_WARN_DEPRECATED_YEAR_UNSIGNED = 3956
ER_CLONE_NETWORK_PACKET = 3957
ER_SDI_OPERATION_FAILED_MISSING_RECORD = 3958
ER_DEPENDENT_BY_CHECK_CONSTRAINT = 3959
ER_GRP_OPERATION_NOT_ALLOWED_GR_MUST_STOP = 3960
ER_WARN_DEPRECATED_JSON_TABLE_ON_ERROR_ON_EMPTY = 3961
ER_WARN_DEPRECATED_INNER_INTO = 3962
ER_WARN_DEPRECATED_VALUES_FUNCTION_ALWAYS_NULL = 3963
ER_WARN_DEPRECATED_SQL_CALC_FOUND_ROWS = 3964
ER_WARN_DEPRECATED_FOUND_ROWS = 3965
ER_MISSING_JSON_VALUE = 3966
ER_MULTIPLE_JSON_VALUES = 3967
ER_HOSTNAME_TOO_LONG = 3968
ER_WARN_CLIENT_DEPRECATED_PARTITION_PREFIX_KEY = 3969
ER_GROUP_REPLICATION_USER_EMPTY_MSG = 3970
ER_GROUP_REPLICATION_USER_MANDATORY_MSG = 3971
ER_GROUP_REPLICATION_PASSWORD_LENGTH = 3972
ER_SUBQUERY_TRANSFORM_REJECTED = 3973
ER_DA_GRP_RPL_RECOVERY_ENDPOINT_FORMAT = 3974
ER_DA_GRP_RPL_RECOVERY_ENDPOINT_INVALID = 3975
ER_WRONG_VALUE_FOR_VAR_PLUS_ACTIONABLE_PART = 3976
ER_STATEMENT_NOT_ALLOWED_AFTER_START_TRANSACTION = 3977
ER_FOREIGN_KEY_WITH_ATOMIC_CREATE_SELECT = 3978
ER_NOT_ALLOWED_WITH_START_TRANSACTION = 3979
ER_INVALID_JSON_ATTRIBUTE = 3980
ER_ENGINE_ATTRIBUTE_NOT_SUPPORTED = 3981
ER_INVALID_USER_ATTRIBUTE_JSON = 3982
ER_INNODB_REDO_DISABLED = 3983
ER_INNODB_REDO_ARCHIVING_ENABLED = 3984
ER_MDL_OUT_OF_RESOURCES = 3985
ER_IMPLICIT_COMPARISON_FOR_JSON = 3986
ER_FUNCTION_DOES_NOT_SUPPORT_CHARACTER_SET = 3987
ER_IMPOSSIBLE_STRING_CONVERSION = 3988
ER_SCHEMA_READ_ONLY = 3989
ER_RPL_ASYNC_RECONNECT_GTID_MODE_OFF = 3990
ER_RPL_ASYNC_RECONNECT_AUTO_POSITION_OFF = 3991
ER_DISABLE_GTID_MODE_REQUIRES_ASYNC_RECONNECT_OFF = 3992
ER_DISABLE_AUTO_POSITION_REQUIRES_ASYNC_RECONNECT_OFF = 3993
ER_INVALID_PARAMETER_USE = 3994
ER_CHARACTER_SET_MISMATCH = 3995
ER_WARN_VAR_VALUE_CHANGE_NOT_SUPPORTED = 3996
ER_INVALID_TIME_ZONE_INTERVAL = 3997
ER_INVALID_CAST = 3998
ER_HYPERGRAPH_NOT_SUPPORTED_YET = 3999
ER_WARN_HYPERGRAPH_EXPERIMENTAL = 4000
ER_DA_NO_ERROR_LOG_PARSER_CONFIGURED = 4001
ER_DA_ERROR_LOG_TABLE_DISABLED = 4002
ER_DA_ERROR_LOG_MULTIPLE_FILTERS = 4003
ER_DA_CANT_OPEN_ERROR_LOG = 4004
ER_USER_REFERENCED_AS_DEFINER = 4005
ER_CANNOT_USER_REFERENCED_AS_DEFINER = 4006
ER_REGEX_NUMBER_TOO_BIG = 4007
ER_SPVAR_NONINTEGER_TYPE = 4008
WARN_UNSUPPORTED_ACL_TABLES_READ = 4009
ER_BINLOG_UNSAFE_ACL_TABLE_READ_IN_DML_DDL = 4010
ER_STOP_REPLICA_MONITOR_IO_THREAD_TIMEOUT = 4011
ER_STARTING_REPLICA_MONITOR_IO_THREAD = 4012
ER_CANT_USE_ANONYMOUS_TO_GTID_WITH_GTID_MODE_NOT_ON = 4013
ER_CANT_COMBINE_ANONYMOUS_TO_GTID_AND_AUTOPOSITION = 4014
ER_ASSIGN_GTIDS_TO_ANONYMOUS_TRANSACTIONS_REQUIRES_GTID_MODE_ON = 4015
ER_SQL_REPLICA_SKIP_COUNTER_USED_WITH_GTID_MODE_ON = 4016
ER_USING_ASSIGN_GTIDS_TO_ANONYMOUS_TRANSACTIONS_AS_LOCAL_OR_UUID = 4017
ER_CANT_SET_ANONYMOUS_TO_GTID_AND_WAIT_UNTIL_SQL_THD_AFTER_GTIDS = 4018
ER_CANT_SET_SQL_AFTER_OR_BEFORE_GTIDS_WITH_ANONYMOUS_TO_GTID = 4019
ER_ANONYMOUS_TO_GTID_UUID_SAME_AS_GROUP_NAME = 4020
ER_CANT_USE_SAME_UUID_AS_GROUP_NAME = 4021
ER_GRP_RPL_RECOVERY_CHANNEL_STILL_RUNNING = 4022
ER_INNODB_INVALID_AUTOEXTEND_SIZE_VALUE = 4023
ER_INNODB_INCOMPATIBLE_WITH_TABLESPACE = 4024
ER_INNODB_AUTOEXTEND_SIZE_OUT_OF_RANGE = 4025
ER_CANNOT_USE_AUTOEXTEND_SIZE_CLAUSE = 4026
ER_ROLE_GRANTED_TO_ITSELF = 4027
ER_TABLE_MUST_HAVE_A_VISIBLE_COLUMN = 4028
ER_INNODB_COMPRESSION_FAILURE = 4029
ER_WARN_ASYNC_CONN_FAILOVER_NETWORK_NAMESPACE = 4030
ER_CLIENT_INTERACTION_TIMEOUT = 4031
ER_INVALID_CAST_TO_GEOMETRY = 4032
ER_INVALID_CAST_POLYGON_RING_DIRECTION = 4033
ER_GIS_DIFFERENT_SRIDS_AGGREGATION = 4034
ER_RELOAD_KEYRING_FAILURE = 4035
ER_SDI_GET_KEYS_INVALID_TABLESPACE = 4036
ER_CHANGE_RPL_SRC_WRONG_COMPRESSION_ALGORITHM_SIZE = 4037
ER_WARN_DEPRECATED_TLS_VERSION_FOR_CHANNEL_CLI = 4038
ER_CANT_USE_SAME_UUID_AS_VIEW_CHANGE_UUID = 4039
ER_ANONYMOUS_TO_GTID_UUID_SAME_AS_VIEW_CHANGE_UUID = 4040
ER_GRP_RPL_VIEW_CHANGE_UUID_FAIL_GET_VARIABLE = 4041
ER_WARN_ADUIT_LOG_MAX_SIZE_AND_PRUNE_SECONDS = 4042
ER_WARN_ADUIT_LOG_MAX_SIZE_CLOSE_TO_ROTATE_ON_SIZE = 4043
ER_KERBEROS_CREATE_USER = 4044
ER_INSTALL_PLUGIN_CONFLICT_CLIENT = 4045
ER_DA_ERROR_LOG_COMPONENT_FLUSH_FAILED = 4046
ER_WARN_SQL_AFTER_MTS_GAPS_GAP_NOT_CALCULATED = 4047
ER_INVALID_ASSIGNMENT_TARGET = 4048
ER_OPERATION_NOT_ALLOWED_ON_GR_SECONDARY = 4049
ER_GRP_RPL_FAILOVER_CHANNEL_STATUS_PROPAGATION = 4050
ER_WARN_AUDIT_LOG_FORMAT_UNIX_TIMESTAMP_ONLY_WHEN_JSON = 4051
ER_INVALID_MFA_PLUGIN_SPECIFIED = 4052
ER_IDENTIFIED_BY_UNSUPPORTED = 4053
ER_INVALID_PLUGIN_FOR_REGISTRATION = 4054
ER_PLUGIN_REQUIRES_REGISTRATION = 4055
ER_MFA_METHOD_EXISTS = 4056
ER_MFA_METHOD_NOT_EXISTS = 4057
ER_AUTHENTICATION_POLICY_MISMATCH = 4058
ER_PLUGIN_REGISTRATION_DONE = 4059
ER_INVALID_USER_FOR_REGISTRATION = 4060
ER_USER_REGISTRATION_FAILED = 4061
ER_MFA_METHODS_INVALID_ORDER = 4062
ER_MFA_METHODS_IDENTICAL = 4063
ER_INVALID_MFA_OPERATIONS_FOR_PASSWORDLESS_USER = 4064
ER_CHANGE_REPLICATION_SOURCE_NO_OPTIONS_FOR_GTID_ONLY = 4065
ER_CHANGE_REP_SOURCE_CANT_DISABLE_REQ_ROW_FORMAT_WITH_GTID_ONLY = 4066
ER_CHANGE_REP_SOURCE_CANT_DISABLE_AUTO_POSITION_WITH_GTID_ONLY = 4067
ER_CHANGE_REP_SOURCE_CANT_DISABLE_GTID_ONLY_WITHOUT_POSITIONS = 4068
ER_CHANGE_REP_SOURCE_CANT_DISABLE_AUTO_POS_WITHOUT_POSITIONS = 4069
ER_CHANGE_REP_SOURCE_GR_CHANNEL_WITH_GTID_MODE_NOT_ON = 4070
ER_CANT_USE_GTID_ONLY_WITH_GTID_MODE_NOT_ON = 4071
ER_WARN_C_DISABLE_GTID_ONLY_WITH_SOURCE_AUTO_POS_INVALID_POS = 4072
ER_DA_SSL_FIPS_MODE_ERROR = 4073
CR_UNKNOWN_ERROR = 2000
CR_SOCKET_CREATE_ERROR = 2001
CR_CONNECTION_ERROR = 2002
CR_CONN_HOST_ERROR = 2003
CR_IPSOCK_ERROR = 2004
CR_UNKNOWN_HOST = 2005
CR_SERVER_GONE_ERROR = 2006
CR_VERSION_ERROR = 2007
CR_OUT_OF_MEMORY = 2008
CR_WRONG_HOST_INFO = 2009
CR_LOCALHOST_CONNECTION = 2010
CR_TCP_CONNECTION = 2011
CR_SERVER_HANDSHAKE_ERR = 2012
CR_SERVER_LOST = 2013
CR_COMMANDS_OUT_OF_SYNC = 2014
CR_NAMEDPIPE_CONNECTION = 2015
CR_NAMEDPIPEWAIT_ERROR = 2016
CR_NAMEDPIPEOPEN_ERROR = 2017
CR_NAMEDPIPESETSTATE_ERROR = 2018
CR_CANT_READ_CHARSET = 2019
CR_NET_PACKET_TOO_LARGE = 2020
CR_EMBEDDED_CONNECTION = 2021
CR_PROBE_SLAVE_STATUS = 2022
CR_PROBE_SLAVE_HOSTS = 2023
CR_PROBE_SLAVE_CONNECT = 2024
CR_PROBE_MASTER_CONNECT = 2025
CR_SSL_CONNECTION_ERROR = 2026
CR_MALFORMED_PACKET = 2027
CR_WRONG_LICENSE = 2028
CR_NULL_POINTER = 2029
CR_NO_PREPARE_STMT = 2030
CR_PARAMS_NOT_BOUND = 2031
CR_DATA_TRUNCATED = 2032
CR_NO_PARAMETERS_EXISTS = 2033
CR_INVALID_PARAMETER_NO = 2034
CR_INVALID_BUFFER_USE = 2035
CR_UNSUPPORTED_PARAM_TYPE = 2036
CR_SHARED_MEMORY_CONNECTION = 2037
CR_SHARED_MEMORY_CONNECT_REQUEST_ERROR = 2038
CR_SHARED_MEMORY_CONNECT_ANSWER_ERROR = 2039
CR_SHARED_MEMORY_CONNECT_FILE_MAP_ERROR = 2040
CR_SHARED_MEMORY_CONNECT_MAP_ERROR = 2041
CR_SHARED_MEMORY_FILE_MAP_ERROR = 2042
CR_SHARED_MEMORY_MAP_ERROR = 2043
CR_SHARED_MEMORY_EVENT_ERROR = 2044
CR_SHARED_MEMORY_CONNECT_ABANDONED_ERROR = 2045
CR_SHARED_MEMORY_CONNECT_SET_ERROR = 2046
CR_CONN_UNKNOW_PROTOCOL = 2047
CR_INVALID_CONN_HANDLE = 2048
CR_UNUSED_1 = 2049
CR_FETCH_CANCELED = 2050
CR_NO_DATA = 2051
CR_NO_STMT_METADATA = 2052
CR_NO_RESULT_SET = 2053
CR_NOT_IMPLEMENTED = 2054
CR_SERVER_LOST_EXTENDED = 2055
CR_STMT_CLOSED = 2056
CR_NEW_STMT_METADATA = 2057
CR_ALREADY_CONNECTED = 2058
CR_AUTH_PLUGIN_CANNOT_LOAD = 2059
CR_DUPLICATE_CONNECTION_ATTR = 2060
CR_AUTH_PLUGIN_ERR = 2061
CR_INSECURE_API_ERR = 2062
CR_FILE_NAME_TOO_LONG = 2063
CR_SSL_FIPS_MODE_ERR = 2064
CR_DEPRECATED_COMPRESSION_NOT_SUPPORTED = 2065
CR_COMPRESSION_WRONGLY_CONFIGURED = 2066
CR_KERBEROS_USER_NOT_FOUND = 2067
CR_LOAD_DATA_LOCAL_INFILE_REJECTED = 2068
CR_LOAD_DATA_LOCAL_INFILE_REALPATH_FAIL = 2069
CR_DNS_SRV_LOOKUP_FAILED = 2070
CR_MANDATORY_TRACKER_NOT_FOUND = 2071
CR_INVALID_FACTOR_NO = 2072
# End MySQL Errors

# Start X Plugin Errors
ER_X_BAD_MESSAGE = 5000
ER_X_CAPABILITIES_PREPARE_FAILED = 5001
ER_X_CAPABILITY_NOT_FOUND = 5002
ER_X_INVALID_PROTOCOL_DATA = 5003
ER_X_BAD_CONNECTION_SESSION_ATTRIBUTE_VALUE_LENGTH = 5004
ER_X_BAD_CONNECTION_SESSION_ATTRIBUTE_KEY_LENGTH = 5005
ER_X_BAD_CONNECTION_SESSION_ATTRIBUTE_EMPTY_KEY = 5006
ER_X_BAD_CONNECTION_SESSION_ATTRIBUTE_LENGTH = 5007
ER_X_BAD_CONNECTION_SESSION_ATTRIBUTE_TYPE = 5008
ER_X_CAPABILITY_SET_NOT_ALLOWED = 5009
ER_X_SERVICE_ERROR = 5010
ER_X_SESSION = 5011
ER_X_INVALID_ARGUMENT = 5012
ER_X_MISSING_ARGUMENT = 5013
ER_X_BAD_INSERT_DATA = 5014
ER_X_CMD_NUM_ARGUMENTS = 5015
ER_X_CMD_ARGUMENT_TYPE = 5016
ER_X_CMD_ARGUMENT_VALUE = 5017
ER_X_BAD_UPSERT_DATA = 5018
ER_X_DUPLICATED_CAPABILITIES = 5019
ER_X_CMD_ARGUMENT_OBJECT_EMPTY = 5020
ER_X_CMD_INVALID_ARGUMENT = 5021
ER_X_BAD_UPDATE_DATA = 5050
ER_X_BAD_TYPE_OF_UPDATE = 5051
ER_X_BAD_COLUMN_TO_UPDATE = 5052
ER_X_BAD_MEMBER_TO_UPDATE = 5053
ER_X_BAD_STATEMENT_ID = 5110
ER_X_BAD_CURSOR_ID = 5111
ER_X_BAD_SCHEMA = 5112
ER_X_BAD_TABLE = 5113
ER_X_BAD_PROJECTION = 5114
ER_X_DOC_ID_MISSING = 5115
ER_X_DUPLICATE_ENTRY = 5116
ER_X_DOC_REQUIRED_FIELD_MISSING = 5117
ER_X_PROJ_BAD_KEY_NAME = 5120
ER_X_BAD_DOC_PATH = 5121
ER_X_CURSOR_EXISTS = 5122
ER_X_CURSOR_REACHED_EOF = 5123
ER_X_PREPARED_STATMENT_CAN_HAVE_ONE_CURSOR = 5131
ER_X_PREPARED_EXECUTE_ARGUMENT_NOT_SUPPORTED = 5133
ER_X_PREPARED_EXECUTE_ARGUMENT_CONSISTENCY = 5134
ER_X_EXPR_BAD_OPERATOR = 5150
ER_X_EXPR_BAD_NUM_ARGS = 5151
ER_X_EXPR_MISSING_ARG = 5152
ER_X_EXPR_BAD_TYPE_VALUE = 5153
ER_X_EXPR_BAD_VALUE = 5154
ER_X_INVALID_COLLECTION = 5156
ER_X_INVALID_ADMIN_COMMAND = 5157
ER_X_EXPECT_NOT_OPEN = 5158
ER_X_EXPECT_NO_ERROR_FAILED = 5159
ER_X_EXPECT_BAD_CONDITION = 5160
ER_X_EXPECT_BAD_CONDITION_VALUE = 5161
ER_X_INVALID_NAMESPACE = 5162
ER_X_BAD_NOTICE = 5163
ER_X_CANNOT_DISABLE_NOTICE = 5164
ER_X_BAD_CONFIGURATION = 5165
ER_X_MYSQLX_ACCOUNT_MISSING_PERMISSIONS = 5167
ER_X_EXPECT_FIELD_EXISTS_FAILED = 5168
ER_X_BAD_LOCKING = 5169
ER_X_FRAME_COMPRESSION_DISABLED = 5170
ER_X_DECOMPRESSION_FAILED = 5171
ER_X_BAD_COMPRESSED_FRAME = 5174
ER_X_CAPABILITY_COMPRESSION_INVALID_ALGORITHM = 5175
ER_X_CAPABILITY_COMPRESSION_INVALID_SERVER_STYLE = 5176
ER_X_CAPABILITY_COMPRESSION_INVALID_CLIENT_STYLE = 5177
ER_X_CAPABILITY_COMPRESSION_INVALID_OPTION = 5178
ER_X_CAPABILITY_COMPRESSION_MISSING_REQUIRED_FIELDS = 5179
ER_X_DOCUMENT_DOESNT_MATCH_EXPECTED_SCHEMA = 5180
ER_X_COLLECTION_OPTION_DOESNT_EXISTS = 5181
ER_X_INVALID_VALIDATION_SCHEMA = 5182
# End X Plugin Errors

virt/lib/python3.9/site-packages/mysqlx/errorcode 3.py
PrinceDisant/LostInNL
# -*- coding: utf-8 -*-

# Copyright (c) 2013, 2022, Oracle and/or its affiliates. All rights reserved.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

"""This module contains the MySQL Server and Client error codes."""

# This file was auto-generated.
_GENERATED_ON = "2021-08-11"
_MYSQL_VERSION = (8, 0, 27)

# Start MySQL Errors
OBSOLETE_ER_HASHCHK = 1000
OBSOLETE_ER_NISAMCHK = 1001
ER_NO = 1002
ER_YES = 1003
ER_CANT_CREATE_FILE = 1004
ER_CANT_CREATE_TABLE = 1005
ER_CANT_CREATE_DB = 1006
ER_DB_CREATE_EXISTS = 1007
ER_DB_DROP_EXISTS = 1008
OBSOLETE_ER_DB_DROP_DELETE = 1009
ER_DB_DROP_RMDIR = 1010
OBSOLETE_ER_CANT_DELETE_FILE = 1011
ER_CANT_FIND_SYSTEM_REC = 1012
ER_CANT_GET_STAT = 1013
OBSOLETE_ER_CANT_GET_WD = 1014
ER_CANT_LOCK = 1015
ER_CANT_OPEN_FILE = 1016
ER_FILE_NOT_FOUND = 1017
ER_CANT_READ_DIR = 1018
OBSOLETE_ER_CANT_SET_WD = 1019
ER_CHECKREAD = 1020
OBSOLETE_ER_DISK_FULL = 1021
ER_DUP_KEY = 1022
OBSOLETE_ER_ERROR_ON_CLOSE = 1023
ER_ERROR_ON_READ = 1024
ER_ERROR_ON_RENAME = 1025
ER_ERROR_ON_WRITE = 1026
ER_FILE_USED = 1027
OBSOLETE_ER_FILSORT_ABORT = 1028
OBSOLETE_ER_FORM_NOT_FOUND = 1029
ER_GET_ERRNO = 1030
ER_ILLEGAL_HA = 1031
ER_KEY_NOT_FOUND = 1032
ER_NOT_FORM_FILE = 1033
ER_NOT_KEYFILE = 1034
ER_OLD_KEYFILE = 1035
ER_OPEN_AS_READONLY = 1036
ER_OUTOFMEMORY = 1037
ER_OUT_OF_SORTMEMORY = 1038
OBSOLETE_ER_UNEXPECTED_EOF = 1039
ER_CON_COUNT_ERROR = 1040
ER_OUT_OF_RESOURCES = 1041
ER_BAD_HOST_ERROR = 1042
ER_HANDSHAKE_ERROR = 1043
ER_DBACCESS_DENIED_ERROR = 1044
ER_ACCESS_DENIED_ERROR = 1045
ER_NO_DB_ERROR = 1046
ER_UNKNOWN_COM_ERROR = 1047
ER_BAD_NULL_ERROR = 1048
ER_BAD_DB_ERROR = 1049
ER_TABLE_EXISTS_ERROR = 1050
ER_BAD_TABLE_ERROR = 1051
ER_NON_UNIQ_ERROR = 1052
ER_SERVER_SHUTDOWN = 1053
ER_BAD_FIELD_ERROR = 1054
ER_WRONG_FIELD_WITH_GROUP = 1055
ER_WRONG_GROUP_FIELD = 1056
ER_WRONG_SUM_SELECT = 1057
ER_WRONG_VALUE_COUNT = 1058
ER_TOO_LONG_IDENT = 1059
ER_DUP_FIELDNAME = 1060
ER_DUP_KEYNAME = 1061
ER_DUP_ENTRY = 1062
ER_WRONG_FIELD_SPEC = 1063
ER_PARSE_ERROR = 1064
ER_EMPTY_QUERY = 1065
ER_NONUNIQ_TABLE = 1066
ER_INVALID_DEFAULT = 1067
ER_MULTIPLE_PRI_KEY = 1068
ER_TOO_MANY_KEYS = 1069
ER_TOO_MANY_KEY_PARTS = 1070
ER_TOO_LONG_KEY = 1071
ER_KEY_COLUMN_DOES_NOT_EXITS = 1072
ER_BLOB_USED_AS_KEY = 1073
ER_TOO_BIG_FIELDLENGTH = 1074
ER_WRONG_AUTO_KEY = 1075
ER_READY = 1076
OBSOLETE_ER_NORMAL_SHUTDOWN = 1077
OBSOLETE_ER_GOT_SIGNAL = 1078
ER_SHUTDOWN_COMPLETE = 1079
ER_FORCING_CLOSE = 1080
ER_IPSOCK_ERROR = 1081
ER_NO_SUCH_INDEX = 1082
ER_WRONG_FIELD_TERMINATORS = 1083
ER_BLOBS_AND_NO_TERMINATED = 1084
ER_TEXTFILE_NOT_READABLE = 1085
ER_FILE_EXISTS_ERROR = 1086
ER_LOAD_INFO = 1087
ER_ALTER_INFO = 1088
ER_WRONG_SUB_KEY = 1089
ER_CANT_REMOVE_ALL_FIELDS = 1090
ER_CANT_DROP_FIELD_OR_KEY = 1091
ER_INSERT_INFO = 1092
ER_UPDATE_TABLE_USED = 1093
ER_NO_SUCH_THREAD = 1094
ER_KILL_DENIED_ERROR = 1095
ER_NO_TABLES_USED = 1096
ER_TOO_BIG_SET = 1097
ER_NO_UNIQUE_LOGFILE = 1098
ER_TABLE_NOT_LOCKED_FOR_WRITE = 1099
ER_TABLE_NOT_LOCKED = 1100
ER_BLOB_CANT_HAVE_DEFAULT = 1101
ER_WRONG_DB_NAME = 1102
ER_WRONG_TABLE_NAME = 1103
ER_TOO_BIG_SELECT = 1104
ER_UNKNOWN_ERROR = 1105
ER_UNKNOWN_PROCEDURE = 1106
ER_WRONG_PARAMCOUNT_TO_PROCEDURE = 1107
ER_WRONG_PARAMETERS_TO_PROCEDURE = 1108
ER_UNKNOWN_TABLE = 1109
ER_FIELD_SPECIFIED_TWICE = 1110
ER_INVALID_GROUP_FUNC_USE = 1111
ER_UNSUPPORTED_EXTENSION = 1112
ER_TABLE_MUST_HAVE_COLUMNS = 1113
ER_RECORD_FILE_FULL = 1114
ER_UNKNOWN_CHARACTER_SET = 1115
ER_TOO_MANY_TABLES = 1116
ER_TOO_MANY_FIELDS = 1117
ER_TOO_BIG_ROWSIZE = 1118
ER_STACK_OVERRUN = 1119
ER_WRONG_OUTER_JOIN_UNUSED = 1120
ER_NULL_COLUMN_IN_INDEX = 1121
ER_CANT_FIND_UDF = 1122
ER_CANT_INITIALIZE_UDF = 1123
ER_UDF_NO_PATHS = 1124
ER_UDF_EXISTS = 1125
ER_CANT_OPEN_LIBRARY = 1126
ER_CANT_FIND_DL_ENTRY = 1127
ER_FUNCTION_NOT_DEFINED = 1128
ER_HOST_IS_BLOCKED = 1129
ER_HOST_NOT_PRIVILEGED = 1130
ER_PASSWORD_ANONYMOUS_USER = 1131
ER_PASSWORD_NOT_ALLOWED = 1132
ER_PASSWORD_NO_MATCH = 1133
ER_UPDATE_INFO = 1134
ER_CANT_CREATE_THREAD = 1135
ER_WRONG_VALUE_COUNT_ON_ROW = 1136
ER_CANT_REOPEN_TABLE = 1137
ER_INVALID_USE_OF_NULL = 1138
ER_REGEXP_ERROR = 1139
ER_MIX_OF_GROUP_FUNC_AND_FIELDS = 1140
ER_NONEXISTING_GRANT = 1141
ER_TABLEACCESS_DENIED_ERROR = 1142
ER_COLUMNACCESS_DENIED_ERROR = 1143
ER_ILLEGAL_GRANT_FOR_TABLE = 1144
ER_GRANT_WRONG_HOST_OR_USER = 1145
ER_NO_SUCH_TABLE = 1146
ER_NONEXISTING_TABLE_GRANT = 1147
ER_NOT_ALLOWED_COMMAND = 1148
ER_SYNTAX_ERROR = 1149
OBSOLETE_ER_UNUSED1 = 1150
OBSOLETE_ER_UNUSED2 = 1151
ER_ABORTING_CONNECTION = 1152
ER_NET_PACKET_TOO_LARGE = 1153
ER_NET_READ_ERROR_FROM_PIPE = 1154
ER_NET_FCNTL_ERROR = 1155
ER_NET_PACKETS_OUT_OF_ORDER = 1156
ER_NET_UNCOMPRESS_ERROR = 1157
ER_NET_READ_ERROR = 1158
ER_NET_READ_INTERRUPTED = 1159
ER_NET_ERROR_ON_WRITE = 1160
ER_NET_WRITE_INTERRUPTED = 1161
ER_TOO_LONG_STRING = 1162
ER_TABLE_CANT_HANDLE_BLOB = 1163
ER_TABLE_CANT_HANDLE_AUTO_INCREMENT = 1164
OBSOLETE_ER_UNUSED3 = 1165
ER_WRONG_COLUMN_NAME = 1166
ER_WRONG_KEY_COLUMN = 1167
ER_WRONG_MRG_TABLE = 1168
ER_DUP_UNIQUE = 1169
ER_BLOB_KEY_WITHOUT_LENGTH = 1170
ER_PRIMARY_CANT_HAVE_NULL = 1171
ER_TOO_MANY_ROWS = 1172
ER_REQUIRES_PRIMARY_KEY = 1173
OBSOLETE_ER_NO_RAID_COMPILED = 1174
ER_UPDATE_WITHOUT_KEY_IN_SAFE_MODE = 1175
ER_KEY_DOES_NOT_EXITS = 1176
ER_CHECK_NO_SUCH_TABLE = 1177
ER_CHECK_NOT_IMPLEMENTED = 1178
ER_CANT_DO_THIS_DURING_AN_TRANSACTION = 1179
ER_ERROR_DURING_COMMIT = 1180
ER_ERROR_DURING_ROLLBACK = 1181
ER_ERROR_DURING_FLUSH_LOGS = 1182
OBSOLETE_ER_ERROR_DURING_CHECKPOINT = 1183
ER_NEW_ABORTING_CONNECTION = 1184
OBSOLETE_ER_DUMP_NOT_IMPLEMENTED = 1185
OBSOLETE_ER_FLUSH_MASTER_BINLOG_CLOSED = 1186
OBSOLETE_ER_INDEX_REBUILD = 1187
ER_MASTER = 1188
ER_MASTER_NET_READ = 1189
ER_MASTER_NET_WRITE = 1190
ER_FT_MATCHING_KEY_NOT_FOUND = 1191
ER_LOCK_OR_ACTIVE_TRANSACTION = 1192
ER_UNKNOWN_SYSTEM_VARIABLE = 1193
ER_CRASHED_ON_USAGE = 1194
ER_CRASHED_ON_REPAIR = 1195
ER_WARNING_NOT_COMPLETE_ROLLBACK = 1196
ER_TRANS_CACHE_FULL = 1197
OBSOLETE_ER_SLAVE_MUST_STOP = 1198
ER_SLAVE_NOT_RUNNING = 1199
ER_BAD_SLAVE = 1200
ER_MASTER_INFO = 1201
ER_SLAVE_THREAD = 1202
ER_TOO_MANY_USER_CONNECTIONS = 1203
ER_SET_CONSTANTS_ONLY = 1204
ER_LOCK_WAIT_TIMEOUT = 1205
ER_LOCK_TABLE_FULL = 1206
ER_READ_ONLY_TRANSACTION = 1207
OBSOLETE_ER_DROP_DB_WITH_READ_LOCK = 1208
OBSOLETE_ER_CREATE_DB_WITH_READ_LOCK = 1209
ER_WRONG_ARGUMENTS = 1210
ER_NO_PERMISSION_TO_CREATE_USER = 1211
OBSOLETE_ER_UNION_TABLES_IN_DIFFERENT_DIR = 1212
ER_LOCK_DEADLOCK = 1213
ER_TABLE_CANT_HANDLE_FT = 1214
ER_CANNOT_ADD_FOREIGN = 1215
ER_NO_REFERENCED_ROW = 1216
ER_ROW_IS_REFERENCED = 1217
ER_CONNECT_TO_MASTER = 1218
OBSOLETE_ER_QUERY_ON_MASTER = 1219
ER_ERROR_WHEN_EXECUTING_COMMAND = 1220
ER_WRONG_USAGE = 1221
ER_WRONG_NUMBER_OF_COLUMNS_IN_SELECT = 1222
ER_CANT_UPDATE_WITH_READLOCK = 1223
ER_MIXING_NOT_ALLOWED = 1224
ER_DUP_ARGUMENT = 1225
ER_USER_LIMIT_REACHED = 1226
ER_SPECIFIC_ACCESS_DENIED_ERROR = 1227
ER_LOCAL_VARIABLE = 1228
ER_GLOBAL_VARIABLE = 1229
ER_NO_DEFAULT = 1230
ER_WRONG_VALUE_FOR_VAR = 1231
ER_WRONG_TYPE_FOR_VAR = 1232
ER_VAR_CANT_BE_READ = 1233
ER_CANT_USE_OPTION_HERE = 1234
ER_NOT_SUPPORTED_YET = 1235
ER_MASTER_FATAL_ERROR_READING_BINLOG = 1236
ER_SLAVE_IGNORED_TABLE = 1237
ER_INCORRECT_GLOBAL_LOCAL_VAR = 1238
ER_WRONG_FK_DEF = 1239
ER_KEY_REF_DO_NOT_MATCH_TABLE_REF = 1240
ER_OPERAND_COLUMNS = 1241
ER_SUBQUERY_NO_1_ROW = 1242
ER_UNKNOWN_STMT_HANDLER = 1243
ER_CORRUPT_HELP_DB = 1244
OBSOLETE_ER_CYCLIC_REFERENCE = 1245
ER_AUTO_CONVERT = 1246
ER_ILLEGAL_REFERENCE = 1247
ER_DERIVED_MUST_HAVE_ALIAS = 1248
ER_SELECT_REDUCED = 1249
ER_TABLENAME_NOT_ALLOWED_HERE = 1250
ER_NOT_SUPPORTED_AUTH_MODE = 1251
ER_SPATIAL_CANT_HAVE_NULL = 1252
ER_COLLATION_CHARSET_MISMATCH = 1253
OBSOLETE_ER_SLAVE_WAS_RUNNING = 1254
OBSOLETE_ER_SLAVE_WAS_NOT_RUNNING = 1255
ER_TOO_BIG_FOR_UNCOMPRESS = 1256
ER_ZLIB_Z_MEM_ERROR = 1257
ER_ZLIB_Z_BUF_ERROR = 1258
ER_ZLIB_Z_DATA_ERROR = 1259
ER_CUT_VALUE_GROUP_CONCAT = 1260
ER_WARN_TOO_FEW_RECORDS = 1261
ER_WARN_TOO_MANY_RECORDS = 1262
ER_WARN_NULL_TO_NOTNULL = 1263
ER_WARN_DATA_OUT_OF_RANGE = 1264
WARN_DATA_TRUNCATED = 1265
ER_WARN_USING_OTHER_HANDLER = 1266
ER_CANT_AGGREGATE_2COLLATIONS = 1267
OBSOLETE_ER_DROP_USER = 1268
ER_REVOKE_GRANTS = 1269
ER_CANT_AGGREGATE_3COLLATIONS = 1270
ER_CANT_AGGREGATE_NCOLLATIONS = 1271
ER_VARIABLE_IS_NOT_STRUCT = 1272
ER_UNKNOWN_COLLATION = 1273
ER_SLAVE_IGNORED_SSL_PARAMS = 1274
OBSOLETE_ER_SERVER_IS_IN_SECURE_AUTH_MODE = 1275
ER_WARN_FIELD_RESOLVED = 1276
ER_BAD_SLAVE_UNTIL_COND = 1277
ER_MISSING_SKIP_SLAVE = 1278
ER_UNTIL_COND_IGNORED = 1279
ER_WRONG_NAME_FOR_INDEX = 1280
ER_WRONG_NAME_FOR_CATALOG = 1281
OBSOLETE_ER_WARN_QC_RESIZE = 1282
ER_BAD_FT_COLUMN = 1283
ER_UNKNOWN_KEY_CACHE = 1284
ER_WARN_HOSTNAME_WONT_WORK = 1285
ER_UNKNOWN_STORAGE_ENGINE = 1286
ER_WARN_DEPRECATED_SYNTAX = 1287
ER_NON_UPDATABLE_TABLE = 1288
ER_FEATURE_DISABLED = 1289
ER_OPTION_PREVENTS_STATEMENT = 1290
ER_DUPLICATED_VALUE_IN_TYPE = 1291
ER_TRUNCATED_WRONG_VALUE = 1292
OBSOLETE_ER_TOO_MUCH_AUTO_TIMESTAMP_COLS = 1293
ER_INVALID_ON_UPDATE = 1294
ER_UNSUPPORTED_PS = 1295
ER_GET_ERRMSG = 1296
ER_GET_TEMPORARY_ERRMSG = 1297
ER_UNKNOWN_TIME_ZONE = 1298
ER_WARN_INVALID_TIMESTAMP = 1299
ER_INVALID_CHARACTER_STRING = 1300
ER_WARN_ALLOWED_PACKET_OVERFLOWED = 1301
ER_CONFLICTING_DECLARATIONS = 1302
ER_SP_NO_RECURSIVE_CREATE = 1303
ER_SP_ALREADY_EXISTS = 1304
ER_SP_DOES_NOT_EXIST = 1305
ER_SP_DROP_FAILED = 1306
ER_SP_STORE_FAILED = 1307
ER_SP_LILABEL_MISMATCH = 1308
ER_SP_LABEL_REDEFINE = 1309
ER_SP_LABEL_MISMATCH = 1310
ER_SP_UNINIT_VAR = 1311
ER_SP_BADSELECT = 1312
ER_SP_BADRETURN = 1313
ER_SP_BADSTATEMENT = 1314
ER_UPDATE_LOG_DEPRECATED_IGNORED = 1315
ER_UPDATE_LOG_DEPRECATED_TRANSLATED = 1316
ER_QUERY_INTERRUPTED = 1317
ER_SP_WRONG_NO_OF_ARGS = 1318
ER_SP_COND_MISMATCH = 1319
ER_SP_NORETURN = 1320
ER_SP_NORETURNEND = 1321
ER_SP_BAD_CURSOR_QUERY = 1322
ER_SP_BAD_CURSOR_SELECT = 1323
ER_SP_CURSOR_MISMATCH = 1324
ER_SP_CURSOR_ALREADY_OPEN = 1325
ER_SP_CURSOR_NOT_OPEN = 1326
ER_SP_UNDECLARED_VAR = 1327
ER_SP_WRONG_NO_OF_FETCH_ARGS = 1328
ER_SP_FETCH_NO_DATA = 1329
ER_SP_DUP_PARAM = 1330
ER_SP_DUP_VAR = 1331
ER_SP_DUP_COND = 1332
ER_SP_DUP_CURS = 1333
ER_SP_CANT_ALTER = 1334
ER_SP_SUBSELECT_NYI = 1335
ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG = 1336
ER_SP_VARCOND_AFTER_CURSHNDLR = 1337
ER_SP_CURSOR_AFTER_HANDLER = 1338
ER_SP_CASE_NOT_FOUND = 1339
ER_FPARSER_TOO_BIG_FILE = 1340
ER_FPARSER_BAD_HEADER = 1341
ER_FPARSER_EOF_IN_COMMENT = 1342
ER_FPARSER_ERROR_IN_PARAMETER = 1343
ER_FPARSER_EOF_IN_UNKNOWN_PARAMETER = 1344
ER_VIEW_NO_EXPLAIN = 1345
OBSOLETE_ER_FRM_UNKNOWN_TYPE = 1346
ER_WRONG_OBJECT = 1347
ER_NONUPDATEABLE_COLUMN = 1348
OBSOLETE_ER_VIEW_SELECT_DERIVED_UNUSED = 1349
ER_VIEW_SELECT_CLAUSE = 1350
ER_VIEW_SELECT_VARIABLE = 1351
ER_VIEW_SELECT_TMPTABLE = 1352
ER_VIEW_WRONG_LIST = 1353
ER_WARN_VIEW_MERGE = 1354
ER_WARN_VIEW_WITHOUT_KEY = 1355
ER_VIEW_INVALID = 1356
ER_SP_NO_DROP_SP = 1357
OBSOLETE_ER_SP_GOTO_IN_HNDLR = 1358
ER_TRG_ALREADY_EXISTS = 1359
ER_TRG_DOES_NOT_EXIST = 1360
ER_TRG_ON_VIEW_OR_TEMP_TABLE = 1361
ER_TRG_CANT_CHANGE_ROW = 1362
ER_TRG_NO_SUCH_ROW_IN_TRG = 1363
ER_NO_DEFAULT_FOR_FIELD = 1364
ER_DIVISION_BY_ZERO = 1365
ER_TRUNCATED_WRONG_VALUE_FOR_FIELD = 1366
ER_ILLEGAL_VALUE_FOR_TYPE = 1367
ER_VIEW_NONUPD_CHECK = 1368
ER_VIEW_CHECK_FAILED = 1369
ER_PROCACCESS_DENIED_ERROR = 1370
ER_RELAY_LOG_FAIL = 1371
OBSOLETE_ER_PASSWD_LENGTH = 1372
ER_UNKNOWN_TARGET_BINLOG = 1373
ER_IO_ERR_LOG_INDEX_READ = 1374
ER_BINLOG_PURGE_PROHIBITED = 1375
ER_FSEEK_FAIL = 1376
ER_BINLOG_PURGE_FATAL_ERR = 1377
ER_LOG_IN_USE = 1378
ER_LOG_PURGE_UNKNOWN_ERR = 1379
ER_RELAY_LOG_INIT = 1380
ER_NO_BINARY_LOGGING = 1381
ER_RESERVED_SYNTAX = 1382
OBSOLETE_ER_WSAS_FAILED = 1383
OBSOLETE_ER_DIFF_GROUPS_PROC = 1384
OBSOLETE_ER_NO_GROUP_FOR_PROC = 1385
OBSOLETE_ER_ORDER_WITH_PROC = 1386
OBSOLETE_ER_LOGGING_PROHIBIT_CHANGING_OF = 1387
OBSOLETE_ER_NO_FILE_MAPPING = 1388
OBSOLETE_ER_WRONG_MAGIC = 1389
ER_PS_MANY_PARAM = 1390
ER_KEY_PART_0 = 1391
ER_VIEW_CHECKSUM = 1392
ER_VIEW_MULTIUPDATE = 1393
ER_VIEW_NO_INSERT_FIELD_LIST = 1394
ER_VIEW_DELETE_MERGE_VIEW = 1395
ER_CANNOT_USER = 1396
ER_XAER_NOTA = 1397
ER_XAER_INVAL = 1398
ER_XAER_RMFAIL = 1399
ER_XAER_OUTSIDE = 1400
ER_XAER_RMERR = 1401
ER_XA_RBROLLBACK = 1402
ER_NONEXISTING_PROC_GRANT = 1403
ER_PROC_AUTO_GRANT_FAIL = 1404
ER_PROC_AUTO_REVOKE_FAIL = 1405
ER_DATA_TOO_LONG = 1406
ER_SP_BAD_SQLSTATE = 1407
ER_STARTUP = 1408
ER_LOAD_FROM_FIXED_SIZE_ROWS_TO_VAR = 1409
ER_CANT_CREATE_USER_WITH_GRANT = 1410
ER_WRONG_VALUE_FOR_TYPE = 1411
ER_TABLE_DEF_CHANGED = 1412
ER_SP_DUP_HANDLER = 1413
ER_SP_NOT_VAR_ARG = 1414
ER_SP_NO_RETSET = 1415
ER_CANT_CREATE_GEOMETRY_OBJECT = 1416
OBSOLETE_ER_FAILED_ROUTINE_BREAK_BINLOG = 1417
ER_BINLOG_UNSAFE_ROUTINE = 1418
ER_BINLOG_CREATE_ROUTINE_NEED_SUPER = 1419
OBSOLETE_ER_EXEC_STMT_WITH_OPEN_CURSOR = 1420
ER_STMT_HAS_NO_OPEN_CURSOR = 1421
ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG = 1422
ER_NO_DEFAULT_FOR_VIEW_FIELD = 1423
ER_SP_NO_RECURSION = 1424
ER_TOO_BIG_SCALE = 1425
ER_TOO_BIG_PRECISION = 1426
ER_M_BIGGER_THAN_D = 1427
ER_WRONG_LOCK_OF_SYSTEM_TABLE = 1428
ER_CONNECT_TO_FOREIGN_DATA_SOURCE = 1429
ER_QUERY_ON_FOREIGN_DATA_SOURCE = 1430
ER_FOREIGN_DATA_SOURCE_DOESNT_EXIST = 1431
ER_FOREIGN_DATA_STRING_INVALID_CANT_CREATE = 1432
ER_FOREIGN_DATA_STRING_INVALID = 1433
OBSOLETE_ER_CANT_CREATE_FEDERATED_TABLE = 1434
ER_TRG_IN_WRONG_SCHEMA = 1435
ER_STACK_OVERRUN_NEED_MORE = 1436
ER_TOO_LONG_BODY = 1437
ER_WARN_CANT_DROP_DEFAULT_KEYCACHE = 1438
ER_TOO_BIG_DISPLAYWIDTH = 1439
ER_XAER_DUPID = 1440
ER_DATETIME_FUNCTION_OVERFLOW = 1441
ER_CANT_UPDATE_USED_TABLE_IN_SF_OR_TRG = 1442
ER_VIEW_PREVENT_UPDATE = 1443
ER_PS_NO_RECURSION = 1444
ER_SP_CANT_SET_AUTOCOMMIT = 1445
OBSOLETE_ER_MALFORMED_DEFINER = 1446
ER_VIEW_FRM_NO_USER = 1447
ER_VIEW_OTHER_USER = 1448
ER_NO_SUCH_USER = 1449
ER_FORBID_SCHEMA_CHANGE = 1450
ER_ROW_IS_REFERENCED_2 = 1451
ER_NO_REFERENCED_ROW_2 = 1452
ER_SP_BAD_VAR_SHADOW = 1453
ER_TRG_NO_DEFINER = 1454
ER_OLD_FILE_FORMAT = 1455
ER_SP_RECURSION_LIMIT = 1456
OBSOLETE_ER_SP_PROC_TABLE_CORRUPT = 1457
ER_SP_WRONG_NAME = 1458
ER_TABLE_NEEDS_UPGRADE = 1459
ER_SP_NO_AGGREGATE = 1460
ER_MAX_PREPARED_STMT_COUNT_REACHED = 1461
ER_VIEW_RECURSIVE = 1462
ER_NON_GROUPING_FIELD_USED = 1463
ER_TABLE_CANT_HANDLE_SPKEYS = 1464
ER_NO_TRIGGERS_ON_SYSTEM_SCHEMA = 1465
ER_REMOVED_SPACES = 1466
ER_AUTOINC_READ_FAILED = 1467
ER_USERNAME = 1468
ER_HOSTNAME = 1469
ER_WRONG_STRING_LENGTH = 1470
ER_NON_INSERTABLE_TABLE = 1471
ER_ADMIN_WRONG_MRG_TABLE = 1472
ER_TOO_HIGH_LEVEL_OF_NESTING_FOR_SELECT = 1473
ER_NAME_BECOMES_EMPTY = 1474
ER_AMBIGUOUS_FIELD_TERM = 1475
ER_FOREIGN_SERVER_EXISTS = 1476
ER_FOREIGN_SERVER_DOESNT_EXIST = 1477
ER_ILLEGAL_HA_CREATE_OPTION = 1478
ER_PARTITION_REQUIRES_VALUES_ERROR = 1479
ER_PARTITION_WRONG_VALUES_ERROR = 1480
ER_PARTITION_MAXVALUE_ERROR = 1481
OBSOLETE_ER_PARTITION_SUBPARTITION_ERROR = 1482
OBSOLETE_ER_PARTITION_SUBPART_MIX_ERROR = 1483
ER_PARTITION_WRONG_NO_PART_ERROR = 1484
ER_PARTITION_WRONG_NO_SUBPART_ERROR = 1485
ER_WRONG_EXPR_IN_PARTITION_FUNC_ERROR = 1486
OBSOLETE_ER_NO_CONST_EXPR_IN_RANGE_OR_LIST_ERROR = 1487
ER_FIELD_NOT_FOUND_PART_ERROR = 1488
OBSOLETE_ER_LIST_OF_FIELDS_ONLY_IN_HASH_ERROR = 1489
ER_INCONSISTENT_PARTITION_INFO_ERROR = 1490
ER_PARTITION_FUNC_NOT_ALLOWED_ERROR = 1491
ER_PARTITIONS_MUST_BE_DEFINED_ERROR = 1492
ER_RANGE_NOT_INCREASING_ERROR = 1493
ER_INCONSISTENT_TYPE_OF_FUNCTIONS_ERROR = 1494
ER_MULTIPLE_DEF_CONST_IN_LIST_PART_ERROR = 1495
ER_PARTITION_ENTRY_ERROR = 1496
ER_MIX_HANDLER_ERROR = 1497
ER_PARTITION_NOT_DEFINED_ERROR = 1498
ER_TOO_MANY_PARTITIONS_ERROR = 1499
ER_SUBPARTITION_ERROR = 1500
ER_CANT_CREATE_HANDLER_FILE = 1501
ER_BLOB_FIELD_IN_PART_FUNC_ERROR = 1502
ER_UNIQUE_KEY_NEED_ALL_FIELDS_IN_PF = 1503
ER_NO_PARTS_ERROR = 1504
ER_PARTITION_MGMT_ON_NONPARTITIONED = 1505
ER_FOREIGN_KEY_ON_PARTITIONED = 1506
ER_DROP_PARTITION_NON_EXISTENT = 1507
ER_DROP_LAST_PARTITION = 1508
ER_COALESCE_ONLY_ON_HASH_PARTITION = 1509
ER_REORG_HASH_ONLY_ON_SAME_NO = 1510
ER_REORG_NO_PARAM_ERROR = 1511
ER_ONLY_ON_RANGE_LIST_PARTITION = 1512
ER_ADD_PARTITION_SUBPART_ERROR = 1513
ER_ADD_PARTITION_NO_NEW_PARTITION = 1514
ER_COALESCE_PARTITION_NO_PARTITION = 1515
ER_REORG_PARTITION_NOT_EXIST = 1516
ER_SAME_NAME_PARTITION = 1517
ER_NO_BINLOG_ERROR = 1518
ER_CONSECUTIVE_REORG_PARTITIONS = 1519
ER_REORG_OUTSIDE_RANGE = 1520
ER_PARTITION_FUNCTION_FAILURE = 1521
OBSOLETE_ER_PART_STATE_ERROR = 1522
ER_LIMITED_PART_RANGE = 1523
ER_PLUGIN_IS_NOT_LOADED = 1524
ER_WRONG_VALUE = 1525
ER_NO_PARTITION_FOR_GIVEN_VALUE = 1526
ER_FILEGROUP_OPTION_ONLY_ONCE = 1527
ER_CREATE_FILEGROUP_FAILED = 1528
ER_DROP_FILEGROUP_FAILED = 1529
ER_TABLESPACE_AUTO_EXTEND_ERROR = 1530
ER_WRONG_SIZE_NUMBER = 1531
ER_SIZE_OVERFLOW_ERROR = 1532
ER_ALTER_FILEGROUP_FAILED = 1533
ER_BINLOG_ROW_LOGGING_FAILED = 1534
OBSOLETE_ER_BINLOG_ROW_WRONG_TABLE_DEF = 1535
OBSOLETE_ER_BINLOG_ROW_RBR_TO_SBR = 1536
ER_EVENT_ALREADY_EXISTS = 1537
OBSOLETE_ER_EVENT_STORE_FAILED = 1538
ER_EVENT_DOES_NOT_EXIST = 1539
OBSOLETE_ER_EVENT_CANT_ALTER = 1540
OBSOLETE_ER_EVENT_DROP_FAILED = 1541
ER_EVENT_INTERVAL_NOT_POSITIVE_OR_TOO_BIG = 1542
ER_EVENT_ENDS_BEFORE_STARTS = 1543
ER_EVENT_EXEC_TIME_IN_THE_PAST = 1544
OBSOLETE_ER_EVENT_OPEN_TABLE_FAILED = 1545
OBSOLETE_ER_EVENT_NEITHER_M_EXPR_NOR_M_AT = 1546
OBSOLETE_ER_COL_COUNT_DOESNT_MATCH_CORRUPTED = 1547
OBSOLETE_ER_CANNOT_LOAD_FROM_TABLE = 1548
OBSOLETE_ER_EVENT_CANNOT_DELETE = 1549
OBSOLETE_ER_EVENT_COMPILE_ERROR = 1550
ER_EVENT_SAME_NAME = 1551
OBSOLETE_ER_EVENT_DATA_TOO_LONG = 1552
ER_DROP_INDEX_FK = 1553
ER_WARN_DEPRECATED_SYNTAX_WITH_VER = 1554
OBSOLETE_ER_CANT_WRITE_LOCK_LOG_TABLE = 1555
ER_CANT_LOCK_LOG_TABLE = 1556
ER_FOREIGN_DUPLICATE_KEY_OLD_UNUSED = 1557
ER_COL_COUNT_DOESNT_MATCH_PLEASE_UPDATE = 1558
OBSOLETE_ER_TEMP_TABLE_PREVENTS_SWITCH_OUT_OF_RBR = 1559
ER_STORED_FUNCTION_PREVENTS_SWITCH_BINLOG_FORMAT = 1560
OBSOLETE_ER_NDB_CANT_SWITCH_BINLOG_FORMAT = 1561
ER_PARTITION_NO_TEMPORARY = 1562
ER_PARTITION_CONST_DOMAIN_ERROR = 1563
ER_PARTITION_FUNCTION_IS_NOT_ALLOWED = 1564
OBSOLETE_ER_DDL_LOG_ERROR_UNUSED = 1565
ER_NULL_IN_VALUES_LESS_THAN = 1566
ER_WRONG_PARTITION_NAME = 1567
ER_CANT_CHANGE_TX_CHARACTERISTICS = 1568
ER_DUP_ENTRY_AUTOINCREMENT_CASE = 1569
OBSOLETE_ER_EVENT_MODIFY_QUEUE_ERROR = 1570
ER_EVENT_SET_VAR_ERROR = 1571
ER_PARTITION_MERGE_ERROR = 1572
OBSOLETE_ER_CANT_ACTIVATE_LOG = 1573
OBSOLETE_ER_RBR_NOT_AVAILABLE = 1574
ER_BASE64_DECODE_ERROR = 1575
ER_EVENT_RECURSION_FORBIDDEN = 1576
OBSOLETE_ER_EVENTS_DB_ERROR = 1577
ER_ONLY_INTEGERS_ALLOWED = 1578
ER_UNSUPORTED_LOG_ENGINE = 1579
ER_BAD_LOG_STATEMENT = 1580
ER_CANT_RENAME_LOG_TABLE = 1581
ER_WRONG_PARAMCOUNT_TO_NATIVE_FCT = 1582
ER_WRONG_PARAMETERS_TO_NATIVE_FCT = 1583
ER_WRONG_PARAMETERS_TO_STORED_FCT = 1584
ER_NATIVE_FCT_NAME_COLLISION = 1585
ER_DUP_ENTRY_WITH_KEY_NAME = 1586
ER_BINLOG_PURGE_EMFILE = 1587
ER_EVENT_CANNOT_CREATE_IN_THE_PAST = 1588
ER_EVENT_CANNOT_ALTER_IN_THE_PAST = 1589
OBSOLETE_ER_SLAVE_INCIDENT = 1590
ER_NO_PARTITION_FOR_GIVEN_VALUE_SILENT = 1591
ER_BINLOG_UNSAFE_STATEMENT = 1592
ER_BINLOG_FATAL_ERROR = 1593
OBSOLETE_ER_SLAVE_RELAY_LOG_READ_FAILURE = 1594
OBSOLETE_ER_SLAVE_RELAY_LOG_WRITE_FAILURE = 1595
OBSOLETE_ER_SLAVE_CREATE_EVENT_FAILURE = 1596
OBSOLETE_ER_SLAVE_MASTER_COM_FAILURE = 1597
ER_BINLOG_LOGGING_IMPOSSIBLE = 1598
ER_VIEW_NO_CREATION_CTX = 1599
ER_VIEW_INVALID_CREATION_CTX = 1600
OBSOLETE_ER_SR_INVALID_CREATION_CTX = 1601
ER_TRG_CORRUPTED_FILE = 1602
ER_TRG_NO_CREATION_CTX = 1603
ER_TRG_INVALID_CREATION_CTX = 1604
ER_EVENT_INVALID_CREATION_CTX = 1605
ER_TRG_CANT_OPEN_TABLE = 1606
OBSOLETE_ER_CANT_CREATE_SROUTINE = 1607
OBSOLETE_ER_NEVER_USED = 1608
ER_NO_FORMAT_DESCRIPTION_EVENT_BEFORE_BINLOG_STATEMENT = 1609
ER_SLAVE_CORRUPT_EVENT = 1610
OBSOLETE_ER_LOAD_DATA_INVALID_COLUMN_UNUSED = 1611
ER_LOG_PURGE_NO_FILE = 1612
ER_XA_RBTIMEOUT = 1613
ER_XA_RBDEADLOCK = 1614
ER_NEED_REPREPARE = 1615
OBSOLETE_ER_DELAYED_NOT_SUPPORTED = 1616
WARN_NO_MASTER_INFO = 1617
WARN_OPTION_IGNORED = 1618
ER_PLUGIN_DELETE_BUILTIN = 1619
WARN_PLUGIN_BUSY = 1620
ER_VARIABLE_IS_READONLY = 1621
ER_WARN_ENGINE_TRANSACTION_ROLLBACK = 1622
OBSOLETE_ER_SLAVE_HEARTBEAT_FAILURE = 1623
ER_SLAVE_HEARTBEAT_VALUE_OUT_OF_RANGE = 1624
ER_NDB_REPLICATION_SCHEMA_ERROR = 1625
ER_CONFLICT_FN_PARSE_ERROR = 1626
ER_EXCEPTIONS_WRITE_ERROR = 1627
ER_TOO_LONG_TABLE_COMMENT = 1628
ER_TOO_LONG_FIELD_COMMENT = 1629
ER_FUNC_INEXISTENT_NAME_COLLISION = 1630
ER_DATABASE_NAME = 1631
ER_TABLE_NAME = 1632
ER_PARTITION_NAME = 1633
ER_SUBPARTITION_NAME = 1634
ER_TEMPORARY_NAME = 1635
ER_RENAMED_NAME = 1636
ER_TOO_MANY_CONCURRENT_TRXS = 1637
WARN_NON_ASCII_SEPARATOR_NOT_IMPLEMENTED = 1638
ER_DEBUG_SYNC_TIMEOUT = 1639
ER_DEBUG_SYNC_HIT_LIMIT = 1640
ER_DUP_SIGNAL_SET = 1641
ER_SIGNAL_WARN = 1642
ER_SIGNAL_NOT_FOUND = 1643
ER_SIGNAL_EXCEPTION = 1644
ER_RESIGNAL_WITHOUT_ACTIVE_HANDLER = 1645
ER_SIGNAL_BAD_CONDITION_TYPE = 1646
WARN_COND_ITEM_TRUNCATED = 1647
ER_COND_ITEM_TOO_LONG = 1648
ER_UNKNOWN_LOCALE = 1649
ER_SLAVE_IGNORE_SERVER_IDS = 1650
OBSOLETE_ER_QUERY_CACHE_DISABLED = 1651
ER_SAME_NAME_PARTITION_FIELD = 1652
ER_PARTITION_COLUMN_LIST_ERROR = 1653
ER_WRONG_TYPE_COLUMN_VALUE_ERROR = 1654
ER_TOO_MANY_PARTITION_FUNC_FIELDS_ERROR = 1655
ER_MAXVALUE_IN_VALUES_IN = 1656
ER_TOO_MANY_VALUES_ERROR = 1657
ER_ROW_SINGLE_PARTITION_FIELD_ERROR = 1658
ER_FIELD_TYPE_NOT_ALLOWED_AS_PARTITION_FIELD = 1659
ER_PARTITION_FIELDS_TOO_LONG = 1660
ER_BINLOG_ROW_ENGINE_AND_STMT_ENGINE = 1661
ER_BINLOG_ROW_MODE_AND_STMT_ENGINE = 1662
ER_BINLOG_UNSAFE_AND_STMT_ENGINE = 1663
ER_BINLOG_ROW_INJECTION_AND_STMT_ENGINE = 1664
ER_BINLOG_STMT_MODE_AND_ROW_ENGINE = 1665
ER_BINLOG_ROW_INJECTION_AND_STMT_MODE = 1666
ER_BINLOG_MULTIPLE_ENGINES_AND_SELF_LOGGING_ENGINE = 1667
ER_BINLOG_UNSAFE_LIMIT = 1668
OBSOLETE_ER_UNUSED4 = 1669
ER_BINLOG_UNSAFE_SYSTEM_TABLE = 1670
ER_BINLOG_UNSAFE_AUTOINC_COLUMNS = 1671
ER_BINLOG_UNSAFE_UDF = 1672
ER_BINLOG_UNSAFE_SYSTEM_VARIABLE = 1673
ER_BINLOG_UNSAFE_SYSTEM_FUNCTION = 1674
ER_BINLOG_UNSAFE_NONTRANS_AFTER_TRANS = 1675
ER_MESSAGE_AND_STATEMENT = 1676
OBSOLETE_ER_SLAVE_CONVERSION_FAILED = 1677
ER_SLAVE_CANT_CREATE_CONVERSION = 1678
ER_INSIDE_TRANSACTION_PREVENTS_SWITCH_BINLOG_FORMAT = 1679
ER_PATH_LENGTH = 1680
ER_WARN_DEPRECATED_SYNTAX_NO_REPLACEMENT = 1681
ER_WRONG_NATIVE_TABLE_STRUCTURE = 1682
ER_WRONG_PERFSCHEMA_USAGE = 1683
ER_WARN_I_S_SKIPPED_TABLE = 1684
ER_INSIDE_TRANSACTION_PREVENTS_SWITCH_BINLOG_DIRECT = 1685
ER_STORED_FUNCTION_PREVENTS_SWITCH_BINLOG_DIRECT = 1686
ER_SPATIAL_MUST_HAVE_GEOM_COL = 1687
ER_TOO_LONG_INDEX_COMMENT = 1688
ER_LOCK_ABORTED = 1689
ER_DATA_OUT_OF_RANGE = 1690
OBSOLETE_ER_WRONG_SPVAR_TYPE_IN_LIMIT = 1691
ER_BINLOG_UNSAFE_MULTIPLE_ENGINES_AND_SELF_LOGGING_ENGINE = 1692
ER_BINLOG_UNSAFE_MIXED_STATEMENT = 1693
ER_INSIDE_TRANSACTION_PREVENTS_SWITCH_SQL_LOG_BIN = 1694
ER_STORED_FUNCTION_PREVENTS_SWITCH_SQL_LOG_BIN = 1695
ER_FAILED_READ_FROM_PAR_FILE = 1696
ER_VALUES_IS_NOT_INT_TYPE_ERROR = 1697
ER_ACCESS_DENIED_NO_PASSWORD_ERROR = 1698
ER_SET_PASSWORD_AUTH_PLUGIN = 1699
OBSOLETE_ER_GRANT_PLUGIN_USER_EXISTS = 1700
ER_TRUNCATE_ILLEGAL_FK = 1701
ER_PLUGIN_IS_PERMANENT = 1702
ER_SLAVE_HEARTBEAT_VALUE_OUT_OF_RANGE_MIN = 1703
ER_SLAVE_HEARTBEAT_VALUE_OUT_OF_RANGE_MAX = 1704
ER_STMT_CACHE_FULL = 1705
ER_MULTI_UPDATE_KEY_CONFLICT = 1706
ER_TABLE_NEEDS_REBUILD = 1707
WARN_OPTION_BELOW_LIMIT = 1708
ER_INDEX_COLUMN_TOO_LONG = 1709
ER_ERROR_IN_TRIGGER_BODY = 1710
ER_ERROR_IN_UNKNOWN_TRIGGER_BODY = 1711
ER_INDEX_CORRUPT = 1712
ER_UNDO_RECORD_TOO_BIG = 1713
ER_BINLOG_UNSAFE_INSERT_IGNORE_SELECT = 1714
ER_BINLOG_UNSAFE_INSERT_SELECT_UPDATE = 1715
ER_BINLOG_UNSAFE_REPLACE_SELECT = 1716
ER_BINLOG_UNSAFE_CREATE_IGNORE_SELECT = 1717
ER_BINLOG_UNSAFE_CREATE_REPLACE_SELECT = 1718
ER_BINLOG_UNSAFE_UPDATE_IGNORE = 1719
ER_PLUGIN_NO_UNINSTALL = 1720
ER_PLUGIN_NO_INSTALL = 1721
ER_BINLOG_UNSAFE_WRITE_AUTOINC_SELECT = 1722
ER_BINLOG_UNSAFE_CREATE_SELECT_AUTOINC = 1723
ER_BINLOG_UNSAFE_INSERT_TWO_KEYS = 1724
ER_TABLE_IN_FK_CHECK = 1725
ER_UNSUPPORTED_ENGINE = 1726
ER_BINLOG_UNSAFE_AUTOINC_NOT_FIRST = 1727
ER_CANNOT_LOAD_FROM_TABLE_V2 = 1728
ER_MASTER_DELAY_VALUE_OUT_OF_RANGE = 1729
ER_ONLY_FD_AND_RBR_EVENTS_ALLOWED_IN_BINLOG_STATEMENT = 1730
ER_PARTITION_EXCHANGE_DIFFERENT_OPTION = 1731
ER_PARTITION_EXCHANGE_PART_TABLE = 1732
ER_PARTITION_EXCHANGE_TEMP_TABLE = 1733
ER_PARTITION_INSTEAD_OF_SUBPARTITION = 1734
ER_UNKNOWN_PARTITION = 1735
ER_TABLES_DIFFERENT_METADATA = 1736
ER_ROW_DOES_NOT_MATCH_PARTITION = 1737
ER_BINLOG_CACHE_SIZE_GREATER_THAN_MAX = 1738
ER_WARN_INDEX_NOT_APPLICABLE = 1739
ER_PARTITION_EXCHANGE_FOREIGN_KEY = 1740
OBSOLETE_ER_NO_SUCH_KEY_VALUE = 1741
ER_RPL_INFO_DATA_TOO_LONG = 1742
OBSOLETE_ER_NETWORK_READ_EVENT_CHECKSUM_FAILURE = 1743
OBSOLETE_ER_BINLOG_READ_EVENT_CHECKSUM_FAILURE = 1744
ER_BINLOG_STMT_CACHE_SIZE_GREATER_THAN_MAX = 1745
ER_CANT_UPDATE_TABLE_IN_CREATE_TABLE_SELECT = 1746
ER_PARTITION_CLAUSE_ON_NONPARTITIONED = 1747
ER_ROW_DOES_NOT_MATCH_GIVEN_PARTITION_SET = 1748
OBSOLETE_ER_NO_SUCH_PARTITION__UNUSED = 1749
ER_CHANGE_RPL_INFO_REPOSITORY_FAILURE = 1750
ER_WARNING_NOT_COMPLETE_ROLLBACK_WITH_CREATED_TEMP_TABLE = 1751
ER_WARNING_NOT_COMPLETE_ROLLBACK_WITH_DROPPED_TEMP_TABLE = 1752
ER_MTS_FEATURE_IS_NOT_SUPPORTED = 1753
ER_MTS_UPDATED_DBS_GREATER_MAX = 1754
ER_MTS_CANT_PARALLEL = 1755
ER_MTS_INCONSISTENT_DATA = 1756
ER_FULLTEXT_NOT_SUPPORTED_WITH_PARTITIONING = 1757
ER_DA_INVALID_CONDITION_NUMBER = 1758
ER_INSECURE_PLAIN_TEXT = 1759
ER_INSECURE_CHANGE_MASTER = 1760
ER_FOREIGN_DUPLICATE_KEY_WITH_CHILD_INFO = 1761
ER_FOREIGN_DUPLICATE_KEY_WITHOUT_CHILD_INFO = 1762
ER_SQLTHREAD_WITH_SECURE_SLAVE = 1763
ER_TABLE_HAS_NO_FT = 1764
ER_VARIABLE_NOT_SETTABLE_IN_SF_OR_TRIGGER = 1765
ER_VARIABLE_NOT_SETTABLE_IN_TRANSACTION = 1766
OBSOLETE_ER_GTID_NEXT_IS_NOT_IN_GTID_NEXT_LIST = 1767
OBSOLETE_ER_CANT_CHANGE_GTID_NEXT_IN_TRANSACTION = 1768
ER_SET_STATEMENT_CANNOT_INVOKE_FUNCTION = 1769
ER_GTID_NEXT_CANT_BE_AUTOMATIC_IF_GTID_NEXT_LIST_IS_NON_NULL = 1770
OBSOLETE_ER_SKIPPING_LOGGED_TRANSACTION = 1771
ER_MALFORMED_GTID_SET_SPECIFICATION = 1772
ER_MALFORMED_GTID_SET_ENCODING = 1773
ER_MALFORMED_GTID_SPECIFICATION = 1774
ER_GNO_EXHAUSTED = 1775
ER_BAD_SLAVE_AUTO_POSITION = 1776
ER_AUTO_POSITION_REQUIRES_GTID_MODE_NOT_OFF = 1777
ER_CANT_DO_IMPLICIT_COMMIT_IN_TRX_WHEN_GTID_NEXT_IS_SET = 1778
ER_GTID_MODE_ON_REQUIRES_ENFORCE_GTID_CONSISTENCY_ON = 1779
OBSOLETE_ER_GTID_MODE_REQUIRES_BINLOG = 1780
ER_CANT_SET_GTID_NEXT_TO_GTID_WHEN_GTID_MODE_IS_OFF = 1781
ER_CANT_SET_GTID_NEXT_TO_ANONYMOUS_WHEN_GTID_MODE_IS_ON = 1782
ER_CANT_SET_GTID_NEXT_LIST_TO_NON_NULL_WHEN_GTID_MODE_IS_OFF = 1783
OBSOLETE_ER_FOUND_GTID_EVENT_WHEN_GTID_MODE_IS_OFF__UNUSED = 1784
ER_GTID_UNSAFE_NON_TRANSACTIONAL_TABLE = 1785
ER_GTID_UNSAFE_CREATE_SELECT = 1786
OBSOLETE_ER_GTID_UNSAFE_CREATE_DROP_TEMP_TABLE_IN_TRANSACTION = 1787
ER_GTID_MODE_CAN_ONLY_CHANGE_ONE_STEP_AT_A_TIME = 1788
ER_MASTER_HAS_PURGED_REQUIRED_GTIDS = 1789
ER_CANT_SET_GTID_NEXT_WHEN_OWNING_GTID = 1790
ER_UNKNOWN_EXPLAIN_FORMAT = 1791
ER_CANT_EXECUTE_IN_READ_ONLY_TRANSACTION = 1792
ER_TOO_LONG_TABLE_PARTITION_COMMENT = 1793
ER_SLAVE_CONFIGURATION = 1794
ER_INNODB_FT_LIMIT = 1795
ER_INNODB_NO_FT_TEMP_TABLE = 1796
ER_INNODB_FT_WRONG_DOCID_COLUMN = 1797
ER_INNODB_FT_WRONG_DOCID_INDEX = 1798
ER_INNODB_ONLINE_LOG_TOO_BIG = 1799
ER_UNKNOWN_ALTER_ALGORITHM = 1800
ER_UNKNOWN_ALTER_LOCK = 1801
ER_MTS_CHANGE_MASTER_CANT_RUN_WITH_GAPS = 1802
ER_MTS_RECOVERY_FAILURE = 1803
ER_MTS_RESET_WORKERS = 1804
ER_COL_COUNT_DOESNT_MATCH_CORRUPTED_V2 = 1805
ER_SLAVE_SILENT_RETRY_TRANSACTION = 1806
ER_DISCARD_FK_CHECKS_RUNNING = 1807
ER_TABLE_SCHEMA_MISMATCH = 1808
ER_TABLE_IN_SYSTEM_TABLESPACE = 1809
ER_IO_READ_ERROR = 1810
ER_IO_WRITE_ERROR = 1811
ER_TABLESPACE_MISSING = 1812
ER_TABLESPACE_EXISTS = 1813
ER_TABLESPACE_DISCARDED = 1814
ER_INTERNAL_ERROR = 1815
ER_INNODB_IMPORT_ERROR = 1816
ER_INNODB_INDEX_CORRUPT = 1817
ER_INVALID_YEAR_COLUMN_LENGTH = 1818
ER_NOT_VALID_PASSWORD = 1819
ER_MUST_CHANGE_PASSWORD = 1820
ER_FK_NO_INDEX_CHILD = 1821
ER_FK_NO_INDEX_PARENT = 1822
ER_FK_FAIL_ADD_SYSTEM = 1823
ER_FK_CANNOT_OPEN_PARENT = 1824
ER_FK_INCORRECT_OPTION = 1825
ER_FK_DUP_NAME = 1826
ER_PASSWORD_FORMAT = 1827
ER_FK_COLUMN_CANNOT_DROP = 1828
ER_FK_COLUMN_CANNOT_DROP_CHILD = 1829
ER_FK_COLUMN_NOT_NULL = 1830
ER_DUP_INDEX = 1831
ER_FK_COLUMN_CANNOT_CHANGE = 1832
ER_FK_COLUMN_CANNOT_CHANGE_CHILD = 1833
OBSOLETE_ER_UNUSED5 = 1834
ER_MALFORMED_PACKET = 1835
ER_READ_ONLY_MODE = 1836
ER_GTID_NEXT_TYPE_UNDEFINED_GTID = 1837
ER_VARIABLE_NOT_SETTABLE_IN_SP = 1838
OBSOLETE_ER_CANT_SET_GTID_PURGED_WHEN_GTID_MODE_IS_OFF = 1839
ER_CANT_SET_GTID_PURGED_WHEN_GTID_EXECUTED_IS_NOT_EMPTY = 1840
ER_CANT_SET_GTID_PURGED_WHEN_OWNED_GTIDS_IS_NOT_EMPTY = 1841
ER_GTID_PURGED_WAS_CHANGED = 1842
ER_GTID_EXECUTED_WAS_CHANGED = 1843
ER_BINLOG_STMT_MODE_AND_NO_REPL_TABLES = 1844
ER_ALTER_OPERATION_NOT_SUPPORTED = 1845
ER_ALTER_OPERATION_NOT_SUPPORTED_REASON = 1846
ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_COPY = 1847
ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_PARTITION = 1848
ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_FK_RENAME = 1849
ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_COLUMN_TYPE = 1850
ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_FK_CHECK = 1851
OBSOLETE_ER_UNUSED6 = 1852
ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_NOPK = 1853
ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_AUTOINC = 1854
ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_HIDDEN_FTS = 1855
ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_CHANGE_FTS = 1856
ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_FTS = 1857
OBSOLETE_ER_SQL_REPLICA_SKIP_COUNTER_NOT_SETTABLE_IN_GTID_MODE = 1858
ER_DUP_UNKNOWN_IN_INDEX = 1859
ER_IDENT_CAUSES_TOO_LONG_PATH = 1860
ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_NOT_NULL = 1861
ER_MUST_CHANGE_PASSWORD_LOGIN = 1862
ER_ROW_IN_WRONG_PARTITION = 1863
ER_MTS_EVENT_BIGGER_PENDING_JOBS_SIZE_MAX = 1864
OBSOLETE_ER_INNODB_NO_FT_USES_PARSER = 1865
ER_BINLOG_LOGICAL_CORRUPTION = 1866
ER_WARN_PURGE_LOG_IN_USE = 1867
ER_WARN_PURGE_LOG_IS_ACTIVE = 1868
ER_AUTO_INCREMENT_CONFLICT = 1869
WARN_ON_BLOCKHOLE_IN_RBR = 1870
ER_SLAVE_MI_INIT_REPOSITORY = 1871
ER_SLAVE_RLI_INIT_REPOSITORY = 1872
ER_ACCESS_DENIED_CHANGE_USER_ERROR = 1873
ER_INNODB_READ_ONLY = 1874
ER_STOP_SLAVE_SQL_THREAD_TIMEOUT = 1875
ER_STOP_SLAVE_IO_THREAD_TIMEOUT = 1876
ER_TABLE_CORRUPT = 1877
ER_TEMP_FILE_WRITE_FAILURE = 1878
ER_INNODB_FT_AUX_NOT_HEX_ID = 1879
ER_OLD_TEMPORALS_UPGRADED = 1880
ER_INNODB_FORCED_RECOVERY = 1881
ER_AES_INVALID_IV = 1882
ER_PLUGIN_CANNOT_BE_UNINSTALLED = 1883
ER_GTID_UNSAFE_BINLOG_SPLITTABLE_STATEMENT_AND_ASSIGNED_GTID = 1884
ER_SLAVE_HAS_MORE_GTIDS_THAN_MASTER = 1885
ER_MISSING_KEY = 1886
WARN_NAMED_PIPE_ACCESS_EVERYONE = 1887
ER_FILE_CORRUPT = 3000
ER_ERROR_ON_MASTER = 3001
OBSOLETE_ER_INCONSISTENT_ERROR = 3002
ER_STORAGE_ENGINE_NOT_LOADED = 3003
ER_GET_STACKED_DA_WITHOUT_ACTIVE_HANDLER = 3004
ER_WARN_LEGACY_SYNTAX_CONVERTED = 3005
ER_BINLOG_UNSAFE_FULLTEXT_PLUGIN = 3006
ER_CANNOT_DISCARD_TEMPORARY_TABLE = 3007
ER_FK_DEPTH_EXCEEDED = 3008
ER_COL_COUNT_DOESNT_MATCH_PLEASE_UPDATE_V2 = 3009
ER_WARN_TRIGGER_DOESNT_HAVE_CREATED = 3010
ER_REFERENCED_TRG_DOES_NOT_EXIST = 3011
ER_EXPLAIN_NOT_SUPPORTED = 3012
ER_INVALID_FIELD_SIZE = 3013
ER_MISSING_HA_CREATE_OPTION = 3014
ER_ENGINE_OUT_OF_MEMORY = 3015
ER_PASSWORD_EXPIRE_ANONYMOUS_USER = 3016
ER_SLAVE_SQL_THREAD_MUST_STOP = 3017
ER_NO_FT_MATERIALIZED_SUBQUERY = 3018
ER_INNODB_UNDO_LOG_FULL = 3019
ER_INVALID_ARGUMENT_FOR_LOGARITHM = 3020
ER_SLAVE_CHANNEL_IO_THREAD_MUST_STOP = 3021
ER_WARN_OPEN_TEMP_TABLES_MUST_BE_ZERO = 3022
ER_WARN_ONLY_MASTER_LOG_FILE_NO_POS = 3023
ER_QUERY_TIMEOUT = 3024
ER_NON_RO_SELECT_DISABLE_TIMER = 3025
ER_DUP_LIST_ENTRY = 3026
OBSOLETE_ER_SQL_MODE_NO_EFFECT = 3027
ER_AGGREGATE_ORDER_FOR_UNION = 3028
ER_AGGREGATE_ORDER_NON_AGG_QUERY = 3029
ER_SLAVE_WORKER_STOPPED_PREVIOUS_THD_ERROR = 3030
ER_DONT_SUPPORT_REPLICA_PRESERVE_COMMIT_ORDER = 3031
ER_SERVER_OFFLINE_MODE = 3032
ER_GIS_DIFFERENT_SRIDS = 3033
ER_GIS_UNSUPPORTED_ARGUMENT = 3034
ER_GIS_UNKNOWN_ERROR = 3035
ER_GIS_UNKNOWN_EXCEPTION = 3036
ER_GIS_INVALID_DATA = 3037
ER_BOOST_GEOMETRY_EMPTY_INPUT_EXCEPTION = 3038
ER_BOOST_GEOMETRY_CENTROID_EXCEPTION = 3039
ER_BOOST_GEOMETRY_OVERLAY_INVALID_INPUT_EXCEPTION = 3040
ER_BOOST_GEOMETRY_TURN_INFO_EXCEPTION = 3041
ER_BOOST_GEOMETRY_SELF_INTERSECTION_POINT_EXCEPTION = 3042
ER_BOOST_GEOMETRY_UNKNOWN_EXCEPTION = 3043
ER_STD_BAD_ALLOC_ERROR = 3044
ER_STD_DOMAIN_ERROR = 3045
ER_STD_LENGTH_ERROR = 3046
ER_STD_INVALID_ARGUMENT = 3047
ER_STD_OUT_OF_RANGE_ERROR = 3048
ER_STD_OVERFLOW_ERROR = 3049
ER_STD_RANGE_ERROR = 3050
ER_STD_UNDERFLOW_ERROR = 3051
ER_STD_LOGIC_ERROR = 3052
ER_STD_RUNTIME_ERROR = 3053
ER_STD_UNKNOWN_EXCEPTION = 3054
ER_GIS_DATA_WRONG_ENDIANESS = 3055
ER_CHANGE_MASTER_PASSWORD_LENGTH = 3056
ER_USER_LOCK_WRONG_NAME = 3057
ER_USER_LOCK_DEADLOCK = 3058
ER_REPLACE_INACCESSIBLE_ROWS = 3059
ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_GIS = 3060
ER_ILLEGAL_USER_VAR = 3061
ER_GTID_MODE_OFF = 3062
OBSOLETE_ER_UNSUPPORTED_BY_REPLICATION_THREAD = 3063
ER_INCORRECT_TYPE = 3064
ER_FIELD_IN_ORDER_NOT_SELECT = 3065
ER_AGGREGATE_IN_ORDER_NOT_SELECT = 3066
ER_INVALID_RPL_WILD_TABLE_FILTER_PATTERN = 3067
ER_NET_OK_PACKET_TOO_LARGE = 3068
ER_INVALID_JSON_DATA = 3069
ER_INVALID_GEOJSON_MISSING_MEMBER = 3070
ER_INVALID_GEOJSON_WRONG_TYPE = 3071
ER_INVALID_GEOJSON_UNSPECIFIED = 3072
ER_DIMENSION_UNSUPPORTED = 3073
ER_SLAVE_CHANNEL_DOES_NOT_EXIST = 3074
OBSOLETE_ER_SLAVE_MULTIPLE_CHANNELS_HOST_PORT = 3075
ER_SLAVE_CHANNEL_NAME_INVALID_OR_TOO_LONG = 3076
ER_SLAVE_NEW_CHANNEL_WRONG_REPOSITORY = 3077
OBSOLETE_ER_SLAVE_CHANNEL_DELETE = 3078
ER_SLAVE_MULTIPLE_CHANNELS_CMD = 3079
ER_SLAVE_MAX_CHANNELS_EXCEEDED = 3080
ER_SLAVE_CHANNEL_MUST_STOP = 3081
ER_SLAVE_CHANNEL_NOT_RUNNING = 3082
ER_SLAVE_CHANNEL_WAS_RUNNING = 3083
ER_SLAVE_CHANNEL_WAS_NOT_RUNNING = 3084
ER_SLAVE_CHANNEL_SQL_THREAD_MUST_STOP = 3085
ER_SLAVE_CHANNEL_SQL_SKIP_COUNTER = 3086
ER_WRONG_FIELD_WITH_GROUP_V2 = 3087
ER_MIX_OF_GROUP_FUNC_AND_FIELDS_V2 = 3088
ER_WARN_DEPRECATED_SYSVAR_UPDATE = 3089
ER_WARN_DEPRECATED_SQLMODE = 3090
ER_CANNOT_LOG_PARTIAL_DROP_DATABASE_WITH_GTID = 3091
ER_GROUP_REPLICATION_CONFIGURATION = 3092
ER_GROUP_REPLICATION_RUNNING = 3093
ER_GROUP_REPLICATION_APPLIER_INIT_ERROR = 3094
ER_GROUP_REPLICATION_STOP_APPLIER_THREAD_TIMEOUT = 3095
ER_GROUP_REPLICATION_COMMUNICATION_LAYER_SESSION_ERROR = 3096
ER_GROUP_REPLICATION_COMMUNICATION_LAYER_JOIN_ERROR = 3097
ER_BEFORE_DML_VALIDATION_ERROR = 3098
ER_PREVENTS_VARIABLE_WITHOUT_RBR = 3099
ER_RUN_HOOK_ERROR = 3100
ER_TRANSACTION_ROLLBACK_DURING_COMMIT = 3101
ER_GENERATED_COLUMN_FUNCTION_IS_NOT_ALLOWED = 3102
ER_UNSUPPORTED_ALTER_INPLACE_ON_VIRTUAL_COLUMN = 3103
ER_WRONG_FK_OPTION_FOR_GENERATED_COLUMN = 3104
ER_NON_DEFAULT_VALUE_FOR_GENERATED_COLUMN = 3105
ER_UNSUPPORTED_ACTION_ON_GENERATED_COLUMN = 3106
ER_GENERATED_COLUMN_NON_PRIOR = 3107
ER_DEPENDENT_BY_GENERATED_COLUMN = 3108
ER_GENERATED_COLUMN_REF_AUTO_INC = 3109
ER_FEATURE_NOT_AVAILABLE = 3110
ER_CANT_SET_GTID_MODE = 3111
ER_CANT_USE_AUTO_POSITION_WITH_GTID_MODE_OFF = 3112
OBSOLETE_ER_CANT_REPLICATE_ANONYMOUS_WITH_AUTO_POSITION = 3113
OBSOLETE_ER_CANT_REPLICATE_ANONYMOUS_WITH_GTID_MODE_ON = 3114
OBSOLETE_ER_CANT_REPLICATE_GTID_WITH_GTID_MODE_OFF = 3115
ER_CANT_ENFORCE_GTID_CONSISTENCY_WITH_ONGOING_GTID_VIOLATING_TX = 3116
ER_ENFORCE_GTID_CONSISTENCY_WARN_WITH_ONGOING_GTID_VIOLATING_TX = 3117
ER_ACCOUNT_HAS_BEEN_LOCKED = 3118
ER_WRONG_TABLESPACE_NAME = 3119
ER_TABLESPACE_IS_NOT_EMPTY = 3120
ER_WRONG_FILE_NAME = 3121
ER_BOOST_GEOMETRY_INCONSISTENT_TURNS_EXCEPTION = 3122
ER_WARN_OPTIMIZER_HINT_SYNTAX_ERROR = 3123
ER_WARN_BAD_MAX_EXECUTION_TIME = 3124
ER_WARN_UNSUPPORTED_MAX_EXECUTION_TIME = 3125
ER_WARN_CONFLICTING_HINT = 3126
ER_WARN_UNKNOWN_QB_NAME = 3127
ER_UNRESOLVED_HINT_NAME = 3128
ER_WARN_ON_MODIFYING_GTID_EXECUTED_TABLE = 3129
ER_PLUGGABLE_PROTOCOL_COMMAND_NOT_SUPPORTED = 3130
ER_LOCKING_SERVICE_WRONG_NAME = 3131
ER_LOCKING_SERVICE_DEADLOCK = 3132
ER_LOCKING_SERVICE_TIMEOUT = 3133
ER_GIS_MAX_POINTS_IN_GEOMETRY_OVERFLOWED = 3134
ER_SQL_MODE_MERGED = 3135
ER_VTOKEN_PLUGIN_TOKEN_MISMATCH = 3136
ER_VTOKEN_PLUGIN_TOKEN_NOT_FOUND = 3137
ER_CANT_SET_VARIABLE_WHEN_OWNING_GTID = 3138
ER_SLAVE_CHANNEL_OPERATION_NOT_ALLOWED = 3139
ER_INVALID_JSON_TEXT = 3140
ER_INVALID_JSON_TEXT_IN_PARAM = 3141
ER_INVALID_JSON_BINARY_DATA = 3142
ER_INVALID_JSON_PATH = 3143
ER_INVALID_JSON_CHARSET = 3144
ER_INVALID_JSON_CHARSET_IN_FUNCTION = 3145
ER_INVALID_TYPE_FOR_JSON = 3146
ER_INVALID_CAST_TO_JSON = 3147
ER_INVALID_JSON_PATH_CHARSET = 3148
ER_INVALID_JSON_PATH_WILDCARD = 3149
ER_JSON_VALUE_TOO_BIG = 3150
ER_JSON_KEY_TOO_BIG = 3151
ER_JSON_USED_AS_KEY = 3152
ER_JSON_VACUOUS_PATH = 3153
ER_JSON_BAD_ONE_OR_ALL_ARG = 3154
ER_NUMERIC_JSON_VALUE_OUT_OF_RANGE = 3155
ER_INVALID_JSON_VALUE_FOR_CAST = 3156
ER_JSON_DOCUMENT_TOO_DEEP = 3157
ER_JSON_DOCUMENT_NULL_KEY = 3158
ER_SECURE_TRANSPORT_REQUIRED = 3159
ER_NO_SECURE_TRANSPORTS_CONFIGURED = 3160
ER_DISABLED_STORAGE_ENGINE = 3161
ER_USER_DOES_NOT_EXIST = 3162
ER_USER_ALREADY_EXISTS = 3163
ER_AUDIT_API_ABORT = 3164
ER_INVALID_JSON_PATH_ARRAY_CELL = 3165
ER_BUFPOOL_RESIZE_INPROGRESS = 3166
ER_FEATURE_DISABLED_SEE_DOC = 3167
ER_SERVER_ISNT_AVAILABLE = 3168
ER_SESSION_WAS_KILLED = 3169
ER_CAPACITY_EXCEEDED = 3170
ER_CAPACITY_EXCEEDED_IN_RANGE_OPTIMIZER = 3171
OBSOLETE_ER_TABLE_NEEDS_UPG_PART = 3172
ER_CANT_WAIT_FOR_EXECUTED_GTID_SET_WHILE_OWNING_A_GTID = 3173
ER_CANNOT_ADD_FOREIGN_BASE_COL_VIRTUAL = 3174
ER_CANNOT_CREATE_VIRTUAL_INDEX_CONSTRAINT = 3175
ER_ERROR_ON_MODIFYING_GTID_EXECUTED_TABLE = 3176
ER_LOCK_REFUSED_BY_ENGINE = 3177
ER_UNSUPPORTED_ALTER_ONLINE_ON_VIRTUAL_COLUMN = 3178
ER_MASTER_KEY_ROTATION_NOT_SUPPORTED_BY_SE = 3179
OBSOLETE_ER_MASTER_KEY_ROTATION_ERROR_BY_SE = 3180
ER_MASTER_KEY_ROTATION_BINLOG_FAILED = 3181
ER_MASTER_KEY_ROTATION_SE_UNAVAILABLE = 3182
ER_TABLESPACE_CANNOT_ENCRYPT = 3183
ER_INVALID_ENCRYPTION_OPTION = 3184
ER_CANNOT_FIND_KEY_IN_KEYRING = 3185
ER_CAPACITY_EXCEEDED_IN_PARSER = 3186
ER_UNSUPPORTED_ALTER_ENCRYPTION_INPLACE = 3187
ER_KEYRING_UDF_KEYRING_SERVICE_ERROR = 3188
ER_USER_COLUMN_OLD_LENGTH = 3189
ER_CANT_RESET_MASTER = 3190
ER_GROUP_REPLICATION_MAX_GROUP_SIZE = 3191
ER_CANNOT_ADD_FOREIGN_BASE_COL_STORED = 3192
ER_TABLE_REFERENCED = 3193
OBSOLETE_ER_PARTITION_ENGINE_DEPRECATED_FOR_TABLE = 3194
OBSOLETE_ER_WARN_USING_GEOMFROMWKB_TO_SET_SRID_ZERO = 3195
OBSOLETE_ER_WARN_USING_GEOMFROMWKB_TO_SET_SRID = 3196
ER_XA_RETRY = 3197
ER_KEYRING_AWS_UDF_AWS_KMS_ERROR = 3198
ER_BINLOG_UNSAFE_XA = 3199
ER_UDF_ERROR = 3200
ER_KEYRING_MIGRATION_FAILURE = 3201
ER_KEYRING_ACCESS_DENIED_ERROR = 3202
ER_KEYRING_MIGRATION_STATUS = 3203
OBSOLETE_ER_PLUGIN_FAILED_TO_OPEN_TABLES = 3204
OBSOLETE_ER_PLUGIN_FAILED_TO_OPEN_TABLE = 3205
OBSOLETE_ER_AUDIT_LOG_NO_KEYRING_PLUGIN_INSTALLED = 3206
OBSOLETE_ER_AUDIT_LOG_ENCRYPTION_PASSWORD_HAS_NOT_BEEN_SET = 3207
OBSOLETE_ER_AUDIT_LOG_COULD_NOT_CREATE_AES_KEY = 3208
OBSOLETE_ER_AUDIT_LOG_ENCRYPTION_PASSWORD_CANNOT_BE_FETCHED = 3209
OBSOLETE_ER_AUDIT_LOG_JSON_FILTERING_NOT_ENABLED = 3210
OBSOLETE_ER_AUDIT_LOG_UDF_INSUFFICIENT_PRIVILEGE = 3211
OBSOLETE_ER_AUDIT_LOG_SUPER_PRIVILEGE_REQUIRED = 3212
OBSOLETE_ER_COULD_NOT_REINITIALIZE_AUDIT_LOG_FILTERS = 3213
OBSOLETE_ER_AUDIT_LOG_UDF_INVALID_ARGUMENT_TYPE = 3214
OBSOLETE_ER_AUDIT_LOG_UDF_INVALID_ARGUMENT_COUNT = 3215
OBSOLETE_ER_AUDIT_LOG_HAS_NOT_BEEN_INSTALLED = 3216
OBSOLETE_ER_AUDIT_LOG_UDF_READ_INVALID_MAX_ARRAY_LENGTH_ARG_TYPE = 3217
ER_AUDIT_LOG_UDF_READ_INVALID_MAX_ARRAY_LENGTH_ARG_VALUE = 3218
OBSOLETE_ER_AUDIT_LOG_JSON_FILTER_PARSING_ERROR = 3219
OBSOLETE_ER_AUDIT_LOG_JSON_FILTER_NAME_CANNOT_BE_EMPTY = 3220
OBSOLETE_ER_AUDIT_LOG_JSON_USER_NAME_CANNOT_BE_EMPTY = 3221
OBSOLETE_ER_AUDIT_LOG_JSON_FILTER_DOES_NOT_EXISTS = 3222
OBSOLETE_ER_AUDIT_LOG_USER_FIRST_CHARACTER_MUST_BE_ALPHANUMERIC = 3223
OBSOLETE_ER_AUDIT_LOG_USER_NAME_INVALID_CHARACTER = 3224
OBSOLETE_ER_AUDIT_LOG_HOST_NAME_INVALID_CHARACTER = 3225
OBSOLETE_ER_XA_REPLICATION_FILTERS = 3226
OBSOLETE_ER_CANT_OPEN_ERROR_LOG = 3227
OBSOLETE_ER_GROUPING_ON_TIMESTAMP_IN_DST = 3228
OBSOLETE_ER_CANT_START_SERVER_NAMED_PIPE = 3229
ER_WRITE_SET_EXCEEDS_LIMIT = 3230
ER_UNSUPPORT_COMPRESSED_TEMPORARY_TABLE = 3500
ER_ACL_OPERATION_FAILED = 3501
ER_UNSUPPORTED_INDEX_ALGORITHM = 3502
ER_NO_SUCH_DB = 3503
ER_TOO_BIG_ENUM = 3504
ER_TOO_LONG_SET_ENUM_VALUE = 3505
ER_INVALID_DD_OBJECT = 3506
ER_UPDATING_DD_TABLE = 3507
ER_INVALID_DD_OBJECT_ID = 3508
ER_INVALID_DD_OBJECT_NAME = 3509
ER_TABLESPACE_MISSING_WITH_NAME = 3510
ER_TOO_LONG_ROUTINE_COMMENT = 3511
ER_SP_LOAD_FAILED = 3512
ER_INVALID_BITWISE_OPERANDS_SIZE = 3513
ER_INVALID_BITWISE_AGGREGATE_OPERANDS_SIZE = 3514
ER_WARN_UNSUPPORTED_HINT = 3515
ER_UNEXPECTED_GEOMETRY_TYPE = 3516
ER_SRS_PARSE_ERROR = 3517
ER_SRS_PROJ_PARAMETER_MISSING = 3518
ER_WARN_SRS_NOT_FOUND = 3519
ER_SRS_NOT_CARTESIAN = 3520
ER_SRS_NOT_CARTESIAN_UNDEFINED = 3521
ER_PK_INDEX_CANT_BE_INVISIBLE = 3522
ER_UNKNOWN_AUTHID = 3523
ER_FAILED_ROLE_GRANT = 3524
ER_OPEN_ROLE_TABLES = 3525
ER_FAILED_DEFAULT_ROLES = 3526
ER_COMPONENTS_NO_SCHEME = 3527
ER_COMPONENTS_NO_SCHEME_SERVICE = 3528
ER_COMPONENTS_CANT_LOAD = 3529
ER_ROLE_NOT_GRANTED = 3530
ER_FAILED_REVOKE_ROLE = 3531
ER_RENAME_ROLE = 3532
ER_COMPONENTS_CANT_ACQUIRE_SERVICE_IMPLEMENTATION = 3533
ER_COMPONENTS_CANT_SATISFY_DEPENDENCY = 3534
ER_COMPONENTS_LOAD_CANT_REGISTER_SERVICE_IMPLEMENTATION = 3535
ER_COMPONENTS_LOAD_CANT_INITIALIZE = 3536
ER_COMPONENTS_UNLOAD_NOT_LOADED = 3537
ER_COMPONENTS_UNLOAD_CANT_DEINITIALIZE = 3538
ER_COMPONENTS_CANT_RELEASE_SERVICE = 3539
ER_COMPONENTS_UNLOAD_CANT_UNREGISTER_SERVICE = 3540
ER_COMPONENTS_CANT_UNLOAD = 3541
ER_WARN_UNLOAD_THE_NOT_PERSISTED = 3542
ER_COMPONENT_TABLE_INCORRECT = 3543
ER_COMPONENT_MANIPULATE_ROW_FAILED = 3544
ER_COMPONENTS_UNLOAD_DUPLICATE_IN_GROUP = 3545
ER_CANT_SET_GTID_PURGED_DUE_SETS_CONSTRAINTS = 3546
ER_CANNOT_LOCK_USER_MANAGEMENT_CACHES = 3547
ER_SRS_NOT_FOUND = 3548
ER_VARIABLE_NOT_PERSISTED = 3549
ER_IS_QUERY_INVALID_CLAUSE = 3550
ER_UNABLE_TO_STORE_STATISTICS = 3551
ER_NO_SYSTEM_SCHEMA_ACCESS = 3552
ER_NO_SYSTEM_TABLESPACE_ACCESS = 3553
ER_NO_SYSTEM_TABLE_ACCESS = 3554
ER_NO_SYSTEM_TABLE_ACCESS_FOR_DICTIONARY_TABLE = 3555
ER_NO_SYSTEM_TABLE_ACCESS_FOR_SYSTEM_TABLE = 3556
ER_NO_SYSTEM_TABLE_ACCESS_FOR_TABLE = 3557
ER_INVALID_OPTION_KEY = 3558
ER_INVALID_OPTION_VALUE = 3559
ER_INVALID_OPTION_KEY_VALUE_PAIR = 3560
ER_INVALID_OPTION_START_CHARACTER = 3561
ER_INVALID_OPTION_END_CHARACTER = 3562
ER_INVALID_OPTION_CHARACTERS = 3563
ER_DUPLICATE_OPTION_KEY = 3564
ER_WARN_SRS_NOT_FOUND_AXIS_ORDER = 3565
ER_NO_ACCESS_TO_NATIVE_FCT = 3566
ER_RESET_MASTER_TO_VALUE_OUT_OF_RANGE = 3567
ER_UNRESOLVED_TABLE_LOCK = 3568
ER_DUPLICATE_TABLE_LOCK = 3569
ER_BINLOG_UNSAFE_SKIP_LOCKED = 3570
ER_BINLOG_UNSAFE_NOWAIT = 3571
ER_LOCK_NOWAIT = 3572
ER_CTE_RECURSIVE_REQUIRES_UNION = 3573
ER_CTE_RECURSIVE_REQUIRES_NONRECURSIVE_FIRST = 3574
ER_CTE_RECURSIVE_FORBIDS_AGGREGATION = 3575
ER_CTE_RECURSIVE_FORBIDDEN_JOIN_ORDER = 3576
ER_CTE_RECURSIVE_REQUIRES_SINGLE_REFERENCE = 3577
ER_SWITCH_TMP_ENGINE = 3578
ER_WINDOW_NO_SUCH_WINDOW = 3579
ER_WINDOW_CIRCULARITY_IN_WINDOW_GRAPH = 3580
ER_WINDOW_NO_CHILD_PARTITIONING = 3581
ER_WINDOW_NO_INHERIT_FRAME = 3582
ER_WINDOW_NO_REDEFINE_ORDER_BY = 3583
ER_WINDOW_FRAME_START_ILLEGAL = 3584
ER_WINDOW_FRAME_END_ILLEGAL = 3585
ER_WINDOW_FRAME_ILLEGAL = 3586
ER_WINDOW_RANGE_FRAME_ORDER_TYPE = 3587
ER_WINDOW_RANGE_FRAME_TEMPORAL_TYPE = 3588
ER_WINDOW_RANGE_FRAME_NUMERIC_TYPE = 3589
ER_WINDOW_RANGE_BOUND_NOT_CONSTANT = 3590
ER_WINDOW_DUPLICATE_NAME = 3591
ER_WINDOW_ILLEGAL_ORDER_BY = 3592
ER_WINDOW_INVALID_WINDOW_FUNC_USE = 3593
ER_WINDOW_INVALID_WINDOW_FUNC_ALIAS_USE = 3594
ER_WINDOW_NESTED_WINDOW_FUNC_USE_IN_WINDOW_SPEC = 3595
ER_WINDOW_ROWS_INTERVAL_USE = 3596
ER_WINDOW_NO_GROUP_ORDER_UNUSED = 3597
ER_WINDOW_EXPLAIN_JSON = 3598
ER_WINDOW_FUNCTION_IGNORES_FRAME = 3599
ER_WL9236_NOW_UNUSED = 3600
ER_INVALID_NO_OF_ARGS = 3601
ER_FIELD_IN_GROUPING_NOT_GROUP_BY = 3602
ER_TOO_LONG_TABLESPACE_COMMENT = 3603
ER_ENGINE_CANT_DROP_TABLE = 3604
ER_ENGINE_CANT_DROP_MISSING_TABLE = 3605
ER_TABLESPACE_DUP_FILENAME = 3606
ER_DB_DROP_RMDIR2 = 3607
ER_IMP_NO_FILES_MATCHED = 3608
ER_IMP_SCHEMA_DOES_NOT_EXIST = 3609
ER_IMP_TABLE_ALREADY_EXISTS = 3610
ER_IMP_INCOMPATIBLE_MYSQLD_VERSION = 3611
ER_IMP_INCOMPATIBLE_DD_VERSION = 3612
ER_IMP_INCOMPATIBLE_SDI_VERSION = 3613
ER_WARN_INVALID_HINT = 3614
ER_VAR_DOES_NOT_EXIST = 3615
ER_LONGITUDE_OUT_OF_RANGE = 3616
ER_LATITUDE_OUT_OF_RANGE = 3617
ER_NOT_IMPLEMENTED_FOR_GEOGRAPHIC_SRS = 3618
ER_ILLEGAL_PRIVILEGE_LEVEL = 3619
ER_NO_SYSTEM_VIEW_ACCESS = 3620
ER_COMPONENT_FILTER_FLABBERGASTED = 3621
ER_PART_EXPR_TOO_LONG = 3622
ER_UDF_DROP_DYNAMICALLY_REGISTERED = 3623
ER_UNABLE_TO_STORE_COLUMN_STATISTICS = 3624
ER_UNABLE_TO_UPDATE_COLUMN_STATISTICS = 3625
ER_UNABLE_TO_DROP_COLUMN_STATISTICS = 3626
ER_UNABLE_TO_BUILD_HISTOGRAM = 3627
ER_MANDATORY_ROLE = 3628
ER_MISSING_TABLESPACE_FILE = 3629
ER_PERSIST_ONLY_ACCESS_DENIED_ERROR = 3630
ER_CMD_NEED_SUPER = 3631
ER_PATH_IN_DATADIR = 3632
ER_CLONE_DDL_IN_PROGRESS = 3633
ER_CLONE_TOO_MANY_CONCURRENT_CLONES = 3634
ER_APPLIER_LOG_EVENT_VALIDATION_ERROR = 3635
ER_CTE_MAX_RECURSION_DEPTH = 3636
ER_NOT_HINT_UPDATABLE_VARIABLE = 3637
ER_CREDENTIALS_CONTRADICT_TO_HISTORY = 3638
ER_WARNING_PASSWORD_HISTORY_CLAUSES_VOID = 3639
ER_CLIENT_DOES_NOT_SUPPORT = 3640
ER_I_S_SKIPPED_TABLESPACE = 3641
ER_TABLESPACE_ENGINE_MISMATCH = 3642
ER_WRONG_SRID_FOR_COLUMN = 3643
ER_CANNOT_ALTER_SRID_DUE_TO_INDEX = 3644
ER_WARN_BINLOG_PARTIAL_UPDATES_DISABLED = 3645
ER_WARN_BINLOG_V1_ROW_EVENTS_DISABLED = 3646
ER_WARN_BINLOG_PARTIAL_UPDATES_SUGGESTS_PARTIAL_IMAGES = 3647
ER_COULD_NOT_APPLY_JSON_DIFF = 3648
ER_CORRUPTED_JSON_DIFF = 3649
ER_RESOURCE_GROUP_EXISTS = 3650
ER_RESOURCE_GROUP_NOT_EXISTS = 3651
ER_INVALID_VCPU_ID = 3652
ER_INVALID_VCPU_RANGE = 3653
ER_INVALID_THREAD_PRIORITY = 3654
ER_DISALLOWED_OPERATION = 3655
ER_RESOURCE_GROUP_BUSY = 3656
ER_RESOURCE_GROUP_DISABLED = 3657
ER_FEATURE_UNSUPPORTED = 3658
ER_ATTRIBUTE_IGNORED = 3659
ER_INVALID_THREAD_ID = 3660
ER_RESOURCE_GROUP_BIND_FAILED = 3661
ER_INVALID_USE_OF_FORCE_OPTION = 3662
ER_GROUP_REPLICATION_COMMAND_FAILURE = 3663
ER_SDI_OPERATION_FAILED = 3664
ER_MISSING_JSON_TABLE_VALUE = 3665
ER_WRONG_JSON_TABLE_VALUE = 3666
ER_TF_MUST_HAVE_ALIAS = 3667
ER_TF_FORBIDDEN_JOIN_TYPE = 3668
ER_JT_VALUE_OUT_OF_RANGE = 3669
ER_JT_MAX_NESTED_PATH = 3670
ER_PASSWORD_EXPIRATION_NOT_SUPPORTED_BY_AUTH_METHOD = 3671
ER_INVALID_GEOJSON_CRS_NOT_TOP_LEVEL = 3672
ER_BAD_NULL_ERROR_NOT_IGNORED = 3673
WARN_USELESS_SPATIAL_INDEX = 3674
ER_DISK_FULL_NOWAIT = 3675
ER_PARSE_ERROR_IN_DIGEST_FN = 3676
ER_UNDISCLOSED_PARSE_ERROR_IN_DIGEST_FN = 3677
ER_SCHEMA_DIR_EXISTS = 3678
ER_SCHEMA_DIR_MISSING = 3679
ER_SCHEMA_DIR_CREATE_FAILED = 3680
ER_SCHEMA_DIR_UNKNOWN = 3681
ER_ONLY_IMPLEMENTED_FOR_SRID_0_AND_4326 = 3682
ER_BINLOG_EXPIRE_LOG_DAYS_AND_SECS_USED_TOGETHER = 3683
ER_REGEXP_BUFFER_OVERFLOW = 3684
ER_REGEXP_ILLEGAL_ARGUMENT = 3685
ER_REGEXP_INDEX_OUTOFBOUNDS_ERROR = 3686
ER_REGEXP_INTERNAL_ERROR = 3687
ER_REGEXP_RULE_SYNTAX = 3688
ER_REGEXP_BAD_ESCAPE_SEQUENCE = 3689
ER_REGEXP_UNIMPLEMENTED = 3690
ER_REGEXP_MISMATCHED_PAREN = 3691
ER_REGEXP_BAD_INTERVAL = 3692
ER_REGEXP_MAX_LT_MIN = 3693
ER_REGEXP_INVALID_BACK_REF = 3694
ER_REGEXP_LOOK_BEHIND_LIMIT = 3695
ER_REGEXP_MISSING_CLOSE_BRACKET = 3696
ER_REGEXP_INVALID_RANGE = 3697
ER_REGEXP_STACK_OVERFLOW = 3698
ER_REGEXP_TIME_OUT = 3699
ER_REGEXP_PATTERN_TOO_BIG = 3700
ER_CANT_SET_ERROR_LOG_SERVICE = 3701
ER_EMPTY_PIPELINE_FOR_ERROR_LOG_SERVICE = 3702
ER_COMPONENT_FILTER_DIAGNOSTICS = 3703
ER_NOT_IMPLEMENTED_FOR_CARTESIAN_SRS = 3704
ER_NOT_IMPLEMENTED_FOR_PROJECTED_SRS = 3705
ER_NONPOSITIVE_RADIUS = 3706
ER_RESTART_SERVER_FAILED = 3707
ER_SRS_MISSING_MANDATORY_ATTRIBUTE = 3708
ER_SRS_MULTIPLE_ATTRIBUTE_DEFINITIONS = 3709
ER_SRS_NAME_CANT_BE_EMPTY_OR_WHITESPACE = 3710
ER_SRS_ORGANIZATION_CANT_BE_EMPTY_OR_WHITESPACE = 3711
ER_SRS_ID_ALREADY_EXISTS = 3712
ER_WARN_SRS_ID_ALREADY_EXISTS = 3713
ER_CANT_MODIFY_SRID_0 = 3714
ER_WARN_RESERVED_SRID_RANGE = 3715
ER_CANT_MODIFY_SRS_USED_BY_COLUMN = 3716
ER_SRS_INVALID_CHARACTER_IN_ATTRIBUTE = 3717
ER_SRS_ATTRIBUTE_STRING_TOO_LONG = 3718
ER_DEPRECATED_UTF8_ALIAS = 3719
ER_DEPRECATED_NATIONAL = 3720
ER_INVALID_DEFAULT_UTF8MB4_COLLATION = 3721
ER_UNABLE_TO_COLLECT_LOG_STATUS = 3722
ER_RESERVED_TABLESPACE_NAME = 3723
ER_UNABLE_TO_SET_OPTION = 3724
ER_SLAVE_POSSIBLY_DIVERGED_AFTER_DDL = 3725
ER_SRS_NOT_GEOGRAPHIC = 3726
ER_POLYGON_TOO_LARGE = 3727
ER_SPATIAL_UNIQUE_INDEX = 3728
ER_INDEX_TYPE_NOT_SUPPORTED_FOR_SPATIAL_INDEX = 3729
ER_FK_CANNOT_DROP_PARENT = 3730
ER_GEOMETRY_PARAM_LONGITUDE_OUT_OF_RANGE = 3731
ER_GEOMETRY_PARAM_LATITUDE_OUT_OF_RANGE = 3732
ER_FK_CANNOT_USE_VIRTUAL_COLUMN = 3733
ER_FK_NO_COLUMN_PARENT = 3734
ER_CANT_SET_ERROR_SUPPRESSION_LIST = 3735
ER_SRS_GEOGCS_INVALID_AXES = 3736
ER_SRS_INVALID_SEMI_MAJOR_AXIS = 3737
ER_SRS_INVALID_INVERSE_FLATTENING = 3738
ER_SRS_INVALID_ANGULAR_UNIT = 3739
ER_SRS_INVALID_PRIME_MERIDIAN = 3740
ER_TRANSFORM_SOURCE_SRS_NOT_SUPPORTED = 3741
ER_TRANSFORM_TARGET_SRS_NOT_SUPPORTED = 3742
ER_TRANSFORM_SOURCE_SRS_MISSING_TOWGS84 = 3743
ER_TRANSFORM_TARGET_SRS_MISSING_TOWGS84 = 3744
ER_TEMP_TABLE_PREVENTS_SWITCH_SESSION_BINLOG_FORMAT = 3745
ER_TEMP_TABLE_PREVENTS_SWITCH_GLOBAL_BINLOG_FORMAT = 3746
ER_RUNNING_APPLIER_PREVENTS_SWITCH_GLOBAL_BINLOG_FORMAT = 3747
ER_CLIENT_GTID_UNSAFE_CREATE_DROP_TEMP_TABLE_IN_TRX_IN_SBR = 3748
OBSOLETE_ER_XA_CANT_CREATE_MDL_BACKUP = 3749
ER_TABLE_WITHOUT_PK = 3750
ER_WARN_DATA_TRUNCATED_FUNCTIONAL_INDEX = 3751
ER_WARN_DATA_OUT_OF_RANGE_FUNCTIONAL_INDEX = 3752
ER_FUNCTIONAL_INDEX_ON_JSON_OR_GEOMETRY_FUNCTION = 3753
ER_FUNCTIONAL_INDEX_REF_AUTO_INCREMENT = 3754
ER_CANNOT_DROP_COLUMN_FUNCTIONAL_INDEX = 3755
ER_FUNCTIONAL_INDEX_PRIMARY_KEY = 3756
ER_FUNCTIONAL_INDEX_ON_LOB = 3757
ER_FUNCTIONAL_INDEX_FUNCTION_IS_NOT_ALLOWED = 3758
ER_FULLTEXT_FUNCTIONAL_INDEX = 3759
ER_SPATIAL_FUNCTIONAL_INDEX = 3760
ER_WRONG_KEY_COLUMN_FUNCTIONAL_INDEX = 3761
ER_FUNCTIONAL_INDEX_ON_FIELD = 3762
ER_GENERATED_COLUMN_NAMED_FUNCTION_IS_NOT_ALLOWED = 3763
ER_GENERATED_COLUMN_ROW_VALUE = 3764
ER_GENERATED_COLUMN_VARIABLES = 3765
ER_DEPENDENT_BY_DEFAULT_GENERATED_VALUE = 3766
ER_DEFAULT_VAL_GENERATED_NON_PRIOR = 3767
ER_DEFAULT_VAL_GENERATED_REF_AUTO_INC = 3768
ER_DEFAULT_VAL_GENERATED_FUNCTION_IS_NOT_ALLOWED = 3769
ER_DEFAULT_VAL_GENERATED_NAMED_FUNCTION_IS_NOT_ALLOWED = 3770
ER_DEFAULT_VAL_GENERATED_ROW_VALUE = 3771
ER_DEFAULT_VAL_GENERATED_VARIABLES = 3772
ER_DEFAULT_AS_VAL_GENERATED = 3773
ER_UNSUPPORTED_ACTION_ON_DEFAULT_VAL_GENERATED = 3774
ER_GTID_UNSAFE_ALTER_ADD_COL_WITH_DEFAULT_EXPRESSION = 3775
ER_FK_CANNOT_CHANGE_ENGINE = 3776
ER_WARN_DEPRECATED_USER_SET_EXPR = 3777
ER_WARN_DEPRECATED_UTF8MB3_COLLATION = 3778
ER_WARN_DEPRECATED_NESTED_COMMENT_SYNTAX = 3779
ER_FK_INCOMPATIBLE_COLUMNS = 3780
ER_GR_HOLD_WAIT_TIMEOUT = 3781
ER_GR_HOLD_KILLED = 3782
ER_GR_HOLD_MEMBER_STATUS_ERROR = 3783
ER_RPL_ENCRYPTION_FAILED_TO_FETCH_KEY = 3784
ER_RPL_ENCRYPTION_KEY_NOT_FOUND = 3785
ER_RPL_ENCRYPTION_KEYRING_INVALID_KEY = 3786
ER_RPL_ENCRYPTION_HEADER_ERROR = 3787
ER_RPL_ENCRYPTION_FAILED_TO_ROTATE_LOGS = 3788
ER_RPL_ENCRYPTION_KEY_EXISTS_UNEXPECTED = 3789
ER_RPL_ENCRYPTION_FAILED_TO_GENERATE_KEY = 3790
ER_RPL_ENCRYPTION_FAILED_TO_STORE_KEY = 3791
ER_RPL_ENCRYPTION_FAILED_TO_REMOVE_KEY = 3792
ER_RPL_ENCRYPTION_UNABLE_TO_CHANGE_OPTION = 3793
ER_RPL_ENCRYPTION_MASTER_KEY_RECOVERY_FAILED = 3794
ER_SLOW_LOG_MODE_IGNORED_WHEN_NOT_LOGGING_TO_FILE = 3795
ER_GRP_TRX_CONSISTENCY_NOT_ALLOWED = 3796
ER_GRP_TRX_CONSISTENCY_BEFORE = 3797
ER_GRP_TRX_CONSISTENCY_AFTER_ON_TRX_BEGIN = 3798
ER_GRP_TRX_CONSISTENCY_BEGIN_NOT_ALLOWED = 3799
ER_FUNCTIONAL_INDEX_ROW_VALUE_IS_NOT_ALLOWED = 3800
ER_RPL_ENCRYPTION_FAILED_TO_ENCRYPT = 3801
ER_PAGE_TRACKING_NOT_STARTED = 3802
ER_PAGE_TRACKING_RANGE_NOT_TRACKED = 3803
ER_PAGE_TRACKING_CANNOT_PURGE = 3804
ER_RPL_ENCRYPTION_CANNOT_ROTATE_BINLOG_MASTER_KEY = 3805
ER_BINLOG_MASTER_KEY_RECOVERY_OUT_OF_COMBINATION = 3806
ER_BINLOG_MASTER_KEY_ROTATION_FAIL_TO_OPERATE_KEY = 3807
ER_BINLOG_MASTER_KEY_ROTATION_FAIL_TO_ROTATE_LOGS = 3808
ER_BINLOG_MASTER_KEY_ROTATION_FAIL_TO_REENCRYPT_LOG = 3809
ER_BINLOG_MASTER_KEY_ROTATION_FAIL_TO_CLEANUP_UNUSED_KEYS = 3810
ER_BINLOG_MASTER_KEY_ROTATION_FAIL_TO_CLEANUP_AUX_KEY = 3811
ER_NON_BOOLEAN_EXPR_FOR_CHECK_CONSTRAINT = 3812
ER_COLUMN_CHECK_CONSTRAINT_REFERENCES_OTHER_COLUMN = 3813
ER_CHECK_CONSTRAINT_NAMED_FUNCTION_IS_NOT_ALLOWED = 3814
ER_CHECK_CONSTRAINT_FUNCTION_IS_NOT_ALLOWED = 3815
ER_CHECK_CONSTRAINT_VARIABLES = 3816
ER_CHECK_CONSTRAINT_ROW_VALUE = 3817
ER_CHECK_CONSTRAINT_REFERS_AUTO_INCREMENT_COLUMN = 3818
ER_CHECK_CONSTRAINT_VIOLATED = 3819
ER_CHECK_CONSTRAINT_REFERS_UNKNOWN_COLUMN = 3820
ER_CHECK_CONSTRAINT_NOT_FOUND = 3821
ER_CHECK_CONSTRAINT_DUP_NAME = 3822
ER_CHECK_CONSTRAINT_CLAUSE_USING_FK_REFER_ACTION_COLUMN = 3823
WARN_UNENCRYPTED_TABLE_IN_ENCRYPTED_DB = 3824
ER_INVALID_ENCRYPTION_REQUEST = 3825
ER_CANNOT_SET_TABLE_ENCRYPTION = 3826
ER_CANNOT_SET_DATABASE_ENCRYPTION = 3827
ER_CANNOT_SET_TABLESPACE_ENCRYPTION = 3828
ER_TABLESPACE_CANNOT_BE_ENCRYPTED = 3829
ER_TABLESPACE_CANNOT_BE_DECRYPTED = 3830
ER_TABLESPACE_TYPE_UNKNOWN = 3831
ER_TARGET_TABLESPACE_UNENCRYPTED = 3832
ER_CANNOT_USE_ENCRYPTION_CLAUSE = 3833
ER_INVALID_MULTIPLE_CLAUSES = 3834
ER_UNSUPPORTED_USE_OF_GRANT_AS = 3835
ER_UKNOWN_AUTH_ID_OR_ACCESS_DENIED_FOR_GRANT_AS = 3836
ER_DEPENDENT_BY_FUNCTIONAL_INDEX = 3837
ER_PLUGIN_NOT_EARLY = 3838
ER_INNODB_REDO_LOG_ARCHIVE_START_SUBDIR_PATH = 3839
ER_INNODB_REDO_LOG_ARCHIVE_START_TIMEOUT = 3840
ER_INNODB_REDO_LOG_ARCHIVE_DIRS_INVALID = 3841
ER_INNODB_REDO_LOG_ARCHIVE_LABEL_NOT_FOUND = 3842
ER_INNODB_REDO_LOG_ARCHIVE_DIR_EMPTY = 3843
ER_INNODB_REDO_LOG_ARCHIVE_NO_SUCH_DIR = 3844
ER_INNODB_REDO_LOG_ARCHIVE_DIR_CLASH = 3845
ER_INNODB_REDO_LOG_ARCHIVE_DIR_PERMISSIONS = 3846
ER_INNODB_REDO_LOG_ARCHIVE_FILE_CREATE = 3847
ER_INNODB_REDO_LOG_ARCHIVE_ACTIVE = 3848
ER_INNODB_REDO_LOG_ARCHIVE_INACTIVE = 3849
ER_INNODB_REDO_LOG_ARCHIVE_FAILED = 3850
ER_INNODB_REDO_LOG_ARCHIVE_SESSION = 3851
ER_STD_REGEX_ERROR = 3852
ER_INVALID_JSON_TYPE = 3853
ER_CANNOT_CONVERT_STRING = 3854
ER_DEPENDENT_BY_PARTITION_FUNC = 3855
ER_WARN_DEPRECATED_FLOAT_AUTO_INCREMENT = 3856
ER_RPL_CANT_STOP_SLAVE_WHILE_LOCKED_BACKUP = 3857
ER_WARN_DEPRECATED_FLOAT_DIGITS = 3858
ER_WARN_DEPRECATED_FLOAT_UNSIGNED = 3859
ER_WARN_DEPRECATED_INTEGER_DISPLAY_WIDTH = 3860
ER_WARN_DEPRECATED_ZEROFILL = 3861
ER_CLONE_DONOR = 3862
ER_CLONE_PROTOCOL = 3863
ER_CLONE_DONOR_VERSION = 3864
ER_CLONE_OS = 3865
ER_CLONE_PLATFORM = 3866
ER_CLONE_CHARSET = 3867
ER_CLONE_CONFIG = 3868
ER_CLONE_SYS_CONFIG = 3869
ER_CLONE_PLUGIN_MATCH = 3870
ER_CLONE_LOOPBACK = 3871
ER_CLONE_ENCRYPTION = 3872
ER_CLONE_DISK_SPACE = 3873
ER_CLONE_IN_PROGRESS = 3874
ER_CLONE_DISALLOWED = 3875
ER_CANNOT_GRANT_ROLES_TO_ANONYMOUS_USER = 3876
ER_SECONDARY_ENGINE_PLUGIN = 3877
ER_SECOND_PASSWORD_CANNOT_BE_EMPTY = 3878
ER_DB_ACCESS_DENIED = 3879
ER_DA_AUTH_ID_WITH_SYSTEM_USER_PRIV_IN_MANDATORY_ROLES = 3880
ER_DA_RPL_GTID_TABLE_CANNOT_OPEN = 3881
ER_GEOMETRY_IN_UNKNOWN_LENGTH_UNIT = 3882
ER_DA_PLUGIN_INSTALL_ERROR = 3883
ER_NO_SESSION_TEMP = 3884
ER_DA_UNKNOWN_ERROR_NUMBER = 3885
ER_COLUMN_CHANGE_SIZE = 3886
ER_REGEXP_INVALID_CAPTURE_GROUP_NAME = 3887
ER_DA_SSL_LIBRARY_ERROR = 3888
ER_SECONDARY_ENGINE = 3889
ER_SECONDARY_ENGINE_DDL = 3890
ER_INCORRECT_CURRENT_PASSWORD = 3891
ER_MISSING_CURRENT_PASSWORD = 3892
ER_CURRENT_PASSWORD_NOT_REQUIRED = 3893
ER_PASSWORD_CANNOT_BE_RETAINED_ON_PLUGIN_CHANGE = 3894
ER_CURRENT_PASSWORD_CANNOT_BE_RETAINED = 3895
ER_PARTIAL_REVOKES_EXIST = 3896
ER_CANNOT_GRANT_SYSTEM_PRIV_TO_MANDATORY_ROLE = 3897
ER_XA_REPLICATION_FILTERS = 3898
ER_UNSUPPORTED_SQL_MODE = 3899
ER_REGEXP_INVALID_FLAG = 3900
ER_PARTIAL_REVOKE_AND_DB_GRANT_BOTH_EXISTS = 3901
ER_UNIT_NOT_FOUND = 3902
ER_INVALID_JSON_VALUE_FOR_FUNC_INDEX = 3903
ER_JSON_VALUE_OUT_OF_RANGE_FOR_FUNC_INDEX = 3904
ER_EXCEEDED_MV_KEYS_NUM = 3905
ER_EXCEEDED_MV_KEYS_SPACE = 3906
ER_FUNCTIONAL_INDEX_DATA_IS_TOO_LONG = 3907
ER_WRONG_MVI_VALUE = 3908
ER_WARN_FUNC_INDEX_NOT_APPLICABLE = 3909
ER_GRP_RPL_UDF_ERROR = 3910
ER_UPDATE_GTID_PURGED_WITH_GR = 3911
ER_GROUPING_ON_TIMESTAMP_IN_DST = 3912
ER_TABLE_NAME_CAUSES_TOO_LONG_PATH = 3913
ER_AUDIT_LOG_INSUFFICIENT_PRIVILEGE = 3914
OBSOLETE_ER_AUDIT_LOG_PASSWORD_HAS_BEEN_COPIED = 3915
ER_DA_GRP_RPL_STARTED_AUTO_REJOIN = 3916
ER_SYSVAR_CHANGE_DURING_QUERY = 3917
ER_GLOBSTAT_CHANGE_DURING_QUERY = 3918
ER_GRP_RPL_MESSAGE_SERVICE_INIT_FAILURE = 3919
ER_CHANGE_MASTER_WRONG_COMPRESSION_ALGORITHM_CLIENT = 3920
ER_CHANGE_MASTER_WRONG_COMPRESSION_LEVEL_CLIENT = 3921
ER_WRONG_COMPRESSION_ALGORITHM_CLIENT = 3922
ER_WRONG_COMPRESSION_LEVEL_CLIENT = 3923
ER_CHANGE_MASTER_WRONG_COMPRESSION_ALGORITHM_LIST_CLIENT = 3924
ER_CLIENT_PRIVILEGE_CHECKS_USER_CANNOT_BE_ANONYMOUS = 3925
ER_CLIENT_PRIVILEGE_CHECKS_USER_DOES_NOT_EXIST = 3926
ER_CLIENT_PRIVILEGE_CHECKS_USER_CORRUPT = 3927
ER_CLIENT_PRIVILEGE_CHECKS_USER_NEEDS_RPL_APPLIER_PRIV = 3928
ER_WARN_DA_PRIVILEGE_NOT_REGISTERED = 3929
ER_CLIENT_KEYRING_UDF_KEY_INVALID = 3930
ER_CLIENT_KEYRING_UDF_KEY_TYPE_INVALID = 3931
ER_CLIENT_KEYRING_UDF_KEY_TOO_LONG = 3932
ER_CLIENT_KEYRING_UDF_KEY_TYPE_TOO_LONG = 3933
ER_JSON_SCHEMA_VALIDATION_ERROR_WITH_DETAILED_REPORT = 3934
ER_DA_UDF_INVALID_CHARSET_SPECIFIED = 3935
ER_DA_UDF_INVALID_CHARSET = 3936
ER_DA_UDF_INVALID_COLLATION = 3937
ER_DA_UDF_INVALID_EXTENSION_ARGUMENT_TYPE = 3938
ER_MULTIPLE_CONSTRAINTS_WITH_SAME_NAME = 3939
ER_CONSTRAINT_NOT_FOUND = 3940
ER_ALTER_CONSTRAINT_ENFORCEMENT_NOT_SUPPORTED = 3941
ER_TABLE_VALUE_CONSTRUCTOR_MUST_HAVE_COLUMNS = 3942
ER_TABLE_VALUE_CONSTRUCTOR_CANNOT_HAVE_DEFAULT = 3943
ER_CLIENT_QUERY_FAILURE_INVALID_NON_ROW_FORMAT = 3944
ER_REQUIRE_ROW_FORMAT_INVALID_VALUE = 3945
ER_FAILED_TO_DETERMINE_IF_ROLE_IS_MANDATORY = 3946
ER_FAILED_TO_FETCH_MANDATORY_ROLE_LIST = 3947
ER_CLIENT_LOCAL_FILES_DISABLED = 3948
ER_IMP_INCOMPATIBLE_CFG_VERSION = 3949
ER_DA_OOM = 3950
ER_DA_UDF_INVALID_ARGUMENT_TO_SET_CHARSET = 3951
ER_DA_UDF_INVALID_RETURN_TYPE_TO_SET_CHARSET = 3952
ER_MULTIPLE_INTO_CLAUSES = 3953
ER_MISPLACED_INTO = 3954
ER_USER_ACCESS_DENIED_FOR_USER_ACCOUNT_BLOCKED_BY_PASSWORD_LOCK = 3955
ER_WARN_DEPRECATED_YEAR_UNSIGNED = 3956
ER_CLONE_NETWORK_PACKET = 3957
ER_SDI_OPERATION_FAILED_MISSING_RECORD = 3958
ER_DEPENDENT_BY_CHECK_CONSTRAINT = 3959
ER_GRP_OPERATION_NOT_ALLOWED_GR_MUST_STOP = 3960
ER_WARN_DEPRECATED_JSON_TABLE_ON_ERROR_ON_EMPTY = 3961
ER_WARN_DEPRECATED_INNER_INTO = 3962
ER_WARN_DEPRECATED_VALUES_FUNCTION_ALWAYS_NULL = 3963
ER_WARN_DEPRECATED_SQL_CALC_FOUND_ROWS = 3964
ER_WARN_DEPRECATED_FOUND_ROWS = 3965
ER_MISSING_JSON_VALUE = 3966
ER_MULTIPLE_JSON_VALUES = 3967
ER_HOSTNAME_TOO_LONG = 3968
ER_WARN_CLIENT_DEPRECATED_PARTITION_PREFIX_KEY = 3969
ER_GROUP_REPLICATION_USER_EMPTY_MSG = 3970
ER_GROUP_REPLICATION_USER_MANDATORY_MSG = 3971
ER_GROUP_REPLICATION_PASSWORD_LENGTH = 3972
ER_SUBQUERY_TRANSFORM_REJECTED = 3973
ER_DA_GRP_RPL_RECOVERY_ENDPOINT_FORMAT = 3974
ER_DA_GRP_RPL_RECOVERY_ENDPOINT_INVALID = 3975
ER_WRONG_VALUE_FOR_VAR_PLUS_ACTIONABLE_PART = 3976
ER_STATEMENT_NOT_ALLOWED_AFTER_START_TRANSACTION = 3977
ER_FOREIGN_KEY_WITH_ATOMIC_CREATE_SELECT = 3978
ER_NOT_ALLOWED_WITH_START_TRANSACTION = 3979
ER_INVALID_JSON_ATTRIBUTE = 3980
ER_ENGINE_ATTRIBUTE_NOT_SUPPORTED = 3981
ER_INVALID_USER_ATTRIBUTE_JSON = 3982
ER_INNODB_REDO_DISABLED = 3983
ER_INNODB_REDO_ARCHIVING_ENABLED = 3984
ER_MDL_OUT_OF_RESOURCES = 3985
ER_IMPLICIT_COMPARISON_FOR_JSON = 3986
ER_FUNCTION_DOES_NOT_SUPPORT_CHARACTER_SET = 3987
ER_IMPOSSIBLE_STRING_CONVERSION = 3988
ER_SCHEMA_READ_ONLY = 3989
ER_RPL_ASYNC_RECONNECT_GTID_MODE_OFF = 3990
ER_RPL_ASYNC_RECONNECT_AUTO_POSITION_OFF = 3991
ER_DISABLE_GTID_MODE_REQUIRES_ASYNC_RECONNECT_OFF = 3992
ER_DISABLE_AUTO_POSITION_REQUIRES_ASYNC_RECONNECT_OFF = 3993
ER_INVALID_PARAMETER_USE = 3994
ER_CHARACTER_SET_MISMATCH = 3995
ER_WARN_VAR_VALUE_CHANGE_NOT_SUPPORTED = 3996
ER_INVALID_TIME_ZONE_INTERVAL = 3997
ER_INVALID_CAST = 3998
ER_HYPERGRAPH_NOT_SUPPORTED_YET = 3999
ER_WARN_HYPERGRAPH_EXPERIMENTAL = 4000
ER_DA_NO_ERROR_LOG_PARSER_CONFIGURED = 4001
ER_DA_ERROR_LOG_TABLE_DISABLED = 4002
ER_DA_ERROR_LOG_MULTIPLE_FILTERS = 4003
ER_DA_CANT_OPEN_ERROR_LOG = 4004
ER_USER_REFERENCED_AS_DEFINER = 4005
ER_CANNOT_USER_REFERENCED_AS_DEFINER = 4006
ER_REGEX_NUMBER_TOO_BIG = 4007
ER_SPVAR_NONINTEGER_TYPE = 4008
WARN_UNSUPPORTED_ACL_TABLES_READ = 4009
ER_BINLOG_UNSAFE_ACL_TABLE_READ_IN_DML_DDL = 4010
ER_STOP_REPLICA_MONITOR_IO_THREAD_TIMEOUT = 4011
ER_STARTING_REPLICA_MONITOR_IO_THREAD = 4012
ER_CANT_USE_ANONYMOUS_TO_GTID_WITH_GTID_MODE_NOT_ON = 4013
ER_CANT_COMBINE_ANONYMOUS_TO_GTID_AND_AUTOPOSITION = 4014
ER_ASSIGN_GTIDS_TO_ANONYMOUS_TRANSACTIONS_REQUIRES_GTID_MODE_ON = 4015
ER_SQL_REPLICA_SKIP_COUNTER_USED_WITH_GTID_MODE_ON = 4016
ER_USING_ASSIGN_GTIDS_TO_ANONYMOUS_TRANSACTIONS_AS_LOCAL_OR_UUID = 4017
ER_CANT_SET_ANONYMOUS_TO_GTID_AND_WAIT_UNTIL_SQL_THD_AFTER_GTIDS = 4018
ER_CANT_SET_SQL_AFTER_OR_BEFORE_GTIDS_WITH_ANONYMOUS_TO_GTID = 4019
ER_ANONYMOUS_TO_GTID_UUID_SAME_AS_GROUP_NAME = 4020
ER_CANT_USE_SAME_UUID_AS_GROUP_NAME = 4021
ER_GRP_RPL_RECOVERY_CHANNEL_STILL_RUNNING = 4022
ER_INNODB_INVALID_AUTOEXTEND_SIZE_VALUE = 4023
ER_INNODB_INCOMPATIBLE_WITH_TABLESPACE = 4024
ER_INNODB_AUTOEXTEND_SIZE_OUT_OF_RANGE = 4025
ER_CANNOT_USE_AUTOEXTEND_SIZE_CLAUSE = 4026
ER_ROLE_GRANTED_TO_ITSELF = 4027
ER_TABLE_MUST_HAVE_A_VISIBLE_COLUMN = 4028
ER_INNODB_COMPRESSION_FAILURE = 4029
ER_WARN_ASYNC_CONN_FAILOVER_NETWORK_NAMESPACE = 4030
ER_CLIENT_INTERACTION_TIMEOUT = 4031
ER_INVALID_CAST_TO_GEOMETRY = 4032
ER_INVALID_CAST_POLYGON_RING_DIRECTION = 4033
ER_GIS_DIFFERENT_SRIDS_AGGREGATION = 4034
ER_RELOAD_KEYRING_FAILURE = 4035
ER_SDI_GET_KEYS_INVALID_TABLESPACE = 4036
ER_CHANGE_RPL_SRC_WRONG_COMPRESSION_ALGORITHM_SIZE = 4037
ER_WARN_DEPRECATED_TLS_VERSION_FOR_CHANNEL_CLI = 4038
ER_CANT_USE_SAME_UUID_AS_VIEW_CHANGE_UUID = 4039
ER_ANONYMOUS_TO_GTID_UUID_SAME_AS_VIEW_CHANGE_UUID = 4040
ER_GRP_RPL_VIEW_CHANGE_UUID_FAIL_GET_VARIABLE = 4041
ER_WARN_ADUIT_LOG_MAX_SIZE_AND_PRUNE_SECONDS = 4042
ER_WARN_ADUIT_LOG_MAX_SIZE_CLOSE_TO_ROTATE_ON_SIZE = 4043
ER_KERBEROS_CREATE_USER = 4044
ER_INSTALL_PLUGIN_CONFLICT_CLIENT = 4045
ER_DA_ERROR_LOG_COMPONENT_FLUSH_FAILED = 4046
ER_WARN_SQL_AFTER_MTS_GAPS_GAP_NOT_CALCULATED = 4047
ER_INVALID_ASSIGNMENT_TARGET = 4048
ER_OPERATION_NOT_ALLOWED_ON_GR_SECONDARY = 4049
ER_GRP_RPL_FAILOVER_CHANNEL_STATUS_PROPAGATION = 4050
ER_WARN_AUDIT_LOG_FORMAT_UNIX_TIMESTAMP_ONLY_WHEN_JSON = 4051
ER_INVALID_MFA_PLUGIN_SPECIFIED = 4052
ER_IDENTIFIED_BY_UNSUPPORTED = 4053
ER_INVALID_PLUGIN_FOR_REGISTRATION = 4054
ER_PLUGIN_REQUIRES_REGISTRATION = 4055
ER_MFA_METHOD_EXISTS = 4056
ER_MFA_METHOD_NOT_EXISTS = 4057
ER_AUTHENTICATION_POLICY_MISMATCH = 4058
ER_PLUGIN_REGISTRATION_DONE = 4059
ER_INVALID_USER_FOR_REGISTRATION = 4060
ER_USER_REGISTRATION_FAILED = 4061
ER_MFA_METHODS_INVALID_ORDER = 4062
ER_MFA_METHODS_IDENTICAL = 4063
ER_INVALID_MFA_OPERATIONS_FOR_PASSWORDLESS_USER = 4064
ER_CHANGE_REPLICATION_SOURCE_NO_OPTIONS_FOR_GTID_ONLY = 4065
ER_CHANGE_REP_SOURCE_CANT_DISABLE_REQ_ROW_FORMAT_WITH_GTID_ONLY = 4066
ER_CHANGE_REP_SOURCE_CANT_DISABLE_AUTO_POSITION_WITH_GTID_ONLY = 4067
ER_CHANGE_REP_SOURCE_CANT_DISABLE_GTID_ONLY_WITHOUT_POSITIONS = 4068
ER_CHANGE_REP_SOURCE_CANT_DISABLE_AUTO_POS_WITHOUT_POSITIONS = 4069
ER_CHANGE_REP_SOURCE_GR_CHANNEL_WITH_GTID_MODE_NOT_ON = 4070
ER_CANT_USE_GTID_ONLY_WITH_GTID_MODE_NOT_ON = 4071
ER_WARN_C_DISABLE_GTID_ONLY_WITH_SOURCE_AUTO_POS_INVALID_POS = 4072
ER_DA_SSL_FIPS_MODE_ERROR = 4073
CR_UNKNOWN_ERROR = 2000
CR_SOCKET_CREATE_ERROR = 2001
CR_CONNECTION_ERROR = 2002
CR_CONN_HOST_ERROR = 2003
CR_IPSOCK_ERROR = 2004
CR_UNKNOWN_HOST = 2005
CR_SERVER_GONE_ERROR = 2006
CR_VERSION_ERROR = 2007
CR_OUT_OF_MEMORY = 2008
CR_WRONG_HOST_INFO = 2009
CR_LOCALHOST_CONNECTION = 2010
CR_TCP_CONNECTION = 2011
CR_SERVER_HANDSHAKE_ERR = 2012
CR_SERVER_LOST = 2013
CR_COMMANDS_OUT_OF_SYNC = 2014
CR_NAMEDPIPE_CONNECTION = 2015
CR_NAMEDPIPEWAIT_ERROR = 2016
CR_NAMEDPIPEOPEN_ERROR = 2017
CR_NAMEDPIPESETSTATE_ERROR = 2018
CR_CANT_READ_CHARSET = 2019
CR_NET_PACKET_TOO_LARGE = 2020
CR_EMBEDDED_CONNECTION = 2021
CR_PROBE_SLAVE_STATUS = 2022
CR_PROBE_SLAVE_HOSTS = 2023
CR_PROBE_SLAVE_CONNECT = 2024
CR_PROBE_MASTER_CONNECT = 2025
CR_SSL_CONNECTION_ERROR = 2026
CR_MALFORMED_PACKET = 2027
CR_WRONG_LICENSE = 2028
CR_NULL_POINTER = 2029
CR_NO_PREPARE_STMT = 2030
CR_PARAMS_NOT_BOUND = 2031
CR_DATA_TRUNCATED = 2032
CR_NO_PARAMETERS_EXISTS = 2033
CR_INVALID_PARAMETER_NO = 2034
CR_INVALID_BUFFER_USE = 2035
CR_UNSUPPORTED_PARAM_TYPE = 2036
CR_SHARED_MEMORY_CONNECTION = 2037
CR_SHARED_MEMORY_CONNECT_REQUEST_ERROR = 2038
CR_SHARED_MEMORY_CONNECT_ANSWER_ERROR = 2039
CR_SHARED_MEMORY_CONNECT_FILE_MAP_ERROR = 2040
CR_SHARED_MEMORY_CONNECT_MAP_ERROR = 2041
CR_SHARED_MEMORY_FILE_MAP_ERROR = 2042
CR_SHARED_MEMORY_MAP_ERROR = 2043
CR_SHARED_MEMORY_EVENT_ERROR = 2044
CR_SHARED_MEMORY_CONNECT_ABANDONED_ERROR = 2045
CR_SHARED_MEMORY_CONNECT_SET_ERROR = 2046
CR_CONN_UNKNOW_PROTOCOL = 2047
CR_INVALID_CONN_HANDLE = 2048
CR_UNUSED_1 = 2049
CR_FETCH_CANCELED = 2050
CR_NO_DATA = 2051
CR_NO_STMT_METADATA = 2052
CR_NO_RESULT_SET = 2053
CR_NOT_IMPLEMENTED = 2054
CR_SERVER_LOST_EXTENDED = 2055
CR_STMT_CLOSED = 2056
CR_NEW_STMT_METADATA = 2057
CR_ALREADY_CONNECTED = 2058
CR_AUTH_PLUGIN_CANNOT_LOAD = 2059
CR_DUPLICATE_CONNECTION_ATTR = 2060
CR_AUTH_PLUGIN_ERR = 2061
CR_INSECURE_API_ERR = 2062
CR_FILE_NAME_TOO_LONG = 2063
CR_SSL_FIPS_MODE_ERR = 2064
CR_DEPRECATED_COMPRESSION_NOT_SUPPORTED = 2065
CR_COMPRESSION_WRONGLY_CONFIGURED = 2066
CR_KERBEROS_USER_NOT_FOUND = 2067
CR_LOAD_DATA_LOCAL_INFILE_REJECTED = 2068
CR_LOAD_DATA_LOCAL_INFILE_REALPATH_FAIL = 2069
CR_DNS_SRV_LOOKUP_FAILED = 2070
CR_MANDATORY_TRACKER_NOT_FOUND = 2071
CR_INVALID_FACTOR_NO = 2072
# End MySQL Errors

# Start X Plugin Errors
ER_X_BAD_MESSAGE = 5000
ER_X_CAPABILITIES_PREPARE_FAILED = 5001
ER_X_CAPABILITY_NOT_FOUND = 5002
ER_X_INVALID_PROTOCOL_DATA = 5003
ER_X_BAD_CONNECTION_SESSION_ATTRIBUTE_VALUE_LENGTH = 5004
ER_X_BAD_CONNECTION_SESSION_ATTRIBUTE_KEY_LENGTH = 5005
ER_X_BAD_CONNECTION_SESSION_ATTRIBUTE_EMPTY_KEY = 5006
ER_X_BAD_CONNECTION_SESSION_ATTRIBUTE_LENGTH = 5007
ER_X_BAD_CONNECTION_SESSION_ATTRIBUTE_TYPE = 5008
ER_X_CAPABILITY_SET_NOT_ALLOWED = 5009
ER_X_SERVICE_ERROR = 5010
ER_X_SESSION = 5011
ER_X_INVALID_ARGUMENT = 5012
ER_X_MISSING_ARGUMENT = 5013
ER_X_BAD_INSERT_DATA = 5014
ER_X_CMD_NUM_ARGUMENTS = 5015
ER_X_CMD_ARGUMENT_TYPE = 5016
ER_X_CMD_ARGUMENT_VALUE = 5017
ER_X_BAD_UPSERT_DATA = 5018
ER_X_DUPLICATED_CAPABILITIES = 5019
ER_X_CMD_ARGUMENT_OBJECT_EMPTY = 5020
ER_X_CMD_INVALID_ARGUMENT = 5021
ER_X_BAD_UPDATE_DATA = 5050
ER_X_BAD_TYPE_OF_UPDATE = 5051
ER_X_BAD_COLUMN_TO_UPDATE = 5052
ER_X_BAD_MEMBER_TO_UPDATE = 5053
ER_X_BAD_STATEMENT_ID = 5110
ER_X_BAD_CURSOR_ID = 5111
ER_X_BAD_SCHEMA = 5112
ER_X_BAD_TABLE = 5113
ER_X_BAD_PROJECTION = 5114
ER_X_DOC_ID_MISSING = 5115
ER_X_DUPLICATE_ENTRY = 5116
ER_X_DOC_REQUIRED_FIELD_MISSING = 5117
ER_X_PROJ_BAD_KEY_NAME = 5120
ER_X_BAD_DOC_PATH = 5121
ER_X_CURSOR_EXISTS = 5122
ER_X_CURSOR_REACHED_EOF = 5123
ER_X_PREPARED_STATMENT_CAN_HAVE_ONE_CURSOR = 5131
ER_X_PREPARED_EXECUTE_ARGUMENT_NOT_SUPPORTED = 5133
ER_X_PREPARED_EXECUTE_ARGUMENT_CONSISTENCY = 5134
ER_X_EXPR_BAD_OPERATOR = 5150
ER_X_EXPR_BAD_NUM_ARGS = 5151
ER_X_EXPR_MISSING_ARG = 5152
ER_X_EXPR_BAD_TYPE_VALUE = 5153
ER_X_EXPR_BAD_VALUE = 5154
ER_X_INVALID_COLLECTION = 5156
ER_X_INVALID_ADMIN_COMMAND = 5157
ER_X_EXPECT_NOT_OPEN = 5158
ER_X_EXPECT_NO_ERROR_FAILED = 5159
ER_X_EXPECT_BAD_CONDITION = 5160
ER_X_EXPECT_BAD_CONDITION_VALUE = 5161
ER_X_INVALID_NAMESPACE = 5162
ER_X_BAD_NOTICE = 5163
ER_X_CANNOT_DISABLE_NOTICE = 5164
ER_X_BAD_CONFIGURATION = 5165
ER_X_MYSQLX_ACCOUNT_MISSING_PERMISSIONS = 5167
ER_X_EXPECT_FIELD_EXISTS_FAILED = 5168
ER_X_BAD_LOCKING = 5169
ER_X_FRAME_COMPRESSION_DISABLED = 5170
ER_X_DECOMPRESSION_FAILED = 5171
ER_X_BAD_COMPRESSED_FRAME = 5174
ER_X_CAPABILITY_COMPRESSION_INVALID_ALGORITHM = 5175
ER_X_CAPABILITY_COMPRESSION_INVALID_SERVER_STYLE = 5176
ER_X_CAPABILITY_COMPRESSION_INVALID_CLIENT_STYLE = 5177
ER_X_CAPABILITY_COMPRESSION_INVALID_OPTION = 5178
ER_X_CAPABILITY_COMPRESSION_MISSING_REQUIRED_FIELDS = 5179
ER_X_DOCUMENT_DOESNT_MATCH_EXPECTED_SCHEMA = 5180
ER_X_COLLECTION_OPTION_DOESNT_EXISTS = 5181
ER_X_INVALID_VALIDATION_SCHEMA = 5182
# End X Plugin Errors

virt/lib/python3.9/site-packages/mysqlx/errorcode.py
PrinceDisant/LostInNL
# Copyright (c) 2016, 2023, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

"""Implementation of the Python Database API Specification v2.0 exceptions."""

from struct import unpack as struct_unpack
from typing import Dict, Optional, Tuple, Union

from .locales import get_client_error
from .types import ErrorClassTypes, ErrorTypes, StrOrBytes


class Error(Exception):
    """Exception that is base class for all other error exceptions."""

    def __init__(
        self,
        msg: Optional[str] = None,
        errno: Optional[int] = None,
        values: Optional[Tuple[Union[int, str], ...]] = None,
        sqlstate: Optional[str] = None,
    ) -> None:
        super().__init__()
        self.msg = msg
        self._full_msg = self.msg
        self.errno = errno or -1
        self.sqlstate = sqlstate

        if not self.msg and (2000 <= self.errno < 3000):
            self.msg = get_client_error(self.errno)
            if values is not None:
                try:
                    self.msg = self.msg % values
                except TypeError as err:
                    self.msg = f"{self.msg} (Warning: {err})"
        elif not self.msg:
            self._full_msg = self.msg = "Unknown error"

        if self.msg and self.errno != -1:
            fields = {"errno": self.errno, "msg": self.msg}
            if self.sqlstate:
                fmt = "{errno} ({state}): {msg}"
                fields["state"] = self.sqlstate
            else:
                fmt = "{errno}: {msg}"
            self._full_msg = fmt.format(**fields)

        self.args = (self.errno, self._full_msg, self.sqlstate)

    def __str__(self) -> str:
        return self._full_msg


class InterfaceError(Error):
    """Exception for errors related to the interface."""


class DatabaseError(Error):
    """Exception for errors related to the database."""


class InternalError(DatabaseError):
    """Exception for errors internal database errors."""


class OperationalError(DatabaseError):
    """Exception for errors related to the database's operation."""


class ProgrammingError(DatabaseError):
    """Exception for errors programming errors."""


class IntegrityError(DatabaseError):
    """Exception for errors regarding relational integrity."""


class DataError(DatabaseError):
    """Exception for errors reporting problems with processed data."""


class NotSupportedError(DatabaseError):
    """Exception for errors when an unsupported database feature was used."""


class PoolError(Error):
    """Exception for errors relating to connection pooling."""


class TimeoutError(Error):  # pylint: disable=redefined-builtin
    """Exception for errors relating to connection timeout."""


def intread(buf: bytes) -> int:
    """Unpacks the given buffer to an integer."""
    if isinstance(buf, int):
        return buf
    length = len(buf)
    if length == 1:
        return buf[0]
    if length <= 4:
        tmp = buf + b"\x00" * (4 - length)
        return struct_unpack("<I", tmp)[0]
    tmp = buf + b"\x00" * (8 - length)
    return struct_unpack("<Q", tmp)[0]


def read_int(buf: bytes, size: int) -> Tuple[bytes, int]:
    """Read an integer from buffer.

    Returns a tuple (truncated buffer, int).
    """
    res = intread(buf[0:size])
    return (buf[size:], res)


def read_bytes(buf: bytes, size: int) -> Tuple[bytes, bytes]:
    """Reads bytes from a buffer.

    Returns a tuple with buffer less the read bytes, and the bytes.
    """
    res = buf[0:size]
    return (buf[size:], res)


def get_mysql_exception(
    errno: int, msg: Optional[str] = None, sqlstate: Optional[str] = None
) -> ErrorTypes:
    """Get the exception matching the MySQL error.

    This function will return an exception based on the SQLState. The given
    message will be passed on in the returned exception.

    Returns an Exception.
    """
    try:
        return _ERROR_EXCEPTIONS[errno](msg=msg, errno=errno, sqlstate=sqlstate)
    except KeyError:
        # Error was not mapped to particular exception
        pass

    if not sqlstate:
        return DatabaseError(msg=msg, errno=errno)

    try:
        return _SQLSTATE_CLASS_EXCEPTION[sqlstate[0:2]](
            msg=msg, errno=errno, sqlstate=sqlstate
        )
    except KeyError:
        # Return default InterfaceError
        return DatabaseError(msg=msg, errno=errno, sqlstate=sqlstate)


def get_exception(packet: bytes) -> ErrorTypes:
    """Returns an exception object based on the MySQL error.

    Returns an exception object based on the MySQL error in the given
    packet.

    Returns an Error-Object.
    """
    errno = errmsg = None

    try:
        if packet[4] != 255:
            raise ValueError("Packet is not an error packet")
    except IndexError as err:
        return InterfaceError(f"Failed getting Error information ({err})")

    sqlstate: Optional[StrOrBytes] = None
    try:
        packet = packet[5:]
        packet, errno = read_int(packet, 2)
        if packet[0] != 35:
            # Error without SQLState
            errmsg = (
                packet.decode("utf8")
                if isinstance(packet, (bytes, bytearray))
                else packet
            )
        else:
            packet, sqlstate = read_bytes(packet[1:], 5)
            sqlstate = sqlstate.decode("utf8")
            errmsg = packet.decode("utf8")
    except (IndexError, ValueError) as err:
        return InterfaceError(f"Failed getting Error information ({err})")
    return get_mysql_exception(errno, errmsg, sqlstate)  # type: ignore[arg-type]


_SQLSTATE_CLASS_EXCEPTION: Dict[str, ErrorClassTypes] = {
    "02": DataError,  # no data
    "07": DatabaseError,  # dynamic SQL error
    "08": OperationalError,  # connection exception
    "0A": NotSupportedError,  # feature not supported
    "21": DataError,  # cardinality violation
    "22": DataError,  # data exception
    "23": IntegrityError,  # integrity constraint violation
    "24": ProgrammingError,  # invalid cursor state
    "25": ProgrammingError,  # invalid transaction state
    "26": ProgrammingError,  # invalid SQL statement name
    "27": ProgrammingError,  # triggered data change violation
    "28": ProgrammingError,  # invalid authorization specification
    "2A": ProgrammingError,  # direct SQL syntax error or access rule violation
    "2B": DatabaseError,  # dependent privilege descriptors still exist
    "2C": ProgrammingError,  # invalid character set name
    "2D": DatabaseError,  # invalid transaction termination
    "2E": DatabaseError,  # invalid connection name
    "33": DatabaseError,  # invalid SQL descriptor name
    "34": ProgrammingError,  # invalid cursor name
    "35": ProgrammingError,  # invalid condition number
    "37": ProgrammingError,  # dynamic SQL syntax error or access rule violation
    "3C": ProgrammingError,  # ambiguous cursor name
    "3D": ProgrammingError,  # invalid catalog name
    "3F": ProgrammingError,  # invalid schema name
    "40": InternalError,  # transaction rollback
    "42": ProgrammingError,  # syntax error or access rule violation
    "44": InternalError,  # with check option violation
    "HZ": OperationalError,  # remote database access
    "XA": IntegrityError,
    "0K": OperationalError,
    "HY": DatabaseError,  # default when no SQLState provided by MySQL server
}

_ERROR_EXCEPTIONS: Dict[int, ErrorClassTypes] = {
    1243: ProgrammingError,
    1210: ProgrammingError,
    2002: InterfaceError,
    2013: OperationalError,
    2049: NotSupportedError,
    2055: OperationalError,
    2061: InterfaceError,
}

virt/lib/python3.9/site-packages/mysqlx/errors.py
PrinceDisant/LostInNL
# Copyright (c) 2016, 2022, Oracle and/or its affiliates. All rights reserved.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

"""Expression Parser."""

from typing import Any, Callable, Dict, Iterable, List, Optional, Sequence, Union

from .dbdoc import DbDoc
from .helpers import BYTE_TYPES, get_item_or_attr
from .protobuf import Message, mysqlxpb_enum
from .types import (
    BuildExprTypes,
    BuildScalarTypes,
    MessageType,
    ProtobufMessageCextType,
    ProtobufMessageType,
    StrOrBytes,
)


# pylint: disable=missing-function-docstring
class TokenType:
    """Token types class."""

    NOT = 1
    AND = 2
    OR = 3
    XOR = 4
    IS = 5
    LPAREN = 6
    RPAREN = 7
    LSQBRACKET = 8
    RSQBRACKET = 9
    BETWEEN = 10
    TRUE = 11
    NULL = 12
    FALSE = 13
    IN = 14
    LIKE = 15
    INTERVAL = 16
    REGEXP = 17
    ESCAPE = 18
    IDENT = 19
    LSTRING = 20
    LNUM = 21
    DOT = 22
    DOLLAR = 23
    COMMA = 24
    EQ = 25
    NE = 26
    GT = 27
    GE = 28
    LT = 29
    LE = 30
    BITAND = 31
    BITOR = 32
    BITXOR = 33
    LSHIFT = 34
    RSHIFT = 35
    PLUS = 36
    MINUS = 37
    MUL = 38
    DIV = 39
    HEX = 40
    BIN = 41
    NEG = 42
    BANG = 43
    MICROSECOND = 44
    SECOND = 45
    MINUTE = 46
    HOUR = 47
    DAY = 48
    WEEK = 49
    MONTH = 50
    QUARTER = 51
    YEAR = 52
    EROTEME = 53
    DOUBLESTAR = 54
    MOD = 55
    COLON = 56
    OROR = 57
    ANDAND = 58
    LCURLY = 59
    RCURLY = 60
    CAST = 61
    DOTSTAR = 62
    ORDERBY_ASC = 63
    ORDERBY_DESC = 64
    AS = 65
    ARROW = 66
    QUOTE = 67
    BINARY = 68
    DATETIME = 69
    TIME = 70
    CHAR = 71
    DATE = 72
    DECIMAL = 73
    SIGNED = 74
    INTEGER = 75
    UNSIGNED = 76
    JSON = 77
    SECOND_MICROSECOND = 78
    MINUTE_MICROSECOND = 79
    MINUTE_SECOND = 80
    HOUR_MICROSECOND = 81
    HOUR_SECOND = 82
    HOUR_MINUTE = 83
    DAY_MICROSECOND = 84
    DAY_SECOND = 85
    DAY_MINUTE = 86
    DAY_HOUR = 87
    YEAR_MONTH = 88
    OVERLAPS = 89


_INTERVAL_UNITS = set(
    [
        TokenType.MICROSECOND,
        TokenType.SECOND,
        TokenType.MINUTE,
        TokenType.HOUR,
        TokenType.DAY,
        TokenType.WEEK,
        TokenType.MONTH,
        TokenType.QUARTER,
        TokenType.YEAR,
        TokenType.SECOND_MICROSECOND,
        TokenType.MINUTE_MICROSECOND,
        TokenType.MINUTE_SECOND,
        TokenType.HOUR_MICROSECOND,
        TokenType.HOUR_SECOND,
        TokenType.HOUR_MINUTE,
        TokenType.DAY_MICROSECOND,
        TokenType.DAY_SECOND,
        TokenType.DAY_MINUTE,
        TokenType.DAY_HOUR,
        TokenType.YEAR_MONTH,
    ]
)

# map of reserved word to token type
_RESERVED_WORDS = {
    "and": TokenType.AND,
    "or": TokenType.OR,
    "xor": TokenType.XOR,
    "is": TokenType.IS,
    "not": TokenType.NOT,
    "like": TokenType.LIKE,
    "in": TokenType.IN,
    "overlaps": TokenType.OVERLAPS,
    "regexp": TokenType.REGEXP,
    "between": TokenType.BETWEEN,
    "interval": TokenType.INTERVAL,
    "escape": TokenType.ESCAPE,
    "cast": TokenType.CAST,
    "div": TokenType.DIV,
    "hex": TokenType.HEX,
    "bin": TokenType.BIN,
    "true": TokenType.TRUE,
    "false": TokenType.FALSE,
    "null": TokenType.NULL,
    "second": TokenType.SECOND,
    "minute": TokenType.MINUTE,
    "hour": TokenType.HOUR,
    "day": TokenType.DAY,
    "week": TokenType.WEEK,
    "month": TokenType.MONTH,
    "quarter": TokenType.QUARTER,
    "year": TokenType.YEAR,
    "microsecond": TokenType.MICROSECOND,
    "asc": TokenType.ORDERBY_ASC,
    "desc": TokenType.ORDERBY_DESC,
    "as": TokenType.AS,
    "binary": TokenType.BINARY,
    "datetime": TokenType.DATETIME,
    "time": TokenType.TIME,
    "char": TokenType.CHAR,
    "date": TokenType.DATE,
    "decimal": TokenType.DECIMAL,
    "signed": TokenType.SIGNED,
    "unsigned": TokenType.UNSIGNED,
    "integer": TokenType.INTEGER,
    "json": TokenType.JSON,
    "second_microsecond": TokenType.SECOND_MICROSECOND,
    "minute_microsecond": TokenType.MINUTE_MICROSECOND,
    "minute_second": TokenType.MINUTE_SECOND,
    "hour_microsecond": TokenType.HOUR_MICROSECOND,
    "hour_second": TokenType.HOUR_SECOND,
    "hour_minute": TokenType.HOUR_MINUTE,
    "day_microsecond": TokenType.DAY_MICROSECOND,
    "day_second": TokenType.DAY_SECOND,
    "day_minute": TokenType.DAY_MINUTE,
    "day_hour": TokenType.DAY_HOUR,
    "year_month": TokenType.YEAR_MONTH,
}

_SQL_FUNTION_RESERVED_WORDS_COLLISION = {
    "binary": TokenType.BINARY,
    "cast": TokenType.CAST,
    "char": TokenType.CHAR,
    "date": TokenType.DATE,
    "decimal": TokenType.DECIMAL,
    "signed": TokenType.SIGNED,
    "time": TokenType.TIME,
    "unsigned": TokenType.UNSIGNED,
}

_OPERATORS = {
    "=": "==",
    "==": "==",
    "and": "&&",
    "div": "div",
    "||": "||",
    "or": "||",
    "not": "not",
    "xor": "xor",
    "^": "^",
    "is": "is",
    "between": "between",
    "in": "in",
    "like": "like",
    "!=": "!=",
    "<>": "!=",
    ">": ">",
    ">=": ">=",
    "<": "<",
    "<=": "<=",
    "&": "&",
    "&&": "&&",
    "|": "|",
    "<<": "<<",
    ">>": ">>",
    "+": "+",
    "-": "-",
    "*": "*",
    "/": "/",
    "~": "~",
    "%": "%",
    "cast": "cast",
    "cont_in": "cont_in",
    "overlaps": "overlaps",
}

_UNARY_OPERATORS = {
    "+": "sign_plus",
    "-": "sign_minus",
    "~": "~",
    "not": "not",
    "!": "!",
}

_NEGATION = {
    "is": "is_not",
    "between": "not_between",
    "regexp": "not_regexp",
    "like": "not_like",
    "in": "not_in",
    "cont_in": "not_cont_in",
    "overlaps": "not_overlaps",
}


class Token:
    """Token representation class."""

    def __init__(self, token_type: int, value: str, length: int = 1) -> None:
        self.token_type: int = token_type
        self.value: str = value
        self.length: int = length

    def __repr__(self) -> str:
        return self.__str__()

    def __str__(self) -> str:
        if self.token_type in (
            TokenType.IDENT,
            TokenType.LNUM,
            TokenType.LSTRING,
        ):
            return f"{self.token_type}({self.value})"
        return f"{self.token_type}"


# static protobuf helper functions


def build_expr(value: BuildExprTypes) -> MessageType:
    msg = Message("Mysqlx.Expr.Expr")
    if isinstance(value, (Message)):
        return value
    if isinstance(value, (ExprParser)):
        return value.expr(reparse=True)
    if isinstance(value, (dict, DbDoc)):
        msg["type"] = mysqlxpb_enum("Mysqlx.Expr.Expr.Type.OBJECT")
        msg["object"] = build_object(value).get_message()
    elif isinstance(value, (list, tuple)):
        msg["type"] = mysqlxpb_enum("Mysqlx.Expr.Expr.Type.ARRAY")
        msg["array"] = build_array(value).get_message()
    else:
        msg["type"] = mysqlxpb_enum("Mysqlx.Expr.Expr.Type.LITERAL")
        msg["literal"] = build_scalar(value).get_message()
    return msg


def build_scalar(value: BuildScalarTypes) -> MessageType:
    if isinstance(value, str):
        return build_string_scalar(value)
    if isinstance(value, BYTE_TYPES):
        return build_bytes_scalar(value)
    if isinstance(value, bool):
        return build_bool_scalar(value)
    if isinstance(value, int):
        return build_int_scalar(value)
    if isinstance(value, float):
        return build_double_scalar(value)
    if value is None:
        return build_null_scalar()
    raise ValueError(f"Unsupported data type: {type(value)}")


def build_object(obj: Union[Dict, DbDoc]) -> MessageType:
    if isinstance(obj, DbDoc):
        return build_object(obj.__dict__)

    msg = Message("Mysqlx.Expr.Object")
    for key, value in obj.items():
        pair = Message("Mysqlx.Expr.Object.ObjectField")
        pair["key"] = key.encode() if isinstance(key, str) else key
        pair["value"] = build_expr(value).get_message()
        msg["fld"].extend([pair.get_message()])
    return msg


def build_array(array: Sequence[BuildExprTypes]) -> MessageType:
    msg = Message("Mysqlx.Expr.Array")
    msg["value"].extend([build_expr(value).get_message() for value in array])
    return msg


def build_null_scalar() -> MessageType:
    msg = Message("Mysqlx.Datatypes.Scalar")
    msg["type"] = mysqlxpb_enum("Mysqlx.Datatypes.Scalar.Type.V_NULL")
    return msg


def build_double_scalar(value: float) -> MessageType:
    msg = Message("Mysqlx.Datatypes.Scalar")
    msg["type"] = mysqlxpb_enum("Mysqlx.Datatypes.Scalar.Type.V_DOUBLE")
    msg["v_double"] = value
    return msg


def build_int_scalar(value: int) -> MessageType:
    msg = Message("Mysqlx.Datatypes.Scalar")
    msg["type"] = mysqlxpb_enum("Mysqlx.Datatypes.Scalar.Type.V_SINT")
    msg["v_signed_int"] = value
    return msg


def build_unsigned_int_scalar(value: int) -> MessageType:
    msg = Message("Mysqlx.Datatypes.Scalar")
    msg["type"] = mysqlxpb_enum("Mysqlx.Datatypes.Scalar.Type.V_UINT")
    msg["v_unsigned_int"] = value
    return msg


def build_string_scalar(value: StrOrBytes) -> MessageType:
    if isinstance(value, str):
        value = bytes(bytearray(value, "utf-8"))
    msg = Message("Mysqlx.Datatypes.Scalar")
    msg["type"] = mysqlxpb_enum("Mysqlx.Datatypes.Scalar.Type.V_STRING")
    msg["v_string"] = Message("Mysqlx.Datatypes.Scalar.String", value=value)
    return msg


def build_bool_scalar(value: bool) -> MessageType:
    msg = Message("Mysqlx.Datatypes.Scalar")
    msg["type"] = mysqlxpb_enum("Mysqlx.Datatypes.Scalar.Type.V_BOOL")
    msg["v_bool"] = value
    return msg


def build_bytes_scalar(value: bytes) -> MessageType:
    msg = Message("Mysqlx.Datatypes.Scalar")
    msg["type"] = mysqlxpb_enum("Mysqlx.Datatypes.Scalar.Type.V_OCTETS")
    msg["v_octets"] = Message("Mysqlx.Datatypes.Scalar.Octets", value=value)
    return msg


def build_literal_expr(value: MessageType) -> MessageType:
    msg = Message("Mysqlx.Expr.Expr")
    msg["type"] = mysqlxpb_enum("Mysqlx.Expr.Expr.Type.LITERAL")
    msg["literal"] = value
    return msg


def build_unary_op(name: str, param: MessageType) -> MessageType:
    operator = Message("Mysqlx.Expr.Operator")
    operator["name"] = _UNARY_OPERATORS[name]
    operator["param"] = [param.get_message()]
    msg = Message("Mysqlx.Expr.Expr")
    msg["type"] = mysqlxpb_enum("Mysqlx.Expr.Expr.Type.OPERATOR")
    msg["operator"] = operator.get_message()
    return msg


def escape_literal(string: str) -> str:
    return string.replace('"', '""')


class ExprParser:
    """Expression parser class."""

    def __init__(self, string: str, allow_relational: bool = True) -> None:
        self.string: str = string
        self.tokens: List[Token] = []
        self.path_name_queue: List[str] = []
        self.pos: int = 0
        self._allow_relational_columns: bool = allow_relational
        self.placeholder_name_to_position: Dict[str, int] = {}
        self.positional_placeholder_count: int = 0
        self.clean_expression()
        self.lex()

    def __str__(self) -> str:
        return f"<mysqlx.ExprParser '{self.string}'>"

    def clean_expression(self) -> None:
        """Removes the keywords that does not form part of the expression.

        Removes the keywords "SELECT" and "WHERE" that does not form part of
        the expression itself.
        """
        if not isinstance(self.string, str):
            self.string = repr(self.string)
        self.string = self.string.strip(" ")
        if len(self.string) > 1 and self.string[-1] == ";":
            self.string = self.string[:-1]
        if "SELECT" in self.string[:6].upper():
            self.string = self.string[6:]
        if "WHERE" in self.string[:5].upper():
            self.string = self.string[5:]

    # convencience checker for lexer
    def next_char_is(self, key: int, char: str) -> bool:
        return key + 1 < len(self.string) and self.string[key + 1] == char

    def lex_number(self, pos: int) -> Token:
        # numeric literal
        start = pos
        found_dot = False
        while pos < len(self.string) and (
            self.string[pos].isdigit() or self.string[pos] == "."
        ):
            if self.string[pos] == ".":
                if found_dot is True:
                    raise ValueError("Invalid number. Found multiple '.'")
                found_dot = True
            # technically we allow more than one "." and let float()'s parsing
            # complain later
            pos += 1
        val = self.string[start:pos]
        return Token(TokenType.LNUM, val, len(val))

    def lex_alpha(self, i: int, allow_space: bool = False) -> Token:
        start = i
        while i < len(self.string) and (
            self.string[i].isalnum()
            or self.string[i] == "_"
            or (self.string[i].isspace() and allow_space)
        ):
            i += 1

        val = self.string[start:i]
        try:
            if (
                i < len(self.string)
                and self.string[i] == "("
                and val.lower() not in _SQL_FUNTION_RESERVED_WORDS_COLLISION
            ):
                token = Token(TokenType.IDENT, val, len(val))
            else:
                token = Token(_RESERVED_WORDS[val.lower()], val.lower(), len(val))
        except KeyError:
            token = Token(TokenType.IDENT, val, len(val))
        return token

    def lex_quoted_token(self, key: int) -> Token:
        quote_char = self.string[key]
        val = ""
        key += 1
        start = key
        while key < len(self.string):
            char = self.string[key]
            if (
                char == quote_char
                and key + 1 < len(self.string)
                and self.string[key + 1] != quote_char
            ):
                # break if we have a quote char that's not double
                break
            if char in (quote_char, "\\"):
                # this quote char has to be doubled
                if key + 1 >= len(self.string):
                    break
                key += 1
                val += self.string[key]
            else:
                val += char
            key += 1
        if key >= len(self.string) or self.string[key] != quote_char:
            raise ValueError(f"Unterminated quoted string starting at {start}")
        if quote_char == "`":
            return Token(TokenType.IDENT, val, len(val) + 2)
        return Token(TokenType.LSTRING, val, len(val) + 2)

    def lex(self) -> None:
        i = 0
        arrow_last = False
        inside_arrow = False
        while i < len(self.string):
            char = self.string[i]
            if char.isspace():
                i += 1
                continue
            if char.isdigit():
                token = self.lex_number(i)
            elif char.isalpha() or char == "_":
                token = self.lex_alpha(i, inside_arrow)
            elif char == "?":
                token = Token(TokenType.EROTEME, char)
            elif char == ":":
                token = Token(TokenType.COLON, char)
            elif char == "{":
                token = Token(TokenType.LCURLY, char)
            elif char == "}":
                token = Token(TokenType.RCURLY, char)
            elif char == "+":
                token = Token(TokenType.PLUS, char)
            elif char == "-":
                if self.next_char_is(i, ">") and not arrow_last:
                    token = Token(TokenType.ARROW, "->", 2)
                    arrow_last = True
                else:
                    token = Token(TokenType.MINUS, char)
            elif char == "*":
                if self.next_char_is(i, "*"):
                    token = Token(TokenType.DOUBLESTAR, "**", 2)
                else:
                    token = Token(TokenType.MUL, char)
            elif char == "/":
                token = Token(TokenType.DIV, char)
            elif char == "$":
                token = Token(TokenType.DOLLAR, char)
            elif char == "%":
                token = Token(TokenType.MOD, char)
            elif char == "=":
                if self.next_char_is(i, "="):
                    token = Token(TokenType.EQ, "==", 2)
                else:
                    token = Token(TokenType.EQ, "==", 1)
            elif char == "&":
                if self.next_char_is(i, "&"):
                    token = Token(TokenType.ANDAND, "&&", 2)
                else:
                    token = Token(TokenType.BITAND, char)
            elif char == "^":
                token = Token(TokenType.BITXOR, char)
            elif char == "|":
                if self.next_char_is(i, "|"):
                    token = Token(TokenType.OROR, "||", 2)
                else:
                    token = Token(TokenType.BITOR, char)
            elif char == "(":
                token = Token(TokenType.LPAREN, char)
            elif char == ")":
                token = Token(TokenType.RPAREN, char)
            elif char == "[":
                token = Token(TokenType.LSQBRACKET, char)
            elif char == "]":
                token = Token(TokenType.RSQBRACKET, char)
            elif char == "~":
                token = Token(TokenType.NEG, char)
            elif char == ",":
                token = Token(TokenType.COMMA, char)
            elif char == "!":
                if self.next_char_is(i, "="):
                    token = Token(TokenType.NE, "!=", 2)
                else:
                    token = Token(TokenType.BANG, char)
            elif char == "<":
                if self.next_char_is(i, ">"):
                    token = Token(TokenType.NE, "<>", 2)
                elif self.next_char_is(i, "<"):
                    token = Token(TokenType.LSHIFT, "<<", 2)
                elif self.next_char_is(i, "="):
                    token = Token(TokenType.LE, "<=", 2)
                else:
                    token = Token(TokenType.LT, char)
            elif char == ">":
                if self.next_char_is(i, ">"):
                    token = Token(TokenType.RSHIFT, ">>", 2)
                elif self.next_char_is(i, "="):
                    token = Token(TokenType.GE, ">=", 2)
                else:
                    token = Token(TokenType.GT, char)
            elif char == ".":
                if self.next_char_is(i, "*"):
                    token = Token(TokenType.DOTSTAR, ".*", 2)
                elif i + 1 < len(self.string) and self.string[i + 1].isdigit():
                    token = self.lex_number(i)
                else:
                    token = Token(TokenType.DOT, char)
            elif char in ("'", '"') and arrow_last:
                token = Token(TokenType.QUOTE, char)
                if not inside_arrow:
                    inside_arrow = True
                else:
                    arrow_last = False
                    inside_arrow = False
            elif char in ('"', "'", "`"):
                token = self.lex_quoted_token(i)
            else:
                raise ValueError(f"Unknown character at {i}")
            self.tokens.append(token)
            i += token.length

    def assert_cur_token(self, token_type: int) -> None:
        if self.pos >= len(self.tokens):
            raise ValueError(
                f"Expected token type {token_type} at pos {self.pos} but no "
                "tokens left"
            )
        if self.tokens[self.pos].token_type != token_type:
            raise ValueError(
                f"Expected token type {token_type} at pos {self.pos} but found "
                f"type {self.tokens[self.pos]}, on tokens {self.tokens}"
            )

    def cur_token_type_is(self, token_type: int) -> bool:
        return self.pos_token_type_is(self.pos, token_type)

    def cur_token_type_in(self, *types: int) -> bool:
        return self.pos < len(self.tokens) and self.tokens[self.pos].token_type in types

    def next_token_type_is(self, token_type: int) -> bool:
        return self.pos_token_type_is(self.pos + 1, token_type)

    def next_token_type_in(self, *types: int) -> bool:
        return (
            self.pos < len(self.tokens)
            and self.tokens[self.pos + 1].token_type in types
        )

    def pos_token_type_is(self, pos: int, token_type: int) -> bool:
        return pos < len(self.tokens) and self.tokens[pos].token_type == token_type

    def consume_token(self, token_type: int) -> str:
        self.assert_cur_token(token_type)
        value = self.tokens[self.pos].value
        self.pos += 1
        return value

    def paren_expr_list(
        self,
    ) -> List[Union[ProtobufMessageType, ProtobufMessageCextType]]:
        """Parse a paren-bounded expression list for function arguments or IN
        list and return a list of Expr objects.
        """
        exprs = []
        path_name_added = False
        self.consume_token(TokenType.LPAREN)
        if not self.cur_token_type_is(TokenType.RPAREN):
            msg_expr = self._expr().get_message()
            if hasattr(msg_expr, "identifier") and msg_expr.identifier.name:
                self.path_name_queue.insert(0, msg_expr.identifier.name)
                path_name_added = True
            elif (
                not hasattr(msg_expr, "identifier")
                and "identifier" in msg_expr
                and "name" in msg_expr["identifier"]
            ):
                self.path_name_queue.insert(0, msg_expr["identifier"]["name"])
                path_name_added = True
            exprs.append(msg_expr)
            while self.cur_token_type_is(TokenType.COMMA):
                self.pos += 1
                exprs.append(self._expr().get_message())
        self.consume_token(TokenType.RPAREN)
        if path_name_added:
            self.path_name_queue.pop()
        return exprs

    def identifier(self) -> MessageType:
        self.assert_cur_token(TokenType.IDENT)
        ident = Message("Mysqlx.Expr.Identifier")
        if self.next_token_type_is(TokenType.DOT):
            ident["schema_name"] = self.consume_token(TokenType.IDENT)
            self.consume_token(TokenType.DOT)
        ident["name"] = self.tokens[self.pos].value
        self.pos += 1
        return ident

    def function_call(self) -> MessageType:
        function_call = Message("Mysqlx.Expr.FunctionCall")
        function_call["name"] = self.identifier()
        function_call["param"] = self.paren_expr_list()
        msg_expr = Message("Mysqlx.Expr.Expr")
        msg_expr["type"] = mysqlxpb_enum("Mysqlx.Expr.Expr.Type.FUNC_CALL")
        msg_expr["function_call"] = function_call.get_message()
        return msg_expr

    def docpath_member(self) -> MessageType:
        self.consume_token(TokenType.DOT)
        token = self.tokens[self.pos]

        if token.token_type == TokenType.IDENT:
            if token.value.startswith("`") and token.value.endswith("`"):
                raise ValueError(
                    f"{token.value} is not a valid JSON/ECMAScript identifier"
                )
            self.consume_token(TokenType.IDENT)
            member_name = token.value
        elif token.token_type == TokenType.LSTRING:
            self.consume_token(TokenType.LSTRING)
            member_name = token.value
        else:
            raise ValueError(
                "Expected token type IDENT or LSTRING in JSON path at token "
                f"pos {self.pos}"
            )
        doc_path_item = Message("Mysqlx.Expr.DocumentPathItem")
        doc_path_item["type"] = mysqlxpb_enum(
            "Mysqlx.Expr.DocumentPathItem.Type.MEMBER"
        )
        doc_path_item["value"] = member_name
        return doc_path_item

    def docpath_array_loc(self) -> MessageType:
        self.consume_token(TokenType.LSQBRACKET)
        if self.cur_token_type_is(TokenType.MUL):
            self.consume_token(TokenType.MUL)
            self.consume_token(TokenType.RSQBRACKET)
            doc_path_item = Message("Mysqlx.Expr.DocumentPathItem")
            doc_path_item["type"] = mysqlxpb_enum(
                "Mysqlx.Expr.DocumentPathItem.Type.ARRAY_INDEX_ASTERISK"
            )
            return doc_path_item
        if self.cur_token_type_is(TokenType.LNUM):
            value = int(self.consume_token(TokenType.LNUM))
            if value < 0:
                raise IndexError(f"Array index cannot be negative at {self.pos}")
            self.consume_token(TokenType.RSQBRACKET)
            doc_path_item = Message("Mysqlx.Expr.DocumentPathItem")
            doc_path_item["type"] = mysqlxpb_enum(
                "Mysqlx.Expr.DocumentPathItem.Type.ARRAY_INDEX"
            )
            doc_path_item["index"] = value
            return doc_path_item
        raise ValueError(
            "Exception token type MUL or LNUM in JSON path array index at "
            f"token pos {self.pos}"
        )

    def document_field(self) -> MessageType:
        if not self.tokens:
            raise ValueError("Empty string cannot be used as document field")
        if self.cur_token_type_is(TokenType.DOLLAR):
            self.consume_token(TokenType.DOLLAR)
        col_id = Message("Mysqlx.Expr.ColumnIdentifier")
        if self.cur_token_type_is(TokenType.IDENT):
            doc_path_item = Message("Mysqlx.Expr.DocumentPathItem")
            doc_path_item["type"] = mysqlxpb_enum(
                "Mysqlx.Expr.DocumentPathItem.Type.MEMBER"
            )
            doc_path_item["value"] = self.consume_token(TokenType.IDENT)
            col_id["document_path"].extend([doc_path_item.get_message()])
        col_id["document_path"].extend(self.document_path())
        if self.path_name_queue:
            col_id["name"] = self.path_name_queue[0]
        expr = Message("Mysqlx.Expr.Expr")
        expr["type"] = mysqlxpb_enum("Mysqlx.Expr.Expr.Type.IDENT")
        expr["identifier"] = col_id
        return expr

    def document_path(
        self,
    ) -> List[Union[ProtobufMessageType, ProtobufMessageCextType]]:
        """Parse a JSON-style document path, like WL#7909, but prefix by @.
        instead of $. We parse this as a string because the protocol doesn't
        support it. (yet)
        """
        doc_path = []
        while True:
            if self.cur_token_type_is(TokenType.DOT):
                doc_path.append(self.docpath_member().get_message())
            elif self.cur_token_type_is(TokenType.DOTSTAR):
                self.consume_token(TokenType.DOTSTAR)
                doc_path_item = Message("Mysqlx.Expr.DocumentPathItem")
                doc_path_item["type"] = mysqlxpb_enum(
                    "Mysqlx.Expr.DocumentPathItem.Type.MEMBER_ASTERISK"
                )
                doc_path.append(doc_path_item.get_message())
            elif self.cur_token_type_is(TokenType.LSQBRACKET):
                doc_path.append(self.docpath_array_loc().get_message())
            elif self.cur_token_type_is(TokenType.DOUBLESTAR):
                self.consume_token(TokenType.DOUBLESTAR)
                doc_path_item = Message("Mysqlx.Expr.DocumentPathItem")
                doc_path_item["type"] = mysqlxpb_enum(
                    "Mysqlx.Expr.DocumentPathItem.Type.DOUBLE_ASTERISK"
                )
                doc_path.append(doc_path_item.get_message())
            else:
                break
        items = len(doc_path)
        if items > 0 and get_item_or_attr(doc_path[items - 1], "type") == mysqlxpb_enum(
            "Mysqlx.Expr.DocumentPathItem.Type.DOUBLE_ASTERISK"
        ):
            raise ValueError(f"JSON path may not end in '**' at {self.pos}")
        return doc_path

    def column_identifier(self) -> MessageType:
        parts = []
        parts.append(self.consume_token(TokenType.IDENT))
        while self.cur_token_type_is(TokenType.DOT):
            self.consume_token(TokenType.DOT)
            parts.append(self.consume_token(TokenType.IDENT))
        if len(parts) > 3:
            raise ValueError(f"Too many parts to identifier at {self.pos}")
        parts.reverse()
        col_id = Message("Mysqlx.Expr.ColumnIdentifier")
        # clever way to apply them to the struct
        for i in range(0, len(parts)):
            if i == 0:
                col_id["name"] = parts[0]
            elif i == 1:
                col_id["table_name"] = parts[1]
            elif i == 2:
                col_id["schema_name"] = parts[2]

        is_doc = False
        if self.cur_token_type_is(TokenType.DOLLAR):
            is_doc = True
            self.consume_token(TokenType.DOLLAR)
            col_id["document_path"] = self.document_path()
        elif self.cur_token_type_is(TokenType.ARROW):
            is_doc = True
            self.consume_token(TokenType.ARROW)
            is_quoted = False
            if self.cur_token_type_is(TokenType.QUOTE):
                is_quoted = True
                self.consume_token(TokenType.QUOTE)
            self.consume_token(TokenType.DOLLAR)
            col_id["document_path"] = self.document_path()
            if is_quoted:
                self.consume_token(TokenType.QUOTE)

        if is_doc and len(col_id["document_path"]) == 0:
            doc_path_item = Message("Mysqlx.Expr.DocumentPathItem")
            doc_path_item["type"] = mysqlxpb_enum(
                "Mysqlx.Expr.DocumentPathItem.Type.MEMBER"
            )
            doc_path_item["value"] = ""
            col_id["document_path"].extend([doc_path_item.get_message()])

        msg_expr = Message("Mysqlx.Expr.Expr")
        msg_expr["type"] = mysqlxpb_enum("Mysqlx.Expr.Expr.Type.IDENT")
        msg_expr["identifier"] = col_id
        return msg_expr

    def next_token(self) -> Token:
        if self.pos >= len(self.tokens):
            raise ValueError("Unexpected end of token stream")
        token = self.tokens[self.pos]
        self.pos += 1
        return token

    def expect_token(self, token_type: int) -> None:
        token = self.next_token()
        if token.token_type != token_type:
            raise ValueError(f"Expected token type {token_type}")

    def peek_token(self) -> Token:
        return self.tokens[self.pos]

    def consume_any_token(self) -> str:
        value = self.tokens[self.pos].value
        self.pos += 1
        return value

    def parse_json_array(self) -> MessageType:
        """
        jsonArray            ::=  "[" [ expr ("," expr)* ] "]"
        """
        msg = Message("Mysqlx.Expr.Array")
        while self.pos < len(self.tokens) and not self.cur_token_type_is(
            TokenType.RSQBRACKET
        ):
            msg["value"].extend([self._expr().get_message()])
            if not self.cur_token_type_is(TokenType.COMMA):
                break
            self.consume_token(TokenType.COMMA)
        self.consume_token(TokenType.RSQBRACKET)

        expr = Message("Mysqlx.Expr.Expr")
        expr["type"] = mysqlxpb_enum("Mysqlx.Expr.Expr.Type.ARRAY")
        expr["array"] = msg.get_message()
        return expr

    def parse_json_doc(self) -> MessageType:
        """
        jsonDoc              ::=  "{" [jsonKeyValue ("," jsonKeyValue)*] "}"
        jsonKeyValue         ::=  STRING_DQ ":" expr
        """
        msg = Message("Mysqlx.Expr.Object")
        while self.pos < len(self.tokens) and not self.cur_token_type_is(
            TokenType.RCURLY
        ):
            item = Message("Mysqlx.Expr.Object.ObjectField")
            item["key"] = self.consume_token(TokenType.LSTRING)
            self.consume_token(TokenType.COLON)
            item["value"] = self._expr().get_message()
            msg["fld"].extend([item.get_message()])
            if not self.cur_token_type_is(TokenType.COMMA):
                break
            self.consume_token(TokenType.COMMA)
        self.consume_token(TokenType.RCURLY)

        expr = Message("Mysqlx.Expr.Expr")
        expr["type"] = mysqlxpb_enum("Mysqlx.Expr.Expr.Type.OBJECT")
        expr["object"] = msg.get_message()
        return expr

    def parse_place_holder(self, token: Token) -> MessageType:
        place_holder_name = ""
        if self.cur_token_type_is(TokenType.LNUM):
            place_holder_name = self.consume_token(TokenType.LNUM)
        elif self.cur_token_type_is(TokenType.IDENT):
            place_holder_name = self.consume_token(TokenType.IDENT)
        elif token.token_type == TokenType.EROTEME:
            place_holder_name = str(self.positional_placeholder_count)
        else:
            raise ValueError(f"Invalid placeholder name at token pos {self.pos}")

        msg_expr = Message("Mysqlx.Expr.Expr")
        msg_expr["type"] = mysqlxpb_enum("Mysqlx.Expr.Expr.Type.PLACEHOLDER")
        if place_holder_name in self.placeholder_name_to_position:
            msg_expr["position"] = self.placeholder_name_to_position[place_holder_name]
        else:
            msg_expr["position"] = self.positional_placeholder_count
            self.placeholder_name_to_position[
                place_holder_name
            ] = self.positional_placeholder_count
            self.positional_placeholder_count += 1
        return msg_expr

    def cast(self) -> MessageType:
        """cast ::= CAST LPAREN expr AS cast_data_type RPAREN"""
        operator = Message("Mysqlx.Expr.Operator", name="cast")
        self.consume_token(TokenType.LPAREN)
        operator["param"].extend([self._expr().get_message()])
        self.consume_token(TokenType.AS)

        type_scalar = build_bytes_scalar(str.encode(self.cast_data_type()))
        operator["param"].extend([build_literal_expr(type_scalar).get_message()])
        self.consume_token(TokenType.RPAREN)
        msg = Message("Mysqlx.Expr.Expr", operator=operator.get_message())
        msg["type"] = mysqlxpb_enum("Mysqlx.Expr.Expr.Type.OPERATOR")
        return msg

    def cast_data_type(self) -> str:
        """cast_data_type ::= ( BINARY dimension? ) |
        ( CHAR dimension? ) |
        ( DATE ) |
        ( DATETIME dimension? ) |
        ( TIME dimension? ) |
        ( DECIMAL dimension? ) |
        ( SIGNED INTEGER? ) |
        ( UNSIGNED INTEGER? ) |
        JSON
        """
        token = self.next_token()
        if token.token_type in (
            TokenType.BINARY,
            TokenType.CHAR,
            TokenType.DATETIME,
            TokenType.TIME,
        ):
            dimension = self.cast_data_type_dimension()
            return f"{token.value}{dimension}" if dimension else token.value
        if token.token_type is TokenType.DECIMAL:
            dimension = self.cast_data_type_dimension(True)
            return f"{token.value}{dimension}" if dimension else token.value
        if token.token_type in (TokenType.SIGNED, TokenType.UNSIGNED):
            if self.cur_token_type_is(TokenType.INTEGER):
                self.consume_token(TokenType.INTEGER)
            return token.value
        if token.token_type in (
            TokenType.INTEGER,
            TokenType.JSON,
            TokenType.DATE,
        ):
            return token.value

        raise ValueError(
            f"Unknown token type {token.token_type} at position {self.pos} "
            f"({token.value})"
        )

    def cast_data_type_dimension(self, decimal: bool = False) -> Optional[str]:
        """dimension ::= LPAREN LNUM (, LNUM)? RPAREN"""
        if not self.cur_token_type_is(TokenType.LPAREN):
            return None

        dimension = []
        self.consume_token(TokenType.LPAREN)
        dimension.append(self.consume_token(TokenType.LNUM))
        if decimal and self.cur_token_type_is(TokenType.COMMA):
            self.consume_token(TokenType.COMMA)
            dimension.append(self.consume_token(TokenType.LNUM))
        self.consume_token(TokenType.RPAREN)

        return (
            f"({dimension[0]})"
            if len(dimension) == 1
            else f"({dimension[0]},{dimension[1]})"
        )

    @staticmethod
    def star_operator() -> MessageType:
        msg = Message("Mysqlx.Expr.Expr")
        msg["type"] = mysqlxpb_enum("Mysqlx.Expr.Expr.Type.OPERATOR")
        msg["operator"] = Message("Mysqlx.Expr.Operator", name="*")
        return msg

    def atomic_expr(self) -> MessageType:
        """Parse an atomic expression and return a protobuf Expr object"""
        token = self.next_token()

        if token.token_type in [TokenType.EROTEME, TokenType.COLON]:
            return self.parse_place_holder(token)
        if token.token_type == TokenType.LCURLY:
            return self.parse_json_doc()
        if token.token_type == TokenType.LSQBRACKET:
            return self.parse_json_array()
        if token.token_type == TokenType.CAST:
            return self.cast()
        if token.token_type == TokenType.LPAREN:
            expr = self._expr()
            self.expect_token(TokenType.RPAREN)
            return expr
        if token.token_type in [TokenType.PLUS, TokenType.MINUS]:
            peek = self.peek_token()
            if peek.token_type == TokenType.LNUM:
                self.tokens[self.pos].value = token.value + peek.value
                return self.atomic_expr()
            return build_unary_op(token.value, self.atomic_expr())
        if token.token_type in (TokenType.NOT, TokenType.NEG, TokenType.BANG):
            return build_unary_op(token.value, self.atomic_expr())
        if token.token_type == TokenType.LSTRING:
            return build_literal_expr(build_string_scalar(token.value))
        if token.token_type == TokenType.NULL:
            return build_literal_expr(build_null_scalar())
        if token.token_type == TokenType.LNUM:
            if "." in token.value:
                return build_literal_expr(build_double_scalar(float(token.value)))
            return build_literal_expr(build_int_scalar(int(token.value)))
        if token.token_type in [TokenType.TRUE, TokenType.FALSE]:
            return build_literal_expr(
                build_bool_scalar(token.token_type == TokenType.TRUE)
            )
        if token.token_type == TokenType.DOLLAR:
            return self.document_field()
        if token.token_type == TokenType.MUL:
            return self.star_operator()
        if token.token_type == TokenType.IDENT:
            self.pos = self.pos - 1  # stay on the identifier
            if self.next_token_type_is(TokenType.LPAREN) or (
                self.next_token_type_is(TokenType.DOT)
                and self.pos_token_type_is(self.pos + 2, TokenType.IDENT)
                and self.pos_token_type_is(self.pos + 3, TokenType.LPAREN)
            ):
                # Function call
                return self.function_call()
            return (
                self.document_field()
                if not self._allow_relational_columns
                else self.column_identifier()
            )

        raise ValueError(
            f"Unknown token type = {token.token_type}  when expecting atomic "
            f"expression at {self.pos}"
        )

    def parse_left_assoc_binary_op_expr(
        self, types: Iterable[int], inner_parser: Callable[[], MessageType]
    ) -> MessageType:
        """Given a `set' of types and an Expr-returning inner parser function,
        parse a left associate binary operator expression"""
        lhs = inner_parser()
        while self.pos < len(self.tokens) and self.tokens[self.pos].token_type in types:
            msg = Message("Mysqlx.Expr.Expr")
            msg["type"] = mysqlxpb_enum("Mysqlx.Expr.Expr.Type.OPERATOR")
            operator = Message("Mysqlx.Expr.Operator")
            operator["name"] = _OPERATORS[self.tokens[self.pos].value]
            operator["param"] = [lhs.get_message()]
            self.pos += 1
            operator["param"].extend([inner_parser().get_message()])
            msg["operator"] = operator
            lhs = msg
        return lhs

    # operator precedence is implemented here
    def add_sub_interval(self) -> MessageType:
        lhs = self.atomic_expr()
        if self.cur_token_type_in(
            TokenType.PLUS, TokenType.MINUS
        ) and self.next_token_type_is(TokenType.INTERVAL):
            token = self.next_token()

            operator = Message("Mysqlx.Expr.Operator")
            operator["param"].extend([lhs.get_message()])
            operator["name"] = (
                "date_add" if token.token_type is TokenType.PLUS else "date_sub"
            )

            self.consume_token(TokenType.INTERVAL)
            operator["param"].extend([self.bit_expr().get_message()])

            if not self.cur_token_type_in(*_INTERVAL_UNITS):
                raise ValueError(f"Expected interval type at position {self.pos}")

            token = str.encode(self.consume_any_token().upper())
            operator["param"].extend(
                [build_literal_expr(build_bytes_scalar(token)).get_message()]
            )

            lhs = Message("Mysqlx.Expr.Expr", operator=operator)
            lhs["type"] = mysqlxpb_enum("Mysqlx.Expr.Expr.Type.OPERATOR")

        return lhs

    def mul_div_expr(self) -> MessageType:
        return self.parse_left_assoc_binary_op_expr(
            set([TokenType.MUL, TokenType.DIV, TokenType.MOD]),
            self.add_sub_interval,
        )

    def add_sub_expr(self) -> MessageType:
        return self.parse_left_assoc_binary_op_expr(
            set([TokenType.PLUS, TokenType.MINUS]), self.mul_div_expr
        )

    def shift_expr(self) -> MessageType:
        return self.parse_left_assoc_binary_op_expr(
            set([TokenType.LSHIFT, TokenType.RSHIFT]), self.add_sub_expr
        )

    def bit_expr(self) -> MessageType:
        return self.parse_left_assoc_binary_op_expr(
            set([TokenType.BITAND, TokenType.BITOR, TokenType.BITXOR]),
            self.shift_expr,
        )

    def comp_expr(self) -> MessageType:
        return self.parse_left_assoc_binary_op_expr(
            set(
                [
                    TokenType.GE,
                    TokenType.GT,
                    TokenType.LE,
                    TokenType.LT,
                    TokenType.EQ,
                    TokenType.NE,
                ]
            ),
            self.bit_expr,
        )

    def ilri_expr(self) -> MessageType:
        params = []
        lhs = self.comp_expr()
        is_not = False
        if self.cur_token_type_is(TokenType.NOT):
            is_not = True
            self.consume_token(TokenType.NOT)
        if self.pos < len(self.tokens):
            params.append(lhs.get_message())
            op_name = self.tokens[self.pos].value
            if self.cur_token_type_is(TokenType.IS):
                self.consume_token(TokenType.IS)
                # for IS, NOT comes AFTER
                if self.cur_token_type_is(TokenType.NOT):
                    is_not = True
                    self.consume_token(TokenType.NOT)
                params.append(self.comp_expr().get_message())
            elif self.cur_token_type_is(TokenType.IN):
                self.consume_token(TokenType.IN)
                if self.cur_token_type_is(TokenType.LPAREN):
                    params.extend(self.paren_expr_list())
                else:
                    op_name = "cont_in"
                    params.append(self.comp_expr().get_message())
            elif self.cur_token_type_is(TokenType.OVERLAPS):
                self.consume_token(TokenType.OVERLAPS)
                params.append(self.comp_expr().get_message())

            elif self.cur_token_type_is(TokenType.LIKE):
                self.consume_token(TokenType.LIKE)
                params.append(self.comp_expr().get_message())
                if self.cur_token_type_is(TokenType.ESCAPE):
                    self.consume_token(TokenType.ESCAPE)
                    params.append(self.comp_expr().get_message())
            elif self.cur_token_type_is(TokenType.BETWEEN):
                self.consume_token(TokenType.BETWEEN)
                params.append(self.comp_expr().get_message())
                self.consume_token(TokenType.AND)
                params.append(self.comp_expr().get_message())
            elif self.cur_token_type_is(TokenType.REGEXP):
                self.consume_token(TokenType.REGEXP)
                params.append(self.comp_expr().get_message())
            else:
                if is_not:
                    raise ValueError(f"Unknown token after NOT as pos {self.pos}")
                op_name = None  # not an operator we're interested in
            if op_name:
                operator = Message("Mysqlx.Expr.Operator")
                operator["name"] = _NEGATION[op_name] if is_not else op_name
                operator["param"] = params
                msg_expr = Message("Mysqlx.Expr.Expr")
                msg_expr["type"] = mysqlxpb_enum("Mysqlx.Expr.Expr.Type.OPERATOR")
                msg_expr["operator"] = operator.get_message()
                lhs = msg_expr
        return lhs

    def and_expr(self) -> MessageType:
        return self.parse_left_assoc_binary_op_expr(
            set([TokenType.AND, TokenType.ANDAND]), self.ilri_expr
        )

    def xor_expr(self) -> MessageType:
        return self.parse_left_assoc_binary_op_expr(set([TokenType.XOR]), self.and_expr)

    def or_expr(self) -> MessageType:
        return self.parse_left_assoc_binary_op_expr(
            set([TokenType.OR, TokenType.OROR]), self.xor_expr
        )

    def _expr(self, reparse: bool = False) -> MessageType:
        if reparse:
            self.tokens = []
            self.pos = 0
            self.placeholder_name_to_position = {}
            self.positional_placeholder_count = 0
            self.lex()
        return self.or_expr()

    def expr(self, reparse: bool = False) -> MessageType:
        expression = self._expr(reparse)
        used_tokens = self.pos
        if self.pos_token_type_is(len(self.tokens) - 2, TokenType.AS):
            used_tokens += 2
        if used_tokens < len(self.tokens):
            raise ValueError(
                f"Unused token types {self.tokens[self.pos :]} found in "
                f"expression at position: {self.pos}"
            )
        return expression

    def parse_table_insert_field(self) -> MessageType:
        return Message("Mysqlx.Crud.Column", name=self.consume_token(TokenType.IDENT))

    def parse_table_update_field(self) -> Any:
        return self.column_identifier().identifier

    def _table_fields(self) -> List[str]:
        fields = []
        temp = self.string.split(",")
        temp.reverse()
        while temp:
            field = temp.pop()
            while (
                field.count("(") != field.count(")")
                or field.count("[") != field.count("]")
                or field.count("{") != field.count("}")
            ):
                field = f"{temp.pop()},{field}"
            fields.append(field.strip())
        return fields

    def parse_table_select_projection(
        self,
    ) -> List[Union[ProtobufMessageType, ProtobufMessageCextType]]:
        project_expr: List = []
        first = True
        fields = self._table_fields()
        while self.pos < len(self.tokens):
            if not first:
                self.consume_token(TokenType.COMMA)
            first = False
            projection = Message("Mysqlx.Crud.Projection", source=self._expr())
            if self.cur_token_type_is(TokenType.AS):
                self.consume_token(TokenType.AS)
                projection["alias"] = self.consume_token(TokenType.IDENT)
            else:
                projection["alias"] = fields[len(project_expr)]
            project_expr.append(projection.get_message())

        return project_expr

    def parse_order_spec(
        self,
    ) -> List[Union[ProtobufMessageType, ProtobufMessageCextType]]:
        order_specs = []
        first = True
        while self.pos < len(self.tokens):
            if not first:
                self.consume_token(TokenType.COMMA)
            first = False
            order = Message("Mysqlx.Crud.Order", expr=self._expr())
            if self.cur_token_type_is(TokenType.ORDERBY_ASC):
                order["direction"] = mysqlxpb_enum("Mysqlx.Crud.Order.Direction.ASC")
                self.consume_token(TokenType.ORDERBY_ASC)
            elif self.cur_token_type_is(TokenType.ORDERBY_DESC):
                order["direction"] = mysqlxpb_enum("Mysqlx.Crud.Order.Direction.DESC")
                self.consume_token(TokenType.ORDERBY_DESC)
            order_specs.append(order.get_message())
        return order_specs

    def parse_expr_list(
        self,
    ) -> List[Union[ProtobufMessageType, ProtobufMessageCextType]]:
        expr_list = []
        first = True
        while self.pos < len(self.tokens):
            if not first:
                self.consume_token(TokenType.COMMA)
            first = False
            expr_list.append(self._expr().get_message())
        return expr_list

virt/lib/python3.9/site-packages/mysqlx/expr 2.py
PrinceDisant/LostInNL
# Copyright (c) 2016, 2022, Oracle and/or its affiliates. All rights reserved.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

"""Expression Parser."""

from typing import Any, Callable, Dict, Iterable, List, Optional, Sequence, Union

from .dbdoc import DbDoc
from .helpers import BYTE_TYPES, get_item_or_attr
from .protobuf import Message, mysqlxpb_enum
from .types import (
    BuildExprTypes,
    BuildScalarTypes,
    MessageType,
    ProtobufMessageCextType,
    ProtobufMessageType,
    StrOrBytes,
)


# pylint: disable=missing-function-docstring
class TokenType:
    """Token types class."""

    NOT = 1
    AND = 2
    OR = 3
    XOR = 4
    IS = 5
    LPAREN = 6
    RPAREN = 7
    LSQBRACKET = 8
    RSQBRACKET = 9
    BETWEEN = 10
    TRUE = 11
    NULL = 12
    FALSE = 13
    IN = 14
    LIKE = 15
    INTERVAL = 16
    REGEXP = 17
    ESCAPE = 18
    IDENT = 19
    LSTRING = 20
    LNUM = 21
    DOT = 22
    DOLLAR = 23
    COMMA = 24
    EQ = 25
    NE = 26
    GT = 27
    GE = 28
    LT = 29
    LE = 30
    BITAND = 31
    BITOR = 32
    BITXOR = 33
    LSHIFT = 34
    RSHIFT = 35
    PLUS = 36
    MINUS = 37
    MUL = 38
    DIV = 39
    HEX = 40
    BIN = 41
    NEG = 42
    BANG = 43
    MICROSECOND = 44
    SECOND = 45
    MINUTE = 46
    HOUR = 47
    DAY = 48
    WEEK = 49
    MONTH = 50
    QUARTER = 51
    YEAR = 52
    EROTEME = 53
    DOUBLESTAR = 54
    MOD = 55
    COLON = 56
    OROR = 57
    ANDAND = 58
    LCURLY = 59
    RCURLY = 60
    CAST = 61
    DOTSTAR = 62
    ORDERBY_ASC = 63
    ORDERBY_DESC = 64
    AS = 65
    ARROW = 66
    QUOTE = 67
    BINARY = 68
    DATETIME = 69
    TIME = 70
    CHAR = 71
    DATE = 72
    DECIMAL = 73
    SIGNED = 74
    INTEGER = 75
    UNSIGNED = 76
    JSON = 77
    SECOND_MICROSECOND = 78
    MINUTE_MICROSECOND = 79
    MINUTE_SECOND = 80
    HOUR_MICROSECOND = 81
    HOUR_SECOND = 82
    HOUR_MINUTE = 83
    DAY_MICROSECOND = 84
    DAY_SECOND = 85
    DAY_MINUTE = 86
    DAY_HOUR = 87
    YEAR_MONTH = 88
    OVERLAPS = 89


_INTERVAL_UNITS = set(
    [
        TokenType.MICROSECOND,
        TokenType.SECOND,
        TokenType.MINUTE,
        TokenType.HOUR,
        TokenType.DAY,
        TokenType.WEEK,
        TokenType.MONTH,
        TokenType.QUARTER,
        TokenType.YEAR,
        TokenType.SECOND_MICROSECOND,
        TokenType.MINUTE_MICROSECOND,
        TokenType.MINUTE_SECOND,
        TokenType.HOUR_MICROSECOND,
        TokenType.HOUR_SECOND,
        TokenType.HOUR_MINUTE,
        TokenType.DAY_MICROSECOND,
        TokenType.DAY_SECOND,
        TokenType.DAY_MINUTE,
        TokenType.DAY_HOUR,
        TokenType.YEAR_MONTH,
    ]
)

# map of reserved word to token type
_RESERVED_WORDS = {
    "and": TokenType.AND,
    "or": TokenType.OR,
    "xor": TokenType.XOR,
    "is": TokenType.IS,
    "not": TokenType.NOT,
    "like": TokenType.LIKE,
    "in": TokenType.IN,
    "overlaps": TokenType.OVERLAPS,
    "regexp": TokenType.REGEXP,
    "between": TokenType.BETWEEN,
    "interval": TokenType.INTERVAL,
    "escape": TokenType.ESCAPE,
    "cast": TokenType.CAST,
    "div": TokenType.DIV,
    "hex": TokenType.HEX,
    "bin": TokenType.BIN,
    "true": TokenType.TRUE,
    "false": TokenType.FALSE,
    "null": TokenType.NULL,
    "second": TokenType.SECOND,
    "minute": TokenType.MINUTE,
    "hour": TokenType.HOUR,
    "day": TokenType.DAY,
    "week": TokenType.WEEK,
    "month": TokenType.MONTH,
    "quarter": TokenType.QUARTER,
    "year": TokenType.YEAR,
    "microsecond": TokenType.MICROSECOND,
    "asc": TokenType.ORDERBY_ASC,
    "desc": TokenType.ORDERBY_DESC,
    "as": TokenType.AS,
    "binary": TokenType.BINARY,
    "datetime": TokenType.DATETIME,
    "time": TokenType.TIME,
    "char": TokenType.CHAR,
    "date": TokenType.DATE,
    "decimal": TokenType.DECIMAL,
    "signed": TokenType.SIGNED,
    "unsigned": TokenType.UNSIGNED,
    "integer": TokenType.INTEGER,
    "json": TokenType.JSON,
    "second_microsecond": TokenType.SECOND_MICROSECOND,
    "minute_microsecond": TokenType.MINUTE_MICROSECOND,
    "minute_second": TokenType.MINUTE_SECOND,
    "hour_microsecond": TokenType.HOUR_MICROSECOND,
    "hour_second": TokenType.HOUR_SECOND,
    "hour_minute": TokenType.HOUR_MINUTE,
    "day_microsecond": TokenType.DAY_MICROSECOND,
    "day_second": TokenType.DAY_SECOND,
    "day_minute": TokenType.DAY_MINUTE,
    "day_hour": TokenType.DAY_HOUR,
    "year_month": TokenType.YEAR_MONTH,
}

_SQL_FUNTION_RESERVED_WORDS_COLLISION = {
    "binary": TokenType.BINARY,
    "cast": TokenType.CAST,
    "char": TokenType.CHAR,
    "date": TokenType.DATE,
    "decimal": TokenType.DECIMAL,
    "signed": TokenType.SIGNED,
    "time": TokenType.TIME,
    "unsigned": TokenType.UNSIGNED,
}

_OPERATORS = {
    "=": "==",
    "==": "==",
    "and": "&&",
    "div": "div",
    "||": "||",
    "or": "||",
    "not": "not",
    "xor": "xor",
    "^": "^",
    "is": "is",
    "between": "between",
    "in": "in",
    "like": "like",
    "!=": "!=",
    "<>": "!=",
    ">": ">",
    ">=": ">=",
    "<": "<",
    "<=": "<=",
    "&": "&",
    "&&": "&&",
    "|": "|",
    "<<": "<<",
    ">>": ">>",
    "+": "+",
    "-": "-",
    "*": "*",
    "/": "/",
    "~": "~",
    "%": "%",
    "cast": "cast",
    "cont_in": "cont_in",
    "overlaps": "overlaps",
}

_UNARY_OPERATORS = {
    "+": "sign_plus",
    "-": "sign_minus",
    "~": "~",
    "not": "not",
    "!": "!",
}

_NEGATION = {
    "is": "is_not",
    "between": "not_between",
    "regexp": "not_regexp",
    "like": "not_like",
    "in": "not_in",
    "cont_in": "not_cont_in",
    "overlaps": "not_overlaps",
}


class Token:
    """Token representation class."""

    def __init__(self, token_type: int, value: str, length: int = 1) -> None:
        self.token_type: int = token_type
        self.value: str = value
        self.length: int = length

    def __repr__(self) -> str:
        return self.__str__()

    def __str__(self) -> str:
        if self.token_type in (
            TokenType.IDENT,
            TokenType.LNUM,
            TokenType.LSTRING,
        ):
            return f"{self.token_type}({self.value})"
        return f"{self.token_type}"


# static protobuf helper functions


def build_expr(value: BuildExprTypes) -> MessageType:
    msg = Message("Mysqlx.Expr.Expr")
    if isinstance(value, (Message)):
        return value
    if isinstance(value, (ExprParser)):
        return value.expr(reparse=True)
    if isinstance(value, (dict, DbDoc)):
        msg["type"] = mysqlxpb_enum("Mysqlx.Expr.Expr.Type.OBJECT")
        msg["object"] = build_object(value).get_message()
    elif isinstance(value, (list, tuple)):
        msg["type"] = mysqlxpb_enum("Mysqlx.Expr.Expr.Type.ARRAY")
        msg["array"] = build_array(value).get_message()
    else:
        msg["type"] = mysqlxpb_enum("Mysqlx.Expr.Expr.Type.LITERAL")
        msg["literal"] = build_scalar(value).get_message()
    return msg


def build_scalar(value: BuildScalarTypes) -> MessageType:
    if isinstance(value, str):
        return build_string_scalar(value)
    if isinstance(value, BYTE_TYPES):
        return build_bytes_scalar(value)
    if isinstance(value, bool):
        return build_bool_scalar(value)
    if isinstance(value, int):
        return build_int_scalar(value)
    if isinstance(value, float):
        return build_double_scalar(value)
    if value is None:
        return build_null_scalar()
    raise ValueError(f"Unsupported data type: {type(value)}")


def build_object(obj: Union[Dict, DbDoc]) -> MessageType:
    if isinstance(obj, DbDoc):
        return build_object(obj.__dict__)

    msg = Message("Mysqlx.Expr.Object")
    for key, value in obj.items():
        pair = Message("Mysqlx.Expr.Object.ObjectField")
        pair["key"] = key.encode() if isinstance(key, str) else key
        pair["value"] = build_expr(value).get_message()
        msg["fld"].extend([pair.get_message()])
    return msg


def build_array(array: Sequence[BuildExprTypes]) -> MessageType:
    msg = Message("Mysqlx.Expr.Array")
    msg["value"].extend([build_expr(value).get_message() for value in array])
    return msg


def build_null_scalar() -> MessageType:
    msg = Message("Mysqlx.Datatypes.Scalar")
    msg["type"] = mysqlxpb_enum("Mysqlx.Datatypes.Scalar.Type.V_NULL")
    return msg


def build_double_scalar(value: float) -> MessageType:
    msg = Message("Mysqlx.Datatypes.Scalar")
    msg["type"] = mysqlxpb_enum("Mysqlx.Datatypes.Scalar.Type.V_DOUBLE")
    msg["v_double"] = value
    return msg


def build_int_scalar(value: int) -> MessageType:
    msg = Message("Mysqlx.Datatypes.Scalar")
    msg["type"] = mysqlxpb_enum("Mysqlx.Datatypes.Scalar.Type.V_SINT")
    msg["v_signed_int"] = value
    return msg


def build_unsigned_int_scalar(value: int) -> MessageType:
    msg = Message("Mysqlx.Datatypes.Scalar")
    msg["type"] = mysqlxpb_enum("Mysqlx.Datatypes.Scalar.Type.V_UINT")
    msg["v_unsigned_int"] = value
    return msg


def build_string_scalar(value: StrOrBytes) -> MessageType:
    if isinstance(value, str):
        value = bytes(bytearray(value, "utf-8"))
    msg = Message("Mysqlx.Datatypes.Scalar")
    msg["type"] = mysqlxpb_enum("Mysqlx.Datatypes.Scalar.Type.V_STRING")
    msg["v_string"] = Message("Mysqlx.Datatypes.Scalar.String", value=value)
    return msg


def build_bool_scalar(value: bool) -> MessageType:
    msg = Message("Mysqlx.Datatypes.Scalar")
    msg["type"] = mysqlxpb_enum("Mysqlx.Datatypes.Scalar.Type.V_BOOL")
    msg["v_bool"] = value
    return msg


def build_bytes_scalar(value: bytes) -> MessageType:
    msg = Message("Mysqlx.Datatypes.Scalar")
    msg["type"] = mysqlxpb_enum("Mysqlx.Datatypes.Scalar.Type.V_OCTETS")
    msg["v_octets"] = Message("Mysqlx.Datatypes.Scalar.Octets", value=value)
    return msg


def build_literal_expr(value: MessageType) -> MessageType:
    msg = Message("Mysqlx.Expr.Expr")
    msg["type"] = mysqlxpb_enum("Mysqlx.Expr.Expr.Type.LITERAL")
    msg["literal"] = value
    return msg


def build_unary_op(name: str, param: MessageType) -> MessageType:
    operator = Message("Mysqlx.Expr.Operator")
    operator["name"] = _UNARY_OPERATORS[name]
    operator["param"] = [param.get_message()]
    msg = Message("Mysqlx.Expr.Expr")
    msg["type"] = mysqlxpb_enum("Mysqlx.Expr.Expr.Type.OPERATOR")
    msg["operator"] = operator.get_message()
    return msg


def escape_literal(string: str) -> str:
    return string.replace('"', '""')


class ExprParser:
    """Expression parser class."""

    def __init__(self, string: str, allow_relational: bool = True) -> None:
        self.string: str = string
        self.tokens: List[Token] = []
        self.path_name_queue: List[str] = []
        self.pos: int = 0
        self._allow_relational_columns: bool = allow_relational
        self.placeholder_name_to_position: Dict[str, int] = {}
        self.positional_placeholder_count: int = 0
        self.clean_expression()
        self.lex()

    def __str__(self) -> str:
        return f"<mysqlx.ExprParser '{self.string}'>"

    def clean_expression(self) -> None:
        """Removes the keywords that does not form part of the expression.

        Removes the keywords "SELECT" and "WHERE" that does not form part of
        the expression itself.
        """
        if not isinstance(self.string, str):
            self.string = repr(self.string)
        self.string = self.string.strip(" ")
        if len(self.string) > 1 and self.string[-1] == ";":
            self.string = self.string[:-1]
        if "SELECT" in self.string[:6].upper():
            self.string = self.string[6:]
        if "WHERE" in self.string[:5].upper():
            self.string = self.string[5:]

    # convencience checker for lexer
    def next_char_is(self, key: int, char: str) -> bool:
        return key + 1 < len(self.string) and self.string[key + 1] == char

    def lex_number(self, pos: int) -> Token:
        # numeric literal
        start = pos
        found_dot = False
        while pos < len(self.string) and (
            self.string[pos].isdigit() or self.string[pos] == "."
        ):
            if self.string[pos] == ".":
                if found_dot is True:
                    raise ValueError("Invalid number. Found multiple '.'")
                found_dot = True
            # technically we allow more than one "." and let float()'s parsing
            # complain later
            pos += 1
        val = self.string[start:pos]
        return Token(TokenType.LNUM, val, len(val))

    def lex_alpha(self, i: int, allow_space: bool = False) -> Token:
        start = i
        while i < len(self.string) and (
            self.string[i].isalnum()
            or self.string[i] == "_"
            or (self.string[i].isspace() and allow_space)
        ):
            i += 1

        val = self.string[start:i]
        try:
            if (
                i < len(self.string)
                and self.string[i] == "("
                and val.lower() not in _SQL_FUNTION_RESERVED_WORDS_COLLISION
            ):
                token = Token(TokenType.IDENT, val, len(val))
            else:
                token = Token(_RESERVED_WORDS[val.lower()], val.lower(), len(val))
        except KeyError:
            token = Token(TokenType.IDENT, val, len(val))
        return token

    def lex_quoted_token(self, key: int) -> Token:
        quote_char = self.string[key]
        val = ""
        key += 1
        start = key
        while key < len(self.string):
            char = self.string[key]
            if (
                char == quote_char
                and key + 1 < len(self.string)
                and self.string[key + 1] != quote_char
            ):
                # break if we have a quote char that's not double
                break
            if char in (quote_char, "\\"):
                # this quote char has to be doubled
                if key + 1 >= len(self.string):
                    break
                key += 1
                val += self.string[key]
            else:
                val += char
            key += 1
        if key >= len(self.string) or self.string[key] != quote_char:
            raise ValueError(f"Unterminated quoted string starting at {start}")
        if quote_char == "`":
            return Token(TokenType.IDENT, val, len(val) + 2)
        return Token(TokenType.LSTRING, val, len(val) + 2)

    def lex(self) -> None:
        i = 0
        arrow_last = False
        inside_arrow = False
        while i < len(self.string):
            char = self.string[i]
            if char.isspace():
                i += 1
                continue
            if char.isdigit():
                token = self.lex_number(i)
            elif char.isalpha() or char == "_":
                token = self.lex_alpha(i, inside_arrow)
            elif char == "?":
                token = Token(TokenType.EROTEME, char)
            elif char == ":":
                token = Token(TokenType.COLON, char)
            elif char == "{":
                token = Token(TokenType.LCURLY, char)
            elif char == "}":
                token = Token(TokenType.RCURLY, char)
            elif char == "+":
                token = Token(TokenType.PLUS, char)
            elif char == "-":
                if self.next_char_is(i, ">") and not arrow_last:
                    token = Token(TokenType.ARROW, "->", 2)
                    arrow_last = True
                else:
                    token = Token(TokenType.MINUS, char)
            elif char == "*":
                if self.next_char_is(i, "*"):
                    token = Token(TokenType.DOUBLESTAR, "**", 2)
                else:
                    token = Token(TokenType.MUL, char)
            elif char == "/":
                token = Token(TokenType.DIV, char)
            elif char == "$":
                token = Token(TokenType.DOLLAR, char)
            elif char == "%":
                token = Token(TokenType.MOD, char)
            elif char == "=":
                if self.next_char_is(i, "="):
                    token = Token(TokenType.EQ, "==", 2)
                else:
                    token = Token(TokenType.EQ, "==", 1)
            elif char == "&":
                if self.next_char_is(i, "&"):
                    token = Token(TokenType.ANDAND, "&&", 2)
                else:
                    token = Token(TokenType.BITAND, char)
            elif char == "^":
                token = Token(TokenType.BITXOR, char)
            elif char == "|":
                if self.next_char_is(i, "|"):
                    token = Token(TokenType.OROR, "||", 2)
                else:
                    token = Token(TokenType.BITOR, char)
            elif char == "(":
                token = Token(TokenType.LPAREN, char)
            elif char == ")":
                token = Token(TokenType.RPAREN, char)
            elif char == "[":
                token = Token(TokenType.LSQBRACKET, char)
            elif char == "]":
                token = Token(TokenType.RSQBRACKET, char)
            elif char == "~":
                token = Token(TokenType.NEG, char)
            elif char == ",":
                token = Token(TokenType.COMMA, char)
            elif char == "!":
                if self.next_char_is(i, "="):
                    token = Token(TokenType.NE, "!=", 2)
                else:
                    token = Token(TokenType.BANG, char)
            elif char == "<":
                if self.next_char_is(i, ">"):
                    token = Token(TokenType.NE, "<>", 2)
                elif self.next_char_is(i, "<"):
                    token = Token(TokenType.LSHIFT, "<<", 2)
                elif self.next_char_is(i, "="):
                    token = Token(TokenType.LE, "<=", 2)
                else:
                    token = Token(TokenType.LT, char)
            elif char == ">":
                if self.next_char_is(i, ">"):
                    token = Token(TokenType.RSHIFT, ">>", 2)
                elif self.next_char_is(i, "="):
                    token = Token(TokenType.GE, ">=", 2)
                else:
                    token = Token(TokenType.GT, char)
            elif char == ".":
                if self.next_char_is(i, "*"):
                    token = Token(TokenType.DOTSTAR, ".*", 2)
                elif i + 1 < len(self.string) and self.string[i + 1].isdigit():
                    token = self.lex_number(i)
                else:
                    token = Token(TokenType.DOT, char)
            elif char in ("'", '"') and arrow_last:
                token = Token(TokenType.QUOTE, char)
                if not inside_arrow:
                    inside_arrow = True
                else:
                    arrow_last = False
                    inside_arrow = False
            elif char in ('"', "'", "`"):
                token = self.lex_quoted_token(i)
            else:
                raise ValueError(f"Unknown character at {i}")
            self.tokens.append(token)
            i += token.length

    def assert_cur_token(self, token_type: int) -> None:
        if self.pos >= len(self.tokens):
            raise ValueError(
                f"Expected token type {token_type} at pos {self.pos} but no "
                "tokens left"
            )
        if self.tokens[self.pos].token_type != token_type:
            raise ValueError(
                f"Expected token type {token_type} at pos {self.pos} but found "
                f"type {self.tokens[self.pos]}, on tokens {self.tokens}"
            )

    def cur_token_type_is(self, token_type: int) -> bool:
        return self.pos_token_type_is(self.pos, token_type)

    def cur_token_type_in(self, *types: int) -> bool:
        return self.pos < len(self.tokens) and self.tokens[self.pos].token_type in types

    def next_token_type_is(self, token_type: int) -> bool:
        return self.pos_token_type_is(self.pos + 1, token_type)

    def next_token_type_in(self, *types: int) -> bool:
        return (
            self.pos < len(self.tokens)
            and self.tokens[self.pos + 1].token_type in types
        )

    def pos_token_type_is(self, pos: int, token_type: int) -> bool:
        return pos < len(self.tokens) and self.tokens[pos].token_type == token_type

    def consume_token(self, token_type: int) -> str:
        self.assert_cur_token(token_type)
        value = self.tokens[self.pos].value
        self.pos += 1
        return value

    def paren_expr_list(
        self,
    ) -> List[Union[ProtobufMessageType, ProtobufMessageCextType]]:
        """Parse a paren-bounded expression list for function arguments or IN
        list and return a list of Expr objects.
        """
        exprs = []
        path_name_added = False
        self.consume_token(TokenType.LPAREN)
        if not self.cur_token_type_is(TokenType.RPAREN):
            msg_expr = self._expr().get_message()
            if hasattr(msg_expr, "identifier") and msg_expr.identifier.name:
                self.path_name_queue.insert(0, msg_expr.identifier.name)
                path_name_added = True
            elif (
                not hasattr(msg_expr, "identifier")
                and "identifier" in msg_expr
                and "name" in msg_expr["identifier"]
            ):
                self.path_name_queue.insert(0, msg_expr["identifier"]["name"])
                path_name_added = True
            exprs.append(msg_expr)
            while self.cur_token_type_is(TokenType.COMMA):
                self.pos += 1
                exprs.append(self._expr().get_message())
        self.consume_token(TokenType.RPAREN)
        if path_name_added:
            self.path_name_queue.pop()
        return exprs

    def identifier(self) -> MessageType:
        self.assert_cur_token(TokenType.IDENT)
        ident = Message("Mysqlx.Expr.Identifier")
        if self.next_token_type_is(TokenType.DOT):
            ident["schema_name"] = self.consume_token(TokenType.IDENT)
            self.consume_token(TokenType.DOT)
        ident["name"] = self.tokens[self.pos].value
        self.pos += 1
        return ident

    def function_call(self) -> MessageType:
        function_call = Message("Mysqlx.Expr.FunctionCall")
        function_call["name"] = self.identifier()
        function_call["param"] = self.paren_expr_list()
        msg_expr = Message("Mysqlx.Expr.Expr")
        msg_expr["type"] = mysqlxpb_enum("Mysqlx.Expr.Expr.Type.FUNC_CALL")
        msg_expr["function_call"] = function_call.get_message()
        return msg_expr

    def docpath_member(self) -> MessageType:
        self.consume_token(TokenType.DOT)
        token = self.tokens[self.pos]

        if token.token_type == TokenType.IDENT:
            if token.value.startswith("`") and token.value.endswith("`"):
                raise ValueError(
                    f"{token.value} is not a valid JSON/ECMAScript identifier"
                )
            self.consume_token(TokenType.IDENT)
            member_name = token.value
        elif token.token_type == TokenType.LSTRING:
            self.consume_token(TokenType.LSTRING)
            member_name = token.value
        else:
            raise ValueError(
                "Expected token type IDENT or LSTRING in JSON path at token "
                f"pos {self.pos}"
            )
        doc_path_item = Message("Mysqlx.Expr.DocumentPathItem")
        doc_path_item["type"] = mysqlxpb_enum(
            "Mysqlx.Expr.DocumentPathItem.Type.MEMBER"
        )
        doc_path_item["value"] = member_name
        return doc_path_item

    def docpath_array_loc(self) -> MessageType:
        self.consume_token(TokenType.LSQBRACKET)
        if self.cur_token_type_is(TokenType.MUL):
            self.consume_token(TokenType.MUL)
            self.consume_token(TokenType.RSQBRACKET)
            doc_path_item = Message("Mysqlx.Expr.DocumentPathItem")
            doc_path_item["type"] = mysqlxpb_enum(
                "Mysqlx.Expr.DocumentPathItem.Type.ARRAY_INDEX_ASTERISK"
            )
            return doc_path_item
        if self.cur_token_type_is(TokenType.LNUM):
            value = int(self.consume_token(TokenType.LNUM))
            if value < 0:
                raise IndexError(f"Array index cannot be negative at {self.pos}")
            self.consume_token(TokenType.RSQBRACKET)
            doc_path_item = Message("Mysqlx.Expr.DocumentPathItem")
            doc_path_item["type"] = mysqlxpb_enum(
                "Mysqlx.Expr.DocumentPathItem.Type.ARRAY_INDEX"
            )
            doc_path_item["index"] = value
            return doc_path_item
        raise ValueError(
            "Exception token type MUL or LNUM in JSON path array index at "
            f"token pos {self.pos}"
        )

    def document_field(self) -> MessageType:
        if not self.tokens:
            raise ValueError("Empty string cannot be used as document field")
        if self.cur_token_type_is(TokenType.DOLLAR):
            self.consume_token(TokenType.DOLLAR)
        col_id = Message("Mysqlx.Expr.ColumnIdentifier")
        if self.cur_token_type_is(TokenType.IDENT):
            doc_path_item = Message("Mysqlx.Expr.DocumentPathItem")
            doc_path_item["type"] = mysqlxpb_enum(
                "Mysqlx.Expr.DocumentPathItem.Type.MEMBER"
            )
            doc_path_item["value"] = self.consume_token(TokenType.IDENT)
            col_id["document_path"].extend([doc_path_item.get_message()])
        col_id["document_path"].extend(self.document_path())
        if self.path_name_queue:
            col_id["name"] = self.path_name_queue[0]
        expr = Message("Mysqlx.Expr.Expr")
        expr["type"] = mysqlxpb_enum("Mysqlx.Expr.Expr.Type.IDENT")
        expr["identifier"] = col_id
        return expr

    def document_path(
        self,
    ) -> List[Union[ProtobufMessageType, ProtobufMessageCextType]]:
        """Parse a JSON-style document path, like WL#7909, but prefix by @.
        instead of $. We parse this as a string because the protocol doesn't
        support it. (yet)
        """
        doc_path = []
        while True:
            if self.cur_token_type_is(TokenType.DOT):
                doc_path.append(self.docpath_member().get_message())
            elif self.cur_token_type_is(TokenType.DOTSTAR):
                self.consume_token(TokenType.DOTSTAR)
                doc_path_item = Message("Mysqlx.Expr.DocumentPathItem")
                doc_path_item["type"] = mysqlxpb_enum(
                    "Mysqlx.Expr.DocumentPathItem.Type.MEMBER_ASTERISK"
                )
                doc_path.append(doc_path_item.get_message())
            elif self.cur_token_type_is(TokenType.LSQBRACKET):
                doc_path.append(self.docpath_array_loc().get_message())
            elif self.cur_token_type_is(TokenType.DOUBLESTAR):
                self.consume_token(TokenType.DOUBLESTAR)
                doc_path_item = Message("Mysqlx.Expr.DocumentPathItem")
                doc_path_item["type"] = mysqlxpb_enum(
                    "Mysqlx.Expr.DocumentPathItem.Type.DOUBLE_ASTERISK"
                )
                doc_path.append(doc_path_item.get_message())
            else:
                break
        items = len(doc_path)
        if items > 0 and get_item_or_attr(doc_path[items - 1], "type") == mysqlxpb_enum(
            "Mysqlx.Expr.DocumentPathItem.Type.DOUBLE_ASTERISK"
        ):
            raise ValueError(f"JSON path may not end in '**' at {self.pos}")
        return doc_path

    def column_identifier(self) -> MessageType:
        parts = []
        parts.append(self.consume_token(TokenType.IDENT))
        while self.cur_token_type_is(TokenType.DOT):
            self.consume_token(TokenType.DOT)
            parts.append(self.consume_token(TokenType.IDENT))
        if len(parts) > 3:
            raise ValueError(f"Too many parts to identifier at {self.pos}")
        parts.reverse()
        col_id = Message("Mysqlx.Expr.ColumnIdentifier")
        # clever way to apply them to the struct
        for i in range(0, len(parts)):
            if i == 0:
                col_id["name"] = parts[0]
            elif i == 1:
                col_id["table_name"] = parts[1]
            elif i == 2:
                col_id["schema_name"] = parts[2]

        is_doc = False
        if self.cur_token_type_is(TokenType.DOLLAR):
            is_doc = True
            self.consume_token(TokenType.DOLLAR)
            col_id["document_path"] = self.document_path()
        elif self.cur_token_type_is(TokenType.ARROW):
            is_doc = True
            self.consume_token(TokenType.ARROW)
            is_quoted = False
            if self.cur_token_type_is(TokenType.QUOTE):
                is_quoted = True
                self.consume_token(TokenType.QUOTE)
            self.consume_token(TokenType.DOLLAR)
            col_id["document_path"] = self.document_path()
            if is_quoted:
                self.consume_token(TokenType.QUOTE)

        if is_doc and len(col_id["document_path"]) == 0:
            doc_path_item = Message("Mysqlx.Expr.DocumentPathItem")
            doc_path_item["type"] = mysqlxpb_enum(
                "Mysqlx.Expr.DocumentPathItem.Type.MEMBER"
            )
            doc_path_item["value"] = ""
            col_id["document_path"].extend([doc_path_item.get_message()])

        msg_expr = Message("Mysqlx.Expr.Expr")
        msg_expr["type"] = mysqlxpb_enum("Mysqlx.Expr.Expr.Type.IDENT")
        msg_expr["identifier"] = col_id
        return msg_expr

    def next_token(self) -> Token:
        if self.pos >= len(self.tokens):
            raise ValueError("Unexpected end of token stream")
        token = self.tokens[self.pos]
        self.pos += 1
        return token

    def expect_token(self, token_type: int) -> None:
        token = self.next_token()
        if token.token_type != token_type:
            raise ValueError(f"Expected token type {token_type}")

    def peek_token(self) -> Token:
        return self.tokens[self.pos]

    def consume_any_token(self) -> str:
        value = self.tokens[self.pos].value
        self.pos += 1
        return value

    def parse_json_array(self) -> MessageType:
        """
        jsonArray            ::=  "[" [ expr ("," expr)* ] "]"
        """
        msg = Message("Mysqlx.Expr.Array")
        while self.pos < len(self.tokens) and not self.cur_token_type_is(
            TokenType.RSQBRACKET
        ):
            msg["value"].extend([self._expr().get_message()])
            if not self.cur_token_type_is(TokenType.COMMA):
                break
            self.consume_token(TokenType.COMMA)
        self.consume_token(TokenType.RSQBRACKET)

        expr = Message("Mysqlx.Expr.Expr")
        expr["type"] = mysqlxpb_enum("Mysqlx.Expr.Expr.Type.ARRAY")
        expr["array"] = msg.get_message()
        return expr

    def parse_json_doc(self) -> MessageType:
        """
        jsonDoc              ::=  "{" [jsonKeyValue ("," jsonKeyValue)*] "}"
        jsonKeyValue         ::=  STRING_DQ ":" expr
        """
        msg = Message("Mysqlx.Expr.Object")
        while self.pos < len(self.tokens) and not self.cur_token_type_is(
            TokenType.RCURLY
        ):
            item = Message("Mysqlx.Expr.Object.ObjectField")
            item["key"] = self.consume_token(TokenType.LSTRING)
            self.consume_token(TokenType.COLON)
            item["value"] = self._expr().get_message()
            msg["fld"].extend([item.get_message()])
            if not self.cur_token_type_is(TokenType.COMMA):
                break
            self.consume_token(TokenType.COMMA)
        self.consume_token(TokenType.RCURLY)

        expr = Message("Mysqlx.Expr.Expr")
        expr["type"] = mysqlxpb_enum("Mysqlx.Expr.Expr.Type.OBJECT")
        expr["object"] = msg.get_message()
        return expr

    def parse_place_holder(self, token: Token) -> MessageType:
        place_holder_name = ""
        if self.cur_token_type_is(TokenType.LNUM):
            place_holder_name = self.consume_token(TokenType.LNUM)
        elif self.cur_token_type_is(TokenType.IDENT):
            place_holder_name = self.consume_token(TokenType.IDENT)
        elif token.token_type == TokenType.EROTEME:
            place_holder_name = str(self.positional_placeholder_count)
        else:
            raise ValueError(f"Invalid placeholder name at token pos {self.pos}")

        msg_expr = Message("Mysqlx.Expr.Expr")
        msg_expr["type"] = mysqlxpb_enum("Mysqlx.Expr.Expr.Type.PLACEHOLDER")
        if place_holder_name in self.placeholder_name_to_position:
            msg_expr["position"] = self.placeholder_name_to_position[place_holder_name]
        else:
            msg_expr["position"] = self.positional_placeholder_count
            self.placeholder_name_to_position[
                place_holder_name
            ] = self.positional_placeholder_count
            self.positional_placeholder_count += 1
        return msg_expr

    def cast(self) -> MessageType:
        """cast ::= CAST LPAREN expr AS cast_data_type RPAREN"""
        operator = Message("Mysqlx.Expr.Operator", name="cast")
        self.consume_token(TokenType.LPAREN)
        operator["param"].extend([self._expr().get_message()])
        self.consume_token(TokenType.AS)

        type_scalar = build_bytes_scalar(str.encode(self.cast_data_type()))
        operator["param"].extend([build_literal_expr(type_scalar).get_message()])
        self.consume_token(TokenType.RPAREN)
        msg = Message("Mysqlx.Expr.Expr", operator=operator.get_message())
        msg["type"] = mysqlxpb_enum("Mysqlx.Expr.Expr.Type.OPERATOR")
        return msg

    def cast_data_type(self) -> str:
        """cast_data_type ::= ( BINARY dimension? ) |
        ( CHAR dimension? ) |
        ( DATE ) |
        ( DATETIME dimension? ) |
        ( TIME dimension? ) |
        ( DECIMAL dimension? ) |
        ( SIGNED INTEGER? ) |
        ( UNSIGNED INTEGER? ) |
        JSON
        """
        token = self.next_token()
        if token.token_type in (
            TokenType.BINARY,
            TokenType.CHAR,
            TokenType.DATETIME,
            TokenType.TIME,
        ):
            dimension = self.cast_data_type_dimension()
            return f"{token.value}{dimension}" if dimension else token.value
        if token.token_type is TokenType.DECIMAL:
            dimension = self.cast_data_type_dimension(True)
            return f"{token.value}{dimension}" if dimension else token.value
        if token.token_type in (TokenType.SIGNED, TokenType.UNSIGNED):
            if self.cur_token_type_is(TokenType.INTEGER):
                self.consume_token(TokenType.INTEGER)
            return token.value
        if token.token_type in (
            TokenType.INTEGER,
            TokenType.JSON,
            TokenType.DATE,
        ):
            return token.value

        raise ValueError(
            f"Unknown token type {token.token_type} at position {self.pos} "
            f"({token.value})"
        )

    def cast_data_type_dimension(self, decimal: bool = False) -> Optional[str]:
        """dimension ::= LPAREN LNUM (, LNUM)? RPAREN"""
        if not self.cur_token_type_is(TokenType.LPAREN):
            return None

        dimension = []
        self.consume_token(TokenType.LPAREN)
        dimension.append(self.consume_token(TokenType.LNUM))
        if decimal and self.cur_token_type_is(TokenType.COMMA):
            self.consume_token(TokenType.COMMA)
            dimension.append(self.consume_token(TokenType.LNUM))
        self.consume_token(TokenType.RPAREN)

        return (
            f"({dimension[0]})"
            if len(dimension) == 1
            else f"({dimension[0]},{dimension[1]})"
        )

    @staticmethod
    def star_operator() -> MessageType:
        msg = Message("Mysqlx.Expr.Expr")
        msg["type"] = mysqlxpb_enum("Mysqlx.Expr.Expr.Type.OPERATOR")
        msg["operator"] = Message("Mysqlx.Expr.Operator", name="*")
        return msg

    def atomic_expr(self) -> MessageType:
        """Parse an atomic expression and return a protobuf Expr object"""
        token = self.next_token()

        if token.token_type in [TokenType.EROTEME, TokenType.COLON]:
            return self.parse_place_holder(token)
        if token.token_type == TokenType.LCURLY:
            return self.parse_json_doc()
        if token.token_type == TokenType.LSQBRACKET:
            return self.parse_json_array()
        if token.token_type == TokenType.CAST:
            return self.cast()
        if token.token_type == TokenType.LPAREN:
            expr = self._expr()
            self.expect_token(TokenType.RPAREN)
            return expr
        if token.token_type in [TokenType.PLUS, TokenType.MINUS]:
            peek = self.peek_token()
            if peek.token_type == TokenType.LNUM:
                self.tokens[self.pos].value = token.value + peek.value
                return self.atomic_expr()
            return build_unary_op(token.value, self.atomic_expr())
        if token.token_type in (TokenType.NOT, TokenType.NEG, TokenType.BANG):
            return build_unary_op(token.value, self.atomic_expr())
        if token.token_type == TokenType.LSTRING:
            return build_literal_expr(build_string_scalar(token.value))
        if token.token_type == TokenType.NULL:
            return build_literal_expr(build_null_scalar())
        if token.token_type == TokenType.LNUM:
            if "." in token.value:
                return build_literal_expr(build_double_scalar(float(token.value)))
            return build_literal_expr(build_int_scalar(int(token.value)))
        if token.token_type in [TokenType.TRUE, TokenType.FALSE]:
            return build_literal_expr(
                build_bool_scalar(token.token_type == TokenType.TRUE)
            )
        if token.token_type == TokenType.DOLLAR:
            return self.document_field()
        if token.token_type == TokenType.MUL:
            return self.star_operator()
        if token.token_type == TokenType.IDENT:
            self.pos = self.pos - 1  # stay on the identifier
            if self.next_token_type_is(TokenType.LPAREN) or (
                self.next_token_type_is(TokenType.DOT)
                and self.pos_token_type_is(self.pos + 2, TokenType.IDENT)
                and self.pos_token_type_is(self.pos + 3, TokenType.LPAREN)
            ):
                # Function call
                return self.function_call()
            return (
                self.document_field()
                if not self._allow_relational_columns
                else self.column_identifier()
            )

        raise ValueError(
            f"Unknown token type = {token.token_type}  when expecting atomic "
            f"expression at {self.pos}"
        )

    def parse_left_assoc_binary_op_expr(
        self, types: Iterable[int], inner_parser: Callable[[], MessageType]
    ) -> MessageType:
        """Given a `set' of types and an Expr-returning inner parser function,
        parse a left associate binary operator expression"""
        lhs = inner_parser()
        while self.pos < len(self.tokens) and self.tokens[self.pos].token_type in types:
            msg = Message("Mysqlx.Expr.Expr")
            msg["type"] = mysqlxpb_enum("Mysqlx.Expr.Expr.Type.OPERATOR")
            operator = Message("Mysqlx.Expr.Operator")
            operator["name"] = _OPERATORS[self.tokens[self.pos].value]
            operator["param"] = [lhs.get_message()]
            self.pos += 1
            operator["param"].extend([inner_parser().get_message()])
            msg["operator"] = operator
            lhs = msg
        return lhs

    # operator precedence is implemented here
    def add_sub_interval(self) -> MessageType:
        lhs = self.atomic_expr()
        if self.cur_token_type_in(
            TokenType.PLUS, TokenType.MINUS
        ) and self.next_token_type_is(TokenType.INTERVAL):
            token = self.next_token()

            operator = Message("Mysqlx.Expr.Operator")
            operator["param"].extend([lhs.get_message()])
            operator["name"] = (
                "date_add" if token.token_type is TokenType.PLUS else "date_sub"
            )

            self.consume_token(TokenType.INTERVAL)
            operator["param"].extend([self.bit_expr().get_message()])

            if not self.cur_token_type_in(*_INTERVAL_UNITS):
                raise ValueError(f"Expected interval type at position {self.pos}")

            token = str.encode(self.consume_any_token().upper())
            operator["param"].extend(
                [build_literal_expr(build_bytes_scalar(token)).get_message()]
            )

            lhs = Message("Mysqlx.Expr.Expr", operator=operator)
            lhs["type"] = mysqlxpb_enum("Mysqlx.Expr.Expr.Type.OPERATOR")

        return lhs

    def mul_div_expr(self) -> MessageType:
        return self.parse_left_assoc_binary_op_expr(
            set([TokenType.MUL, TokenType.DIV, TokenType.MOD]),
            self.add_sub_interval,
        )

    def add_sub_expr(self) -> MessageType:
        return self.parse_left_assoc_binary_op_expr(
            set([TokenType.PLUS, TokenType.MINUS]), self.mul_div_expr
        )

    def shift_expr(self) -> MessageType:
        return self.parse_left_assoc_binary_op_expr(
            set([TokenType.LSHIFT, TokenType.RSHIFT]), self.add_sub_expr
        )

    def bit_expr(self) -> MessageType:
        return self.parse_left_assoc_binary_op_expr(
            set([TokenType.BITAND, TokenType.BITOR, TokenType.BITXOR]),
            self.shift_expr,
        )

    def comp_expr(self) -> MessageType:
        return self.parse_left_assoc_binary_op_expr(
            set(
                [
                    TokenType.GE,
                    TokenType.GT,
                    TokenType.LE,
                    TokenType.LT,
                    TokenType.EQ,
                    TokenType.NE,
                ]
            ),
            self.bit_expr,
        )

    def ilri_expr(self) -> MessageType:
        params = []
        lhs = self.comp_expr()
        is_not = False
        if self.cur_token_type_is(TokenType.NOT):
            is_not = True
            self.consume_token(TokenType.NOT)
        if self.pos < len(self.tokens):
            params.append(lhs.get_message())
            op_name = self.tokens[self.pos].value
            if self.cur_token_type_is(TokenType.IS):
                self.consume_token(TokenType.IS)
                # for IS, NOT comes AFTER
                if self.cur_token_type_is(TokenType.NOT):
                    is_not = True
                    self.consume_token(TokenType.NOT)
                params.append(self.comp_expr().get_message())
            elif self.cur_token_type_is(TokenType.IN):
                self.consume_token(TokenType.IN)
                if self.cur_token_type_is(TokenType.LPAREN):
                    params.extend(self.paren_expr_list())
                else:
                    op_name = "cont_in"
                    params.append(self.comp_expr().get_message())
            elif self.cur_token_type_is(TokenType.OVERLAPS):
                self.consume_token(TokenType.OVERLAPS)
                params.append(self.comp_expr().get_message())

            elif self.cur_token_type_is(TokenType.LIKE):
                self.consume_token(TokenType.LIKE)
                params.append(self.comp_expr().get_message())
                if self.cur_token_type_is(TokenType.ESCAPE):
                    self.consume_token(TokenType.ESCAPE)
                    params.append(self.comp_expr().get_message())
            elif self.cur_token_type_is(TokenType.BETWEEN):
                self.consume_token(TokenType.BETWEEN)
                params.append(self.comp_expr().get_message())
                self.consume_token(TokenType.AND)
                params.append(self.comp_expr().get_message())
            elif self.cur_token_type_is(TokenType.REGEXP):
                self.consume_token(TokenType.REGEXP)
                params.append(self.comp_expr().get_message())
            else:
                if is_not:
                    raise ValueError(f"Unknown token after NOT as pos {self.pos}")
                op_name = None  # not an operator we're interested in
            if op_name:
                operator = Message("Mysqlx.Expr.Operator")
                operator["name"] = _NEGATION[op_name] if is_not else op_name
                operator["param"] = params
                msg_expr = Message("Mysqlx.Expr.Expr")
                msg_expr["type"] = mysqlxpb_enum("Mysqlx.Expr.Expr.Type.OPERATOR")
                msg_expr["operator"] = operator.get_message()
                lhs = msg_expr
        return lhs

    def and_expr(self) -> MessageType:
        return self.parse_left_assoc_binary_op_expr(
            set([TokenType.AND, TokenType.ANDAND]), self.ilri_expr
        )

    def xor_expr(self) -> MessageType:
        return self.parse_left_assoc_binary_op_expr(set([TokenType.XOR]), self.and_expr)

    def or_expr(self) -> MessageType:
        return self.parse_left_assoc_binary_op_expr(
            set([TokenType.OR, TokenType.OROR]), self.xor_expr
        )

    def _expr(self, reparse: bool = False) -> MessageType:
        if reparse:
            self.tokens = []
            self.pos = 0
            self.placeholder_name_to_position = {}
            self.positional_placeholder_count = 0
            self.lex()
        return self.or_expr()

    def expr(self, reparse: bool = False) -> MessageType:
        expression = self._expr(reparse)
        used_tokens = self.pos
        if self.pos_token_type_is(len(self.tokens) - 2, TokenType.AS):
            used_tokens += 2
        if used_tokens < len(self.tokens):
            raise ValueError(
                f"Unused token types {self.tokens[self.pos :]} found in "
                f"expression at position: {self.pos}"
            )
        return expression

    def parse_table_insert_field(self) -> MessageType:
        return Message("Mysqlx.Crud.Column", name=self.consume_token(TokenType.IDENT))

    def parse_table_update_field(self) -> Any:
        return self.column_identifier().identifier

    def _table_fields(self) -> List[str]:
        fields = []
        temp = self.string.split(",")
        temp.reverse()
        while temp:
            field = temp.pop()
            while (
                field.count("(") != field.count(")")
                or field.count("[") != field.count("]")
                or field.count("{") != field.count("}")
            ):
                field = f"{temp.pop()},{field}"
            fields.append(field.strip())
        return fields

    def parse_table_select_projection(
        self,
    ) -> List[Union[ProtobufMessageType, ProtobufMessageCextType]]:
        project_expr: List = []
        first = True
        fields = self._table_fields()
        while self.pos < len(self.tokens):
            if not first:
                self.consume_token(TokenType.COMMA)
            first = False
            projection = Message("Mysqlx.Crud.Projection", source=self._expr())
            if self.cur_token_type_is(TokenType.AS):
                self.consume_token(TokenType.AS)
                projection["alias"] = self.consume_token(TokenType.IDENT)
            else:
                projection["alias"] = fields[len(project_expr)]
            project_expr.append(projection.get_message())

        return project_expr

    def parse_order_spec(
        self,
    ) -> List[Union[ProtobufMessageType, ProtobufMessageCextType]]:
        order_specs = []
        first = True
        while self.pos < len(self.tokens):
            if not first:
                self.consume_token(TokenType.COMMA)
            first = False
            order = Message("Mysqlx.Crud.Order", expr=self._expr())
            if self.cur_token_type_is(TokenType.ORDERBY_ASC):
                order["direction"] = mysqlxpb_enum("Mysqlx.Crud.Order.Direction.ASC")
                self.consume_token(TokenType.ORDERBY_ASC)
            elif self.cur_token_type_is(TokenType.ORDERBY_DESC):
                order["direction"] = mysqlxpb_enum("Mysqlx.Crud.Order.Direction.DESC")
                self.consume_token(TokenType.ORDERBY_DESC)
            order_specs.append(order.get_message())
        return order_specs

    def parse_expr_list(
        self,
    ) -> List[Union[ProtobufMessageType, ProtobufMessageCextType]]:
        expr_list = []
        first = True
        while self.pos < len(self.tokens):
            if not first:
                self.consume_token(TokenType.COMMA)
            first = False
            expr_list.append(self._expr().get_message())
        return expr_list

virt/lib/python3.9/site-packages/mysqlx/expr 3.py
PrinceDisant/LostInNL
# Copyright (c) 2016, 2022, Oracle and/or its affiliates. All rights reserved.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

"""Expression Parser."""

from typing import Any, Callable, Dict, Iterable, List, Optional, Sequence, Union

from .dbdoc import DbDoc
from .helpers import BYTE_TYPES, get_item_or_attr
from .protobuf import Message, mysqlxpb_enum
from .types import (
    BuildExprTypes,
    BuildScalarTypes,
    MessageType,
    ProtobufMessageCextType,
    ProtobufMessageType,
    StrOrBytes,
)


# pylint: disable=missing-function-docstring
class TokenType:
    """Token types class."""

    NOT = 1
    AND = 2
    OR = 3
    XOR = 4
    IS = 5
    LPAREN = 6
    RPAREN = 7
    LSQBRACKET = 8
    RSQBRACKET = 9
    BETWEEN = 10
    TRUE = 11
    NULL = 12
    FALSE = 13
    IN = 14
    LIKE = 15
    INTERVAL = 16
    REGEXP = 17
    ESCAPE = 18
    IDENT = 19
    LSTRING = 20
    LNUM = 21
    DOT = 22
    DOLLAR = 23
    COMMA = 24
    EQ = 25
    NE = 26
    GT = 27
    GE = 28
    LT = 29
    LE = 30
    BITAND = 31
    BITOR = 32
    BITXOR = 33
    LSHIFT = 34
    RSHIFT = 35
    PLUS = 36
    MINUS = 37
    MUL = 38
    DIV = 39
    HEX = 40
    BIN = 41
    NEG = 42
    BANG = 43
    MICROSECOND = 44
    SECOND = 45
    MINUTE = 46
    HOUR = 47
    DAY = 48
    WEEK = 49
    MONTH = 50
    QUARTER = 51
    YEAR = 52
    EROTEME = 53
    DOUBLESTAR = 54
    MOD = 55
    COLON = 56
    OROR = 57
    ANDAND = 58
    LCURLY = 59
    RCURLY = 60
    CAST = 61
    DOTSTAR = 62
    ORDERBY_ASC = 63
    ORDERBY_DESC = 64
    AS = 65
    ARROW = 66
    QUOTE = 67
    BINARY = 68
    DATETIME = 69
    TIME = 70
    CHAR = 71
    DATE = 72
    DECIMAL = 73
    SIGNED = 74
    INTEGER = 75
    UNSIGNED = 76
    JSON = 77
    SECOND_MICROSECOND = 78
    MINUTE_MICROSECOND = 79
    MINUTE_SECOND = 80
    HOUR_MICROSECOND = 81
    HOUR_SECOND = 82
    HOUR_MINUTE = 83
    DAY_MICROSECOND = 84
    DAY_SECOND = 85
    DAY_MINUTE = 86
    DAY_HOUR = 87
    YEAR_MONTH = 88
    OVERLAPS = 89


_INTERVAL_UNITS = set(
    [
        TokenType.MICROSECOND,
        TokenType.SECOND,
        TokenType.MINUTE,
        TokenType.HOUR,
        TokenType.DAY,
        TokenType.WEEK,
        TokenType.MONTH,
        TokenType.QUARTER,
        TokenType.YEAR,
        TokenType.SECOND_MICROSECOND,
        TokenType.MINUTE_MICROSECOND,
        TokenType.MINUTE_SECOND,
        TokenType.HOUR_MICROSECOND,
        TokenType.HOUR_SECOND,
        TokenType.HOUR_MINUTE,
        TokenType.DAY_MICROSECOND,
        TokenType.DAY_SECOND,
        TokenType.DAY_MINUTE,
        TokenType.DAY_HOUR,
        TokenType.YEAR_MONTH,
    ]
)

# map of reserved word to token type
_RESERVED_WORDS = {
    "and": TokenType.AND,
    "or": TokenType.OR,
    "xor": TokenType.XOR,
    "is": TokenType.IS,
    "not": TokenType.NOT,
    "like": TokenType.LIKE,
    "in": TokenType.IN,
    "overlaps": TokenType.OVERLAPS,
    "regexp": TokenType.REGEXP,
    "between": TokenType.BETWEEN,
    "interval": TokenType.INTERVAL,
    "escape": TokenType.ESCAPE,
    "cast": TokenType.CAST,
    "div": TokenType.DIV,
    "hex": TokenType.HEX,
    "bin": TokenType.BIN,
    "true": TokenType.TRUE,
    "false": TokenType.FALSE,
    "null": TokenType.NULL,
    "second": TokenType.SECOND,
    "minute": TokenType.MINUTE,
    "hour": TokenType.HOUR,
    "day": TokenType.DAY,
    "week": TokenType.WEEK,
    "month": TokenType.MONTH,
    "quarter": TokenType.QUARTER,
    "year": TokenType.YEAR,
    "microsecond": TokenType.MICROSECOND,
    "asc": TokenType.ORDERBY_ASC,
    "desc": TokenType.ORDERBY_DESC,
    "as": TokenType.AS,
    "binary": TokenType.BINARY,
    "datetime": TokenType.DATETIME,
    "time": TokenType.TIME,
    "char": TokenType.CHAR,
    "date": TokenType.DATE,
    "decimal": TokenType.DECIMAL,
    "signed": TokenType.SIGNED,
    "unsigned": TokenType.UNSIGNED,
    "integer": TokenType.INTEGER,
    "json": TokenType.JSON,
    "second_microsecond": TokenType.SECOND_MICROSECOND,
    "minute_microsecond": TokenType.MINUTE_MICROSECOND,
    "minute_second": TokenType.MINUTE_SECOND,
    "hour_microsecond": TokenType.HOUR_MICROSECOND,
    "hour_second": TokenType.HOUR_SECOND,
    "hour_minute": TokenType.HOUR_MINUTE,
    "day_microsecond": TokenType.DAY_MICROSECOND,
    "day_second": TokenType.DAY_SECOND,
    "day_minute": TokenType.DAY_MINUTE,
    "day_hour": TokenType.DAY_HOUR,
    "year_month": TokenType.YEAR_MONTH,
}

_SQL_FUNTION_RESERVED_WORDS_COLLISION = {
    "binary": TokenType.BINARY,
    "cast": TokenType.CAST,
    "char": TokenType.CHAR,
    "date": TokenType.DATE,
    "decimal": TokenType.DECIMAL,
    "signed": TokenType.SIGNED,
    "time": TokenType.TIME,
    "unsigned": TokenType.UNSIGNED,
}

_OPERATORS = {
    "=": "==",
    "==": "==",
    "and": "&&",
    "div": "div",
    "||": "||",
    "or": "||",
    "not": "not",
    "xor": "xor",
    "^": "^",
    "is": "is",
    "between": "between",
    "in": "in",
    "like": "like",
    "!=": "!=",
    "<>": "!=",
    ">": ">",
    ">=": ">=",
    "<": "<",
    "<=": "<=",
    "&": "&",
    "&&": "&&",
    "|": "|",
    "<<": "<<",
    ">>": ">>",
    "+": "+",
    "-": "-",
    "*": "*",
    "/": "/",
    "~": "~",
    "%": "%",
    "cast": "cast",
    "cont_in": "cont_in",
    "overlaps": "overlaps",
}

_UNARY_OPERATORS = {
    "+": "sign_plus",
    "-": "sign_minus",
    "~": "~",
    "not": "not",
    "!": "!",
}

_NEGATION = {
    "is": "is_not",
    "between": "not_between",
    "regexp": "not_regexp",
    "like": "not_like",
    "in": "not_in",
    "cont_in": "not_cont_in",
    "overlaps": "not_overlaps",
}


class Token:
    """Token representation class."""

    def __init__(self, token_type: int, value: str, length: int = 1) -> None:
        self.token_type: int = token_type
        self.value: str = value
        self.length: int = length

    def __repr__(self) -> str:
        return self.__str__()

    def __str__(self) -> str:
        if self.token_type in (
            TokenType.IDENT,
            TokenType.LNUM,
            TokenType.LSTRING,
        ):
            return f"{self.token_type}({self.value})"
        return f"{self.token_type}"


# static protobuf helper functions


def build_expr(value: BuildExprTypes) -> MessageType:
    msg = Message("Mysqlx.Expr.Expr")
    if isinstance(value, (Message)):
        return value
    if isinstance(value, (ExprParser)):
        return value.expr(reparse=True)
    if isinstance(value, (dict, DbDoc)):
        msg["type"] = mysqlxpb_enum("Mysqlx.Expr.Expr.Type.OBJECT")
        msg["object"] = build_object(value).get_message()
    elif isinstance(value, (list, tuple)):
        msg["type"] = mysqlxpb_enum("Mysqlx.Expr.Expr.Type.ARRAY")
        msg["array"] = build_array(value).get_message()
    else:
        msg["type"] = mysqlxpb_enum("Mysqlx.Expr.Expr.Type.LITERAL")
        msg["literal"] = build_scalar(value).get_message()
    return msg


def build_scalar(value: BuildScalarTypes) -> MessageType:
    if isinstance(value, str):
        return build_string_scalar(value)
    if isinstance(value, BYTE_TYPES):
        return build_bytes_scalar(value)
    if isinstance(value, bool):
        return build_bool_scalar(value)
    if isinstance(value, int):
        return build_int_scalar(value)
    if isinstance(value, float):
        return build_double_scalar(value)
    if value is None:
        return build_null_scalar()
    raise ValueError(f"Unsupported data type: {type(value)}")


def build_object(obj: Union[Dict, DbDoc]) -> MessageType:
    if isinstance(obj, DbDoc):
        return build_object(obj.__dict__)

    msg = Message("Mysqlx.Expr.Object")
    for key, value in obj.items():
        pair = Message("Mysqlx.Expr.Object.ObjectField")
        pair["key"] = key.encode() if isinstance(key, str) else key
        pair["value"] = build_expr(value).get_message()
        msg["fld"].extend([pair.get_message()])
    return msg


def build_array(array: Sequence[BuildExprTypes]) -> MessageType:
    msg = Message("Mysqlx.Expr.Array")
    msg["value"].extend([build_expr(value).get_message() for value in array])
    return msg


def build_null_scalar() -> MessageType:
    msg = Message("Mysqlx.Datatypes.Scalar")
    msg["type"] = mysqlxpb_enum("Mysqlx.Datatypes.Scalar.Type.V_NULL")
    return msg


def build_double_scalar(value: float) -> MessageType:
    msg = Message("Mysqlx.Datatypes.Scalar")
    msg["type"] = mysqlxpb_enum("Mysqlx.Datatypes.Scalar.Type.V_DOUBLE")
    msg["v_double"] = value
    return msg


def build_int_scalar(value: int) -> MessageType:
    msg = Message("Mysqlx.Datatypes.Scalar")
    msg["type"] = mysqlxpb_enum("Mysqlx.Datatypes.Scalar.Type.V_SINT")
    msg["v_signed_int"] = value
    return msg


def build_unsigned_int_scalar(value: int) -> MessageType:
    msg = Message("Mysqlx.Datatypes.Scalar")
    msg["type"] = mysqlxpb_enum("Mysqlx.Datatypes.Scalar.Type.V_UINT")
    msg["v_unsigned_int"] = value
    return msg


def build_string_scalar(value: StrOrBytes) -> MessageType:
    if isinstance(value, str):
        value = bytes(bytearray(value, "utf-8"))
    msg = Message("Mysqlx.Datatypes.Scalar")
    msg["type"] = mysqlxpb_enum("Mysqlx.Datatypes.Scalar.Type.V_STRING")
    msg["v_string"] = Message("Mysqlx.Datatypes.Scalar.String", value=value)
    return msg


def build_bool_scalar(value: bool) -> MessageType:
    msg = Message("Mysqlx.Datatypes.Scalar")
    msg["type"] = mysqlxpb_enum("Mysqlx.Datatypes.Scalar.Type.V_BOOL")
    msg["v_bool"] = value
    return msg


def build_bytes_scalar(value: bytes) -> MessageType:
    msg = Message("Mysqlx.Datatypes.Scalar")
    msg["type"] = mysqlxpb_enum("Mysqlx.Datatypes.Scalar.Type.V_OCTETS")
    msg["v_octets"] = Message("Mysqlx.Datatypes.Scalar.Octets", value=value)
    return msg


def build_literal_expr(value: MessageType) -> MessageType:
    msg = Message("Mysqlx.Expr.Expr")
    msg["type"] = mysqlxpb_enum("Mysqlx.Expr.Expr.Type.LITERAL")
    msg["literal"] = value
    return msg


def build_unary_op(name: str, param: MessageType) -> MessageType:
    operator = Message("Mysqlx.Expr.Operator")
    operator["name"] = _UNARY_OPERATORS[name]
    operator["param"] = [param.get_message()]
    msg = Message("Mysqlx.Expr.Expr")
    msg["type"] = mysqlxpb_enum("Mysqlx.Expr.Expr.Type.OPERATOR")
    msg["operator"] = operator.get_message()
    return msg


def escape_literal(string: str) -> str:
    return string.replace('"', '""')


class ExprParser:
    """Expression parser class."""

    def __init__(self, string: str, allow_relational: bool = True) -> None:
        self.string: str = string
        self.tokens: List[Token] = []
        self.path_name_queue: List[str] = []
        self.pos: int = 0
        self._allow_relational_columns: bool = allow_relational
        self.placeholder_name_to_position: Dict[str, int] = {}
        self.positional_placeholder_count: int = 0
        self.clean_expression()
        self.lex()

    def __str__(self) -> str:
        return f"<mysqlx.ExprParser '{self.string}'>"

    def clean_expression(self) -> None:
        """Removes the keywords that does not form part of the expression.

        Removes the keywords "SELECT" and "WHERE" that does not form part of
        the expression itself.
        """
        if not isinstance(self.string, str):
            self.string = repr(self.string)
        self.string = self.string.strip(" ")
        if len(self.string) > 1 and self.string[-1] == ";":
            self.string = self.string[:-1]
        if "SELECT" in self.string[:6].upper():
            self.string = self.string[6:]
        if "WHERE" in self.string[:5].upper():
            self.string = self.string[5:]

    # convencience checker for lexer
    def next_char_is(self, key: int, char: str) -> bool:
        return key + 1 < len(self.string) and self.string[key + 1] == char

    def lex_number(self, pos: int) -> Token:
        # numeric literal
        start = pos
        found_dot = False
        while pos < len(self.string) and (
            self.string[pos].isdigit() or self.string[pos] == "."
        ):
            if self.string[pos] == ".":
                if found_dot is True:
                    raise ValueError("Invalid number. Found multiple '.'")
                found_dot = True
            # technically we allow more than one "." and let float()'s parsing
            # complain later
            pos += 1
        val = self.string[start:pos]
        return Token(TokenType.LNUM, val, len(val))

    def lex_alpha(self, i: int, allow_space: bool = False) -> Token:
        start = i
        while i < len(self.string) and (
            self.string[i].isalnum()
            or self.string[i] == "_"
            or (self.string[i].isspace() and allow_space)
        ):
            i += 1

        val = self.string[start:i]
        try:
            if (
                i < len(self.string)
                and self.string[i] == "("
                and val.lower() not in _SQL_FUNTION_RESERVED_WORDS_COLLISION
            ):
                token = Token(TokenType.IDENT, val, len(val))
            else:
                token = Token(_RESERVED_WORDS[val.lower()], val.lower(), len(val))
        except KeyError:
            token = Token(TokenType.IDENT, val, len(val))
        return token

    def lex_quoted_token(self, key: int) -> Token:
        quote_char = self.string[key]
        val = ""
        key += 1
        start = key
        while key < len(self.string):
            char = self.string[key]
            if (
                char == quote_char
                and key + 1 < len(self.string)
                and self.string[key + 1] != quote_char
            ):
                # break if we have a quote char that's not double
                break
            if char in (quote_char, "\\"):
                # this quote char has to be doubled
                if key + 1 >= len(self.string):
                    break
                key += 1
                val += self.string[key]
            else:
                val += char
            key += 1
        if key >= len(self.string) or self.string[key] != quote_char:
            raise ValueError(f"Unterminated quoted string starting at {start}")
        if quote_char == "`":
            return Token(TokenType.IDENT, val, len(val) + 2)
        return Token(TokenType.LSTRING, val, len(val) + 2)

    def lex(self) -> None:
        i = 0
        arrow_last = False
        inside_arrow = False
        while i < len(self.string):
            char = self.string[i]
            if char.isspace():
                i += 1
                continue
            if char.isdigit():
                token = self.lex_number(i)
            elif char.isalpha() or char == "_":
                token = self.lex_alpha(i, inside_arrow)
            elif char == "?":
                token = Token(TokenType.EROTEME, char)
            elif char == ":":
                token = Token(TokenType.COLON, char)
            elif char == "{":
                token = Token(TokenType.LCURLY, char)
            elif char == "}":
                token = Token(TokenType.RCURLY, char)
            elif char == "+":
                token = Token(TokenType.PLUS, char)
            elif char == "-":
                if self.next_char_is(i, ">") and not arrow_last:
                    token = Token(TokenType.ARROW, "->", 2)
                    arrow_last = True
                else:
                    token = Token(TokenType.MINUS, char)
            elif char == "*":
                if self.next_char_is(i, "*"):
                    token = Token(TokenType.DOUBLESTAR, "**", 2)
                else:
                    token = Token(TokenType.MUL, char)
            elif char == "/":
                token = Token(TokenType.DIV, char)
            elif char == "$":
                token = Token(TokenType.DOLLAR, char)
            elif char == "%":
                token = Token(TokenType.MOD, char)
            elif char == "=":
                if self.next_char_is(i, "="):
                    token = Token(TokenType.EQ, "==", 2)
                else:
                    token = Token(TokenType.EQ, "==", 1)
            elif char == "&":
                if self.next_char_is(i, "&"):
                    token = Token(TokenType.ANDAND, "&&", 2)
                else:
                    token = Token(TokenType.BITAND, char)
            elif char == "^":
                token = Token(TokenType.BITXOR, char)
            elif char == "|":
                if self.next_char_is(i, "|"):
                    token = Token(TokenType.OROR, "||", 2)
                else:
                    token = Token(TokenType.BITOR, char)
            elif char == "(":
                token = Token(TokenType.LPAREN, char)
            elif char == ")":
                token = Token(TokenType.RPAREN, char)
            elif char == "[":
                token = Token(TokenType.LSQBRACKET, char)
            elif char == "]":
                token = Token(TokenType.RSQBRACKET, char)
            elif char == "~":
                token = Token(TokenType.NEG, char)
            elif char == ",":
                token = Token(TokenType.COMMA, char)
            elif char == "!":
                if self.next_char_is(i, "="):
                    token = Token(TokenType.NE, "!=", 2)
                else:
                    token = Token(TokenType.BANG, char)
            elif char == "<":
                if self.next_char_is(i, ">"):
                    token = Token(TokenType.NE, "<>", 2)
                elif self.next_char_is(i, "<"):
                    token = Token(TokenType.LSHIFT, "<<", 2)
                elif self.next_char_is(i, "="):
                    token = Token(TokenType.LE, "<=", 2)
                else:
                    token = Token(TokenType.LT, char)
            elif char == ">":
                if self.next_char_is(i, ">"):
                    token = Token(TokenType.RSHIFT, ">>", 2)
                elif self.next_char_is(i, "="):
                    token = Token(TokenType.GE, ">=", 2)
                else:
                    token = Token(TokenType.GT, char)
            elif char == ".":
                if self.next_char_is(i, "*"):
                    token = Token(TokenType.DOTSTAR, ".*", 2)
                elif i + 1 < len(self.string) and self.string[i + 1].isdigit():
                    token = self.lex_number(i)
                else:
                    token = Token(TokenType.DOT, char)
            elif char in ("'", '"') and arrow_last:
                token = Token(TokenType.QUOTE, char)
                if not inside_arrow:
                    inside_arrow = True
                else:
                    arrow_last = False
                    inside_arrow = False
            elif char in ('"', "'", "`"):
                token = self.lex_quoted_token(i)
            else:
                raise ValueError(f"Unknown character at {i}")
            self.tokens.append(token)
            i += token.length

    def assert_cur_token(self, token_type: int) -> None:
        if self.pos >= len(self.tokens):
            raise ValueError(
                f"Expected token type {token_type} at pos {self.pos} but no "
                "tokens left"
            )
        if self.tokens[self.pos].token_type != token_type:
            raise ValueError(
                f"Expected token type {token_type} at pos {self.pos} but found "
                f"type {self.tokens[self.pos]}, on tokens {self.tokens}"
            )

    def cur_token_type_is(self, token_type: int) -> bool:
        return self.pos_token_type_is(self.pos, token_type)

    def cur_token_type_in(self, *types: int) -> bool:
        return self.pos < len(self.tokens) and self.tokens[self.pos].token_type in types

    def next_token_type_is(self, token_type: int) -> bool:
        return self.pos_token_type_is(self.pos + 1, token_type)

    def next_token_type_in(self, *types: int) -> bool:
        return (
            self.pos < len(self.tokens)
            and self.tokens[self.pos + 1].token_type in types
        )

    def pos_token_type_is(self, pos: int, token_type: int) -> bool:
        return pos < len(self.tokens) and self.tokens[pos].token_type == token_type

    def consume_token(self, token_type: int) -> str:
        self.assert_cur_token(token_type)
        value = self.tokens[self.pos].value
        self.pos += 1
        return value

    def paren_expr_list(
        self,
    ) -> List[Union[ProtobufMessageType, ProtobufMessageCextType]]:
        """Parse a paren-bounded expression list for function arguments or IN
        list and return a list of Expr objects.
        """
        exprs = []
        path_name_added = False
        self.consume_token(TokenType.LPAREN)
        if not self.cur_token_type_is(TokenType.RPAREN):
            msg_expr = self._expr().get_message()
            if hasattr(msg_expr, "identifier") and msg_expr.identifier.name:
                self.path_name_queue.insert(0, msg_expr.identifier.name)
                path_name_added = True
            elif (
                not hasattr(msg_expr, "identifier")
                and "identifier" in msg_expr
                and "name" in msg_expr["identifier"]
            ):
                self.path_name_queue.insert(0, msg_expr["identifier"]["name"])
                path_name_added = True
            exprs.append(msg_expr)
            while self.cur_token_type_is(TokenType.COMMA):
                self.pos += 1
                exprs.append(self._expr().get_message())
        self.consume_token(TokenType.RPAREN)
        if path_name_added:
            self.path_name_queue.pop()
        return exprs

    def identifier(self) -> MessageType:
        self.assert_cur_token(TokenType.IDENT)
        ident = Message("Mysqlx.Expr.Identifier")
        if self.next_token_type_is(TokenType.DOT):
            ident["schema_name"] = self.consume_token(TokenType.IDENT)
            self.consume_token(TokenType.DOT)
        ident["name"] = self.tokens[self.pos].value
        self.pos += 1
        return ident

    def function_call(self) -> MessageType:
        function_call = Message("Mysqlx.Expr.FunctionCall")
        function_call["name"] = self.identifier()
        function_call["param"] = self.paren_expr_list()
        msg_expr = Message("Mysqlx.Expr.Expr")
        msg_expr["type"] = mysqlxpb_enum("Mysqlx.Expr.Expr.Type.FUNC_CALL")
        msg_expr["function_call"] = function_call.get_message()
        return msg_expr

    def docpath_member(self) -> MessageType:
        self.consume_token(TokenType.DOT)
        token = self.tokens[self.pos]

        if token.token_type == TokenType.IDENT:
            if token.value.startswith("`") and token.value.endswith("`"):
                raise ValueError(
                    f"{token.value} is not a valid JSON/ECMAScript identifier"
                )
            self.consume_token(TokenType.IDENT)
            member_name = token.value
        elif token.token_type == TokenType.LSTRING:
            self.consume_token(TokenType.LSTRING)
            member_name = token.value
        else:
            raise ValueError(
                "Expected token type IDENT or LSTRING in JSON path at token "
                f"pos {self.pos}"
            )
        doc_path_item = Message("Mysqlx.Expr.DocumentPathItem")
        doc_path_item["type"] = mysqlxpb_enum(
            "Mysqlx.Expr.DocumentPathItem.Type.MEMBER"
        )
        doc_path_item["value"] = member_name
        return doc_path_item

    def docpath_array_loc(self) -> MessageType:
        self.consume_token(TokenType.LSQBRACKET)
        if self.cur_token_type_is(TokenType.MUL):
            self.consume_token(TokenType.MUL)
            self.consume_token(TokenType.RSQBRACKET)
            doc_path_item = Message("Mysqlx.Expr.DocumentPathItem")
            doc_path_item["type"] = mysqlxpb_enum(
                "Mysqlx.Expr.DocumentPathItem.Type.ARRAY_INDEX_ASTERISK"
            )
            return doc_path_item
        if self.cur_token_type_is(TokenType.LNUM):
            value = int(self.consume_token(TokenType.LNUM))
            if value < 0:
                raise IndexError(f"Array index cannot be negative at {self.pos}")
            self.consume_token(TokenType.RSQBRACKET)
            doc_path_item = Message("Mysqlx.Expr.DocumentPathItem")
            doc_path_item["type"] = mysqlxpb_enum(
                "Mysqlx.Expr.DocumentPathItem.Type.ARRAY_INDEX"
            )
            doc_path_item["index"] = value
            return doc_path_item
        raise ValueError(
            "Exception token type MUL or LNUM in JSON path array index at "
            f"token pos {self.pos}"
        )

    def document_field(self) -> MessageType:
        if not self.tokens:
            raise ValueError("Empty string cannot be used as document field")
        if self.cur_token_type_is(TokenType.DOLLAR):
            self.consume_token(TokenType.DOLLAR)
        col_id = Message("Mysqlx.Expr.ColumnIdentifier")
        if self.cur_token_type_is(TokenType.IDENT):
            doc_path_item = Message("Mysqlx.Expr.DocumentPathItem")
            doc_path_item["type"] = mysqlxpb_enum(
                "Mysqlx.Expr.DocumentPathItem.Type.MEMBER"
            )
            doc_path_item["value"] = self.consume_token(TokenType.IDENT)
            col_id["document_path"].extend([doc_path_item.get_message()])
        col_id["document_path"].extend(self.document_path())
        if self.path_name_queue:
            col_id["name"] = self.path_name_queue[0]
        expr = Message("Mysqlx.Expr.Expr")
        expr["type"] = mysqlxpb_enum("Mysqlx.Expr.Expr.Type.IDENT")
        expr["identifier"] = col_id
        return expr

    def document_path(
        self,
    ) -> List[Union[ProtobufMessageType, ProtobufMessageCextType]]:
        """Parse a JSON-style document path, like WL#7909, but prefix by @.
        instead of $. We parse this as a string because the protocol doesn't
        support it. (yet)
        """
        doc_path = []
        while True:
            if self.cur_token_type_is(TokenType.DOT):
                doc_path.append(self.docpath_member().get_message())
            elif self.cur_token_type_is(TokenType.DOTSTAR):
                self.consume_token(TokenType.DOTSTAR)
                doc_path_item = Message("Mysqlx.Expr.DocumentPathItem")
                doc_path_item["type"] = mysqlxpb_enum(
                    "Mysqlx.Expr.DocumentPathItem.Type.MEMBER_ASTERISK"
                )
                doc_path.append(doc_path_item.get_message())
            elif self.cur_token_type_is(TokenType.LSQBRACKET):
                doc_path.append(self.docpath_array_loc().get_message())
            elif self.cur_token_type_is(TokenType.DOUBLESTAR):
                self.consume_token(TokenType.DOUBLESTAR)
                doc_path_item = Message("Mysqlx.Expr.DocumentPathItem")
                doc_path_item["type"] = mysqlxpb_enum(
                    "Mysqlx.Expr.DocumentPathItem.Type.DOUBLE_ASTERISK"
                )
                doc_path.append(doc_path_item.get_message())
            else:
                break
        items = len(doc_path)
        if items > 0 and get_item_or_attr(doc_path[items - 1], "type") == mysqlxpb_enum(
            "Mysqlx.Expr.DocumentPathItem.Type.DOUBLE_ASTERISK"
        ):
            raise ValueError(f"JSON path may not end in '**' at {self.pos}")
        return doc_path

    def column_identifier(self) -> MessageType:
        parts = []
        parts.append(self.consume_token(TokenType.IDENT))
        while self.cur_token_type_is(TokenType.DOT):
            self.consume_token(TokenType.DOT)
            parts.append(self.consume_token(TokenType.IDENT))
        if len(parts) > 3:
            raise ValueError(f"Too many parts to identifier at {self.pos}")
        parts.reverse()
        col_id = Message("Mysqlx.Expr.ColumnIdentifier")
        # clever way to apply them to the struct
        for i in range(0, len(parts)):
            if i == 0:
                col_id["name"] = parts[0]
            elif i == 1:
                col_id["table_name"] = parts[1]
            elif i == 2:
                col_id["schema_name"] = parts[2]

        is_doc = False
        if self.cur_token_type_is(TokenType.DOLLAR):
            is_doc = True
            self.consume_token(TokenType.DOLLAR)
            col_id["document_path"] = self.document_path()
        elif self.cur_token_type_is(TokenType.ARROW):
            is_doc = True
            self.consume_token(TokenType.ARROW)
            is_quoted = False
            if self.cur_token_type_is(TokenType.QUOTE):
                is_quoted = True
                self.consume_token(TokenType.QUOTE)
            self.consume_token(TokenType.DOLLAR)
            col_id["document_path"] = self.document_path()
            if is_quoted:
                self.consume_token(TokenType.QUOTE)

        if is_doc and len(col_id["document_path"]) == 0:
            doc_path_item = Message("Mysqlx.Expr.DocumentPathItem")
            doc_path_item["type"] = mysqlxpb_enum(
                "Mysqlx.Expr.DocumentPathItem.Type.MEMBER"
            )
            doc_path_item["value"] = ""
            col_id["document_path"].extend([doc_path_item.get_message()])

        msg_expr = Message("Mysqlx.Expr.Expr")
        msg_expr["type"] = mysqlxpb_enum("Mysqlx.Expr.Expr.Type.IDENT")
        msg_expr["identifier"] = col_id
        return msg_expr

    def next_token(self) -> Token:
        if self.pos >= len(self.tokens):
            raise ValueError("Unexpected end of token stream")
        token = self.tokens[self.pos]
        self.pos += 1
        return token

    def expect_token(self, token_type: int) -> None:
        token = self.next_token()
        if token.token_type != token_type:
            raise ValueError(f"Expected token type {token_type}")

    def peek_token(self) -> Token:
        return self.tokens[self.pos]

    def consume_any_token(self) -> str:
        value = self.tokens[self.pos].value
        self.pos += 1
        return value

    def parse_json_array(self) -> MessageType:
        """
        jsonArray            ::=  "[" [ expr ("," expr)* ] "]"
        """
        msg = Message("Mysqlx.Expr.Array")
        while self.pos < len(self.tokens) and not self.cur_token_type_is(
            TokenType.RSQBRACKET
        ):
            msg["value"].extend([self._expr().get_message()])
            if not self.cur_token_type_is(TokenType.COMMA):
                break
            self.consume_token(TokenType.COMMA)
        self.consume_token(TokenType.RSQBRACKET)

        expr = Message("Mysqlx.Expr.Expr")
        expr["type"] = mysqlxpb_enum("Mysqlx.Expr.Expr.Type.ARRAY")
        expr["array"] = msg.get_message()
        return expr

    def parse_json_doc(self) -> MessageType:
        """
        jsonDoc              ::=  "{" [jsonKeyValue ("," jsonKeyValue)*] "}"
        jsonKeyValue         ::=  STRING_DQ ":" expr
        """
        msg = Message("Mysqlx.Expr.Object")
        while self.pos < len(self.tokens) and not self.cur_token_type_is(
            TokenType.RCURLY
        ):
            item = Message("Mysqlx.Expr.Object.ObjectField")
            item["key"] = self.consume_token(TokenType.LSTRING)
            self.consume_token(TokenType.COLON)
            item["value"] = self._expr().get_message()
            msg["fld"].extend([item.get_message()])
            if not self.cur_token_type_is(TokenType.COMMA):
                break
            self.consume_token(TokenType.COMMA)
        self.consume_token(TokenType.RCURLY)

        expr = Message("Mysqlx.Expr.Expr")
        expr["type"] = mysqlxpb_enum("Mysqlx.Expr.Expr.Type.OBJECT")
        expr["object"] = msg.get_message()
        return expr

    def parse_place_holder(self, token: Token) -> MessageType:
        place_holder_name = ""
        if self.cur_token_type_is(TokenType.LNUM):
            place_holder_name = self.consume_token(TokenType.LNUM)
        elif self.cur_token_type_is(TokenType.IDENT):
            place_holder_name = self.consume_token(TokenType.IDENT)
        elif token.token_type == TokenType.EROTEME:
            place_holder_name = str(self.positional_placeholder_count)
        else:
            raise ValueError(f"Invalid placeholder name at token pos {self.pos}")

        msg_expr = Message("Mysqlx.Expr.Expr")
        msg_expr["type"] = mysqlxpb_enum("Mysqlx.Expr.Expr.Type.PLACEHOLDER")
        if place_holder_name in self.placeholder_name_to_position:
            msg_expr["position"] = self.placeholder_name_to_position[place_holder_name]
        else:
            msg_expr["position"] = self.positional_placeholder_count
            self.placeholder_name_to_position[
                place_holder_name
            ] = self.positional_placeholder_count
            self.positional_placeholder_count += 1
        return msg_expr

    def cast(self) -> MessageType:
        """cast ::= CAST LPAREN expr AS cast_data_type RPAREN"""
        operator = Message("Mysqlx.Expr.Operator", name="cast")
        self.consume_token(TokenType.LPAREN)
        operator["param"].extend([self._expr().get_message()])
        self.consume_token(TokenType.AS)

        type_scalar = build_bytes_scalar(str.encode(self.cast_data_type()))
        operator["param"].extend([build_literal_expr(type_scalar).get_message()])
        self.consume_token(TokenType.RPAREN)
        msg = Message("Mysqlx.Expr.Expr", operator=operator.get_message())
        msg["type"] = mysqlxpb_enum("Mysqlx.Expr.Expr.Type.OPERATOR")
        return msg

    def cast_data_type(self) -> str:
        """cast_data_type ::= ( BINARY dimension? ) |
        ( CHAR dimension? ) |
        ( DATE ) |
        ( DATETIME dimension? ) |
        ( TIME dimension? ) |
        ( DECIMAL dimension? ) |
        ( SIGNED INTEGER? ) |
        ( UNSIGNED INTEGER? ) |
        JSON
        """
        token = self.next_token()
        if token.token_type in (
            TokenType.BINARY,
            TokenType.CHAR,
            TokenType.DATETIME,
            TokenType.TIME,
        ):
            dimension = self.cast_data_type_dimension()
            return f"{token.value}{dimension}" if dimension else token.value
        if token.token_type is TokenType.DECIMAL:
            dimension = self.cast_data_type_dimension(True)
            return f"{token.value}{dimension}" if dimension else token.value
        if token.token_type in (TokenType.SIGNED, TokenType.UNSIGNED):
            if self.cur_token_type_is(TokenType.INTEGER):
                self.consume_token(TokenType.INTEGER)
            return token.value
        if token.token_type in (
            TokenType.INTEGER,
            TokenType.JSON,
            TokenType.DATE,
        ):
            return token.value

        raise ValueError(
            f"Unknown token type {token.token_type} at position {self.pos} "
            f"({token.value})"
        )

    def cast_data_type_dimension(self, decimal: bool = False) -> Optional[str]:
        """dimension ::= LPAREN LNUM (, LNUM)? RPAREN"""
        if not self.cur_token_type_is(TokenType.LPAREN):
            return None

        dimension = []
        self.consume_token(TokenType.LPAREN)
        dimension.append(self.consume_token(TokenType.LNUM))
        if decimal and self.cur_token_type_is(TokenType.COMMA):
            self.consume_token(TokenType.COMMA)
            dimension.append(self.consume_token(TokenType.LNUM))
        self.consume_token(TokenType.RPAREN)

        return (
            f"({dimension[0]})"
            if len(dimension) == 1
            else f"({dimension[0]},{dimension[1]})"
        )

    @staticmethod
    def star_operator() -> MessageType:
        msg = Message("Mysqlx.Expr.Expr")
        msg["type"] = mysqlxpb_enum("Mysqlx.Expr.Expr.Type.OPERATOR")
        msg["operator"] = Message("Mysqlx.Expr.Operator", name="*")
        return msg

    def atomic_expr(self) -> MessageType:
        """Parse an atomic expression and return a protobuf Expr object"""
        token = self.next_token()

        if token.token_type in [TokenType.EROTEME, TokenType.COLON]:
            return self.parse_place_holder(token)
        if token.token_type == TokenType.LCURLY:
            return self.parse_json_doc()
        if token.token_type == TokenType.LSQBRACKET:
            return self.parse_json_array()
        if token.token_type == TokenType.CAST:
            return self.cast()
        if token.token_type == TokenType.LPAREN:
            expr = self._expr()
            self.expect_token(TokenType.RPAREN)
            return expr
        if token.token_type in [TokenType.PLUS, TokenType.MINUS]:
            peek = self.peek_token()
            if peek.token_type == TokenType.LNUM:
                self.tokens[self.pos].value = token.value + peek.value
                return self.atomic_expr()
            return build_unary_op(token.value, self.atomic_expr())
        if token.token_type in (TokenType.NOT, TokenType.NEG, TokenType.BANG):
            return build_unary_op(token.value, self.atomic_expr())
        if token.token_type == TokenType.LSTRING:
            return build_literal_expr(build_string_scalar(token.value))
        if token.token_type == TokenType.NULL:
            return build_literal_expr(build_null_scalar())
        if token.token_type == TokenType.LNUM:
            if "." in token.value:
                return build_literal_expr(build_double_scalar(float(token.value)))
            return build_literal_expr(build_int_scalar(int(token.value)))
        if token.token_type in [TokenType.TRUE, TokenType.FALSE]:
            return build_literal_expr(
                build_bool_scalar(token.token_type == TokenType.TRUE)
            )
        if token.token_type == TokenType.DOLLAR:
            return self.document_field()
        if token.token_type == TokenType.MUL:
            return self.star_operator()
        if token.token_type == TokenType.IDENT:
            self.pos = self.pos - 1  # stay on the identifier
            if self.next_token_type_is(TokenType.LPAREN) or (
                self.next_token_type_is(TokenType.DOT)
                and self.pos_token_type_is(self.pos + 2, TokenType.IDENT)
                and self.pos_token_type_is(self.pos + 3, TokenType.LPAREN)
            ):
                # Function call
                return self.function_call()
            return (
                self.document_field()
                if not self._allow_relational_columns
                else self.column_identifier()
            )

        raise ValueError(
            f"Unknown token type = {token.token_type}  when expecting atomic "
            f"expression at {self.pos}"
        )

    def parse_left_assoc_binary_op_expr(
        self, types: Iterable[int], inner_parser: Callable[[], MessageType]
    ) -> MessageType:
        """Given a `set' of types and an Expr-returning inner parser function,
        parse a left associate binary operator expression"""
        lhs = inner_parser()
        while self.pos < len(self.tokens) and self.tokens[self.pos].token_type in types:
            msg = Message("Mysqlx.Expr.Expr")
            msg["type"] = mysqlxpb_enum("Mysqlx.Expr.Expr.Type.OPERATOR")
            operator = Message("Mysqlx.Expr.Operator")
            operator["name"] = _OPERATORS[self.tokens[self.pos].value]
            operator["param"] = [lhs.get_message()]
            self.pos += 1
            operator["param"].extend([inner_parser().get_message()])
            msg["operator"] = operator
            lhs = msg
        return lhs

    # operator precedence is implemented here
    def add_sub_interval(self) -> MessageType:
        lhs = self.atomic_expr()
        if self.cur_token_type_in(
            TokenType.PLUS, TokenType.MINUS
        ) and self.next_token_type_is(TokenType.INTERVAL):
            token = self.next_token()

            operator = Message("Mysqlx.Expr.Operator")
            operator["param"].extend([lhs.get_message()])
            operator["name"] = (
                "date_add" if token.token_type is TokenType.PLUS else "date_sub"
            )

            self.consume_token(TokenType.INTERVAL)
            operator["param"].extend([self.bit_expr().get_message()])

            if not self.cur_token_type_in(*_INTERVAL_UNITS):
                raise ValueError(f"Expected interval type at position {self.pos}")

            token = str.encode(self.consume_any_token().upper())
            operator["param"].extend(
                [build_literal_expr(build_bytes_scalar(token)).get_message()]
            )

            lhs = Message("Mysqlx.Expr.Expr", operator=operator)
            lhs["type"] = mysqlxpb_enum("Mysqlx.Expr.Expr.Type.OPERATOR")

        return lhs

    def mul_div_expr(self) -> MessageType:
        return self.parse_left_assoc_binary_op_expr(
            set([TokenType.MUL, TokenType.DIV, TokenType.MOD]),
            self.add_sub_interval,
        )

    def add_sub_expr(self) -> MessageType:
        return self.parse_left_assoc_binary_op_expr(
            set([TokenType.PLUS, TokenType.MINUS]), self.mul_div_expr
        )

    def shift_expr(self) -> MessageType:
        return self.parse_left_assoc_binary_op_expr(
            set([TokenType.LSHIFT, TokenType.RSHIFT]), self.add_sub_expr
        )

    def bit_expr(self) -> MessageType:
        return self.parse_left_assoc_binary_op_expr(
            set([TokenType.BITAND, TokenType.BITOR, TokenType.BITXOR]),
            self.shift_expr,
        )

    def comp_expr(self) -> MessageType:
        return self.parse_left_assoc_binary_op_expr(
            set(
                [
                    TokenType.GE,
                    TokenType.GT,
                    TokenType.LE,
                    TokenType.LT,
                    TokenType.EQ,
                    TokenType.NE,
                ]
            ),
            self.bit_expr,
        )

    def ilri_expr(self) -> MessageType:
        params = []
        lhs = self.comp_expr()
        is_not = False
        if self.cur_token_type_is(TokenType.NOT):
            is_not = True
            self.consume_token(TokenType.NOT)
        if self.pos < len(self.tokens):
            params.append(lhs.get_message())
            op_name = self.tokens[self.pos].value
            if self.cur_token_type_is(TokenType.IS):
                self.consume_token(TokenType.IS)
                # for IS, NOT comes AFTER
                if self.cur_token_type_is(TokenType.NOT):
                    is_not = True
                    self.consume_token(TokenType.NOT)
                params.append(self.comp_expr().get_message())
            elif self.cur_token_type_is(TokenType.IN):
                self.consume_token(TokenType.IN)
                if self.cur_token_type_is(TokenType.LPAREN):
                    params.extend(self.paren_expr_list())
                else:
                    op_name = "cont_in"
                    params.append(self.comp_expr().get_message())
            elif self.cur_token_type_is(TokenType.OVERLAPS):
                self.consume_token(TokenType.OVERLAPS)
                params.append(self.comp_expr().get_message())

            elif self.cur_token_type_is(TokenType.LIKE):
                self.consume_token(TokenType.LIKE)
                params.append(self.comp_expr().get_message())
                if self.cur_token_type_is(TokenType.ESCAPE):
                    self.consume_token(TokenType.ESCAPE)
                    params.append(self.comp_expr().get_message())
            elif self.cur_token_type_is(TokenType.BETWEEN):
                self.consume_token(TokenType.BETWEEN)
                params.append(self.comp_expr().get_message())
                self.consume_token(TokenType.AND)
                params.append(self.comp_expr().get_message())
            elif self.cur_token_type_is(TokenType.REGEXP):
                self.consume_token(TokenType.REGEXP)
                params.append(self.comp_expr().get_message())
            else:
                if is_not:
                    raise ValueError(f"Unknown token after NOT as pos {self.pos}")
                op_name = None  # not an operator we're interested in
            if op_name:
                operator = Message("Mysqlx.Expr.Operator")
                operator["name"] = _NEGATION[op_name] if is_not else op_name
                operator["param"] = params
                msg_expr = Message("Mysqlx.Expr.Expr")
                msg_expr["type"] = mysqlxpb_enum("Mysqlx.Expr.Expr.Type.OPERATOR")
                msg_expr["operator"] = operator.get_message()
                lhs = msg_expr
        return lhs

    def and_expr(self) -> MessageType:
        return self.parse_left_assoc_binary_op_expr(
            set([TokenType.AND, TokenType.ANDAND]), self.ilri_expr
        )

    def xor_expr(self) -> MessageType:
        return self.parse_left_assoc_binary_op_expr(set([TokenType.XOR]), self.and_expr)

    def or_expr(self) -> MessageType:
        return self.parse_left_assoc_binary_op_expr(
            set([TokenType.OR, TokenType.OROR]), self.xor_expr
        )

    def _expr(self, reparse: bool = False) -> MessageType:
        if reparse:
            self.tokens = []
            self.pos = 0
            self.placeholder_name_to_position = {}
            self.positional_placeholder_count = 0
            self.lex()
        return self.or_expr()

    def expr(self, reparse: bool = False) -> MessageType:
        expression = self._expr(reparse)
        used_tokens = self.pos
        if self.pos_token_type_is(len(self.tokens) - 2, TokenType.AS):
            used_tokens += 2
        if used_tokens < len(self.tokens):
            raise ValueError(
                f"Unused token types {self.tokens[self.pos :]} found in "
                f"expression at position: {self.pos}"
            )
        return expression

    def parse_table_insert_field(self) -> MessageType:
        return Message("Mysqlx.Crud.Column", name=self.consume_token(TokenType.IDENT))

    def parse_table_update_field(self) -> Any:
        return self.column_identifier().identifier

    def _table_fields(self) -> List[str]:
        fields = []
        temp = self.string.split(",")
        temp.reverse()
        while temp:
            field = temp.pop()
            while (
                field.count("(") != field.count(")")
                or field.count("[") != field.count("]")
                or field.count("{") != field.count("}")
            ):
                field = f"{temp.pop()},{field}"
            fields.append(field.strip())
        return fields

    def parse_table_select_projection(
        self,
    ) -> List[Union[ProtobufMessageType, ProtobufMessageCextType]]:
        project_expr: List = []
        first = True
        fields = self._table_fields()
        while self.pos < len(self.tokens):
            if not first:
                self.consume_token(TokenType.COMMA)
            first = False
            projection = Message("Mysqlx.Crud.Projection", source=self._expr())
            if self.cur_token_type_is(TokenType.AS):
                self.consume_token(TokenType.AS)
                projection["alias"] = self.consume_token(TokenType.IDENT)
            else:
                projection["alias"] = fields[len(project_expr)]
            project_expr.append(projection.get_message())

        return project_expr

    def parse_order_spec(
        self,
    ) -> List[Union[ProtobufMessageType, ProtobufMessageCextType]]:
        order_specs = []
        first = True
        while self.pos < len(self.tokens):
            if not first:
                self.consume_token(TokenType.COMMA)
            first = False
            order = Message("Mysqlx.Crud.Order", expr=self._expr())
            if self.cur_token_type_is(TokenType.ORDERBY_ASC):
                order["direction"] = mysqlxpb_enum("Mysqlx.Crud.Order.Direction.ASC")
                self.consume_token(TokenType.ORDERBY_ASC)
            elif self.cur_token_type_is(TokenType.ORDERBY_DESC):
                order["direction"] = mysqlxpb_enum("Mysqlx.Crud.Order.Direction.DESC")
                self.consume_token(TokenType.ORDERBY_DESC)
            order_specs.append(order.get_message())
        return order_specs

    def parse_expr_list(
        self,
    ) -> List[Union[ProtobufMessageType, ProtobufMessageCextType]]:
        expr_list = []
        first = True
        while self.pos < len(self.tokens):
            if not first:
                self.consume_token(TokenType.COMMA)
            first = False
            expr_list.append(self._expr().get_message())
        return expr_list

virt/lib/python3.9/site-packages/mysqlx/expr.py
PrinceDisant/LostInNL
# Copyright (c) 2017, 2022, Oracle and/or its affiliates. All rights reserved.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

"""This module contains helper functions."""

import binascii
import decimal
import functools
import inspect
import warnings

from typing import Any, Callable, List, Optional, Union

from .constants import TLS_CIPHER_SUITES, TLS_VERSIONS
from .errors import InterfaceError
from .types import EscapeTypes, StrOrBytes

BYTE_TYPES = (bytearray, bytes)
NUMERIC_TYPES = (int, float, decimal.Decimal)


def encode_to_bytes(value: StrOrBytes, encoding: str = "utf-8") -> bytes:
    """Returns an encoded version of the string as a bytes object.

    Args:
        encoding (str): The encoding.

    Resturns:
        bytes: The encoded version of the string as a bytes object.
    """
    return value if isinstance(value, bytes) else value.encode(encoding)


def decode_from_bytes(value: StrOrBytes, encoding: str = "utf-8") -> str:
    """Returns a string decoded from the given bytes.

    Args:
        value (bytes): The value to be decoded.
        encoding (str): The encoding.

    Returns:
        str: The value decoded from bytes.
    """
    return value.decode(encoding) if isinstance(value, bytes) else value


def get_item_or_attr(obj: object, key: str) -> Any:
    """Get item from dictionary or attribute from object.

    Args:
        obj (object): Dictionary or object.
        key (str): Key.

    Returns:
        object: The object for the provided key.
    """
    return obj[key] if isinstance(obj, dict) else getattr(obj, key)


def escape(*args: EscapeTypes) -> Union[EscapeTypes, List[EscapeTypes]]:
    """Escapes special characters as they are expected to be when MySQL
    receives them.
    As found in MySQL source mysys/charset.c

    Args:
        value (object): Value to be escaped.

    Returns:
        str: The value if not a string, or the escaped string.
    """

    def _escape(value: EscapeTypes) -> EscapeTypes:
        """Escapes special characters."""
        if value is None:
            return value
        if isinstance(value, NUMERIC_TYPES):
            return value
        if isinstance(value, (bytes, bytearray)):
            value = value.replace(b"\\", b"\\\\")
            value = value.replace(b"\n", b"\\n")
            value = value.replace(b"\r", b"\\r")
            value = value.replace(b"\047", b"\134\047")  # single quotes
            value = value.replace(b"\042", b"\134\042")  # double quotes
            value = value.replace(b"\032", b"\134\032")  # for Win32
        else:
            value = value.replace("\\", "\\\\")
            value = value.replace("\n", "\\n")
            value = value.replace("\r", "\\r")
            value = value.replace("\047", "\134\047")  # single quotes
            value = value.replace("\042", "\134\042")  # double quotes
            value = value.replace("\032", "\134\032")  # for Win32
        return value

    if len(args) > 1:
        return [_escape(arg) for arg in args]
    return _escape(args[0])


def quote_identifier(identifier: str, sql_mode: str = "") -> str:
    """Quote the given identifier with backticks, converting backticks (`)
    in the identifier name with the correct escape sequence (``) unless the
    identifier is quoted (") as in sql_mode set to ANSI_QUOTES.

    Args:
        identifier (str): Identifier to quote.

    Returns:
        str: Returns string with the identifier quoted with backticks.
    """
    if sql_mode == "ANSI_QUOTES":
        quoted = identifier.replace('"', '""')
        return f'"{quoted}"'
    quoted = identifier.replace("`", "``")
    return f"`{quoted}`"


def deprecated(version: Optional[str] = None, reason: Optional[str] = None) -> Callable:
    """This is a decorator used to mark functions as deprecated.

    Args:
        version (Optional[string]): Version when was deprecated.
        reason (Optional[string]): Reason or extra information to be shown.

    Returns:
        Callable: A decorator used to mark functions as deprecated.

    Usage:

    .. code-block:: python

       from mysqlx.helpers import deprecated

       @deprecated('8.0.12', 'Please use other_function() instead')
       def deprecated_function(x, y):
           return x + y
    """

    def decorate(func: Callable) -> Callable:
        """Decorate function."""

        @functools.wraps(func)
        def wrapper(*args: Any, **kwargs: Any) -> Callable:
            """Wrapper function.

            Args:
                *args: Variable length argument list.
                **kwargs: Arbitrary keyword arguments.
            """
            message = [f"'{func.__name__}' is deprecated"]
            if version:
                message.append(f" since version {version}")
            if reason:
                message.append(f". {reason}")
            frame = inspect.currentframe().f_back
            warnings.warn_explicit(
                "".join(message),
                category=DeprecationWarning,
                filename=inspect.getfile(frame.f_code),
                lineno=frame.f_lineno,
            )
            return func(*args, **kwargs)

        return wrapper

    return decorate


def iani_to_openssl_cs_name(
    tls_version: str, cipher_suites_names: List[str]
) -> List[str]:
    """Translates a cipher suites names list; from IANI names to OpenSSL names.

    Args:
        TLS_version (str): The TLS version to look at for a translation.
        cipher_suite_names (list): A list of cipher suites names.

    Returns:
        List[str]: List of translated names.
    """
    translated_names = []

    cipher_suites = {}  # TLS_CIPHER_SUITES[TLS_version]

    # Find the previews TLS versions of the given on TLS_version
    for index in range(TLS_VERSIONS.index(tls_version) + 1):
        cipher_suites.update(TLS_CIPHER_SUITES[TLS_VERSIONS[index]])

    for name in cipher_suites_names:
        if "-" in name:
            translated_names.append(name)
        elif name in cipher_suites:
            translated_names.append(cipher_suites[name])
        else:
            raise InterfaceError(
                f"The '{name}' in cipher suites is not a valid cipher suite"
            )
    return translated_names


def hexlify(data: bytes) -> str:
    """Return the hexadecimal representation of the binary data.

    Args:
        data (bytes): The binary data.

    Returns:
        str: The decoded hexadecimal representation of data.
    """
    return binascii.hexlify(data).decode("utf-8")

virt/lib/python3.9/site-packages/mysqlx/helpers.py
PrinceDisant/LostInNL
# Copyright (c) 2012, 2022, Oracle and/or its affiliates. All rights reserved.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

"""Translations."""

__all__ = ["get_client_error"]

from typing import Optional, Union

from .. import errorcode


def get_client_error(error: Union[int, str], language: str = "eng") -> Optional[str]:
    """Lookup client error

    This function will lookup the client error message based on the given
    error and return the error message. If the error was not found,
    None will be returned.

    Error can be either an integer or a string. For example:
        error: 2000
        error: CR_UNKNOWN_ERROR

    The language attribute can be used to retrieve a localized message, when
    available.

    Returns a string or None.
    """
    try:
        tmp = __import__(
            f"mysql.connector.locales.{language}",
            globals(),
            locals(),
            ["client_error"],
        )
    except ImportError:
        raise ImportError(
            f"No localization support for language '{language}'"
        ) from None
    client_error = tmp.client_error

    if isinstance(error, int):
        errno = error
        for key, value in errorcode.__dict__.items():
            if value == errno:
                error = key
                break

    if isinstance(error, (str)):
        try:
            return getattr(client_error, error)
        except AttributeError:
            return None

    raise ValueError("error argument needs to be either an integer or string")

virt/lib/python3.9/site-packages/mysqlx/locales/__init__.py
PrinceDisant/LostInNL
# -*- coding: utf-8 -*-

# Copyright (c) 2013, 2022, Oracle and/or its affiliates. All rights reserved.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

"""MySQL Error Messages."""

# This file was auto-generated.
_GENERATED_ON = "2021-08-11"
_MYSQL_VERSION = (8, 0, 27)

# pylint: disable=line-too-long
# Start MySQL Error messages
CR_UNKNOWN_ERROR = "Unknown MySQL error"
CR_SOCKET_CREATE_ERROR = "Can't create UNIX socket (%s)"
CR_CONNECTION_ERROR = (
    "Can't connect to local MySQL server through socket '%-.100s' (%s)"
)
CR_CONN_HOST_ERROR = "Can't connect to MySQL server on '%-.100s:%u' (%s)"
CR_IPSOCK_ERROR = "Can't create TCP/IP socket (%s)"
CR_UNKNOWN_HOST = "Unknown MySQL server host '%-.100s' (%s)"
CR_SERVER_GONE_ERROR = "MySQL server has gone away"
CR_VERSION_ERROR = (
    "Protocol mismatch; server version = %s, client version = %s"
)
CR_OUT_OF_MEMORY = "MySQL client ran out of memory"
CR_WRONG_HOST_INFO = "Wrong host info"
CR_LOCALHOST_CONNECTION = "Localhost via UNIX socket"
CR_TCP_CONNECTION = "%-.100s via TCP/IP"
CR_SERVER_HANDSHAKE_ERR = "Error in server handshake"
CR_SERVER_LOST = "Lost connection to MySQL server during query"
CR_COMMANDS_OUT_OF_SYNC = (
    "Commands out of sync; you can't run this command now"
)
CR_NAMEDPIPE_CONNECTION = "Named pipe: %-.32s"
CR_NAMEDPIPEWAIT_ERROR = (
    "Can't wait for named pipe to host: %-.64s  pipe: %-.32s (%s)"
)
CR_NAMEDPIPEOPEN_ERROR = (
    "Can't open named pipe to host: %-.64s  pipe: %-.32s (%s)"
)
CR_NAMEDPIPESETSTATE_ERROR = (
    "Can't set state of named pipe to host: %-.64s  pipe: %-.32s (%s)"
)
CR_CANT_READ_CHARSET = "Can't initialize character set %-.32s (path: %-.100s)"
CR_NET_PACKET_TOO_LARGE = "Got packet bigger than 'max_allowed_packet' bytes"
CR_EMBEDDED_CONNECTION = "Embedded server"
CR_PROBE_SLAVE_STATUS = "Error on SHOW SLAVE STATUS:"
CR_PROBE_SLAVE_HOSTS = "Error on SHOW SLAVE HOSTS:"
CR_PROBE_SLAVE_CONNECT = "Error connecting to slave:"
CR_PROBE_MASTER_CONNECT = "Error connecting to master:"
CR_SSL_CONNECTION_ERROR = "SSL connection error: %-.100s"
CR_MALFORMED_PACKET = "Malformed packet"
CR_WRONG_LICENSE = "This client library is licensed only for use with MySQL servers having '%s' license"
CR_NULL_POINTER = "Invalid use of null pointer"
CR_NO_PREPARE_STMT = "Statement not prepared"
CR_PARAMS_NOT_BOUND = "No data supplied for parameters in prepared statement"
CR_DATA_TRUNCATED = "Data truncated"
CR_NO_PARAMETERS_EXISTS = "No parameters exist in the statement"
CR_INVALID_PARAMETER_NO = "Invalid parameter number"
CR_INVALID_BUFFER_USE = (
    "Can't send long data for non-string/non-binary data types (parameter: %s)"
)
CR_UNSUPPORTED_PARAM_TYPE = (
    "Using unsupported buffer type: %s  (parameter: %s)"
)
CR_SHARED_MEMORY_CONNECTION = "Shared memory: %-.100s"
CR_SHARED_MEMORY_CONNECT_REQUEST_ERROR = (
    "Can't open shared memory; client could not create request event (%s)"
)
CR_SHARED_MEMORY_CONNECT_ANSWER_ERROR = (
    "Can't open shared memory; no answer event received from server (%s)"
)
CR_SHARED_MEMORY_CONNECT_FILE_MAP_ERROR = (
    "Can't open shared memory; server could not allocate file mapping (%s)"
)
CR_SHARED_MEMORY_CONNECT_MAP_ERROR = "Can't open shared memory; server could not get pointer to file mapping (%s)"
CR_SHARED_MEMORY_FILE_MAP_ERROR = (
    "Can't open shared memory; client could not allocate file mapping (%s)"
)
CR_SHARED_MEMORY_MAP_ERROR = "Can't open shared memory; client could not get pointer to file mapping (%s)"
CR_SHARED_MEMORY_EVENT_ERROR = (
    "Can't open shared memory; client could not create %s event (%s)"
)
CR_SHARED_MEMORY_CONNECT_ABANDONED_ERROR = (
    "Can't open shared memory; no answer from server (%s)"
)
CR_SHARED_MEMORY_CONNECT_SET_ERROR = (
    "Can't open shared memory; cannot send request event to server (%s)"
)
CR_CONN_UNKNOW_PROTOCOL = "Wrong or unknown protocol"
CR_INVALID_CONN_HANDLE = "Invalid connection handle"
CR_UNUSED_1 = "Connection using old (pre-4.1.1) authentication protocol refused (client option 'secure_auth' enabled)"
CR_FETCH_CANCELED = "Row retrieval was canceled by mysql_stmt_close() call"
CR_NO_DATA = "Attempt to read column without prior row fetch"
CR_NO_STMT_METADATA = "Prepared statement contains no metadata"
CR_NO_RESULT_SET = "Attempt to read a row while there is no result set associated with the statement"
CR_NOT_IMPLEMENTED = "This feature is not implemented yet"
CR_SERVER_LOST_EXTENDED = (
    "Lost connection to MySQL server at '%s', system error: %s"
)
CR_STMT_CLOSED = "Statement closed indirectly because of a preceding %s() call"
CR_NEW_STMT_METADATA = "The number of columns in the result set differs from the number of bound buffers. You must reset the statement, rebind the result set columns, and execute the statement again"
CR_ALREADY_CONNECTED = "This handle is already connected. Use a separate handle for each connection."
CR_AUTH_PLUGIN_CANNOT_LOAD = "Authentication plugin '%s' cannot be loaded: %s"
CR_DUPLICATE_CONNECTION_ATTR = (
    "There is an attribute with the same name already"
)
CR_AUTH_PLUGIN_ERR = "Authentication plugin '%s' reported error: %s"
CR_INSECURE_API_ERR = "Insecure API function call: '%s' Use instead: '%s'"
CR_FILE_NAME_TOO_LONG = "File name is too long"
CR_SSL_FIPS_MODE_ERR = "Set FIPS mode ON/STRICT failed"
CR_DEPRECATED_COMPRESSION_NOT_SUPPORTED = (
    "Compression protocol not supported with asynchronous protocol"
)
CR_COMPRESSION_WRONGLY_CONFIGURED = (
    "Connection failed due to wrongly configured compression algorithm"
)
CR_KERBEROS_USER_NOT_FOUND = (
    "SSO user not found, Please perform SSO authentication using kerberos."
)
CR_LOAD_DATA_LOCAL_INFILE_REJECTED = "LOAD DATA LOCAL INFILE file request rejected due to restrictions on access."
CR_LOAD_DATA_LOCAL_INFILE_REALPATH_FAIL = (
    "Determining the real path for '%s' failed with error (%s): %s"
)
CR_DNS_SRV_LOOKUP_FAILED = "DNS SRV lookup failed with error : %s"
CR_MANDATORY_TRACKER_NOT_FOUND = (
    "Client does not recognise tracker type %s marked as mandatory by server."
)
CR_INVALID_FACTOR_NO = "Invalid first argument for MYSQL_OPT_USER_PASSWORD option. Valid value should be between 1 and 3 inclusive."
# End MySQL Error messages

virt/lib/python3.9/site-packages/mysqlx/locales/eng/client_error.py
PrinceDisant/LostInNL
# Copyright (c) 2022, 2023, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

"""Setup of the `mysqlx` logger."""

import logging

logger = logging.getLogger("mysqlx")

virt/lib/python3.9/site-packages/mysqlx/logger.py
PrinceDisant/LostInNL
# Copyright (c) 2017, 2022, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

# mypy: disable-error-code="attr-defined,union-attr"

"""Implementation of a helper class for MySQL X Protobuf messages."""

# pylint: disable=c-extension-no-member, no-member

from __future__ import annotations

from typing import Any, Dict, List, Optional, Tuple, Type, Union

from ..types import MessageType, ProtobufMessageCextType, ProtobufMessageType

_SERVER_MESSAGES_TUPLES: Tuple[Tuple[str, str], ...] = (
    ("Mysqlx.ServerMessages.Type.OK", "Mysqlx.Ok"),
    ("Mysqlx.ServerMessages.Type.ERROR", "Mysqlx.Error"),
    (
        "Mysqlx.ServerMessages.Type.CONN_CAPABILITIES",
        "Mysqlx.Connection.Capabilities",
    ),
    (
        "Mysqlx.ServerMessages.Type.SESS_AUTHENTICATE_CONTINUE",
        "Mysqlx.Session.AuthenticateContinue",
    ),
    (
        "Mysqlx.ServerMessages.Type.SESS_AUTHENTICATE_OK",
        "Mysqlx.Session.AuthenticateOk",
    ),
    ("Mysqlx.ServerMessages.Type.NOTICE", "Mysqlx.Notice.Frame"),
    (
        "Mysqlx.ServerMessages.Type.RESULTSET_COLUMN_META_DATA",
        "Mysqlx.Resultset.ColumnMetaData",
    ),
    ("Mysqlx.ServerMessages.Type.RESULTSET_ROW", "Mysqlx.Resultset.Row"),
    (
        "Mysqlx.ServerMessages.Type.RESULTSET_FETCH_DONE",
        "Mysqlx.Resultset.FetchDone",
    ),
    (
        "Mysqlx.ServerMessages.Type.RESULTSET_FETCH_SUSPENDED",
        "Mysqlx.Resultset.FetchSuspended",
    ),
    (
        "Mysqlx.ServerMessages.Type.RESULTSET_FETCH_DONE_MORE_RESULTSETS",
        "Mysqlx.Resultset.FetchDoneMoreResultsets",
    ),
    (
        "Mysqlx.ServerMessages.Type.SQL_STMT_EXECUTE_OK",
        "Mysqlx.Sql.StmtExecuteOk",
    ),
    (
        "Mysqlx.ServerMessages.Type.RESULTSET_FETCH_DONE_MORE_OUT_PARAMS",
        "Mysqlx.Resultset.FetchDoneMoreOutParams",
    ),
    (
        "Mysqlx.ServerMessages.Type.COMPRESSION",
        "Mysqlx.Connection.Compression",
    ),
)

PROTOBUF_VERSION: Optional[str] = None
PROTOBUF_REPEATED_TYPES: List[
    Union[
        Type[List[Dict[str, Any]]],
        Type[RepeatedCompositeContainer],
        Type[RepeatedCompositeFieldContainer],
    ]
] = [list]

try:
    import _mysqlxpb

    SERVER_MESSAGES = {
        int(_mysqlxpb.enum_value(key)): val for key, val in _SERVER_MESSAGES_TUPLES
    }
    HAVE_MYSQLXPB_CEXT = True
except ImportError:
    HAVE_MYSQLXPB_CEXT = False

from ..helpers import BYTE_TYPES, NUMERIC_TYPES, encode_to_bytes

try:
    from google import protobuf
    from google.protobuf import (
        descriptor_database,
        descriptor_pb2,
        descriptor_pool,
        message_factory,
    )
    from google.protobuf.internal.containers import RepeatedCompositeFieldContainer

    try:
        from google.protobuf.pyext._message import RepeatedCompositeContainer

        PROTOBUF_REPEATED_TYPES.append(RepeatedCompositeContainer)
    except ImportError:
        pass

    PROTOBUF_REPEATED_TYPES.append(RepeatedCompositeFieldContainer)
    if hasattr(protobuf, "__version__"):
        # Only Protobuf versions >=3.0.0 provide `__version__`
        PROTOBUF_VERSION = protobuf.__version__

    from . import (
        mysqlx_connection_pb2,
        mysqlx_crud_pb2,
        mysqlx_cursor_pb2,
        mysqlx_datatypes_pb2,
        mysqlx_expect_pb2,
        mysqlx_expr_pb2,
        mysqlx_notice_pb2,
        mysqlx_pb2,
        mysqlx_prepare_pb2,
        mysqlx_resultset_pb2,
        mysqlx_session_pb2,
        mysqlx_sql_pb2,
    )

    # Dictionary with all messages descriptors
    _MESSAGES: Dict[str, int] = {}

    # Mysqlx
    for key, val in mysqlx_pb2.ClientMessages.Type.items():
        _MESSAGES[f"Mysqlx.ClientMessages.Type.{key}"] = val
    for key, val in mysqlx_pb2.ServerMessages.Type.items():
        _MESSAGES[f"Mysqlx.ServerMessages.Type.{key}"] = val
    for key, val in mysqlx_pb2.Error.Severity.items():
        _MESSAGES[f"Mysqlx.Error.Severity.{key}"] = val

    # Mysqlx.Crud
    for key, val in mysqlx_crud_pb2.DataModel.items():
        _MESSAGES[f"Mysqlx.Crud.DataModel.{key}"] = val
    for key, val in mysqlx_crud_pb2.Find.RowLock.items():
        _MESSAGES[f"Mysqlx.Crud.Find.RowLock.{key}"] = val
    for key, val in mysqlx_crud_pb2.Order.Direction.items():
        _MESSAGES[f"Mysqlx.Crud.Order.Direction.{key}"] = val
    for key, val in mysqlx_crud_pb2.UpdateOperation.UpdateType.items():
        _MESSAGES[f"Mysqlx.Crud.UpdateOperation.UpdateType.{key}"] = val

    # Mysqlx.Datatypes
    for key, val in mysqlx_datatypes_pb2.Scalar.Type.items():
        _MESSAGES[f"Mysqlx.Datatypes.Scalar.Type.{key}"] = val
    for key, val in mysqlx_datatypes_pb2.Any.Type.items():
        _MESSAGES[f"Mysqlx.Datatypes.Any.Type.{key}"] = val

    # Mysqlx.Expect
    for key, val in mysqlx_expect_pb2.Open.Condition.ConditionOperation.items():
        _MESSAGES[f"Mysqlx.Expect.Open.Condition.ConditionOperation.{key}"] = val
    for key, val in mysqlx_expect_pb2.Open.Condition.Key.items():
        _MESSAGES[f"Mysqlx.Expect.Open.Condition.Key.{key}"] = val
    for key, val in mysqlx_expect_pb2.Open.CtxOperation.items():
        _MESSAGES[f"Mysqlx.Expect.Open.CtxOperation.{key}"] = val

    # Mysqlx.Expr
    for key, val in mysqlx_expr_pb2.Expr.Type.items():
        _MESSAGES[f"Mysqlx.Expr.Expr.Type.{key}"] = val
    for key, val in mysqlx_expr_pb2.DocumentPathItem.Type.items():
        _MESSAGES[f"Mysqlx.Expr.DocumentPathItem.Type.{key}"] = val

    # Mysqlx.Notice
    for key, val in mysqlx_notice_pb2.Frame.Scope.items():
        _MESSAGES[f"Mysqlx.Notice.Frame.Scope.{key}"] = val
    for key, val in mysqlx_notice_pb2.Warning.Level.items():
        _MESSAGES[f"Mysqlx.Notice.Warning.Level.{key}"] = val
    for key, val in mysqlx_notice_pb2.SessionStateChanged.Parameter.items():
        _MESSAGES[f"Mysqlx.Notice.SessionStateChanged.Parameter.{key}"] = val

    # Mysql.Prepare
    for key, val in mysqlx_prepare_pb2.Prepare.OneOfMessage.Type.items():
        _MESSAGES[f"Mysqlx.Prepare.Prepare.OneOfMessage.Type.{key}"] = val

    # Mysql.Resultset
    for key, val in mysqlx_resultset_pb2.ColumnMetaData.FieldType.items():
        _MESSAGES[f"Mysqlx.Resultset.ColumnMetaData.FieldType.{key}"] = val

    # Add messages to the descriptor pool
    _DESCRIPTOR_DB = descriptor_database.DescriptorDatabase()
    _DESCRIPTOR_POOL = descriptor_pool.DescriptorPool(_DESCRIPTOR_DB)

    _DESCRIPTOR_DB.Add(
        descriptor_pb2.FileDescriptorProto.FromString(
            mysqlx_connection_pb2.DESCRIPTOR.serialized_pb
        )
    )
    _DESCRIPTOR_DB.Add(
        descriptor_pb2.FileDescriptorProto.FromString(
            mysqlx_crud_pb2.DESCRIPTOR.serialized_pb
        )
    )
    _DESCRIPTOR_DB.Add(
        descriptor_pb2.FileDescriptorProto.FromString(
            mysqlx_cursor_pb2.DESCRIPTOR.serialized_pb
        )
    )
    _DESCRIPTOR_DB.Add(
        descriptor_pb2.FileDescriptorProto.FromString(
            mysqlx_datatypes_pb2.DESCRIPTOR.serialized_pb
        )
    )
    _DESCRIPTOR_DB.Add(
        descriptor_pb2.FileDescriptorProto.FromString(
            mysqlx_expect_pb2.DESCRIPTOR.serialized_pb
        )
    )
    _DESCRIPTOR_DB.Add(
        descriptor_pb2.FileDescriptorProto.FromString(
            mysqlx_expr_pb2.DESCRIPTOR.serialized_pb
        )
    )
    _DESCRIPTOR_DB.Add(
        descriptor_pb2.FileDescriptorProto.FromString(
            mysqlx_notice_pb2.DESCRIPTOR.serialized_pb
        )
    )
    _DESCRIPTOR_DB.Add(
        descriptor_pb2.FileDescriptorProto.FromString(
            mysqlx_pb2.DESCRIPTOR.serialized_pb
        )
    )
    _DESCRIPTOR_DB.Add(
        descriptor_pb2.FileDescriptorProto.FromString(
            mysqlx_prepare_pb2.DESCRIPTOR.serialized_pb
        )
    )
    _DESCRIPTOR_DB.Add(
        descriptor_pb2.FileDescriptorProto.FromString(
            mysqlx_resultset_pb2.DESCRIPTOR.serialized_pb
        )
    )
    _DESCRIPTOR_DB.Add(
        descriptor_pb2.FileDescriptorProto.FromString(
            mysqlx_session_pb2.DESCRIPTOR.serialized_pb
        )
    )
    _DESCRIPTOR_DB.Add(
        descriptor_pb2.FileDescriptorProto.FromString(
            mysqlx_sql_pb2.DESCRIPTOR.serialized_pb
        )
    )

    SERVER_MESSAGES = {_MESSAGES[key]: val for key, val in _SERVER_MESSAGES_TUPLES}
    HAVE_PROTOBUF = True
    HAVE_PROTOBUF_ERROR = None

    class _mysqlxpb_pure:  # pylint: disable=invalid-name
        """This class implements the methods in pure Python used by the
        _mysqlxpb C++ extension."""

        factory: message_factory.MessageFactory = message_factory.MessageFactory()

        @staticmethod
        def new_message(name: str) -> ProtobufMessageType:
            """Create new Protobuf message.

            Args:
                name (str): Message type name.

            Returns:
                object: Protobuf message.
            """
            cls = _mysqlxpb_pure.factory.GetPrototype(
                _DESCRIPTOR_POOL.FindMessageTypeByName(name)
            )
            return cls()

        @staticmethod
        def enum_value(enum_key: str) -> int:
            """Return enum value.

            Args:
                enum_key (str): Enum key.

            Returns:
                int: enum value.
            """
            return _MESSAGES[enum_key]

        @staticmethod
        def serialize_message(msg: ProtobufMessageType) -> bytes:
            """Serialize message.

            Args:
                msg (object): Protobuf message.

            Returns:
                bytes: Serialized message bytes string.
            """
            return msg.SerializeToString()

        @staticmethod
        def serialize_partial_message(msg: ProtobufMessageType) -> bytes:
            """Serialize partial message.

            Args:
                msg (object): Protobuf message.

            Returns:
                bytes: Serialized partial message bytes string.
            """
            return msg.SerializePartialToString()

        @staticmethod
        def parse_message(msg_type_name: str, payload: bytes) -> ProtobufMessageType:
            """Serialize partial message.

            Args:
                msg_type_name (str): Message type name.
                payload (bytes): Payload.

            Returns:
                object: Message parsed from string.
            """
            msg = _mysqlxpb_pure.new_message(msg_type_name)
            msg.ParseFromString(payload)
            return msg

        @staticmethod
        def parse_server_message(msg_type: int, payload: bytes) -> ProtobufMessageType:
            """Parse server message message.

            Args:
                msg_type (int): Message type.
                payload (bytes): Payload.

            Returns:
                object: Server message parsed from string.
            """
            msg_type_name = SERVER_MESSAGES.get(msg_type)
            if not msg_type_name:
                raise ValueError(f"Unknown msg_type: {msg_type}")
            msg = _mysqlxpb_pure.new_message(msg_type_name)
            msg.ParseFromString(payload)
            return msg

except (ImportError, SyntaxError, TypeError) as err:
    HAVE_PROTOBUF = False
    HAVE_PROTOBUF_ERROR = (
        err if PROTOBUF_VERSION is not None else "Protobuf >=3.11.0 is required"
    )
    if not HAVE_MYSQLXPB_CEXT:
        raise ImportError(f"Protobuf is not available: {HAVE_PROTOBUF_ERROR}") from err

CRUD_PREPARE_MAPPING: Dict[str, Tuple[str, str]] = {
    "Mysqlx.ClientMessages.Type.CRUD_FIND": (
        "Mysqlx.Prepare.Prepare.OneOfMessage.Type.FIND",
        "find",
    ),
    "Mysqlx.ClientMessages.Type.CRUD_INSERT": (
        "Mysqlx.Prepare.Prepare.OneOfMessage.Type.INSERT",
        "insert",
    ),
    "Mysqlx.ClientMessages.Type.CRUD_UPDATE": (
        "Mysqlx.Prepare.Prepare.OneOfMessage.Type.UPDATE",
        "update",
    ),
    "Mysqlx.ClientMessages.Type.CRUD_DELETE": (
        "Mysqlx.Prepare.Prepare.OneOfMessage.Type.DELETE",
        "delete",
    ),
    "Mysqlx.ClientMessages.Type.SQL_STMT_EXECUTE": (
        "Mysqlx.Prepare.Prepare.OneOfMessage.Type.STMT",
        "stmt_execute",
    ),
}


class Protobuf:
    """Protobuf class acts as a container of the Protobuf message class.

    It allows the switch between the C extension and pure Python implementation
    message handlers, by patching the `mysqlxpb` class attribute.
    """

    mysqlxpb = _mysqlxpb if HAVE_MYSQLXPB_CEXT else _mysqlxpb_pure
    use_pure: bool = not HAVE_MYSQLXPB_CEXT

    @staticmethod
    def set_use_pure(use_pure: bool) -> None:
        """Sets whether to use the C extension or pure Python implementation.

        Args:
            use_pure (bool): `True` to use pure Python implementation.
        """
        if use_pure and not HAVE_PROTOBUF:
            raise ImportError(f"Protobuf is not available: {HAVE_PROTOBUF_ERROR}")
        if not use_pure and not HAVE_MYSQLXPB_CEXT:
            raise ImportError("MySQL X Protobuf C extension is not available")
        Protobuf.mysqlxpb = _mysqlxpb_pure if use_pure else _mysqlxpb
        Protobuf.use_pure = use_pure


class Message:
    """Helper class for interfacing with the MySQL X Protobuf extension.

    Args:
        msg_type_name (string): Protobuf type name.
        **kwargs: Arbitrary keyword arguments with values for the message.
    """

    def __init__(self, msg_type_name: Optional[str] = None, **kwargs: Any) -> None:
        # _msg is a protobuf message instance when use_pure=True,
        # else is a dictionary instance.
        self.__dict__["_msg"] = (
            Protobuf.mysqlxpb.new_message(msg_type_name) if msg_type_name else None
        )
        for name, value in kwargs.items():
            self.__setattr__(name, value)

    def __setattr__(self, name: str, value: Any) -> None:
        if Protobuf.use_pure:
            if isinstance(value, str):
                setattr(self._msg, name, encode_to_bytes(value))
            elif isinstance(value, (NUMERIC_TYPES, BYTE_TYPES)):
                setattr(self._msg, name, value)
            elif isinstance(value, list):
                getattr(self._msg, name).extend(value)
            elif isinstance(value, Message):
                getattr(self._msg, name).MergeFrom(value.get_message())
            else:
                getattr(self._msg, name).MergeFrom(value)
        else:
            if isinstance(value, str):
                self._msg[name] = encode_to_bytes(value)
            else:
                self._msg[name] = (
                    value.get_message() if isinstance(value, Message) else value
                )

    def __getattr__(self, name: str) -> Any:
        try:
            return (
                self._msg[name] if not Protobuf.use_pure else getattr(self._msg, name)
            )
        except KeyError:
            raise AttributeError from None

    def __setitem__(self, name: str, value: Any) -> None:
        self.__setattr__(name, value)

    def __getitem__(self, name: str) -> Any:
        return self.__getattr__(name)

    def get(self, name: str, default: Any = None) -> Any:
        """Returns the value of an element of the message dictionary.

        Args:
            name (string): Key name.
            default (object): The default value if the key does not exists.

        Returns:
            object: The value of the provided key name.
        """
        return (
            self.__dict__["_msg"].get(name, default)
            if not Protobuf.use_pure
            else getattr(self.__dict__["_msg"], name, default)
        )

    def set_message(
        self, msg: Union[ProtobufMessageType, ProtobufMessageCextType]
    ) -> None:
        """Sets the message.

        Args:
            msg (dict): Dictionary representing a message.
        """
        self.__dict__["_msg"] = msg

    def get_message(self) -> Union[ProtobufMessageType, ProtobufMessageCextType]:
        """Returns the dictionary representing a message containing parsed
        data.

        Returns:
            dict: The dictionary representing a message containing parsed data.
        """
        return self.__dict__["_msg"]

    def serialize_to_string(self) -> bytes:
        """Serializes a message to a string.

        Returns:
            str: A string representing a message containing parsed data.
        """
        return Protobuf.mysqlxpb.serialize_message(self._msg)

    def serialize_partial_to_string(self) -> bytes:
        """Serializes the protocol message to a binary string.

        This method is similar to serialize_to_string but doesn't check if the
        message is initialized.

        Returns:
            str: A string representation of the partial message.
        """
        return Protobuf.mysqlxpb.serialize_partial_message(self._msg)

    @property
    def type(self) -> str:
        """string: Message type name."""
        return (
            self._msg["_mysqlxpb_type_name"]
            if not Protobuf.use_pure
            else self._msg.DESCRIPTOR.full_name
        )

    @staticmethod
    def parse(
        msg_type_name: str, payload: bytes
    ) -> Union[ProtobufMessageType, ProtobufMessageCextType]:
        """Creates a new message, initialized with parsed data.

        Args:
            msg_type_name (string): Message type name.
            payload (string): Serialized message data.

        Returns:
            dict: The dictionary representing a message containing parsed data.

        .. versionadded:: 8.0.21
        """
        return Protobuf.mysqlxpb.parse_message(msg_type_name, payload)

    @staticmethod
    def byte_size(msg: MessageType) -> int:
        """Returns the size of the message in bytes.

        Args:
            msg (mysqlx.protobuf.Message): MySQL X Protobuf Message.

        Returns:
            int: Size of the message in bytes.

        .. versionadded:: 8.0.21
        """
        return (
            msg.ByteSize()
            if Protobuf.use_pure
            else len(encode_to_bytes(msg.serialize_to_string()))
        )

    @staticmethod
    def parse_from_server(
        msg_type: int, payload: bytes
    ) -> Union[ProtobufMessageType, ProtobufMessageCextType]:
        """Creates a new server-side message, initialized with parsed data.

        Args:
            msg_type (int): Message type.
            payload (string): Serialized message data.

        Returns:
            dict: The dictionary representing a message containing parsed data.
        """
        return Protobuf.mysqlxpb.parse_server_message(msg_type, payload)

    @classmethod
    def from_message(cls, msg_type_name: str, payload: bytes) -> MessageType:
        """Creates a new message, initialized with parsed data and returns a
        :class:`mysqlx.protobuf.Message` object.

        Args:
            msg_type_name (string): Message type name.
            payload (string): Serialized message data.

        Returns:
            mysqlx.protobuf.Message: The Message representing a message
                                     containing parsed data.
        """
        msg = cls()
        msg.set_message(Protobuf.mysqlxpb.parse_message(msg_type_name, payload))
        return msg

    @classmethod
    def from_server_message(cls, msg_type: int, payload: bytes) -> MessageType:
        """Creates a new server-side message, initialized with parsed data and
        returns a :class:`mysqlx.protobuf.Message` object.

        Args:
            msg_type (int): Message type.
            payload (string): Serialized message data.

        Returns:
            mysqlx.protobuf.Message: The Message representing a message
                                     containing parsed data.
        """
        msg = cls()
        msg.set_message(Protobuf.mysqlxpb.parse_server_message(msg_type, payload))
        return msg


def mysqlxpb_enum(name: str) -> int:
    """Returns the value of a MySQL X Protobuf enumerator.

    Args:
        name (string): MySQL X Protobuf numerator name.

    Returns:
        int: Value of the enumerator.
    """
    return Protobuf.mysqlxpb.enum_value(name)

virt/lib/python3.9/site-packages/mysqlx/protobuf/__init__ 2.py
PrinceDisant/LostInNL
# Copyright (c) 2017, 2022, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

# mypy: disable-error-code="attr-defined,union-attr"

"""Implementation of a helper class for MySQL X Protobuf messages."""

# pylint: disable=c-extension-no-member, no-member

from __future__ import annotations

from typing import Any, Dict, List, Optional, Tuple, Type, Union

from ..types import MessageType, ProtobufMessageCextType, ProtobufMessageType

_SERVER_MESSAGES_TUPLES: Tuple[Tuple[str, str], ...] = (
    ("Mysqlx.ServerMessages.Type.OK", "Mysqlx.Ok"),
    ("Mysqlx.ServerMessages.Type.ERROR", "Mysqlx.Error"),
    (
        "Mysqlx.ServerMessages.Type.CONN_CAPABILITIES",
        "Mysqlx.Connection.Capabilities",
    ),
    (
        "Mysqlx.ServerMessages.Type.SESS_AUTHENTICATE_CONTINUE",
        "Mysqlx.Session.AuthenticateContinue",
    ),
    (
        "Mysqlx.ServerMessages.Type.SESS_AUTHENTICATE_OK",
        "Mysqlx.Session.AuthenticateOk",
    ),
    ("Mysqlx.ServerMessages.Type.NOTICE", "Mysqlx.Notice.Frame"),
    (
        "Mysqlx.ServerMessages.Type.RESULTSET_COLUMN_META_DATA",
        "Mysqlx.Resultset.ColumnMetaData",
    ),
    ("Mysqlx.ServerMessages.Type.RESULTSET_ROW", "Mysqlx.Resultset.Row"),
    (
        "Mysqlx.ServerMessages.Type.RESULTSET_FETCH_DONE",
        "Mysqlx.Resultset.FetchDone",
    ),
    (
        "Mysqlx.ServerMessages.Type.RESULTSET_FETCH_SUSPENDED",
        "Mysqlx.Resultset.FetchSuspended",
    ),
    (
        "Mysqlx.ServerMessages.Type.RESULTSET_FETCH_DONE_MORE_RESULTSETS",
        "Mysqlx.Resultset.FetchDoneMoreResultsets",
    ),
    (
        "Mysqlx.ServerMessages.Type.SQL_STMT_EXECUTE_OK",
        "Mysqlx.Sql.StmtExecuteOk",
    ),
    (
        "Mysqlx.ServerMessages.Type.RESULTSET_FETCH_DONE_MORE_OUT_PARAMS",
        "Mysqlx.Resultset.FetchDoneMoreOutParams",
    ),
    (
        "Mysqlx.ServerMessages.Type.COMPRESSION",
        "Mysqlx.Connection.Compression",
    ),
)

PROTOBUF_VERSION: Optional[str] = None
PROTOBUF_REPEATED_TYPES: List[
    Union[
        Type[List[Dict[str, Any]]],
        Type[RepeatedCompositeContainer],
        Type[RepeatedCompositeFieldContainer],
    ]
] = [list]

try:
    import _mysqlxpb

    SERVER_MESSAGES = {
        int(_mysqlxpb.enum_value(key)): val for key, val in _SERVER_MESSAGES_TUPLES
    }
    HAVE_MYSQLXPB_CEXT = True
except ImportError:
    HAVE_MYSQLXPB_CEXT = False

from ..helpers import BYTE_TYPES, NUMERIC_TYPES, encode_to_bytes

try:
    from google import protobuf
    from google.protobuf import (
        descriptor_database,
        descriptor_pb2,
        descriptor_pool,
        message_factory,
    )
    from google.protobuf.internal.containers import RepeatedCompositeFieldContainer

    try:
        from google.protobuf.pyext._message import RepeatedCompositeContainer

        PROTOBUF_REPEATED_TYPES.append(RepeatedCompositeContainer)
    except ImportError:
        pass

    PROTOBUF_REPEATED_TYPES.append(RepeatedCompositeFieldContainer)
    if hasattr(protobuf, "__version__"):
        # Only Protobuf versions >=3.0.0 provide `__version__`
        PROTOBUF_VERSION = protobuf.__version__

    from . import (
        mysqlx_connection_pb2,
        mysqlx_crud_pb2,
        mysqlx_cursor_pb2,
        mysqlx_datatypes_pb2,
        mysqlx_expect_pb2,
        mysqlx_expr_pb2,
        mysqlx_notice_pb2,
        mysqlx_pb2,
        mysqlx_prepare_pb2,
        mysqlx_resultset_pb2,
        mysqlx_session_pb2,
        mysqlx_sql_pb2,
    )

    # Dictionary with all messages descriptors
    _MESSAGES: Dict[str, int] = {}

    # Mysqlx
    for key, val in mysqlx_pb2.ClientMessages.Type.items():
        _MESSAGES[f"Mysqlx.ClientMessages.Type.{key}"] = val
    for key, val in mysqlx_pb2.ServerMessages.Type.items():
        _MESSAGES[f"Mysqlx.ServerMessages.Type.{key}"] = val
    for key, val in mysqlx_pb2.Error.Severity.items():
        _MESSAGES[f"Mysqlx.Error.Severity.{key}"] = val

    # Mysqlx.Crud
    for key, val in mysqlx_crud_pb2.DataModel.items():
        _MESSAGES[f"Mysqlx.Crud.DataModel.{key}"] = val
    for key, val in mysqlx_crud_pb2.Find.RowLock.items():
        _MESSAGES[f"Mysqlx.Crud.Find.RowLock.{key}"] = val
    for key, val in mysqlx_crud_pb2.Order.Direction.items():
        _MESSAGES[f"Mysqlx.Crud.Order.Direction.{key}"] = val
    for key, val in mysqlx_crud_pb2.UpdateOperation.UpdateType.items():
        _MESSAGES[f"Mysqlx.Crud.UpdateOperation.UpdateType.{key}"] = val

    # Mysqlx.Datatypes
    for key, val in mysqlx_datatypes_pb2.Scalar.Type.items():
        _MESSAGES[f"Mysqlx.Datatypes.Scalar.Type.{key}"] = val
    for key, val in mysqlx_datatypes_pb2.Any.Type.items():
        _MESSAGES[f"Mysqlx.Datatypes.Any.Type.{key}"] = val

    # Mysqlx.Expect
    for key, val in mysqlx_expect_pb2.Open.Condition.ConditionOperation.items():
        _MESSAGES[f"Mysqlx.Expect.Open.Condition.ConditionOperation.{key}"] = val
    for key, val in mysqlx_expect_pb2.Open.Condition.Key.items():
        _MESSAGES[f"Mysqlx.Expect.Open.Condition.Key.{key}"] = val
    for key, val in mysqlx_expect_pb2.Open.CtxOperation.items():
        _MESSAGES[f"Mysqlx.Expect.Open.CtxOperation.{key}"] = val

    # Mysqlx.Expr
    for key, val in mysqlx_expr_pb2.Expr.Type.items():
        _MESSAGES[f"Mysqlx.Expr.Expr.Type.{key}"] = val
    for key, val in mysqlx_expr_pb2.DocumentPathItem.Type.items():
        _MESSAGES[f"Mysqlx.Expr.DocumentPathItem.Type.{key}"] = val

    # Mysqlx.Notice
    for key, val in mysqlx_notice_pb2.Frame.Scope.items():
        _MESSAGES[f"Mysqlx.Notice.Frame.Scope.{key}"] = val
    for key, val in mysqlx_notice_pb2.Warning.Level.items():
        _MESSAGES[f"Mysqlx.Notice.Warning.Level.{key}"] = val
    for key, val in mysqlx_notice_pb2.SessionStateChanged.Parameter.items():
        _MESSAGES[f"Mysqlx.Notice.SessionStateChanged.Parameter.{key}"] = val

    # Mysql.Prepare
    for key, val in mysqlx_prepare_pb2.Prepare.OneOfMessage.Type.items():
        _MESSAGES[f"Mysqlx.Prepare.Prepare.OneOfMessage.Type.{key}"] = val

    # Mysql.Resultset
    for key, val in mysqlx_resultset_pb2.ColumnMetaData.FieldType.items():
        _MESSAGES[f"Mysqlx.Resultset.ColumnMetaData.FieldType.{key}"] = val

    # Add messages to the descriptor pool
    _DESCRIPTOR_DB = descriptor_database.DescriptorDatabase()
    _DESCRIPTOR_POOL = descriptor_pool.DescriptorPool(_DESCRIPTOR_DB)

    _DESCRIPTOR_DB.Add(
        descriptor_pb2.FileDescriptorProto.FromString(
            mysqlx_connection_pb2.DESCRIPTOR.serialized_pb
        )
    )
    _DESCRIPTOR_DB.Add(
        descriptor_pb2.FileDescriptorProto.FromString(
            mysqlx_crud_pb2.DESCRIPTOR.serialized_pb
        )
    )
    _DESCRIPTOR_DB.Add(
        descriptor_pb2.FileDescriptorProto.FromString(
            mysqlx_cursor_pb2.DESCRIPTOR.serialized_pb
        )
    )
    _DESCRIPTOR_DB.Add(
        descriptor_pb2.FileDescriptorProto.FromString(
            mysqlx_datatypes_pb2.DESCRIPTOR.serialized_pb
        )
    )
    _DESCRIPTOR_DB.Add(
        descriptor_pb2.FileDescriptorProto.FromString(
            mysqlx_expect_pb2.DESCRIPTOR.serialized_pb
        )
    )
    _DESCRIPTOR_DB.Add(
        descriptor_pb2.FileDescriptorProto.FromString(
            mysqlx_expr_pb2.DESCRIPTOR.serialized_pb
        )
    )
    _DESCRIPTOR_DB.Add(
        descriptor_pb2.FileDescriptorProto.FromString(
            mysqlx_notice_pb2.DESCRIPTOR.serialized_pb
        )
    )
    _DESCRIPTOR_DB.Add(
        descriptor_pb2.FileDescriptorProto.FromString(
            mysqlx_pb2.DESCRIPTOR.serialized_pb
        )
    )
    _DESCRIPTOR_DB.Add(
        descriptor_pb2.FileDescriptorProto.FromString(
            mysqlx_prepare_pb2.DESCRIPTOR.serialized_pb
        )
    )
    _DESCRIPTOR_DB.Add(
        descriptor_pb2.FileDescriptorProto.FromString(
            mysqlx_resultset_pb2.DESCRIPTOR.serialized_pb
        )
    )
    _DESCRIPTOR_DB.Add(
        descriptor_pb2.FileDescriptorProto.FromString(
            mysqlx_session_pb2.DESCRIPTOR.serialized_pb
        )
    )
    _DESCRIPTOR_DB.Add(
        descriptor_pb2.FileDescriptorProto.FromString(
            mysqlx_sql_pb2.DESCRIPTOR.serialized_pb
        )
    )

    SERVER_MESSAGES = {_MESSAGES[key]: val for key, val in _SERVER_MESSAGES_TUPLES}
    HAVE_PROTOBUF = True
    HAVE_PROTOBUF_ERROR = None

    class _mysqlxpb_pure:  # pylint: disable=invalid-name
        """This class implements the methods in pure Python used by the
        _mysqlxpb C++ extension."""

        factory: message_factory.MessageFactory = message_factory.MessageFactory()

        @staticmethod
        def new_message(name: str) -> ProtobufMessageType:
            """Create new Protobuf message.

            Args:
                name (str): Message type name.

            Returns:
                object: Protobuf message.
            """
            cls = _mysqlxpb_pure.factory.GetPrototype(
                _DESCRIPTOR_POOL.FindMessageTypeByName(name)
            )
            return cls()

        @staticmethod
        def enum_value(enum_key: str) -> int:
            """Return enum value.

            Args:
                enum_key (str): Enum key.

            Returns:
                int: enum value.
            """
            return _MESSAGES[enum_key]

        @staticmethod
        def serialize_message(msg: ProtobufMessageType) -> bytes:
            """Serialize message.

            Args:
                msg (object): Protobuf message.

            Returns:
                bytes: Serialized message bytes string.
            """
            return msg.SerializeToString()

        @staticmethod
        def serialize_partial_message(msg: ProtobufMessageType) -> bytes:
            """Serialize partial message.

            Args:
                msg (object): Protobuf message.

            Returns:
                bytes: Serialized partial message bytes string.
            """
            return msg.SerializePartialToString()

        @staticmethod
        def parse_message(msg_type_name: str, payload: bytes) -> ProtobufMessageType:
            """Serialize partial message.

            Args:
                msg_type_name (str): Message type name.
                payload (bytes): Payload.

            Returns:
                object: Message parsed from string.
            """
            msg = _mysqlxpb_pure.new_message(msg_type_name)
            msg.ParseFromString(payload)
            return msg

        @staticmethod
        def parse_server_message(msg_type: int, payload: bytes) -> ProtobufMessageType:
            """Parse server message message.

            Args:
                msg_type (int): Message type.
                payload (bytes): Payload.

            Returns:
                object: Server message parsed from string.
            """
            msg_type_name = SERVER_MESSAGES.get(msg_type)
            if not msg_type_name:
                raise ValueError(f"Unknown msg_type: {msg_type}")
            msg = _mysqlxpb_pure.new_message(msg_type_name)
            msg.ParseFromString(payload)
            return msg

except (ImportError, SyntaxError, TypeError) as err:
    HAVE_PROTOBUF = False
    HAVE_PROTOBUF_ERROR = (
        err if PROTOBUF_VERSION is not None else "Protobuf >=3.11.0 is required"
    )
    if not HAVE_MYSQLXPB_CEXT:
        raise ImportError(f"Protobuf is not available: {HAVE_PROTOBUF_ERROR}") from err

CRUD_PREPARE_MAPPING: Dict[str, Tuple[str, str]] = {
    "Mysqlx.ClientMessages.Type.CRUD_FIND": (
        "Mysqlx.Prepare.Prepare.OneOfMessage.Type.FIND",
        "find",
    ),
    "Mysqlx.ClientMessages.Type.CRUD_INSERT": (
        "Mysqlx.Prepare.Prepare.OneOfMessage.Type.INSERT",
        "insert",
    ),
    "Mysqlx.ClientMessages.Type.CRUD_UPDATE": (
        "Mysqlx.Prepare.Prepare.OneOfMessage.Type.UPDATE",
        "update",
    ),
    "Mysqlx.ClientMessages.Type.CRUD_DELETE": (
        "Mysqlx.Prepare.Prepare.OneOfMessage.Type.DELETE",
        "delete",
    ),
    "Mysqlx.ClientMessages.Type.SQL_STMT_EXECUTE": (
        "Mysqlx.Prepare.Prepare.OneOfMessage.Type.STMT",
        "stmt_execute",
    ),
}


class Protobuf:
    """Protobuf class acts as a container of the Protobuf message class.

    It allows the switch between the C extension and pure Python implementation
    message handlers, by patching the `mysqlxpb` class attribute.
    """

    mysqlxpb = _mysqlxpb if HAVE_MYSQLXPB_CEXT else _mysqlxpb_pure
    use_pure: bool = not HAVE_MYSQLXPB_CEXT

    @staticmethod
    def set_use_pure(use_pure: bool) -> None:
        """Sets whether to use the C extension or pure Python implementation.

        Args:
            use_pure (bool): `True` to use pure Python implementation.
        """
        if use_pure and not HAVE_PROTOBUF:
            raise ImportError(f"Protobuf is not available: {HAVE_PROTOBUF_ERROR}")
        if not use_pure and not HAVE_MYSQLXPB_CEXT:
            raise ImportError("MySQL X Protobuf C extension is not available")
        Protobuf.mysqlxpb = _mysqlxpb_pure if use_pure else _mysqlxpb
        Protobuf.use_pure = use_pure


class Message:
    """Helper class for interfacing with the MySQL X Protobuf extension.

    Args:
        msg_type_name (string): Protobuf type name.
        **kwargs: Arbitrary keyword arguments with values for the message.
    """

    def __init__(self, msg_type_name: Optional[str] = None, **kwargs: Any) -> None:
        # _msg is a protobuf message instance when use_pure=True,
        # else is a dictionary instance.
        self.__dict__["_msg"] = (
            Protobuf.mysqlxpb.new_message(msg_type_name) if msg_type_name else None
        )
        for name, value in kwargs.items():
            self.__setattr__(name, value)

    def __setattr__(self, name: str, value: Any) -> None:
        if Protobuf.use_pure:
            if isinstance(value, str):
                setattr(self._msg, name, encode_to_bytes(value))
            elif isinstance(value, (NUMERIC_TYPES, BYTE_TYPES)):
                setattr(self._msg, name, value)
            elif isinstance(value, list):
                getattr(self._msg, name).extend(value)
            elif isinstance(value, Message):
                getattr(self._msg, name).MergeFrom(value.get_message())
            else:
                getattr(self._msg, name).MergeFrom(value)
        else:
            if isinstance(value, str):
                self._msg[name] = encode_to_bytes(value)
            else:
                self._msg[name] = (
                    value.get_message() if isinstance(value, Message) else value
                )

    def __getattr__(self, name: str) -> Any:
        try:
            return (
                self._msg[name] if not Protobuf.use_pure else getattr(self._msg, name)
            )
        except KeyError:
            raise AttributeError from None

    def __setitem__(self, name: str, value: Any) -> None:
        self.__setattr__(name, value)

    def __getitem__(self, name: str) -> Any:
        return self.__getattr__(name)

    def get(self, name: str, default: Any = None) -> Any:
        """Returns the value of an element of the message dictionary.

        Args:
            name (string): Key name.
            default (object): The default value if the key does not exists.

        Returns:
            object: The value of the provided key name.
        """
        return (
            self.__dict__["_msg"].get(name, default)
            if not Protobuf.use_pure
            else getattr(self.__dict__["_msg"], name, default)
        )

    def set_message(
        self, msg: Union[ProtobufMessageType, ProtobufMessageCextType]
    ) -> None:
        """Sets the message.

        Args:
            msg (dict): Dictionary representing a message.
        """
        self.__dict__["_msg"] = msg

    def get_message(self) -> Union[ProtobufMessageType, ProtobufMessageCextType]:
        """Returns the dictionary representing a message containing parsed
        data.

        Returns:
            dict: The dictionary representing a message containing parsed data.
        """
        return self.__dict__["_msg"]

    def serialize_to_string(self) -> bytes:
        """Serializes a message to a string.

        Returns:
            str: A string representing a message containing parsed data.
        """
        return Protobuf.mysqlxpb.serialize_message(self._msg)

    def serialize_partial_to_string(self) -> bytes:
        """Serializes the protocol message to a binary string.

        This method is similar to serialize_to_string but doesn't check if the
        message is initialized.

        Returns:
            str: A string representation of the partial message.
        """
        return Protobuf.mysqlxpb.serialize_partial_message(self._msg)

    @property
    def type(self) -> str:
        """string: Message type name."""
        return (
            self._msg["_mysqlxpb_type_name"]
            if not Protobuf.use_pure
            else self._msg.DESCRIPTOR.full_name
        )

    @staticmethod
    def parse(
        msg_type_name: str, payload: bytes
    ) -> Union[ProtobufMessageType, ProtobufMessageCextType]:
        """Creates a new message, initialized with parsed data.

        Args:
            msg_type_name (string): Message type name.
            payload (string): Serialized message data.

        Returns:
            dict: The dictionary representing a message containing parsed data.

        .. versionadded:: 8.0.21
        """
        return Protobuf.mysqlxpb.parse_message(msg_type_name, payload)

    @staticmethod
    def byte_size(msg: MessageType) -> int:
        """Returns the size of the message in bytes.

        Args:
            msg (mysqlx.protobuf.Message): MySQL X Protobuf Message.

        Returns:
            int: Size of the message in bytes.

        .. versionadded:: 8.0.21
        """
        return (
            msg.ByteSize()
            if Protobuf.use_pure
            else len(encode_to_bytes(msg.serialize_to_string()))
        )

    @staticmethod
    def parse_from_server(
        msg_type: int, payload: bytes
    ) -> Union[ProtobufMessageType, ProtobufMessageCextType]:
        """Creates a new server-side message, initialized with parsed data.

        Args:
            msg_type (int): Message type.
            payload (string): Serialized message data.

        Returns:
            dict: The dictionary representing a message containing parsed data.
        """
        return Protobuf.mysqlxpb.parse_server_message(msg_type, payload)

    @classmethod
    def from_message(cls, msg_type_name: str, payload: bytes) -> MessageType:
        """Creates a new message, initialized with parsed data and returns a
        :class:`mysqlx.protobuf.Message` object.

        Args:
            msg_type_name (string): Message type name.
            payload (string): Serialized message data.

        Returns:
            mysqlx.protobuf.Message: The Message representing a message
                                     containing parsed data.
        """
        msg = cls()
        msg.set_message(Protobuf.mysqlxpb.parse_message(msg_type_name, payload))
        return msg

    @classmethod
    def from_server_message(cls, msg_type: int, payload: bytes) -> MessageType:
        """Creates a new server-side message, initialized with parsed data and
        returns a :class:`mysqlx.protobuf.Message` object.

        Args:
            msg_type (int): Message type.
            payload (string): Serialized message data.

        Returns:
            mysqlx.protobuf.Message: The Message representing a message
                                     containing parsed data.
        """
        msg = cls()
        msg.set_message(Protobuf.mysqlxpb.parse_server_message(msg_type, payload))
        return msg


def mysqlxpb_enum(name: str) -> int:
    """Returns the value of a MySQL X Protobuf enumerator.

    Args:
        name (string): MySQL X Protobuf numerator name.

    Returns:
        int: Value of the enumerator.
    """
    return Protobuf.mysqlxpb.enum_value(name)

virt/lib/python3.9/site-packages/mysqlx/protobuf/__init__ 3.py
PrinceDisant/LostInNL
# Copyright (c) 2017, 2022, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

# mypy: disable-error-code="attr-defined,union-attr"

"""Implementation of a helper class for MySQL X Protobuf messages."""

# pylint: disable=c-extension-no-member, no-member

from __future__ import annotations

from typing import Any, Dict, List, Optional, Tuple, Type, Union

from ..types import MessageType, ProtobufMessageCextType, ProtobufMessageType

_SERVER_MESSAGES_TUPLES: Tuple[Tuple[str, str], ...] = (
    ("Mysqlx.ServerMessages.Type.OK", "Mysqlx.Ok"),
    ("Mysqlx.ServerMessages.Type.ERROR", "Mysqlx.Error"),
    (
        "Mysqlx.ServerMessages.Type.CONN_CAPABILITIES",
        "Mysqlx.Connection.Capabilities",
    ),
    (
        "Mysqlx.ServerMessages.Type.SESS_AUTHENTICATE_CONTINUE",
        "Mysqlx.Session.AuthenticateContinue",
    ),
    (
        "Mysqlx.ServerMessages.Type.SESS_AUTHENTICATE_OK",
        "Mysqlx.Session.AuthenticateOk",
    ),
    ("Mysqlx.ServerMessages.Type.NOTICE", "Mysqlx.Notice.Frame"),
    (
        "Mysqlx.ServerMessages.Type.RESULTSET_COLUMN_META_DATA",
        "Mysqlx.Resultset.ColumnMetaData",
    ),
    ("Mysqlx.ServerMessages.Type.RESULTSET_ROW", "Mysqlx.Resultset.Row"),
    (
        "Mysqlx.ServerMessages.Type.RESULTSET_FETCH_DONE",
        "Mysqlx.Resultset.FetchDone",
    ),
    (
        "Mysqlx.ServerMessages.Type.RESULTSET_FETCH_SUSPENDED",
        "Mysqlx.Resultset.FetchSuspended",
    ),
    (
        "Mysqlx.ServerMessages.Type.RESULTSET_FETCH_DONE_MORE_RESULTSETS",
        "Mysqlx.Resultset.FetchDoneMoreResultsets",
    ),
    (
        "Mysqlx.ServerMessages.Type.SQL_STMT_EXECUTE_OK",
        "Mysqlx.Sql.StmtExecuteOk",
    ),
    (
        "Mysqlx.ServerMessages.Type.RESULTSET_FETCH_DONE_MORE_OUT_PARAMS",
        "Mysqlx.Resultset.FetchDoneMoreOutParams",
    ),
    (
        "Mysqlx.ServerMessages.Type.COMPRESSION",
        "Mysqlx.Connection.Compression",
    ),
)

PROTOBUF_VERSION: Optional[str] = None
PROTOBUF_REPEATED_TYPES: List[
    Union[
        Type[List[Dict[str, Any]]],
        Type[RepeatedCompositeContainer],
        Type[RepeatedCompositeFieldContainer],
    ]
] = [list]

try:
    import _mysqlxpb

    SERVER_MESSAGES = {
        int(_mysqlxpb.enum_value(key)): val for key, val in _SERVER_MESSAGES_TUPLES
    }
    HAVE_MYSQLXPB_CEXT = True
except ImportError:
    HAVE_MYSQLXPB_CEXT = False

from ..helpers import BYTE_TYPES, NUMERIC_TYPES, encode_to_bytes

try:
    from google import protobuf
    from google.protobuf import (
        descriptor_database,
        descriptor_pb2,
        descriptor_pool,
        message_factory,
    )
    from google.protobuf.internal.containers import RepeatedCompositeFieldContainer

    try:
        from google.protobuf.pyext._message import RepeatedCompositeContainer

        PROTOBUF_REPEATED_TYPES.append(RepeatedCompositeContainer)
    except ImportError:
        pass

    PROTOBUF_REPEATED_TYPES.append(RepeatedCompositeFieldContainer)
    if hasattr(protobuf, "__version__"):
        # Only Protobuf versions >=3.0.0 provide `__version__`
        PROTOBUF_VERSION = protobuf.__version__

    from . import (
        mysqlx_connection_pb2,
        mysqlx_crud_pb2,
        mysqlx_cursor_pb2,
        mysqlx_datatypes_pb2,
        mysqlx_expect_pb2,
        mysqlx_expr_pb2,
        mysqlx_notice_pb2,
        mysqlx_pb2,
        mysqlx_prepare_pb2,
        mysqlx_resultset_pb2,
        mysqlx_session_pb2,
        mysqlx_sql_pb2,
    )

    # Dictionary with all messages descriptors
    _MESSAGES: Dict[str, int] = {}

    # Mysqlx
    for key, val in mysqlx_pb2.ClientMessages.Type.items():
        _MESSAGES[f"Mysqlx.ClientMessages.Type.{key}"] = val
    for key, val in mysqlx_pb2.ServerMessages.Type.items():
        _MESSAGES[f"Mysqlx.ServerMessages.Type.{key}"] = val
    for key, val in mysqlx_pb2.Error.Severity.items():
        _MESSAGES[f"Mysqlx.Error.Severity.{key}"] = val

    # Mysqlx.Crud
    for key, val in mysqlx_crud_pb2.DataModel.items():
        _MESSAGES[f"Mysqlx.Crud.DataModel.{key}"] = val
    for key, val in mysqlx_crud_pb2.Find.RowLock.items():
        _MESSAGES[f"Mysqlx.Crud.Find.RowLock.{key}"] = val
    for key, val in mysqlx_crud_pb2.Order.Direction.items():
        _MESSAGES[f"Mysqlx.Crud.Order.Direction.{key}"] = val
    for key, val in mysqlx_crud_pb2.UpdateOperation.UpdateType.items():
        _MESSAGES[f"Mysqlx.Crud.UpdateOperation.UpdateType.{key}"] = val

    # Mysqlx.Datatypes
    for key, val in mysqlx_datatypes_pb2.Scalar.Type.items():
        _MESSAGES[f"Mysqlx.Datatypes.Scalar.Type.{key}"] = val
    for key, val in mysqlx_datatypes_pb2.Any.Type.items():
        _MESSAGES[f"Mysqlx.Datatypes.Any.Type.{key}"] = val

    # Mysqlx.Expect
    for key, val in mysqlx_expect_pb2.Open.Condition.ConditionOperation.items():
        _MESSAGES[f"Mysqlx.Expect.Open.Condition.ConditionOperation.{key}"] = val
    for key, val in mysqlx_expect_pb2.Open.Condition.Key.items():
        _MESSAGES[f"Mysqlx.Expect.Open.Condition.Key.{key}"] = val
    for key, val in mysqlx_expect_pb2.Open.CtxOperation.items():
        _MESSAGES[f"Mysqlx.Expect.Open.CtxOperation.{key}"] = val

    # Mysqlx.Expr
    for key, val in mysqlx_expr_pb2.Expr.Type.items():
        _MESSAGES[f"Mysqlx.Expr.Expr.Type.{key}"] = val
    for key, val in mysqlx_expr_pb2.DocumentPathItem.Type.items():
        _MESSAGES[f"Mysqlx.Expr.DocumentPathItem.Type.{key}"] = val

    # Mysqlx.Notice
    for key, val in mysqlx_notice_pb2.Frame.Scope.items():
        _MESSAGES[f"Mysqlx.Notice.Frame.Scope.{key}"] = val
    for key, val in mysqlx_notice_pb2.Warning.Level.items():
        _MESSAGES[f"Mysqlx.Notice.Warning.Level.{key}"] = val
    for key, val in mysqlx_notice_pb2.SessionStateChanged.Parameter.items():
        _MESSAGES[f"Mysqlx.Notice.SessionStateChanged.Parameter.{key}"] = val

    # Mysql.Prepare
    for key, val in mysqlx_prepare_pb2.Prepare.OneOfMessage.Type.items():
        _MESSAGES[f"Mysqlx.Prepare.Prepare.OneOfMessage.Type.{key}"] = val

    # Mysql.Resultset
    for key, val in mysqlx_resultset_pb2.ColumnMetaData.FieldType.items():
        _MESSAGES[f"Mysqlx.Resultset.ColumnMetaData.FieldType.{key}"] = val

    # Add messages to the descriptor pool
    _DESCRIPTOR_DB = descriptor_database.DescriptorDatabase()
    _DESCRIPTOR_POOL = descriptor_pool.DescriptorPool(_DESCRIPTOR_DB)

    _DESCRIPTOR_DB.Add(
        descriptor_pb2.FileDescriptorProto.FromString(
            mysqlx_connection_pb2.DESCRIPTOR.serialized_pb
        )
    )
    _DESCRIPTOR_DB.Add(
        descriptor_pb2.FileDescriptorProto.FromString(
            mysqlx_crud_pb2.DESCRIPTOR.serialized_pb
        )
    )
    _DESCRIPTOR_DB.Add(
        descriptor_pb2.FileDescriptorProto.FromString(
            mysqlx_cursor_pb2.DESCRIPTOR.serialized_pb
        )
    )
    _DESCRIPTOR_DB.Add(
        descriptor_pb2.FileDescriptorProto.FromString(
            mysqlx_datatypes_pb2.DESCRIPTOR.serialized_pb
        )
    )
    _DESCRIPTOR_DB.Add(
        descriptor_pb2.FileDescriptorProto.FromString(
            mysqlx_expect_pb2.DESCRIPTOR.serialized_pb
        )
    )
    _DESCRIPTOR_DB.Add(
        descriptor_pb2.FileDescriptorProto.FromString(
            mysqlx_expr_pb2.DESCRIPTOR.serialized_pb
        )
    )
    _DESCRIPTOR_DB.Add(
        descriptor_pb2.FileDescriptorProto.FromString(
            mysqlx_notice_pb2.DESCRIPTOR.serialized_pb
        )
    )
    _DESCRIPTOR_DB.Add(
        descriptor_pb2.FileDescriptorProto.FromString(
            mysqlx_pb2.DESCRIPTOR.serialized_pb
        )
    )
    _DESCRIPTOR_DB.Add(
        descriptor_pb2.FileDescriptorProto.FromString(
            mysqlx_prepare_pb2.DESCRIPTOR.serialized_pb
        )
    )
    _DESCRIPTOR_DB.Add(
        descriptor_pb2.FileDescriptorProto.FromString(
            mysqlx_resultset_pb2.DESCRIPTOR.serialized_pb
        )
    )
    _DESCRIPTOR_DB.Add(
        descriptor_pb2.FileDescriptorProto.FromString(
            mysqlx_session_pb2.DESCRIPTOR.serialized_pb
        )
    )
    _DESCRIPTOR_DB.Add(
        descriptor_pb2.FileDescriptorProto.FromString(
            mysqlx_sql_pb2.DESCRIPTOR.serialized_pb
        )
    )

    SERVER_MESSAGES = {_MESSAGES[key]: val for key, val in _SERVER_MESSAGES_TUPLES}
    HAVE_PROTOBUF = True
    HAVE_PROTOBUF_ERROR = None

    class _mysqlxpb_pure:  # pylint: disable=invalid-name
        """This class implements the methods in pure Python used by the
        _mysqlxpb C++ extension."""

        factory: message_factory.MessageFactory = message_factory.MessageFactory()

        @staticmethod
        def new_message(name: str) -> ProtobufMessageType:
            """Create new Protobuf message.

            Args:
                name (str): Message type name.

            Returns:
                object: Protobuf message.
            """
            cls = _mysqlxpb_pure.factory.GetPrototype(
                _DESCRIPTOR_POOL.FindMessageTypeByName(name)
            )
            return cls()

        @staticmethod
        def enum_value(enum_key: str) -> int:
            """Return enum value.

            Args:
                enum_key (str): Enum key.

            Returns:
                int: enum value.
            """
            return _MESSAGES[enum_key]

        @staticmethod
        def serialize_message(msg: ProtobufMessageType) -> bytes:
            """Serialize message.

            Args:
                msg (object): Protobuf message.

            Returns:
                bytes: Serialized message bytes string.
            """
            return msg.SerializeToString()

        @staticmethod
        def serialize_partial_message(msg: ProtobufMessageType) -> bytes:
            """Serialize partial message.

            Args:
                msg (object): Protobuf message.

            Returns:
                bytes: Serialized partial message bytes string.
            """
            return msg.SerializePartialToString()

        @staticmethod
        def parse_message(msg_type_name: str, payload: bytes) -> ProtobufMessageType:
            """Serialize partial message.

            Args:
                msg_type_name (str): Message type name.
                payload (bytes): Payload.

            Returns:
                object: Message parsed from string.
            """
            msg = _mysqlxpb_pure.new_message(msg_type_name)
            msg.ParseFromString(payload)
            return msg

        @staticmethod
        def parse_server_message(msg_type: int, payload: bytes) -> ProtobufMessageType:
            """Parse server message message.

            Args:
                msg_type (int): Message type.
                payload (bytes): Payload.

            Returns:
                object: Server message parsed from string.
            """
            msg_type_name = SERVER_MESSAGES.get(msg_type)
            if not msg_type_name:
                raise ValueError(f"Unknown msg_type: {msg_type}")
            msg = _mysqlxpb_pure.new_message(msg_type_name)
            msg.ParseFromString(payload)
            return msg

except (ImportError, SyntaxError, TypeError) as err:
    HAVE_PROTOBUF = False
    HAVE_PROTOBUF_ERROR = (
        err if PROTOBUF_VERSION is not None else "Protobuf >=3.11.0 is required"
    )
    if not HAVE_MYSQLXPB_CEXT:
        raise ImportError(f"Protobuf is not available: {HAVE_PROTOBUF_ERROR}") from err

CRUD_PREPARE_MAPPING: Dict[str, Tuple[str, str]] = {
    "Mysqlx.ClientMessages.Type.CRUD_FIND": (
        "Mysqlx.Prepare.Prepare.OneOfMessage.Type.FIND",
        "find",
    ),
    "Mysqlx.ClientMessages.Type.CRUD_INSERT": (
        "Mysqlx.Prepare.Prepare.OneOfMessage.Type.INSERT",
        "insert",
    ),
    "Mysqlx.ClientMessages.Type.CRUD_UPDATE": (
        "Mysqlx.Prepare.Prepare.OneOfMessage.Type.UPDATE",
        "update",
    ),
    "Mysqlx.ClientMessages.Type.CRUD_DELETE": (
        "Mysqlx.Prepare.Prepare.OneOfMessage.Type.DELETE",
        "delete",
    ),
    "Mysqlx.ClientMessages.Type.SQL_STMT_EXECUTE": (
        "Mysqlx.Prepare.Prepare.OneOfMessage.Type.STMT",
        "stmt_execute",
    ),
}


class Protobuf:
    """Protobuf class acts as a container of the Protobuf message class.

    It allows the switch between the C extension and pure Python implementation
    message handlers, by patching the `mysqlxpb` class attribute.
    """

    mysqlxpb = _mysqlxpb if HAVE_MYSQLXPB_CEXT else _mysqlxpb_pure
    use_pure: bool = not HAVE_MYSQLXPB_CEXT

    @staticmethod
    def set_use_pure(use_pure: bool) -> None:
        """Sets whether to use the C extension or pure Python implementation.

        Args:
            use_pure (bool): `True` to use pure Python implementation.
        """
        if use_pure and not HAVE_PROTOBUF:
            raise ImportError(f"Protobuf is not available: {HAVE_PROTOBUF_ERROR}")
        if not use_pure and not HAVE_MYSQLXPB_CEXT:
            raise ImportError("MySQL X Protobuf C extension is not available")
        Protobuf.mysqlxpb = _mysqlxpb_pure if use_pure else _mysqlxpb
        Protobuf.use_pure = use_pure


class Message:
    """Helper class for interfacing with the MySQL X Protobuf extension.

    Args:
        msg_type_name (string): Protobuf type name.
        **kwargs: Arbitrary keyword arguments with values for the message.
    """

    def __init__(self, msg_type_name: Optional[str] = None, **kwargs: Any) -> None:
        # _msg is a protobuf message instance when use_pure=True,
        # else is a dictionary instance.
        self.__dict__["_msg"] = (
            Protobuf.mysqlxpb.new_message(msg_type_name) if msg_type_name else None
        )
        for name, value in kwargs.items():
            self.__setattr__(name, value)

    def __setattr__(self, name: str, value: Any) -> None:
        if Protobuf.use_pure:
            if isinstance(value, str):
                setattr(self._msg, name, encode_to_bytes(value))
            elif isinstance(value, (NUMERIC_TYPES, BYTE_TYPES)):
                setattr(self._msg, name, value)
            elif isinstance(value, list):
                getattr(self._msg, name).extend(value)
            elif isinstance(value, Message):
                getattr(self._msg, name).MergeFrom(value.get_message())
            else:
                getattr(self._msg, name).MergeFrom(value)
        else:
            if isinstance(value, str):
                self._msg[name] = encode_to_bytes(value)
            else:
                self._msg[name] = (
                    value.get_message() if isinstance(value, Message) else value
                )

    def __getattr__(self, name: str) -> Any:
        try:
            return (
                self._msg[name] if not Protobuf.use_pure else getattr(self._msg, name)
            )
        except KeyError:
            raise AttributeError from None

    def __setitem__(self, name: str, value: Any) -> None:
        self.__setattr__(name, value)

    def __getitem__(self, name: str) -> Any:
        return self.__getattr__(name)

    def get(self, name: str, default: Any = None) -> Any:
        """Returns the value of an element of the message dictionary.

        Args:
            name (string): Key name.
            default (object): The default value if the key does not exists.

        Returns:
            object: The value of the provided key name.
        """
        return (
            self.__dict__["_msg"].get(name, default)
            if not Protobuf.use_pure
            else getattr(self.__dict__["_msg"], name, default)
        )

    def set_message(
        self, msg: Union[ProtobufMessageType, ProtobufMessageCextType]
    ) -> None:
        """Sets the message.

        Args:
            msg (dict): Dictionary representing a message.
        """
        self.__dict__["_msg"] = msg

    def get_message(self) -> Union[ProtobufMessageType, ProtobufMessageCextType]:
        """Returns the dictionary representing a message containing parsed
        data.

        Returns:
            dict: The dictionary representing a message containing parsed data.
        """
        return self.__dict__["_msg"]

    def serialize_to_string(self) -> bytes:
        """Serializes a message to a string.

        Returns:
            str: A string representing a message containing parsed data.
        """
        return Protobuf.mysqlxpb.serialize_message(self._msg)

    def serialize_partial_to_string(self) -> bytes:
        """Serializes the protocol message to a binary string.

        This method is similar to serialize_to_string but doesn't check if the
        message is initialized.

        Returns:
            str: A string representation of the partial message.
        """
        return Protobuf.mysqlxpb.serialize_partial_message(self._msg)

    @property
    def type(self) -> str:
        """string: Message type name."""
        return (
            self._msg["_mysqlxpb_type_name"]
            if not Protobuf.use_pure
            else self._msg.DESCRIPTOR.full_name
        )

    @staticmethod
    def parse(
        msg_type_name: str, payload: bytes
    ) -> Union[ProtobufMessageType, ProtobufMessageCextType]:
        """Creates a new message, initialized with parsed data.

        Args:
            msg_type_name (string): Message type name.
            payload (string): Serialized message data.

        Returns:
            dict: The dictionary representing a message containing parsed data.

        .. versionadded:: 8.0.21
        """
        return Protobuf.mysqlxpb.parse_message(msg_type_name, payload)

    @staticmethod
    def byte_size(msg: MessageType) -> int:
        """Returns the size of the message in bytes.

        Args:
            msg (mysqlx.protobuf.Message): MySQL X Protobuf Message.

        Returns:
            int: Size of the message in bytes.

        .. versionadded:: 8.0.21
        """
        return (
            msg.ByteSize()
            if Protobuf.use_pure
            else len(encode_to_bytes(msg.serialize_to_string()))
        )

    @staticmethod
    def parse_from_server(
        msg_type: int, payload: bytes
    ) -> Union[ProtobufMessageType, ProtobufMessageCextType]:
        """Creates a new server-side message, initialized with parsed data.

        Args:
            msg_type (int): Message type.
            payload (string): Serialized message data.

        Returns:
            dict: The dictionary representing a message containing parsed data.
        """
        return Protobuf.mysqlxpb.parse_server_message(msg_type, payload)

    @classmethod
    def from_message(cls, msg_type_name: str, payload: bytes) -> MessageType:
        """Creates a new message, initialized with parsed data and returns a
        :class:`mysqlx.protobuf.Message` object.

        Args:
            msg_type_name (string): Message type name.
            payload (string): Serialized message data.

        Returns:
            mysqlx.protobuf.Message: The Message representing a message
                                     containing parsed data.
        """
        msg = cls()
        msg.set_message(Protobuf.mysqlxpb.parse_message(msg_type_name, payload))
        return msg

    @classmethod
    def from_server_message(cls, msg_type: int, payload: bytes) -> MessageType:
        """Creates a new server-side message, initialized with parsed data and
        returns a :class:`mysqlx.protobuf.Message` object.

        Args:
            msg_type (int): Message type.
            payload (string): Serialized message data.

        Returns:
            mysqlx.protobuf.Message: The Message representing a message
                                     containing parsed data.
        """
        msg = cls()
        msg.set_message(Protobuf.mysqlxpb.parse_server_message(msg_type, payload))
        return msg


def mysqlxpb_enum(name: str) -> int:
    """Returns the value of a MySQL X Protobuf enumerator.

    Args:
        name (string): MySQL X Protobuf numerator name.

    Returns:
        int: Value of the enumerator.
    """
    return Protobuf.mysqlxpb.enum_value(name)

virt/lib/python3.9/site-packages/mysqlx/protobuf/__init__.py
PrinceDisant/LostInNL
# -*- coding: utf-8 -*-
# Copyright (c) 2017, 2023, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

# Generated by the protocol buffer compiler.  DO NOT EDIT!
# source: mysqlx_connection.proto

# type: ignore

"""Generated protocol buffer code."""
from google.protobuf.internal import builder as _builder
from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool
from google.protobuf import symbol_database as _symbol_database
# @@protoc_insertion_point(imports)

_sym_db = _symbol_database.Default()


from mysqlx.protobuf import mysqlx_datatypes_pb2 as mysqlx__datatypes__pb2
from mysqlx.protobuf import mysqlx_pb2 as mysqlx__pb2


DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x17mysqlx_connection.proto\x12\x11Mysqlx.Connection\x1a\x16mysqlx_datatypes.proto\x1a\x0cmysqlx.proto\"@\n\nCapability\x12\x0c\n\x04name\x18\x01 \x02(\t\x12$\n\x05value\x18\x02 \x02(\x0b\x32\x15.Mysqlx.Datatypes.Any\"C\n\x0c\x43\x61pabilities\x12\x33\n\x0c\x63\x61pabilities\x18\x01 \x03(\x0b\x32\x1d.Mysqlx.Connection.Capability\"\x11\n\x0f\x43\x61pabilitiesGet\"H\n\x0f\x43\x61pabilitiesSet\x12\x35\n\x0c\x63\x61pabilities\x18\x01 \x02(\x0b\x32\x1f.Mysqlx.Connection.Capabilities\"\x07\n\x05\x43lose\"\xa5\x01\n\x0b\x43ompression\x12\x19\n\x11uncompressed_size\x18\x01 \x01(\x04\x12\x34\n\x0fserver_messages\x18\x02 \x01(\x0e\x32\x1b.Mysqlx.ServerMessages.Type\x12\x34\n\x0f\x63lient_messages\x18\x03 \x01(\x0e\x32\x1b.Mysqlx.ClientMessages.Type\x12\x0f\n\x07payload\x18\x04 \x02(\x0c\x42\x1b\n\x17\x63om.mysql.cj.x.protobufH\x03')

_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals())
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'mysqlx_connection_pb2', globals())
if _descriptor._USE_C_DESCRIPTORS == False:

  DESCRIPTOR._options = None
  DESCRIPTOR._serialized_options = b'\n\027com.mysql.cj.x.protobufH\003'
  _CAPABILITY._serialized_start=84
  _CAPABILITY._serialized_end=148
  _CAPABILITIES._serialized_start=150
  _CAPABILITIES._serialized_end=217
  _CAPABILITIESGET._serialized_start=219
  _CAPABILITIESGET._serialized_end=236
  _CAPABILITIESSET._serialized_start=238
  _CAPABILITIESSET._serialized_end=310
  _CLOSE._serialized_start=312
  _CLOSE._serialized_end=319
  _COMPRESSION._serialized_start=322
  _COMPRESSION._serialized_end=487
# @@protoc_insertion_point(module_scope)

virt/lib/python3.9/site-packages/mysqlx/protobuf/mysqlx_connection_pb2.py
PrinceDisant/LostInNL
# -*- coding: utf-8 -*-
# Copyright (c) 2017, 2023, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

# Generated by the protocol buffer compiler.  DO NOT EDIT!
# source: mysqlx_crud.proto

# type: ignore

"""Generated protocol buffer code."""
from google.protobuf.internal import builder as _builder
from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool
from google.protobuf import symbol_database as _symbol_database
# @@protoc_insertion_point(imports)

_sym_db = _symbol_database.Default()


from mysqlx.protobuf import mysqlx_expr_pb2 as mysqlx__expr__pb2
from mysqlx.protobuf import mysqlx_datatypes_pb2 as mysqlx__datatypes__pb2


DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x11mysqlx_crud.proto\x12\x0bMysqlx.Crud\x1a\x11mysqlx_expr.proto\x1a\x16mysqlx_datatypes.proto\"[\n\x06\x43olumn\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\r\n\x05\x61lias\x18\x02 \x01(\t\x12\x34\n\rdocument_path\x18\x03 \x03(\x0b\x32\x1d.Mysqlx.Expr.DocumentPathItem\">\n\nProjection\x12!\n\x06source\x18\x01 \x02(\x0b\x32\x11.Mysqlx.Expr.Expr\x12\r\n\x05\x61lias\x18\x02 \x01(\t\"*\n\nCollection\x12\x0c\n\x04name\x18\x01 \x02(\t\x12\x0e\n\x06schema\x18\x02 \x01(\t\"*\n\x05Limit\x12\x11\n\trow_count\x18\x01 \x02(\x04\x12\x0e\n\x06offset\x18\x02 \x01(\x04\"T\n\tLimitExpr\x12$\n\trow_count\x18\x01 \x02(\x0b\x32\x11.Mysqlx.Expr.Expr\x12!\n\x06offset\x18\x02 \x01(\x0b\x32\x11.Mysqlx.Expr.Expr\"~\n\x05Order\x12\x1f\n\x04\x65xpr\x18\x01 \x02(\x0b\x32\x11.Mysqlx.Expr.Expr\x12\x34\n\tdirection\x18\x02 \x01(\x0e\x32\x1c.Mysqlx.Crud.Order.Direction:\x03\x41SC\"\x1e\n\tDirection\x12\x07\n\x03\x41SC\x10\x01\x12\x08\n\x04\x44\x45SC\x10\x02\"\xac\x02\n\x0fUpdateOperation\x12-\n\x06source\x18\x01 \x02(\x0b\x32\x1d.Mysqlx.Expr.ColumnIdentifier\x12:\n\toperation\x18\x02 \x02(\x0e\x32\'.Mysqlx.Crud.UpdateOperation.UpdateType\x12 \n\x05value\x18\x03 \x01(\x0b\x32\x11.Mysqlx.Expr.Expr\"\x8b\x01\n\nUpdateType\x12\x07\n\x03SET\x10\x01\x12\x0f\n\x0bITEM_REMOVE\x10\x02\x12\x0c\n\x08ITEM_SET\x10\x03\x12\x10\n\x0cITEM_REPLACE\x10\x04\x12\x0e\n\nITEM_MERGE\x10\x05\x12\x10\n\x0c\x41RRAY_INSERT\x10\x06\x12\x10\n\x0c\x41RRAY_APPEND\x10\x07\x12\x0f\n\x0bMERGE_PATCH\x10\x08\"\xe4\x04\n\x04\x46ind\x12+\n\ncollection\x18\x02 \x02(\x0b\x32\x17.Mysqlx.Crud.Collection\x12*\n\ndata_model\x18\x03 \x01(\x0e\x32\x16.Mysqlx.Crud.DataModel\x12+\n\nprojection\x18\x04 \x03(\x0b\x32\x17.Mysqlx.Crud.Projection\x12&\n\x04\x61rgs\x18\x0b \x03(\x0b\x32\x18.Mysqlx.Datatypes.Scalar\x12#\n\x08\x63riteria\x18\x05 \x01(\x0b\x32\x11.Mysqlx.Expr.Expr\x12!\n\x05limit\x18\x06 \x01(\x0b\x32\x12.Mysqlx.Crud.Limit\x12!\n\x05order\x18\x07 \x03(\x0b\x32\x12.Mysqlx.Crud.Order\x12#\n\x08grouping\x18\x08 \x03(\x0b\x32\x11.Mysqlx.Expr.Expr\x12,\n\x11grouping_criteria\x18\t \x01(\x0b\x32\x11.Mysqlx.Expr.Expr\x12*\n\x07locking\x18\x0c \x01(\x0e\x32\x19.Mysqlx.Crud.Find.RowLock\x12\x39\n\x0flocking_options\x18\r \x01(\x0e\x32 .Mysqlx.Crud.Find.RowLockOptions\x12*\n\nlimit_expr\x18\x0e \x01(\x0b\x32\x16.Mysqlx.Crud.LimitExpr\".\n\x07RowLock\x12\x0f\n\x0bSHARED_LOCK\x10\x01\x12\x12\n\x0e\x45XCLUSIVE_LOCK\x10\x02\"-\n\x0eRowLockOptions\x12\n\n\x06NOWAIT\x10\x01\x12\x0f\n\x0bSKIP_LOCKED\x10\x02\"\xa2\x02\n\x06Insert\x12+\n\ncollection\x18\x01 \x02(\x0b\x32\x17.Mysqlx.Crud.Collection\x12*\n\ndata_model\x18\x02 \x01(\x0e\x32\x16.Mysqlx.Crud.DataModel\x12\'\n\nprojection\x18\x03 \x03(\x0b\x32\x13.Mysqlx.Crud.Column\x12)\n\x03row\x18\x04 \x03(\x0b\x32\x1c.Mysqlx.Crud.Insert.TypedRow\x12&\n\x04\x61rgs\x18\x05 \x03(\x0b\x32\x18.Mysqlx.Datatypes.Scalar\x12\x15\n\x06upsert\x18\x06 \x01(\x08:\x05\x66\x61lse\x1a,\n\x08TypedRow\x12 \n\x05\x66ield\x18\x01 \x03(\x0b\x32\x11.Mysqlx.Expr.Expr\"\xd1\x02\n\x06Update\x12+\n\ncollection\x18\x02 \x02(\x0b\x32\x17.Mysqlx.Crud.Collection\x12*\n\ndata_model\x18\x03 \x01(\x0e\x32\x16.Mysqlx.Crud.DataModel\x12#\n\x08\x63riteria\x18\x04 \x01(\x0b\x32\x11.Mysqlx.Expr.Expr\x12!\n\x05limit\x18\x05 \x01(\x0b\x32\x12.Mysqlx.Crud.Limit\x12!\n\x05order\x18\x06 \x03(\x0b\x32\x12.Mysqlx.Crud.Order\x12/\n\toperation\x18\x07 \x03(\x0b\x32\x1c.Mysqlx.Crud.UpdateOperation\x12&\n\x04\x61rgs\x18\x08 \x03(\x0b\x32\x18.Mysqlx.Datatypes.Scalar\x12*\n\nlimit_expr\x18\t \x01(\x0b\x32\x16.Mysqlx.Crud.LimitExpr\"\xa0\x02\n\x06\x44\x65lete\x12+\n\ncollection\x18\x01 \x02(\x0b\x32\x17.Mysqlx.Crud.Collection\x12*\n\ndata_model\x18\x02 \x01(\x0e\x32\x16.Mysqlx.Crud.DataModel\x12#\n\x08\x63riteria\x18\x03 \x01(\x0b\x32\x11.Mysqlx.Expr.Expr\x12!\n\x05limit\x18\x04 \x01(\x0b\x32\x12.Mysqlx.Crud.Limit\x12!\n\x05order\x18\x05 \x03(\x0b\x32\x12.Mysqlx.Crud.Order\x12&\n\x04\x61rgs\x18\x06 \x03(\x0b\x32\x18.Mysqlx.Datatypes.Scalar\x12*\n\nlimit_expr\x18\x07 \x01(\x0b\x32\x16.Mysqlx.Crud.LimitExpr\"\xbc\x02\n\nCreateView\x12+\n\ncollection\x18\x01 \x02(\x0b\x32\x17.Mysqlx.Crud.Collection\x12\x0f\n\x07\x64\x65\x66iner\x18\x02 \x01(\t\x12\x38\n\talgorithm\x18\x03 \x01(\x0e\x32\x1a.Mysqlx.Crud.ViewAlgorithm:\tUNDEFINED\x12\x37\n\x08security\x18\x04 \x01(\x0e\x32\x1c.Mysqlx.Crud.ViewSqlSecurity:\x07\x44\x45\x46INER\x12+\n\x05\x63heck\x18\x05 \x01(\x0e\x32\x1c.Mysqlx.Crud.ViewCheckOption\x12\x0e\n\x06\x63olumn\x18\x06 \x03(\t\x12\x1f\n\x04stmt\x18\x07 \x02(\x0b\x32\x11.Mysqlx.Crud.Find\x12\x1f\n\x10replace_existing\x18\x08 \x01(\x08:\x05\x66\x61lse\"\x87\x02\n\nModifyView\x12+\n\ncollection\x18\x01 \x02(\x0b\x32\x17.Mysqlx.Crud.Collection\x12\x0f\n\x07\x64\x65\x66iner\x18\x02 \x01(\t\x12-\n\talgorithm\x18\x03 \x01(\x0e\x32\x1a.Mysqlx.Crud.ViewAlgorithm\x12.\n\x08security\x18\x04 \x01(\x0e\x32\x1c.Mysqlx.Crud.ViewSqlSecurity\x12+\n\x05\x63heck\x18\x05 \x01(\x0e\x32\x1c.Mysqlx.Crud.ViewCheckOption\x12\x0e\n\x06\x63olumn\x18\x06 \x03(\t\x12\x1f\n\x04stmt\x18\x07 \x01(\x0b\x32\x11.Mysqlx.Crud.Find\"Q\n\x08\x44ropView\x12+\n\ncollection\x18\x01 \x02(\x0b\x32\x17.Mysqlx.Crud.Collection\x12\x18\n\tif_exists\x18\x02 \x01(\x08:\x05\x66\x61lse*$\n\tDataModel\x12\x0c\n\x08\x44OCUMENT\x10\x01\x12\t\n\x05TABLE\x10\x02*8\n\rViewAlgorithm\x12\r\n\tUNDEFINED\x10\x01\x12\t\n\x05MERGE\x10\x02\x12\r\n\tTEMPTABLE\x10\x03*+\n\x0fViewSqlSecurity\x12\x0b\n\x07INVOKER\x10\x01\x12\x0b\n\x07\x44\x45\x46INER\x10\x02**\n\x0fViewCheckOption\x12\t\n\x05LOCAL\x10\x01\x12\x0c\n\x08\x43\x41SCADED\x10\x02\x42\x1b\n\x17\x63om.mysql.cj.x.protobufH\x03')

_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals())
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'mysqlx_crud_pb2', globals())
if _descriptor._USE_C_DESCRIPTORS == False:

  DESCRIPTOR._options = None
  DESCRIPTOR._serialized_options = b'\n\027com.mysql.cj.x.protobufH\003'
  _DATAMODEL._serialized_start=3046
  _DATAMODEL._serialized_end=3082
  _VIEWALGORITHM._serialized_start=3084
  _VIEWALGORITHM._serialized_end=3140
  _VIEWSQLSECURITY._serialized_start=3142
  _VIEWSQLSECURITY._serialized_end=3185
  _VIEWCHECKOPTION._serialized_start=3187
  _VIEWCHECKOPTION._serialized_end=3229
  _COLUMN._serialized_start=77
  _COLUMN._serialized_end=168
  _PROJECTION._serialized_start=170
  _PROJECTION._serialized_end=232
  _COLLECTION._serialized_start=234
  _COLLECTION._serialized_end=276
  _LIMIT._serialized_start=278
  _LIMIT._serialized_end=320
  _LIMITEXPR._serialized_start=322
  _LIMITEXPR._serialized_end=406
  _ORDER._serialized_start=408
  _ORDER._serialized_end=534
  _ORDER_DIRECTION._serialized_start=504
  _ORDER_DIRECTION._serialized_end=534
  _UPDATEOPERATION._serialized_start=537
  _UPDATEOPERATION._serialized_end=837
  _UPDATEOPERATION_UPDATETYPE._serialized_start=698
  _UPDATEOPERATION_UPDATETYPE._serialized_end=837
  _FIND._serialized_start=840
  _FIND._serialized_end=1452
  _FIND_ROWLOCK._serialized_start=1359
  _FIND_ROWLOCK._serialized_end=1405
  _FIND_ROWLOCKOPTIONS._serialized_start=1407
  _FIND_ROWLOCKOPTIONS._serialized_end=1452
  _INSERT._serialized_start=1455
  _INSERT._serialized_end=1745
  _INSERT_TYPEDROW._serialized_start=1701
  _INSERT_TYPEDROW._serialized_end=1745
  _UPDATE._serialized_start=1748
  _UPDATE._serialized_end=2085
  _DELETE._serialized_start=2088
  _DELETE._serialized_end=2376
  _CREATEVIEW._serialized_start=2379
  _CREATEVIEW._serialized_end=2695
  _MODIFYVIEW._serialized_start=2698
  _MODIFYVIEW._serialized_end=2961
  _DROPVIEW._serialized_start=2963
  _DROPVIEW._serialized_end=3044
# @@protoc_insertion_point(module_scope)

virt/lib/python3.9/site-packages/mysqlx/protobuf/mysqlx_crud_pb2.py
PrinceDisant/LostInNL
# -*- coding: utf-8 -*-
# Copyright (c) 2017, 2023, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
# Generated by the protocol buffer compiler.  DO NOT EDIT!
# source: mysqlx_cursor.proto

# type: ignore

"""Generated protocol buffer code."""
from google.protobuf.internal import builder as _builder
from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool
from google.protobuf import symbol_database as _symbol_database
# @@protoc_insertion_point(imports)

_sym_db = _symbol_database.Default()


from mysqlx.protobuf import mysqlx_prepare_pb2 as mysqlx__prepare__pb2


DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x13mysqlx_cursor.proto\x12\rMysqlx.Cursor\x1a\x14mysqlx_prepare.proto\"\xf2\x01\n\x04Open\x12\x11\n\tcursor_id\x18\x01 \x02(\r\x12.\n\x04stmt\x18\x04 \x02(\x0b\x32 .Mysqlx.Cursor.Open.OneOfMessage\x12\x12\n\nfetch_rows\x18\x05 \x01(\x04\x1a\x92\x01\n\x0cOneOfMessage\x12\x33\n\x04type\x18\x01 \x02(\x0e\x32%.Mysqlx.Cursor.Open.OneOfMessage.Type\x12\x30\n\x0fprepare_execute\x18\x02 \x01(\x0b\x32\x17.Mysqlx.Prepare.Execute\"\x1b\n\x04Type\x12\x13\n\x0fPREPARE_EXECUTE\x10\x00\".\n\x05\x46\x65tch\x12\x11\n\tcursor_id\x18\x01 \x02(\r\x12\x12\n\nfetch_rows\x18\x05 \x01(\x04\"\x1a\n\x05\x43lose\x12\x11\n\tcursor_id\x18\x01 \x02(\rB\x1b\n\x17\x63om.mysql.cj.x.protobufH\x03')

_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals())
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'mysqlx_cursor_pb2', globals())
if _descriptor._USE_C_DESCRIPTORS == False:

  DESCRIPTOR._options = None
  DESCRIPTOR._serialized_options = b'\n\027com.mysql.cj.x.protobufH\003'
  _OPEN._serialized_start=61
  _OPEN._serialized_end=303
  _OPEN_ONEOFMESSAGE._serialized_start=157
  _OPEN_ONEOFMESSAGE._serialized_end=303
  _OPEN_ONEOFMESSAGE_TYPE._serialized_start=276
  _OPEN_ONEOFMESSAGE_TYPE._serialized_end=303
  _FETCH._serialized_start=305
  _FETCH._serialized_end=351
  _CLOSE._serialized_start=353
  _CLOSE._serialized_end=379
# @@protoc_insertion_point(module_scope)

virt/lib/python3.9/site-packages/mysqlx/protobuf/mysqlx_cursor_pb2.py
PrinceDisant/LostInNL
# -*- coding: utf-8 -*-
# Copyright (c) 2017, 2023, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

# Generated by the protocol buffer compiler.  DO NOT EDIT!
# source: mysqlx_datatypes.proto

# type: ignore

"""Generated protocol buffer code."""
from google.protobuf.internal import builder as _builder
from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool
from google.protobuf import symbol_database as _symbol_database
# @@protoc_insertion_point(imports)

_sym_db = _symbol_database.Default()




DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x16mysqlx_datatypes.proto\x12\x10Mysqlx.Datatypes\"\xc6\x03\n\x06Scalar\x12+\n\x04type\x18\x01 \x02(\x0e\x32\x1d.Mysqlx.Datatypes.Scalar.Type\x12\x14\n\x0cv_signed_int\x18\x02 \x01(\x12\x12\x16\n\x0ev_unsigned_int\x18\x03 \x01(\x04\x12\x31\n\x08v_octets\x18\x05 \x01(\x0b\x32\x1f.Mysqlx.Datatypes.Scalar.Octets\x12\x10\n\x08v_double\x18\x06 \x01(\x01\x12\x0f\n\x07v_float\x18\x07 \x01(\x02\x12\x0e\n\x06v_bool\x18\x08 \x01(\x08\x12\x31\n\x08v_string\x18\t \x01(\x0b\x32\x1f.Mysqlx.Datatypes.Scalar.String\x1a*\n\x06String\x12\r\n\x05value\x18\x01 \x02(\x0c\x12\x11\n\tcollation\x18\x02 \x01(\x04\x1a-\n\x06Octets\x12\r\n\x05value\x18\x01 \x02(\x0c\x12\x14\n\x0c\x63ontent_type\x18\x02 \x01(\r\"m\n\x04Type\x12\n\n\x06V_SINT\x10\x01\x12\n\n\x06V_UINT\x10\x02\x12\n\n\x06V_NULL\x10\x03\x12\x0c\n\x08V_OCTETS\x10\x04\x12\x0c\n\x08V_DOUBLE\x10\x05\x12\x0b\n\x07V_FLOAT\x10\x06\x12\n\n\x06V_BOOL\x10\x07\x12\x0c\n\x08V_STRING\x10\x08\"}\n\x06Object\x12\x31\n\x03\x66ld\x18\x01 \x03(\x0b\x32$.Mysqlx.Datatypes.Object.ObjectField\x1a@\n\x0bObjectField\x12\x0b\n\x03key\x18\x01 \x02(\t\x12$\n\x05value\x18\x02 \x02(\x0b\x32\x15.Mysqlx.Datatypes.Any\"-\n\x05\x41rray\x12$\n\x05value\x18\x01 \x03(\x0b\x32\x15.Mysqlx.Datatypes.Any\"\xd3\x01\n\x03\x41ny\x12(\n\x04type\x18\x01 \x02(\x0e\x32\x1a.Mysqlx.Datatypes.Any.Type\x12(\n\x06scalar\x18\x02 \x01(\x0b\x32\x18.Mysqlx.Datatypes.Scalar\x12%\n\x03obj\x18\x03 \x01(\x0b\x32\x18.Mysqlx.Datatypes.Object\x12&\n\x05\x61rray\x18\x04 \x01(\x0b\x32\x17.Mysqlx.Datatypes.Array\")\n\x04Type\x12\n\n\x06SCALAR\x10\x01\x12\n\n\x06OBJECT\x10\x02\x12\t\n\x05\x41RRAY\x10\x03\x42\x1b\n\x17\x63om.mysql.cj.x.protobufH\x03')

_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals())
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'mysqlx_datatypes_pb2', globals())
if _descriptor._USE_C_DESCRIPTORS == False:

  DESCRIPTOR._options = None
  DESCRIPTOR._serialized_options = b'\n\027com.mysql.cj.x.protobufH\003'
  _SCALAR._serialized_start=45
  _SCALAR._serialized_end=499
  _SCALAR_STRING._serialized_start=299
  _SCALAR_STRING._serialized_end=341
  _SCALAR_OCTETS._serialized_start=343
  _SCALAR_OCTETS._serialized_end=388
  _SCALAR_TYPE._serialized_start=390
  _SCALAR_TYPE._serialized_end=499
  _OBJECT._serialized_start=501
  _OBJECT._serialized_end=626
  _OBJECT_OBJECTFIELD._serialized_start=562
  _OBJECT_OBJECTFIELD._serialized_end=626
  _ARRAY._serialized_start=628
  _ARRAY._serialized_end=673
  _ANY._serialized_start=676
  _ANY._serialized_end=887
  _ANY_TYPE._serialized_start=846
  _ANY_TYPE._serialized_end=887
# @@protoc_insertion_point(module_scope)

virt/lib/python3.9/site-packages/mysqlx/protobuf/mysqlx_datatypes_pb2.py
PrinceDisant/LostInNL
# -*- coding: utf-8 -*-
# Copyright (c) 2017, 2023, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

# Generated by the protocol buffer compiler.  DO NOT EDIT!
# source: mysqlx_expect.proto

# type: ignore

"""Generated protocol buffer code."""
from google.protobuf.internal import builder as _builder
from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool
from google.protobuf import symbol_database as _symbol_database
# @@protoc_insertion_point(imports)

_sym_db = _symbol_database.Default()




DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x13mysqlx_expect.proto\x12\rMysqlx.Expect\"\xd0\x03\n\x04Open\x12\x42\n\x02op\x18\x01 \x01(\x0e\x32 .Mysqlx.Expect.Open.CtxOperation:\x14\x45XPECT_CTX_COPY_PREV\x12+\n\x04\x63ond\x18\x02 \x03(\x0b\x32\x1d.Mysqlx.Expect.Open.Condition\x1a\x96\x02\n\tCondition\x12\x15\n\rcondition_key\x18\x01 \x02(\r\x12\x17\n\x0f\x63ondition_value\x18\x02 \x01(\x0c\x12K\n\x02op\x18\x03 \x01(\x0e\x32\x30.Mysqlx.Expect.Open.Condition.ConditionOperation:\rEXPECT_OP_SET\"N\n\x03Key\x12\x13\n\x0f\x45XPECT_NO_ERROR\x10\x01\x12\x16\n\x12\x45XPECT_FIELD_EXIST\x10\x02\x12\x1a\n\x16\x45XPECT_DOCID_GENERATED\x10\x03\"<\n\x12\x43onditionOperation\x12\x11\n\rEXPECT_OP_SET\x10\x00\x12\x13\n\x0f\x45XPECT_OP_UNSET\x10\x01\">\n\x0c\x43txOperation\x12\x18\n\x14\x45XPECT_CTX_COPY_PREV\x10\x00\x12\x14\n\x10\x45XPECT_CTX_EMPTY\x10\x01\"\x07\n\x05\x43loseB\x1b\n\x17\x63om.mysql.cj.x.protobufH\x03')

_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals())
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'mysqlx_expect_pb2', globals())
if _descriptor._USE_C_DESCRIPTORS == False:

  DESCRIPTOR._options = None
  DESCRIPTOR._serialized_options = b'\n\027com.mysql.cj.x.protobufH\003'
  _OPEN._serialized_start=39
  _OPEN._serialized_end=503
  _OPEN_CONDITION._serialized_start=161
  _OPEN_CONDITION._serialized_end=439
  _OPEN_CONDITION_KEY._serialized_start=299
  _OPEN_CONDITION_KEY._serialized_end=377
  _OPEN_CONDITION_CONDITIONOPERATION._serialized_start=379
  _OPEN_CONDITION_CONDITIONOPERATION._serialized_end=439
  _OPEN_CTXOPERATION._serialized_start=441
  _OPEN_CTXOPERATION._serialized_end=503
  _CLOSE._serialized_start=505
  _CLOSE._serialized_end=512
# @@protoc_insertion_point(module_scope)

virt/lib/python3.9/site-packages/mysqlx/protobuf/mysqlx_expect_pb2.py
PrinceDisant/LostInNL
# -*- coding: utf-8 -*-
# Copyright (c) 2017, 2023, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

# Generated by the protocol buffer compiler.  DO NOT EDIT!
# source: mysqlx_expr.proto

# type: ignore

"""Generated protocol buffer code."""
from google.protobuf.internal import builder as _builder
from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool
from google.protobuf import symbol_database as _symbol_database
# @@protoc_insertion_point(imports)

_sym_db = _symbol_database.Default()


from mysqlx.protobuf import mysqlx_datatypes_pb2 as mysqlx__datatypes__pb2


DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x11mysqlx_expr.proto\x12\x0bMysqlx.Expr\x1a\x16mysqlx_datatypes.proto\"\xc4\x03\n\x04\x45xpr\x12$\n\x04type\x18\x01 \x02(\x0e\x32\x16.Mysqlx.Expr.Expr.Type\x12\x31\n\nidentifier\x18\x02 \x01(\x0b\x32\x1d.Mysqlx.Expr.ColumnIdentifier\x12\x10\n\x08variable\x18\x03 \x01(\t\x12)\n\x07literal\x18\x04 \x01(\x0b\x32\x18.Mysqlx.Datatypes.Scalar\x12\x30\n\rfunction_call\x18\x05 \x01(\x0b\x32\x19.Mysqlx.Expr.FunctionCall\x12\'\n\x08operator\x18\x06 \x01(\x0b\x32\x15.Mysqlx.Expr.Operator\x12\x10\n\x08position\x18\x07 \x01(\r\x12#\n\x06object\x18\x08 \x01(\x0b\x32\x13.Mysqlx.Expr.Object\x12!\n\x05\x61rray\x18\t \x01(\x0b\x32\x12.Mysqlx.Expr.Array\"q\n\x04Type\x12\t\n\x05IDENT\x10\x01\x12\x0b\n\x07LITERAL\x10\x02\x12\x0c\n\x08VARIABLE\x10\x03\x12\r\n\tFUNC_CALL\x10\x04\x12\x0c\n\x08OPERATOR\x10\x05\x12\x0f\n\x0bPLACEHOLDER\x10\x06\x12\n\n\x06OBJECT\x10\x07\x12\t\n\x05\x41RRAY\x10\x08\"/\n\nIdentifier\x12\x0c\n\x04name\x18\x01 \x02(\t\x12\x13\n\x0bschema_name\x18\x02 \x01(\t\"\xcb\x01\n\x10\x44ocumentPathItem\x12\x30\n\x04type\x18\x01 \x02(\x0e\x32\".Mysqlx.Expr.DocumentPathItem.Type\x12\r\n\x05value\x18\x02 \x01(\t\x12\r\n\x05index\x18\x03 \x01(\r\"g\n\x04Type\x12\n\n\x06MEMBER\x10\x01\x12\x13\n\x0fMEMBER_ASTERISK\x10\x02\x12\x0f\n\x0b\x41RRAY_INDEX\x10\x03\x12\x18\n\x14\x41RRAY_INDEX_ASTERISK\x10\x04\x12\x13\n\x0f\x44OUBLE_ASTERISK\x10\x05\"\x7f\n\x10\x43olumnIdentifier\x12\x34\n\rdocument_path\x18\x01 \x03(\x0b\x32\x1d.Mysqlx.Expr.DocumentPathItem\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x12\n\ntable_name\x18\x03 \x01(\t\x12\x13\n\x0bschema_name\x18\x04 \x01(\t\"W\n\x0c\x46unctionCall\x12%\n\x04name\x18\x01 \x02(\x0b\x32\x17.Mysqlx.Expr.Identifier\x12 \n\x05param\x18\x02 \x03(\x0b\x32\x11.Mysqlx.Expr.Expr\":\n\x08Operator\x12\x0c\n\x04name\x18\x01 \x02(\t\x12 \n\x05param\x18\x02 \x03(\x0b\x32\x11.Mysqlx.Expr.Expr\"t\n\x06Object\x12,\n\x03\x66ld\x18\x01 \x03(\x0b\x32\x1f.Mysqlx.Expr.Object.ObjectField\x1a<\n\x0bObjectField\x12\x0b\n\x03key\x18\x01 \x02(\t\x12 \n\x05value\x18\x02 \x02(\x0b\x32\x11.Mysqlx.Expr.Expr\")\n\x05\x41rray\x12 \n\x05value\x18\x01 \x03(\x0b\x32\x11.Mysqlx.Expr.ExprB\x1b\n\x17\x63om.mysql.cj.x.protobufH\x03')

_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals())
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'mysqlx_expr_pb2', globals())
if _descriptor._USE_C_DESCRIPTORS == False:

  DESCRIPTOR._options = None
  DESCRIPTOR._serialized_options = b'\n\027com.mysql.cj.x.protobufH\003'
  _EXPR._serialized_start=59
  _EXPR._serialized_end=511
  _EXPR_TYPE._serialized_start=398
  _EXPR_TYPE._serialized_end=511
  _IDENTIFIER._serialized_start=513
  _IDENTIFIER._serialized_end=560
  _DOCUMENTPATHITEM._serialized_start=563
  _DOCUMENTPATHITEM._serialized_end=766
  _DOCUMENTPATHITEM_TYPE._serialized_start=663
  _DOCUMENTPATHITEM_TYPE._serialized_end=766
  _COLUMNIDENTIFIER._serialized_start=768
  _COLUMNIDENTIFIER._serialized_end=895
  _FUNCTIONCALL._serialized_start=897
  _FUNCTIONCALL._serialized_end=984
  _OPERATOR._serialized_start=986
  _OPERATOR._serialized_end=1044
  _OBJECT._serialized_start=1046
  _OBJECT._serialized_end=1162
  _OBJECT_OBJECTFIELD._serialized_start=1102
  _OBJECT_OBJECTFIELD._serialized_end=1162
  _ARRAY._serialized_start=1164
  _ARRAY._serialized_end=1205
# @@protoc_insertion_point(module_scope)

virt/lib/python3.9/site-packages/mysqlx/protobuf/mysqlx_expr_pb2.py
PrinceDisant/LostInNL
# -*- coding: utf-8 -*-
# Copyright (c) 2017, 2023, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

# Generated by the protocol buffer compiler.  DO NOT EDIT!
# source: mysqlx_notice.proto

# type: ignore

"""Generated protocol buffer code."""
from google.protobuf.internal import builder as _builder
from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool
from google.protobuf import symbol_database as _symbol_database
# @@protoc_insertion_point(imports)

_sym_db = _symbol_database.Default()


from mysqlx.protobuf import mysqlx_datatypes_pb2 as mysqlx__datatypes__pb2


DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x13mysqlx_notice.proto\x12\rMysqlx.Notice\x1a\x16mysqlx_datatypes.proto\"\xff\x01\n\x05\x46rame\x12\x0c\n\x04type\x18\x01 \x02(\r\x12\x31\n\x05scope\x18\x02 \x01(\x0e\x32\x1a.Mysqlx.Notice.Frame.Scope:\x06GLOBAL\x12\x0f\n\x07payload\x18\x03 \x01(\x0c\"\x1e\n\x05Scope\x12\n\n\x06GLOBAL\x10\x01\x12\t\n\x05LOCAL\x10\x02\"\x83\x01\n\x04Type\x12\x0b\n\x07WARNING\x10\x01\x12\x1c\n\x18SESSION_VARIABLE_CHANGED\x10\x02\x12\x19\n\x15SESSION_STATE_CHANGED\x10\x03\x12#\n\x1fGROUP_REPLICATION_STATE_CHANGED\x10\x04\x12\x10\n\x0cSERVER_HELLO\x10\x05\"\x85\x01\n\x07Warning\x12\x34\n\x05level\x18\x01 \x01(\x0e\x32\x1c.Mysqlx.Notice.Warning.Level:\x07WARNING\x12\x0c\n\x04\x63ode\x18\x02 \x02(\r\x12\x0b\n\x03msg\x18\x03 \x02(\t\")\n\x05Level\x12\x08\n\x04NOTE\x10\x01\x12\x0b\n\x07WARNING\x10\x02\x12\t\n\x05\x45RROR\x10\x03\"P\n\x16SessionVariableChanged\x12\r\n\x05param\x18\x01 \x02(\t\x12\'\n\x05value\x18\x02 \x01(\x0b\x32\x18.Mysqlx.Datatypes.Scalar\"\xf1\x02\n\x13SessionStateChanged\x12;\n\x05param\x18\x01 \x02(\x0e\x32,.Mysqlx.Notice.SessionStateChanged.Parameter\x12\'\n\x05value\x18\x02 \x03(\x0b\x32\x18.Mysqlx.Datatypes.Scalar\"\xf3\x01\n\tParameter\x12\x12\n\x0e\x43URRENT_SCHEMA\x10\x01\x12\x13\n\x0f\x41\x43\x43OUNT_EXPIRED\x10\x02\x12\x17\n\x13GENERATED_INSERT_ID\x10\x03\x12\x11\n\rROWS_AFFECTED\x10\x04\x12\x0e\n\nROWS_FOUND\x10\x05\x12\x10\n\x0cROWS_MATCHED\x10\x06\x12\x11\n\rTRX_COMMITTED\x10\x07\x12\x12\n\x0eTRX_ROLLEDBACK\x10\t\x12\x14\n\x10PRODUCED_MESSAGE\x10\n\x12\x16\n\x12\x43LIENT_ID_ASSIGNED\x10\x0b\x12\x1a\n\x16GENERATED_DOCUMENT_IDS\x10\x0c\"\xae\x01\n\x1cGroupReplicationStateChanged\x12\x0c\n\x04type\x18\x01 \x02(\r\x12\x0f\n\x07view_id\x18\x02 \x01(\t\"o\n\x04Type\x12\x1a\n\x16MEMBERSHIP_QUORUM_LOSS\x10\x01\x12\x1a\n\x16MEMBERSHIP_VIEW_CHANGE\x10\x02\x12\x16\n\x12MEMBER_ROLE_CHANGE\x10\x03\x12\x17\n\x13MEMBER_STATE_CHANGE\x10\x04\"\r\n\x0bServerHelloB\x1b\n\x17\x63om.mysql.cj.x.protobufH\x03')

_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals())
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'mysqlx_notice_pb2', globals())
if _descriptor._USE_C_DESCRIPTORS == False:

  DESCRIPTOR._options = None
  DESCRIPTOR._serialized_options = b'\n\027com.mysql.cj.x.protobufH\003'
  _FRAME._serialized_start=63
  _FRAME._serialized_end=318
  _FRAME_SCOPE._serialized_start=154
  _FRAME_SCOPE._serialized_end=184
  _FRAME_TYPE._serialized_start=187
  _FRAME_TYPE._serialized_end=318
  _WARNING._serialized_start=321
  _WARNING._serialized_end=454
  _WARNING_LEVEL._serialized_start=413
  _WARNING_LEVEL._serialized_end=454
  _SESSIONVARIABLECHANGED._serialized_start=456
  _SESSIONVARIABLECHANGED._serialized_end=536
  _SESSIONSTATECHANGED._serialized_start=539
  _SESSIONSTATECHANGED._serialized_end=908
  _SESSIONSTATECHANGED_PARAMETER._serialized_start=665
  _SESSIONSTATECHANGED_PARAMETER._serialized_end=908
  _GROUPREPLICATIONSTATECHANGED._serialized_start=911
  _GROUPREPLICATIONSTATECHANGED._serialized_end=1085
  _GROUPREPLICATIONSTATECHANGED_TYPE._serialized_start=974
  _GROUPREPLICATIONSTATECHANGED_TYPE._serialized_end=1085
  _SERVERHELLO._serialized_start=1087
  _SERVERHELLO._serialized_end=1100
# @@protoc_insertion_point(module_scope)

virt/lib/python3.9/site-packages/mysqlx/protobuf/mysqlx_notice_pb2.py
PrinceDisant/LostInNL
# -*- coding: utf-8 -*-
# Copyright (c) 2017, 2023, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

# Generated by the protocol buffer compiler.  DO NOT EDIT!
# source: mysqlx.proto

# type: ignore

"""Generated protocol buffer code."""
from google.protobuf.internal import builder as _builder
from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool
from google.protobuf import symbol_database as _symbol_database
# @@protoc_insertion_point(imports)

_sym_db = _symbol_database.Default()




DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0cmysqlx.proto\x12\x06Mysqlx\"\xfc\x03\n\x0e\x43lientMessages\"\xe9\x03\n\x04Type\x12\x18\n\x14\x43ON_CAPABILITIES_GET\x10\x01\x12\x18\n\x14\x43ON_CAPABILITIES_SET\x10\x02\x12\r\n\tCON_CLOSE\x10\x03\x12\x1b\n\x17SESS_AUTHENTICATE_START\x10\x04\x12\x1e\n\x1aSESS_AUTHENTICATE_CONTINUE\x10\x05\x12\x0e\n\nSESS_RESET\x10\x06\x12\x0e\n\nSESS_CLOSE\x10\x07\x12\x14\n\x10SQL_STMT_EXECUTE\x10\x0c\x12\r\n\tCRUD_FIND\x10\x11\x12\x0f\n\x0b\x43RUD_INSERT\x10\x12\x12\x0f\n\x0b\x43RUD_UPDATE\x10\x13\x12\x0f\n\x0b\x43RUD_DELETE\x10\x14\x12\x0f\n\x0b\x45XPECT_OPEN\x10\x18\x12\x10\n\x0c\x45XPECT_CLOSE\x10\x19\x12\x14\n\x10\x43RUD_CREATE_VIEW\x10\x1e\x12\x14\n\x10\x43RUD_MODIFY_VIEW\x10\x1f\x12\x12\n\x0e\x43RUD_DROP_VIEW\x10 \x12\x13\n\x0fPREPARE_PREPARE\x10(\x12\x13\n\x0fPREPARE_EXECUTE\x10)\x12\x16\n\x12PREPARE_DEALLOCATE\x10*\x12\x0f\n\x0b\x43URSOR_OPEN\x10+\x12\x10\n\x0c\x43URSOR_CLOSE\x10,\x12\x10\n\x0c\x43URSOR_FETCH\x10-\x12\x0f\n\x0b\x43OMPRESSION\x10.\"\xf3\x02\n\x0eServerMessages\"\xe0\x02\n\x04Type\x12\x06\n\x02OK\x10\x00\x12\t\n\x05\x45RROR\x10\x01\x12\x15\n\x11\x43ONN_CAPABILITIES\x10\x02\x12\x1e\n\x1aSESS_AUTHENTICATE_CONTINUE\x10\x03\x12\x18\n\x14SESS_AUTHENTICATE_OK\x10\x04\x12\n\n\x06NOTICE\x10\x0b\x12\x1e\n\x1aRESULTSET_COLUMN_META_DATA\x10\x0c\x12\x11\n\rRESULTSET_ROW\x10\r\x12\x18\n\x14RESULTSET_FETCH_DONE\x10\x0e\x12\x1d\n\x19RESULTSET_FETCH_SUSPENDED\x10\x0f\x12(\n$RESULTSET_FETCH_DONE_MORE_RESULTSETS\x10\x10\x12\x17\n\x13SQL_STMT_EXECUTE_OK\x10\x11\x12(\n$RESULTSET_FETCH_DONE_MORE_OUT_PARAMS\x10\x12\x12\x0f\n\x0b\x43OMPRESSION\x10\x13\"\x11\n\x02Ok\x12\x0b\n\x03msg\x18\x01 \x01(\t\"\x88\x01\n\x05\x45rror\x12/\n\x08severity\x18\x01 \x01(\x0e\x32\x16.Mysqlx.Error.Severity:\x05\x45RROR\x12\x0c\n\x04\x63ode\x18\x02 \x02(\r\x12\x11\n\tsql_state\x18\x04 \x02(\t\x12\x0b\n\x03msg\x18\x03 \x02(\t\" \n\x08Severity\x12\t\n\x05\x45RROR\x10\x00\x12\t\n\x05\x46\x41TAL\x10\x01\x42\x1b\n\x17\x63om.mysql.cj.x.protobufH\x03')

_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals())
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'mysqlx_pb2', globals())
if _descriptor._USE_C_DESCRIPTORS == False:

  DESCRIPTOR._options = None
  DESCRIPTOR._serialized_options = b'\n\027com.mysql.cj.x.protobufH\003'
  _CLIENTMESSAGES._serialized_start=25
  _CLIENTMESSAGES._serialized_end=533
  _CLIENTMESSAGES_TYPE._serialized_start=44
  _CLIENTMESSAGES_TYPE._serialized_end=533
  _SERVERMESSAGES._serialized_start=536
  _SERVERMESSAGES._serialized_end=907
  _SERVERMESSAGES_TYPE._serialized_start=555
  _SERVERMESSAGES_TYPE._serialized_end=907
  _OK._serialized_start=909
  _OK._serialized_end=926
  _ERROR._serialized_start=929
  _ERROR._serialized_end=1065
  _ERROR_SEVERITY._serialized_start=1033
  _ERROR_SEVERITY._serialized_end=1065
# @@protoc_insertion_point(module_scope)

virt/lib/python3.9/site-packages/mysqlx/protobuf/mysqlx_pb2.py
PrinceDisant/LostInNL
# -*- coding: utf-8 -*-
# Copyright (c) 2017, 2023, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

# Generated by the protocol buffer compiler.  DO NOT EDIT!
# source: mysqlx_prepare.proto

# type: ignore

"""Generated protocol buffer code."""
from google.protobuf.internal import builder as _builder
from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool
from google.protobuf import symbol_database as _symbol_database
# @@protoc_insertion_point(imports)

_sym_db = _symbol_database.Default()


from mysqlx.protobuf import mysqlx_sql_pb2 as mysqlx__sql__pb2
from mysqlx.protobuf import mysqlx_crud_pb2 as mysqlx__crud__pb2
from mysqlx.protobuf import mysqlx_datatypes_pb2 as mysqlx__datatypes__pb2


DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x14mysqlx_prepare.proto\x12\x0eMysqlx.Prepare\x1a\x10mysqlx_sql.proto\x1a\x11mysqlx_crud.proto\x1a\x16mysqlx_datatypes.proto\"\x97\x03\n\x07Prepare\x12\x0f\n\x07stmt_id\x18\x01 \x02(\r\x12\x32\n\x04stmt\x18\x02 \x02(\x0b\x32$.Mysqlx.Prepare.Prepare.OneOfMessage\x1a\xc6\x02\n\x0cOneOfMessage\x12\x37\n\x04type\x18\x01 \x02(\x0e\x32).Mysqlx.Prepare.Prepare.OneOfMessage.Type\x12\x1f\n\x04\x66ind\x18\x02 \x01(\x0b\x32\x11.Mysqlx.Crud.Find\x12#\n\x06insert\x18\x03 \x01(\x0b\x32\x13.Mysqlx.Crud.Insert\x12#\n\x06update\x18\x04 \x01(\x0b\x32\x13.Mysqlx.Crud.Update\x12#\n\x06\x64\x65lete\x18\x05 \x01(\x0b\x32\x13.Mysqlx.Crud.Delete\x12-\n\x0cstmt_execute\x18\x06 \x01(\x0b\x32\x17.Mysqlx.Sql.StmtExecute\">\n\x04Type\x12\x08\n\x04\x46IND\x10\x00\x12\n\n\x06INSERT\x10\x01\x12\n\n\x06UPDATE\x10\x02\x12\n\n\x06\x44\x45LETE\x10\x04\x12\x08\n\x04STMT\x10\x05\"`\n\x07\x45xecute\x12\x0f\n\x07stmt_id\x18\x01 \x02(\r\x12#\n\x04\x61rgs\x18\x02 \x03(\x0b\x32\x15.Mysqlx.Datatypes.Any\x12\x1f\n\x10\x63ompact_metadata\x18\x03 \x01(\x08:\x05\x66\x61lse\"\x1d\n\nDeallocate\x12\x0f\n\x07stmt_id\x18\x01 \x02(\rB\x1b\n\x17\x63om.mysql.cj.x.protobufH\x03')

_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals())
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'mysqlx_prepare_pb2', globals())
if _descriptor._USE_C_DESCRIPTORS == False:

  DESCRIPTOR._options = None
  DESCRIPTOR._serialized_options = b'\n\027com.mysql.cj.x.protobufH\003'
  _PREPARE._serialized_start=102
  _PREPARE._serialized_end=509
  _PREPARE_ONEOFMESSAGE._serialized_start=183
  _PREPARE_ONEOFMESSAGE._serialized_end=509
  _PREPARE_ONEOFMESSAGE_TYPE._serialized_start=447
  _PREPARE_ONEOFMESSAGE_TYPE._serialized_end=509
  _EXECUTE._serialized_start=511
  _EXECUTE._serialized_end=607
  _DEALLOCATE._serialized_start=609
  _DEALLOCATE._serialized_end=638
# @@protoc_insertion_point(module_scope)

virt/lib/python3.9/site-packages/mysqlx/protobuf/mysqlx_prepare_pb2.py
PrinceDisant/LostInNL
# -*- coding: utf-8 -*-
# Copyright (c) 2017, 2023, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

# Generated by the protocol buffer compiler.  DO NOT EDIT!
# source: mysqlx_resultset.proto

# type: ignore

"""Generated protocol buffer code."""
from google.protobuf.internal import builder as _builder
from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool
from google.protobuf import symbol_database as _symbol_database
# @@protoc_insertion_point(imports)

_sym_db = _symbol_database.Default()




DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x16mysqlx_resultset.proto\x12\x10Mysqlx.Resultset\"\x18\n\x16\x46\x65tchDoneMoreOutParams\"\x19\n\x17\x46\x65tchDoneMoreResultsets\"\x0b\n\tFetchDone\"\x10\n\x0e\x46\x65tchSuspended\"\x9f\x03\n\x0e\x43olumnMetaData\x12\x38\n\x04type\x18\x01 \x02(\x0e\x32*.Mysqlx.Resultset.ColumnMetaData.FieldType\x12\x0c\n\x04name\x18\x02 \x01(\x0c\x12\x15\n\roriginal_name\x18\x03 \x01(\x0c\x12\r\n\x05table\x18\x04 \x01(\x0c\x12\x16\n\x0eoriginal_table\x18\x05 \x01(\x0c\x12\x0e\n\x06schema\x18\x06 \x01(\x0c\x12\x0f\n\x07\x63\x61talog\x18\x07 \x01(\x0c\x12\x11\n\tcollation\x18\x08 \x01(\x04\x12\x19\n\x11\x66ractional_digits\x18\t \x01(\r\x12\x0e\n\x06length\x18\n \x01(\r\x12\r\n\x05\x66lags\x18\x0b \x01(\r\x12\x14\n\x0c\x63ontent_type\x18\x0c \x01(\r\"\x82\x01\n\tFieldType\x12\x08\n\x04SINT\x10\x01\x12\x08\n\x04UINT\x10\x02\x12\n\n\x06\x44OUBLE\x10\x05\x12\t\n\x05\x46LOAT\x10\x06\x12\t\n\x05\x42YTES\x10\x07\x12\x08\n\x04TIME\x10\n\x12\x0c\n\x08\x44\x41TETIME\x10\x0c\x12\x07\n\x03SET\x10\x0f\x12\x08\n\x04\x45NUM\x10\x10\x12\x07\n\x03\x42IT\x10\x11\x12\x0b\n\x07\x44\x45\x43IMAL\x10\x12\"\x14\n\x03Row\x12\r\n\x05\x66ield\x18\x01 \x03(\x0c*4\n\x11\x43ontentType_BYTES\x12\x0c\n\x08GEOMETRY\x10\x01\x12\x08\n\x04JSON\x10\x02\x12\x07\n\x03XML\x10\x03*.\n\x14\x43ontentType_DATETIME\x12\x08\n\x04\x44\x41TE\x10\x01\x12\x0c\n\x08\x44\x41TETIME\x10\x02\x42\x1b\n\x17\x63om.mysql.cj.x.protobufH\x03')

_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals())
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'mysqlx_resultset_pb2', globals())
if _descriptor._USE_C_DESCRIPTORS == False:

  DESCRIPTOR._options = None
  DESCRIPTOR._serialized_options = b'\n\027com.mysql.cj.x.protobufH\003'
  _CONTENTTYPE_BYTES._serialized_start=568
  _CONTENTTYPE_BYTES._serialized_end=620
  _CONTENTTYPE_DATETIME._serialized_start=622
  _CONTENTTYPE_DATETIME._serialized_end=668
  _FETCHDONEMOREOUTPARAMS._serialized_start=44
  _FETCHDONEMOREOUTPARAMS._serialized_end=68
  _FETCHDONEMORERESULTSETS._serialized_start=70
  _FETCHDONEMORERESULTSETS._serialized_end=95
  _FETCHDONE._serialized_start=97
  _FETCHDONE._serialized_end=108
  _FETCHSUSPENDED._serialized_start=110
  _FETCHSUSPENDED._serialized_end=126
  _COLUMNMETADATA._serialized_start=129
  _COLUMNMETADATA._serialized_end=544
  _COLUMNMETADATA_FIELDTYPE._serialized_start=414
  _COLUMNMETADATA_FIELDTYPE._serialized_end=544
  _ROW._serialized_start=546
  _ROW._serialized_end=566
# @@protoc_insertion_point(module_scope)

virt/lib/python3.9/site-packages/mysqlx/protobuf/mysqlx_resultset_pb2.py
PrinceDisant/LostInNL
# -*- coding: utf-8 -*-
# Copyright (c) 2017, 2023, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

# Generated by the protocol buffer compiler.  DO NOT EDIT!
# source: mysqlx_session.proto

# type: ignore

"""Generated protocol buffer code."""
from google.protobuf.internal import builder as _builder
from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool
from google.protobuf import symbol_database as _symbol_database
# @@protoc_insertion_point(imports)

_sym_db = _symbol_database.Default()




DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x14mysqlx_session.proto\x12\x0eMysqlx.Session\"S\n\x11\x41uthenticateStart\x12\x11\n\tmech_name\x18\x01 \x02(\t\x12\x11\n\tauth_data\x18\x02 \x01(\x0c\x12\x18\n\x10initial_response\x18\x03 \x01(\x0c\")\n\x14\x41uthenticateContinue\x12\x11\n\tauth_data\x18\x01 \x02(\x0c\"#\n\x0e\x41uthenticateOk\x12\x11\n\tauth_data\x18\x01 \x01(\x0c\"!\n\x05Reset\x12\x18\n\tkeep_open\x18\x01 \x01(\x08:\x05\x66\x61lse\"\x07\n\x05\x43loseB\x1b\n\x17\x63om.mysql.cj.x.protobufH\x03')

_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals())
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'mysqlx_session_pb2', globals())
if _descriptor._USE_C_DESCRIPTORS == False:

  DESCRIPTOR._options = None
  DESCRIPTOR._serialized_options = b'\n\027com.mysql.cj.x.protobufH\003'
  _AUTHENTICATESTART._serialized_start=40
  _AUTHENTICATESTART._serialized_end=123
  _AUTHENTICATECONTINUE._serialized_start=125
  _AUTHENTICATECONTINUE._serialized_end=166
  _AUTHENTICATEOK._serialized_start=168
  _AUTHENTICATEOK._serialized_end=203
  _RESET._serialized_start=205
  _RESET._serialized_end=238
  _CLOSE._serialized_start=240
  _CLOSE._serialized_end=247
# @@protoc_insertion_point(module_scope)

virt/lib/python3.9/site-packages/mysqlx/protobuf/mysqlx_session_pb2.py
PrinceDisant/LostInNL
# -*- coding: utf-8 -*-
# Copyright (c) 2017, 2023, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

# Generated by the protocol buffer compiler.  DO NOT EDIT!
# source: mysqlx_sql.proto

# type: ignore

"""Generated protocol buffer code."""
from google.protobuf.internal import builder as _builder
from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool
from google.protobuf import symbol_database as _symbol_database
# @@protoc_insertion_point(imports)

_sym_db = _symbol_database.Default()


from mysqlx.protobuf import mysqlx_datatypes_pb2 as mysqlx__datatypes__pb2


DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x10mysqlx_sql.proto\x12\nMysqlx.Sql\x1a\x16mysqlx_datatypes.proto\"y\n\x0bStmtExecute\x12\x16\n\tnamespace\x18\x03 \x01(\t:\x03sql\x12\x0c\n\x04stmt\x18\x01 \x02(\x0c\x12#\n\x04\x61rgs\x18\x02 \x03(\x0b\x32\x15.Mysqlx.Datatypes.Any\x12\x1f\n\x10\x63ompact_metadata\x18\x04 \x01(\x08:\x05\x66\x61lse\"\x0f\n\rStmtExecuteOkB\x1b\n\x17\x63om.mysql.cj.x.protobufH\x03')

_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals())
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'mysqlx_sql_pb2', globals())
if _descriptor._USE_C_DESCRIPTORS == False:

  DESCRIPTOR._options = None
  DESCRIPTOR._serialized_options = b'\n\027com.mysql.cj.x.protobufH\003'
  _STMTEXECUTE._serialized_start=56
  _STMTEXECUTE._serialized_end=177
  _STMTEXECUTEOK._serialized_start=179
  _STMTEXECUTEOK._serialized_end=194
# @@protoc_insertion_point(module_scope)

virt/lib/python3.9/site-packages/mysqlx/protobuf/mysqlx_sql_pb2.py
PrinceDisant/LostInNL
# Copyright (c) 2016, 2023, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

"""Implementation of the X protocol for MySQL servers."""

import struct
import zlib

from io import BytesIO
from typing import Any, Dict, List, Optional, Tuple, Union

try:
    import lz4.frame

    HAVE_LZ4 = True
except ImportError:
    HAVE_LZ4 = False

try:
    import zstandard as zstd

    HAVE_ZSTD = True
except ImportError:
    HAVE_ZSTD = False

from .errors import (
    InterfaceError,
    NotSupportedError,
    OperationalError,
    ProgrammingError,
)
from .expr import (
    ExprParser,
    build_bool_scalar,
    build_expr,
    build_int_scalar,
    build_scalar,
    build_unsigned_int_scalar,
)
from .helpers import encode_to_bytes, get_item_or_attr
from .logger import logger
from .protobuf import CRUD_PREPARE_MAPPING, SERVER_MESSAGES, Message, mysqlxpb_enum
from .result import Column
from .statement import (
    AddStatement,
    DeleteStatement,
    FilterableStatement,
    FindStatement,
    InsertStatement,
    ModifyStatement,
    ReadStatement,
    RemoveStatement,
    SqlStatement,
    UpdateStatement,
)
from .types import (
    ColumnType,
    MessageType,
    ProtobufMessageCextType,
    ProtobufMessageType,
    ResultBaseType,
    SocketType,
    StatementType,
    StrOrBytes,
)

_COMPRESSION_THRESHOLD = 1000


class Compressor:
    """Implements compression/decompression using `zstd_stream`, `lz4_message`
    and `deflate_stream` algorithms.

    Args:
        algorithm (str): Compression algorithm.

    .. versionadded:: 8.0.21

    """

    def __init__(self, algorithm: str) -> None:
        self._algorithm: str = algorithm
        self._compressobj: Any = None
        self._decompressobj: Any = None

        if algorithm == "zstd_stream":
            self._compressobj = zstd.ZstdCompressor()
            self._decompressobj = zstd.ZstdDecompressor()
        elif algorithm == "deflate_stream":
            self._compressobj = zlib.compressobj()
            self._decompressobj = zlib.decompressobj()

    def compress(self, data: StrOrBytes) -> bytes:
        """Compresses data and returns it.

        Args:
            data (str, bytes or buffer object): Data to be compressed.

        Returns:
            bytes: Compressed data.
        """
        if self._algorithm == "zstd_stream":
            return self._compressobj.compress(data)
        if self._algorithm == "lz4_message":
            with lz4.frame.LZ4FrameCompressor() as compressor:
                compressed = compressor.begin()
                compressed += compressor.compress(data)
                compressed += compressor.flush()
            return compressed

        # Using 'deflate_stream' algorithm
        compressed = self._compressobj.compress(data)
        compressed += self._compressobj.flush(zlib.Z_SYNC_FLUSH)
        return compressed

    def decompress(self, data: StrOrBytes) -> bytes:
        """Decompresses a frame of data and returns it as a string of bytes.

        Args:
            data (str, bytes or buffer object): Data to be compressed.

        Returns:
            bytes: Decompresssed data.
        """
        if self._algorithm == "zstd_stream":
            return self._decompressobj.decompress(data)
        if self._algorithm == "lz4_message":
            with lz4.frame.LZ4FrameDecompressor() as decompressor:
                decompressed = decompressor.decompress(data)
            return decompressed

        # Using 'deflate' algorithm
        decompressed = self._decompressobj.decompress(data)
        decompressed += self._decompressobj.flush(zlib.Z_SYNC_FLUSH)
        return decompressed


class MessageReader:
    """Implements a Message Reader.

    Args:
        socket_stream (mysqlx.connection.SocketStream): `SocketStream` object.

    .. versionadded:: 8.0.21
    """

    def __init__(self, socket_stream: SocketType) -> None:
        self._stream: SocketType = socket_stream
        self._compressor: Optional[Compressor] = None
        self._msg: MessageType = None
        self._msg_queue: List[Message] = []

    def _read_message(self) -> MessageType:
        """Reads X Protocol messages from the stream and returns a
        :class:`mysqlx.protobuf.Message` object.

        Raises:
            :class:`mysqlx.ProgrammingError`: If e connected server does not
                                              have the MySQL X protocol plugin
                                              enabled.

        Returns:
            mysqlx.protobuf.Message: MySQL X Protobuf Message.
        """
        if self._msg_queue:
            return self._msg_queue.pop(0)

        frame_size, frame_type = struct.unpack("<LB", self._stream.read(5))

        if frame_type == 10:
            raise ProgrammingError(
                "The connected server does not have the "
                "MySQL X protocol plugin enabled or "
                "protocol mismatch"
            )

        frame_payload = self._stream.read(frame_size - 1)
        if frame_type not in SERVER_MESSAGES:
            raise ValueError(f"Unknown message type: {frame_type}")

        # Do not parse empty notices, Message requires a type in payload
        if frame_type == 11 and frame_payload == b"":
            return self._read_message()

        frame_msg = Message.from_server_message(frame_type, frame_payload)

        if frame_type == 19:  # Mysqlx.ServerMessages.Type.COMPRESSION
            uncompressed_size = frame_msg["uncompressed_size"]
            stream = BytesIO(self._compressor.decompress(frame_msg["payload"]))
            bytes_processed = 0
            while bytes_processed < uncompressed_size:
                payload_size, msg_type = struct.unpack("<LB", stream.read(5))
                payload = stream.read(payload_size - 1)
                self._msg_queue.append(Message.from_server_message(msg_type, payload))
                bytes_processed += payload_size + 4
            return self._msg_queue.pop(0) if self._msg_queue else None

        return frame_msg

    def read_message(self) -> MessageType:
        """Read message.

        Returns:
            mysqlx.protobuf.Message: MySQL X Protobuf Message.
        """
        if self._msg is not None:
            msg = self._msg
            self._msg = None
            return msg
        return self._read_message()

    def push_message(self, msg: MessageType) -> None:
        """Push message.

        Args:
            msg (mysqlx.protobuf.Message): MySQL X Protobuf Message.

        Raises:
            :class:`mysqlx.OperationalError`: If message push slot is full.
        """
        if self._msg is not None:
            raise OperationalError("Message push slot is full")
        self._msg = msg

    def set_compression(self, algorithm: str) -> None:
        """Creates a :class:`mysqlx.protocol.Compressor` object based on the
        compression algorithm.

        Args:
            algorithm (str): Compression algorithm.

        .. versionadded:: 8.0.21

        """
        self._compressor = Compressor(algorithm) if algorithm else None


class MessageWriter:
    """Implements a Message Writer.

    Args:
        socket_stream (mysqlx.connection.SocketStream): `SocketStream` object.

    .. versionadded:: 8.0.21

    """

    def __init__(self, socket_stream: SocketType) -> None:
        self._stream: SocketType = socket_stream
        self._compressor: Optional[Compressor] = None

    def write_message(self, msg_type: int, msg: MessageType) -> None:
        """Write message.

        Args:
            msg_type (int): The message type.
            msg (mysqlx.protobuf.Message): MySQL X Protobuf Message.
        """
        msg_size = msg.byte_size(msg)
        if self._compressor and msg_size > _COMPRESSION_THRESHOLD:
            msg_str = encode_to_bytes(msg.serialize_to_string())
            header = struct.pack("<LB", msg_size + 1, msg_type)
            compressed = self._compressor.compress(b"".join([header, msg_str]))

            msg_first_fields = Message("Mysqlx.Connection.Compression")
            msg_first_fields["client_messages"] = msg_type
            msg_first_fields["uncompressed_size"] = msg_size + 5

            msg_payload = Message("Mysqlx.Connection.Compression")
            msg_payload["payload"] = compressed

            output = b"".join(
                [
                    encode_to_bytes(msg_first_fields.serialize_partial_to_string())[
                        :-2
                    ],
                    encode_to_bytes(msg_payload.serialize_partial_to_string()),
                ]
            )

            msg_comp_id = mysqlxpb_enum("Mysqlx.ClientMessages.Type.COMPRESSION")
            header = struct.pack("<LB", len(output) + 1, msg_comp_id)
            self._stream.sendall(b"".join([header, output]))
        else:
            msg_str = encode_to_bytes(msg.serialize_to_string())
            header = struct.pack("<LB", msg_size + 1, msg_type)
            self._stream.sendall(b"".join([header, msg_str]))

    def set_compression(self, algorithm: str) -> None:
        """Creates a :class:`mysqlx.protocol.Compressor` object based on the
        compression algorithm.

        Args:
            algorithm (str): Compression algorithm.
        """
        self._compressor = Compressor(algorithm) if algorithm else None


class Protocol:
    """Implements the MySQL X Protocol.

    Args:
        read (mysqlx.protocol.MessageReader): A Message Reader object.
        writer (mysqlx.protocol.MessageWriter): A Message Writer object.

    .. versionchanged:: 8.0.21
    """

    def __init__(self, reader: MessageReader, writer: MessageWriter) -> None:
        self._reader: MessageReader = reader
        self._writer: MessageWriter = writer
        self._compression_algorithm: Optional[str] = None
        self._warnings: List[str] = []

    @property
    def compression_algorithm(self) -> Optional[str]:
        """str: The compresion algorithm."""
        return self._compression_algorithm

    @staticmethod
    def _apply_filter(msg: MessageType, stmt: FilterableStatement) -> None:
        """Apply filter.

        Args:
            msg (mysqlx.protobuf.Message): The MySQL X Protobuf Message.
            stmt (Statement): A `Statement` based type object.
        """
        if stmt.has_where:
            msg["criteria"] = stmt.get_where_expr()
        if stmt.has_sort:
            msg["order"].extend(stmt.get_sort_expr())
        if stmt.has_group_by:
            msg["grouping"].extend(stmt.get_grouping())
        if stmt.has_having:
            msg["grouping_criteria"] = stmt.get_having()

    def _create_any(self, arg: Any) -> Optional[MessageType]:
        """Create any.

        Args:
            arg (object): Arbitrary object.

        Returns:
            mysqlx.protobuf.Message: MySQL X Protobuf Message.
        """
        if isinstance(arg, str):
            value = Message("Mysqlx.Datatypes.Scalar.String", value=arg)
            scalar = Message("Mysqlx.Datatypes.Scalar", type=8, v_string=value)
            return Message("Mysqlx.Datatypes.Any", type=1, scalar=scalar)
        if isinstance(arg, bool):
            return Message(
                "Mysqlx.Datatypes.Any", type=1, scalar=build_bool_scalar(arg)
            )
        if isinstance(arg, int):
            if arg < 0:
                return Message(
                    "Mysqlx.Datatypes.Any",
                    type=1,
                    scalar=build_int_scalar(arg),
                )
            return Message(
                "Mysqlx.Datatypes.Any",
                type=1,
                scalar=build_unsigned_int_scalar(arg),
            )
        if isinstance(arg, tuple) and len(arg) == 2:
            arg_key, arg_value = arg
            obj_fld = Message(
                "Mysqlx.Datatypes.Object.ObjectField",
                key=arg_key,
                value=self._create_any(arg_value),
            )
            obj = Message("Mysqlx.Datatypes.Object", fld=[obj_fld.get_message()])
            return Message("Mysqlx.Datatypes.Any", type=2, obj=obj)
        if isinstance(arg, dict) or (
            isinstance(arg, (list, tuple)) and isinstance(arg[0], dict)
        ):
            array_values = []
            for items in arg:
                obj_flds = []
                for key, value in items.items():
                    # Array can only handle Any types, Mysqlx.Datatypes.Any.obj
                    obj_fld = Message(
                        "Mysqlx.Datatypes.Object.ObjectField",
                        key=key,
                        value=self._create_any(value),
                    )
                    obj_flds.append(obj_fld.get_message())
                msg_obj = Message("Mysqlx.Datatypes.Object", fld=obj_flds)
                msg_any = Message("Mysqlx.Datatypes.Any", type=2, obj=msg_obj)
                array_values.append(msg_any.get_message())

            msg = Message("Mysqlx.Datatypes.Array")
            msg["value"] = array_values
            return Message("Mysqlx.Datatypes.Any", type=3, array=msg)
        if isinstance(arg, list):
            obj_flds = []
            for key, value in arg:
                obj_fld = Message(
                    "Mysqlx.Datatypes.Object.ObjectField",
                    key=key,
                    value=self._create_any(value),
                )
                obj_flds.append(obj_fld.get_message())
            msg_obj = Message("Mysqlx.Datatypes.Object", fld=obj_flds)
            msg_any = Message("Mysqlx.Datatypes.Any", type=2, obj=msg_obj)
            return msg_any

        return None

    def _get_binding_args(
        self, stmt: Union[FilterableStatement, SqlStatement], is_scalar: bool = True
    ) -> Union[List[None], List[Union[ProtobufMessageType, ProtobufMessageCextType]]]:
        """Returns the binding any/scalar.

        Args:
            stmt (Statement): A `Statement` based type object.
            is_scalar (bool): `True` to return scalar values.

        Raises:
            :class:`mysqlx.ProgrammingError`: If unable to find placeholder for
                                              parameter.

        Returns:
            list: A list of ``Any`` or ``Scalar`` objects.
        """

        def build_value(
            value: Any,
        ) -> Union[ProtobufMessageType, ProtobufMessageCextType]:
            if is_scalar:
                return build_scalar(value).get_message()
            return self._create_any(value).get_message()

        bindings = stmt.get_bindings()
        binding_map = stmt.get_binding_map()

        # If binding_map is None it's a SqlStatement object
        if binding_map is None:
            return [build_value(value) for value in bindings]

        count = len(binding_map)
        args: List[Any] = count * [None]
        if count != len(bindings):
            raise ProgrammingError(
                "The number of bind parameters and placeholders do not match"
            )
        for name, value in bindings.items():  # type: ignore[union-attr]
            if name not in binding_map:
                raise ProgrammingError(
                    f"Unable to find placeholder for parameter: {name}"
                )
            pos = binding_map[name]
            args[pos] = build_value(value)
        return args

    def _process_frame(self, msg: MessageType, result: ResultBaseType) -> None:
        """Process frame.

        Args:
            msg (mysqlx.protobuf.Message): A MySQL X Protobuf Message.
            result (Result): A `Result` based type object.
        """
        if msg["type"] == 1:
            warn_msg = Message.from_message("Mysqlx.Notice.Warning", msg["payload"])
            self._warnings.append(warn_msg.msg)
            logger.warning(
                "Protocol.process_frame Received Warning Notice code %s: %s",
                warn_msg.code,
                warn_msg.msg,
            )
            result.append_warning(warn_msg.level, warn_msg.code, warn_msg.msg)
        elif msg["type"] == 2:
            Message.from_message("Mysqlx.Notice.SessionVariableChanged", msg["payload"])
        elif msg["type"] == 3:
            sess_state_msg = Message.from_message(
                "Mysqlx.Notice.SessionStateChanged", msg["payload"]
            )
            if sess_state_msg["param"] == mysqlxpb_enum(
                "Mysqlx.Notice.SessionStateChanged.Parameter.GENERATED_DOCUMENT_IDS"
            ):
                result.set_generated_ids(
                    [
                        get_item_or_attr(
                            get_item_or_attr(value, "v_octets"), "value"
                        ).decode()
                        for value in sess_state_msg["value"]
                    ]
                )
            else:  # Following results are unitary and not a list
                sess_state_value = sess_state_msg["value"].pop()
                if sess_state_msg["param"] == mysqlxpb_enum(
                    "Mysqlx.Notice.SessionStateChanged.Parameter.ROWS_AFFECTED"
                ):
                    result.set_rows_affected(
                        get_item_or_attr(sess_state_value, "v_unsigned_int")
                    )
                elif sess_state_msg["param"] == mysqlxpb_enum(
                    "Mysqlx.Notice.SessionStateChanged.Parameter.GENERATED_INSERT_ID"
                ):
                    result.set_generated_insert_id(
                        get_item_or_attr(sess_state_value, "v_unsigned_int")
                    )

    def _read_message(self, result: ResultBaseType) -> Optional[MessageType]:
        """Read message.

        Args:
            result (Result): A `Result` based type object.
        """
        while True:
            try:
                msg = self._reader.read_message()
            except RuntimeError as err:
                warnings = repr(result.get_warnings())
                if warnings:
                    raise RuntimeError(f"{err} reason: {warnings}") from err
            if msg.type == "Mysqlx.Error":
                raise OperationalError(msg["msg"], msg["code"])
            if msg.type == "Mysqlx.Notice.Frame":
                try:
                    self._process_frame(msg, result)
                except (AttributeError, KeyError):
                    continue
            elif msg.type == "Mysqlx.Sql.StmtExecuteOk":
                return None
            elif msg.type == "Mysqlx.Resultset.FetchDone":
                result.set_closed(True)
            elif msg.type == "Mysqlx.Resultset.FetchDoneMoreResultsets":
                result.set_has_more_results(True)
            elif msg.type == "Mysqlx.Resultset.Row":
                result.set_has_data(True)
                break
            else:
                break
        return msg

    def set_compression(self, algorithm: str) -> None:
        """Sets the compression algorithm to be used by the compression
        object, for uplink and downlink.

        Args:
            algorithm (str): Algorithm to be used in compression/decompression.

        .. versionadded:: 8.0.21

        """
        self._compression_algorithm = algorithm
        self._reader.set_compression(algorithm)
        self._writer.set_compression(algorithm)

    def get_capabilites(self) -> MessageType:
        """Get capabilities.

        Returns:
            mysqlx.protobuf.Message: MySQL X Protobuf Message.
        """
        msg = Message("Mysqlx.Connection.CapabilitiesGet")
        self._writer.write_message(
            mysqlxpb_enum("Mysqlx.ClientMessages.Type.CON_CAPABILITIES_GET"),
            msg,
        )
        msg = self._reader.read_message()
        while msg.type == "Mysqlx.Notice.Frame":
            msg = self._reader.read_message()

        if msg.type == "Mysqlx.Error":
            raise OperationalError(msg["msg"], msg["code"])

        return msg

    def set_capabilities(self, **kwargs: Any) -> None:
        """Set capabilities.

        Args:
            **kwargs: Arbitrary keyword arguments.

        Returns:
            mysqlx.protobuf.Message: MySQL X Protobuf Message.
        """
        if not kwargs:
            return None
        capabilities = Message("Mysqlx.Connection.Capabilities")
        for key, value in kwargs.items():
            capability = Message("Mysqlx.Connection.Capability")
            capability["name"] = key
            if isinstance(value, dict):
                items = value
                obj_flds = []
                for item in items:
                    obj_fld = Message(
                        "Mysqlx.Datatypes.Object.ObjectField",
                        key=item,
                        value=self._create_any(items[item]),
                    )
                    obj_flds.append(obj_fld.get_message())
                msg_obj = Message("Mysqlx.Datatypes.Object", fld=obj_flds)
                msg_any = Message("Mysqlx.Datatypes.Any", type=2, obj=msg_obj)
                capability["value"] = msg_any.get_message()
            else:
                capability["value"] = self._create_any(value)

            capabilities["capabilities"].extend([capability.get_message()])
        msg = Message("Mysqlx.Connection.CapabilitiesSet")
        msg["capabilities"] = capabilities
        self._writer.write_message(
            mysqlxpb_enum("Mysqlx.ClientMessages.Type.CON_CAPABILITIES_SET"),
            msg,
        )

        try:
            return self.read_ok()
        except InterfaceError as err:
            # Skip capability "session_connect_attrs" error since
            # is only available on version >= 8.0.16
            if err.errno != 5002:
                raise
        return None

    def send_auth_start(
        self,
        method: str,
        auth_data: Optional[str] = None,
        initial_response: Optional[str] = None,
    ) -> None:
        """Send authenticate start.

        Args:
            method (str): Message method.
            auth_data (Optional[str]): Authentication data.
            initial_response (Optional[str]): Initial response.
        """
        msg = Message("Mysqlx.Session.AuthenticateStart")
        msg["mech_name"] = method
        if auth_data is not None:
            msg["auth_data"] = auth_data
        if initial_response is not None:
            msg["initial_response"] = initial_response
        self._writer.write_message(
            mysqlxpb_enum("Mysqlx.ClientMessages.Type.SESS_AUTHENTICATE_START"),
            msg,
        )

    def read_auth_continue(self) -> bytes:
        """Read authenticate continue.

        Raises:
            :class:`InterfaceError`: If the message type is not
                                     `Mysqlx.Session.AuthenticateContinue`

        Returns:
            str: The authentication data.
        """
        msg = self._reader.read_message()
        while msg.type == "Mysqlx.Notice.Frame":
            msg = self._reader.read_message()
        if msg.type != "Mysqlx.Session.AuthenticateContinue":
            raise InterfaceError(
                "Unexpected message encountered during authentication handshake"
            )
        return msg["auth_data"]

    def send_auth_continue(self, auth_data: str) -> None:
        """Send authenticate continue.

        Args:
            auth_data (str): Authentication data.
        """
        msg = Message("Mysqlx.Session.AuthenticateContinue", auth_data=auth_data)
        self._writer.write_message(
            mysqlxpb_enum("Mysqlx.ClientMessages.Type.SESS_AUTHENTICATE_CONTINUE"),
            msg,
        )

    def read_auth_ok(self) -> None:
        """Read authenticate OK.

        Raises:
            :class:`mysqlx.InterfaceError`: If message type is `Mysqlx.Error`.
        """
        while True:
            msg = self._reader.read_message()
            if msg.type == "Mysqlx.Session.AuthenticateOk":
                break
            if msg.type == "Mysqlx.Error":
                raise InterfaceError(msg.msg)

    def send_prepare_prepare(
        self,
        msg_type: str,
        msg: MessageType,
        stmt: Union[
            FindStatement,
            DeleteStatement,
            ModifyStatement,
            ReadStatement,
            RemoveStatement,
            UpdateStatement,
        ],
    ) -> None:
        """
        Send prepare statement.

        Args:
            msg_type (str): Message ID string.
            msg (mysqlx.protobuf.Message): MySQL X Protobuf Message.
            stmt (Statement): A `Statement` based type object.

        Raises:
            :class:`mysqlx.NotSupportedError`: If prepared statements are not
                                               supported.

        .. versionadded:: 8.0.16
        """
        if stmt.has_limit and msg.type != "Mysqlx.Crud.Insert":
            # Remove 'limit' from message by building a new one
            if msg.type == "Mysqlx.Crud.Find":
                _, msg = self.build_find(stmt)  # type: ignore[arg-type]
            elif msg.type == "Mysqlx.Crud.Update":
                _, msg = self.build_update(stmt)  # type: ignore[arg-type]
            elif msg.type == "Mysqlx.Crud.Delete":
                _, msg = self.build_delete(stmt)  # type: ignore[arg-type]
            else:
                raise ValueError(f"Invalid message type: {msg_type}")
            # Build 'limit_expr' message
            position = len(stmt.get_bindings())
            placeholder = mysqlxpb_enum("Mysqlx.Expr.Expr.Type.PLACEHOLDER")
            msg_limit_expr = Message("Mysqlx.Crud.LimitExpr")
            msg_limit_expr["row_count"] = Message(
                "Mysqlx.Expr.Expr", type=placeholder, position=position
            )
            if msg.type == "Mysqlx.Crud.Find":
                msg_limit_expr["offset"] = Message(
                    "Mysqlx.Expr.Expr", type=placeholder, position=position + 1
                )
            msg["limit_expr"] = msg_limit_expr

        oneof_type, oneof_op = CRUD_PREPARE_MAPPING[msg_type]
        msg_oneof = Message("Mysqlx.Prepare.Prepare.OneOfMessage")
        msg_oneof["type"] = mysqlxpb_enum(oneof_type)
        msg_oneof[oneof_op] = msg
        msg_prepare = Message("Mysqlx.Prepare.Prepare")
        msg_prepare["stmt_id"] = stmt.stmt_id
        msg_prepare["stmt"] = msg_oneof

        self._writer.write_message(
            mysqlxpb_enum("Mysqlx.ClientMessages.Type.PREPARE_PREPARE"),
            msg_prepare,
        )

        try:
            self.read_ok()
        except InterfaceError as err:
            raise NotSupportedError from err

    def send_prepare_execute(
        self, msg_type: str, msg: MessageType, stmt: FilterableStatement
    ) -> None:
        """
        Send execute statement.

        Args:
            msg_type (str): Message ID string.
            msg (mysqlx.protobuf.Message): MySQL X Protobuf Message.
            stmt (Statement): A `Statement` based type object.

        .. versionadded:: 8.0.16
        """
        oneof_type, oneof_op = CRUD_PREPARE_MAPPING[msg_type]
        msg_oneof = Message("Mysqlx.Prepare.Prepare.OneOfMessage")
        msg_oneof["type"] = mysqlxpb_enum(oneof_type)
        msg_oneof[oneof_op] = msg
        msg_execute = Message("Mysqlx.Prepare.Execute")
        msg_execute["stmt_id"] = stmt.stmt_id

        args = self._get_binding_args(stmt, is_scalar=False)
        if args:
            msg_execute["args"].extend(args)

        if stmt.has_limit:
            msg_execute["args"].extend(
                [
                    self._create_any(stmt.get_limit_row_count()).get_message(),
                    self._create_any(stmt.get_limit_offset()).get_message(),
                ]
            )

        self._writer.write_message(
            mysqlxpb_enum("Mysqlx.ClientMessages.Type.PREPARE_EXECUTE"),
            msg_execute,
        )

    def send_prepare_deallocate(self, stmt_id: int) -> None:
        """
        Send prepare deallocate statement.

        Args:
            stmt_id (int): Statement ID.

        .. versionadded:: 8.0.16
        """
        msg_dealloc = Message("Mysqlx.Prepare.Deallocate")
        msg_dealloc["stmt_id"] = stmt_id
        self._writer.write_message(
            mysqlxpb_enum("Mysqlx.ClientMessages.Type.PREPARE_DEALLOCATE"),
            msg_dealloc,
        )
        self.read_ok()

    def send_msg_without_ps(
        self,
        msg_type: str,
        msg: MessageType,
        stmt: Union[FilterableStatement, SqlStatement],
    ) -> None:
        """
        Send a message without prepared statements support.

        Args:
            msg_type (str): Message ID string.
            msg (mysqlx.protobuf.Message): MySQL X Protobuf Message.
            stmt (Statement): A `Statement` based type object.

        .. versionadded:: 8.0.16
        """
        if stmt.has_limit:
            msg_limit = Message("Mysqlx.Crud.Limit")
            msg_limit["row_count"] = stmt.get_limit_row_count()  # type: ignore[union-attr]
            if msg.type == "Mysqlx.Crud.Find":
                msg_limit["offset"] = stmt.get_limit_offset()  # type: ignore[union-attr]
            msg["limit"] = msg_limit
        is_scalar = msg_type != "Mysqlx.ClientMessages.Type.SQL_STMT_EXECUTE"
        args = self._get_binding_args(stmt, is_scalar=is_scalar)
        if args:
            msg["args"].extend(args)
        self.send_msg(msg_type, msg)

    def send_msg(self, msg_type: str, msg: MessageType) -> None:
        """
        Send a message.

        Args:
            msg_type (str): Message ID string.
            msg (mysqlx.protobuf.Message): MySQL X Protobuf Message.

        .. versionadded:: 8.0.16
        """
        self._writer.write_message(mysqlxpb_enum(msg_type), msg)

    def build_find(
        self, stmt: Union[FindStatement, ReadStatement]
    ) -> Tuple[str, MessageType]:
        """Build find/read message.

        Args:
            stmt (Statement): A :class:`mysqlx.ReadStatement` or
                              :class:`mysqlx.FindStatement` object.

        Returns:
            (tuple): Tuple containing:

                * `str`: Message ID string.
                * :class:`mysqlx.protobuf.Message`: MySQL X Protobuf Message.

        .. versionadded:: 8.0.16
        """
        data_model = mysqlxpb_enum(
            "Mysqlx.Crud.DataModel.DOCUMENT"
            if stmt.is_doc_based()
            else "Mysqlx.Crud.DataModel.TABLE"
        )
        collection = Message(
            "Mysqlx.Crud.Collection",
            name=stmt.target.name,
            schema=stmt.schema.name,
        )
        msg = Message("Mysqlx.Crud.Find", data_model=data_model, collection=collection)
        if stmt.has_projection:
            msg["projection"] = stmt.get_projection_expr()
        self._apply_filter(msg, stmt)

        if stmt.is_lock_exclusive():
            msg["locking"] = mysqlxpb_enum("Mysqlx.Crud.Find.RowLock.EXCLUSIVE_LOCK")
        elif stmt.is_lock_shared():
            msg["locking"] = mysqlxpb_enum("Mysqlx.Crud.Find.RowLock.SHARED_LOCK")

        if stmt.lock_contention.value > 0:
            msg["locking_options"] = stmt.lock_contention.value

        return "Mysqlx.ClientMessages.Type.CRUD_FIND", msg

    def build_update(
        self, stmt: Union[ModifyStatement, UpdateStatement]
    ) -> Tuple[str, MessageType]:
        """Build update message.

        Args:
            stmt (Statement): A :class:`mysqlx.ModifyStatement` or
                              :class:`mysqlx.UpdateStatement` object.

        Returns:
            (tuple): Tuple containing:

                * `str`: Message ID string.
                * :class:`mysqlx.protobuf.Message`: MySQL X Protobuf Message.

        .. versionadded:: 8.0.16
        """
        data_model = mysqlxpb_enum(
            "Mysqlx.Crud.DataModel.DOCUMENT"
            if stmt.is_doc_based()
            else "Mysqlx.Crud.DataModel.TABLE"
        )
        collection = Message(
            "Mysqlx.Crud.Collection",
            name=stmt.target.name,
            schema=stmt.schema.name,
        )
        msg = Message(
            "Mysqlx.Crud.Update", data_model=data_model, collection=collection
        )
        self._apply_filter(msg, stmt)
        for _, update_op in stmt.get_update_ops().items():
            operation = Message("Mysqlx.Crud.UpdateOperation")
            operation["operation"] = update_op.update_type
            operation["source"] = update_op.source
            if update_op.value is not None:
                operation["value"] = build_expr(update_op.value)
            msg["operation"].extend([operation.get_message()])

        return "Mysqlx.ClientMessages.Type.CRUD_UPDATE", msg

    def build_delete(
        self, stmt: Union[DeleteStatement, RemoveStatement]
    ) -> Tuple[str, MessageType]:
        """Build delete message.

        Args:
            stmt (Statement): A :class:`mysqlx.DeleteStatement` or
                              :class:`mysqlx.RemoveStatement` object.

        Returns:
            (tuple): Tuple containing:

                * `str`: Message ID string.
                * :class:`mysqlx.protobuf.Message`: MySQL X Protobuf Message.

        .. versionadded:: 8.0.16
        """
        data_model = mysqlxpb_enum(
            "Mysqlx.Crud.DataModel.DOCUMENT"
            if stmt.is_doc_based()
            else "Mysqlx.Crud.DataModel.TABLE"
        )
        collection = Message(
            "Mysqlx.Crud.Collection",
            name=stmt.target.name,
            schema=stmt.schema.name,
        )
        msg = Message(
            "Mysqlx.Crud.Delete", data_model=data_model, collection=collection
        )
        self._apply_filter(msg, stmt)
        return "Mysqlx.ClientMessages.Type.CRUD_DELETE", msg

    def build_execute_statement(
        self,
        namespace: str,
        stmt: Union[str, StatementType],
        fields: Optional[Dict[str, Any]] = None,
    ) -> Tuple[str, MessageType]:
        """Build execute statement.

        Args:
            namespace (str): The namespace.
            stmt (Statement): A `Statement` based type object.
            fields (Optional[dict]): The message fields.

        Returns:
            (tuple): Tuple containing:

                * `str`: Message ID string.
                * :class:`mysqlx.protobuf.Message`: MySQL X Protobuf Message.

        .. versionadded:: 8.0.16
        """
        msg = Message(
            "Mysqlx.Sql.StmtExecute",
            namespace=namespace,
            stmt=stmt,
            compact_metadata=False,
        )

        if fields:
            obj_flds = []
            for key, value in fields.items():
                obj_fld = Message(
                    "Mysqlx.Datatypes.Object.ObjectField",
                    key=key,
                    value=self._create_any(value),
                )
                obj_flds.append(obj_fld.get_message())
            msg_obj = Message("Mysqlx.Datatypes.Object", fld=obj_flds)
            msg_any = Message("Mysqlx.Datatypes.Any", type=2, obj=msg_obj)
            msg["args"] = [msg_any.get_message()]
        return "Mysqlx.ClientMessages.Type.SQL_STMT_EXECUTE", msg

    @staticmethod
    def build_insert(
        stmt: Union[AddStatement, InsertStatement]
    ) -> Tuple[str, MessageType]:
        """Build insert statement.

        Args:
            stmt (Statement): A :class:`mysqlx.AddStatement` or
                              :class:`mysqlx.InsertStatement` object.

        Returns:
            (tuple): Tuple containing:

                * `str`: Message ID string.
                * :class:`mysqlx.protobuf.Message`: MySQL X Protobuf Message.

        .. versionadded:: 8.0.16
        """
        data_model = mysqlxpb_enum(
            "Mysqlx.Crud.DataModel.DOCUMENT"
            if stmt.is_doc_based()
            else "Mysqlx.Crud.DataModel.TABLE"
        )
        collection = Message(
            "Mysqlx.Crud.Collection",
            name=stmt.target.name,
            schema=stmt.schema.name,
        )
        msg = Message(
            "Mysqlx.Crud.Insert", data_model=data_model, collection=collection
        )

        if hasattr(stmt, "_fields"):
            for field in stmt._fields:
                expr = ExprParser(
                    field, not stmt.is_doc_based()
                ).parse_table_insert_field()
                msg["projection"].extend([expr.get_message()])

        for value in stmt.get_values():
            row = Message("Mysqlx.Crud.Insert.TypedRow")
            if isinstance(value, list):
                for val in value:
                    row["field"].extend([build_expr(val).get_message()])
            else:
                row["field"].extend([build_expr(value).get_message()])
            msg["row"].extend([row.get_message()])

        if hasattr(stmt, "is_upsert"):
            msg["upsert"] = stmt.is_upsert()

        return "Mysqlx.ClientMessages.Type.CRUD_INSERT", msg

    def close_result(self, result: ResultBaseType) -> None:
        """Close the result.

        Args:
            result (Result): A `Result` based type object.

        Raises:
            :class:`mysqlx.OperationalError`: If message read is None.
        """
        msg = self._read_message(result)
        if msg is not None:
            raise OperationalError("Expected to close the result")

    def read_row(self, result: ResultBaseType) -> Optional[MessageType]:
        """Read row.

        Args:
            result (Result): A `Result` based type object.
        """
        msg = self._read_message(result)
        if msg is None:
            return None
        if msg.type == "Mysqlx.Resultset.Row":
            return msg
        self._reader.push_message(msg)
        return None

    def get_column_metadata(self, result: ResultBaseType) -> List[ColumnType]:
        """Returns column metadata.

        Args:
            result (Result): A `Result` based type object.

        Raises:
            :class:`mysqlx.InterfaceError`: If unexpected message.
        """
        columns = []
        while True:
            msg = self._read_message(result)
            if msg is None:
                break
            if msg.type == "Mysqlx.Resultset.Row":
                self._reader.push_message(msg)
                break
            if msg.type != "Mysqlx.Resultset.ColumnMetaData":
                raise InterfaceError("Unexpected msg type")
            col = Column(
                msg["type"],
                msg["catalog"],
                msg["schema"],
                msg["table"],
                msg["original_table"],
                msg["name"],
                msg["original_name"],
                msg.get("length", 21),
                msg.get("collation", 0),
                msg.get("fractional_digits", 0),
                msg.get("flags", 16),
                msg.get("content_type"),
            )
            columns.append(col)
        return columns

    def read_ok(self) -> None:
        """Read OK.

        Raises:
            :class:`mysqlx.InterfaceError`: If unexpected message.
        """
        msg = self._reader.read_message()
        if msg.type == "Mysqlx.Error":
            raise InterfaceError(f"Mysqlx.Error: {msg['msg']}", errno=msg["code"])
        if msg.type != "Mysqlx.Ok":
            raise InterfaceError("Unexpected message encountered")

    def send_connection_close(self) -> None:
        """Send connection close."""
        msg = Message("Mysqlx.Connection.Close")
        self._writer.write_message(
            mysqlxpb_enum("Mysqlx.ClientMessages.Type.CON_CLOSE"), msg
        )

    def send_close(self) -> None:
        """Send close."""
        msg = Message("Mysqlx.Session.Close")
        self._writer.write_message(
            mysqlxpb_enum("Mysqlx.ClientMessages.Type.SESS_CLOSE"), msg
        )

    def send_expect_open(self) -> None:
        """Send expectation."""
        cond_key = mysqlxpb_enum("Mysqlx.Expect.Open.Condition.Key.EXPECT_FIELD_EXIST")
        msg_oc = Message("Mysqlx.Expect.Open.Condition")
        msg_oc["condition_key"] = cond_key
        msg_oc["condition_value"] = "6.1"

        msg_eo = Message("Mysqlx.Expect.Open")
        msg_eo["cond"] = [msg_oc.get_message()]

        self._writer.write_message(
            mysqlxpb_enum("Mysqlx.ClientMessages.Type.EXPECT_OPEN"), msg_eo
        )

    def send_reset(self, keep_open: Optional[bool] = None) -> bool:
        """Send reset session message.

        Returns:
            boolean: ``True`` if the server will keep the session open,
                     otherwise ``False``.
        """
        msg = Message("Mysqlx.Session.Reset")
        if keep_open is None:
            try:
                # Send expectation: keep connection open
                self.send_expect_open()
                self.read_ok()
                keep_open = True
            except InterfaceError:
                # Expectation is unkown by this version of the server
                keep_open = False
        if keep_open:
            msg["keep_open"] = True
        self._writer.write_message(
            mysqlxpb_enum("Mysqlx.ClientMessages.Type.SESS_RESET"), msg
        )
        self.read_ok()
        if keep_open:
            return True
        return False

virt/lib/python3.9/site-packages/mysqlx/protocol 2.py
PrinceDisant/LostInNL
# Copyright (c) 2016, 2023, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

"""Implementation of the X protocol for MySQL servers."""

import struct
import zlib

from io import BytesIO
from typing import Any, Dict, List, Optional, Tuple, Union

try:
    import lz4.frame

    HAVE_LZ4 = True
except ImportError:
    HAVE_LZ4 = False

try:
    import zstandard as zstd

    HAVE_ZSTD = True
except ImportError:
    HAVE_ZSTD = False

from .errors import (
    InterfaceError,
    NotSupportedError,
    OperationalError,
    ProgrammingError,
)
from .expr import (
    ExprParser,
    build_bool_scalar,
    build_expr,
    build_int_scalar,
    build_scalar,
    build_unsigned_int_scalar,
)
from .helpers import encode_to_bytes, get_item_or_attr
from .logger import logger
from .protobuf import CRUD_PREPARE_MAPPING, SERVER_MESSAGES, Message, mysqlxpb_enum
from .result import Column
from .statement import (
    AddStatement,
    DeleteStatement,
    FilterableStatement,
    FindStatement,
    InsertStatement,
    ModifyStatement,
    ReadStatement,
    RemoveStatement,
    SqlStatement,
    UpdateStatement,
)
from .types import (
    ColumnType,
    MessageType,
    ProtobufMessageCextType,
    ProtobufMessageType,
    ResultBaseType,
    SocketType,
    StatementType,
    StrOrBytes,
)

_COMPRESSION_THRESHOLD = 1000


class Compressor:
    """Implements compression/decompression using `zstd_stream`, `lz4_message`
    and `deflate_stream` algorithms.

    Args:
        algorithm (str): Compression algorithm.

    .. versionadded:: 8.0.21

    """

    def __init__(self, algorithm: str) -> None:
        self._algorithm: str = algorithm
        self._compressobj: Any = None
        self._decompressobj: Any = None

        if algorithm == "zstd_stream":
            self._compressobj = zstd.ZstdCompressor()
            self._decompressobj = zstd.ZstdDecompressor()
        elif algorithm == "deflate_stream":
            self._compressobj = zlib.compressobj()
            self._decompressobj = zlib.decompressobj()

    def compress(self, data: StrOrBytes) -> bytes:
        """Compresses data and returns it.

        Args:
            data (str, bytes or buffer object): Data to be compressed.

        Returns:
            bytes: Compressed data.
        """
        if self._algorithm == "zstd_stream":
            return self._compressobj.compress(data)
        if self._algorithm == "lz4_message":
            with lz4.frame.LZ4FrameCompressor() as compressor:
                compressed = compressor.begin()
                compressed += compressor.compress(data)
                compressed += compressor.flush()
            return compressed

        # Using 'deflate_stream' algorithm
        compressed = self._compressobj.compress(data)
        compressed += self._compressobj.flush(zlib.Z_SYNC_FLUSH)
        return compressed

    def decompress(self, data: StrOrBytes) -> bytes:
        """Decompresses a frame of data and returns it as a string of bytes.

        Args:
            data (str, bytes or buffer object): Data to be compressed.

        Returns:
            bytes: Decompresssed data.
        """
        if self._algorithm == "zstd_stream":
            return self._decompressobj.decompress(data)
        if self._algorithm == "lz4_message":
            with lz4.frame.LZ4FrameDecompressor() as decompressor:
                decompressed = decompressor.decompress(data)
            return decompressed

        # Using 'deflate' algorithm
        decompressed = self._decompressobj.decompress(data)
        decompressed += self._decompressobj.flush(zlib.Z_SYNC_FLUSH)
        return decompressed


class MessageReader:
    """Implements a Message Reader.

    Args:
        socket_stream (mysqlx.connection.SocketStream): `SocketStream` object.

    .. versionadded:: 8.0.21
    """

    def __init__(self, socket_stream: SocketType) -> None:
        self._stream: SocketType = socket_stream
        self._compressor: Optional[Compressor] = None
        self._msg: MessageType = None
        self._msg_queue: List[Message] = []

    def _read_message(self) -> MessageType:
        """Reads X Protocol messages from the stream and returns a
        :class:`mysqlx.protobuf.Message` object.

        Raises:
            :class:`mysqlx.ProgrammingError`: If e connected server does not
                                              have the MySQL X protocol plugin
                                              enabled.

        Returns:
            mysqlx.protobuf.Message: MySQL X Protobuf Message.
        """
        if self._msg_queue:
            return self._msg_queue.pop(0)

        frame_size, frame_type = struct.unpack("<LB", self._stream.read(5))

        if frame_type == 10:
            raise ProgrammingError(
                "The connected server does not have the "
                "MySQL X protocol plugin enabled or "
                "protocol mismatch"
            )

        frame_payload = self._stream.read(frame_size - 1)
        if frame_type not in SERVER_MESSAGES:
            raise ValueError(f"Unknown message type: {frame_type}")

        # Do not parse empty notices, Message requires a type in payload
        if frame_type == 11 and frame_payload == b"":
            return self._read_message()

        frame_msg = Message.from_server_message(frame_type, frame_payload)

        if frame_type == 19:  # Mysqlx.ServerMessages.Type.COMPRESSION
            uncompressed_size = frame_msg["uncompressed_size"]
            stream = BytesIO(self._compressor.decompress(frame_msg["payload"]))
            bytes_processed = 0
            while bytes_processed < uncompressed_size:
                payload_size, msg_type = struct.unpack("<LB", stream.read(5))
                payload = stream.read(payload_size - 1)
                self._msg_queue.append(Message.from_server_message(msg_type, payload))
                bytes_processed += payload_size + 4
            return self._msg_queue.pop(0) if self._msg_queue else None

        return frame_msg

    def read_message(self) -> MessageType:
        """Read message.

        Returns:
            mysqlx.protobuf.Message: MySQL X Protobuf Message.
        """
        if self._msg is not None:
            msg = self._msg
            self._msg = None
            return msg
        return self._read_message()

    def push_message(self, msg: MessageType) -> None:
        """Push message.

        Args:
            msg (mysqlx.protobuf.Message): MySQL X Protobuf Message.

        Raises:
            :class:`mysqlx.OperationalError`: If message push slot is full.
        """
        if self._msg is not None:
            raise OperationalError("Message push slot is full")
        self._msg = msg

    def set_compression(self, algorithm: str) -> None:
        """Creates a :class:`mysqlx.protocol.Compressor` object based on the
        compression algorithm.

        Args:
            algorithm (str): Compression algorithm.

        .. versionadded:: 8.0.21

        """
        self._compressor = Compressor(algorithm) if algorithm else None


class MessageWriter:
    """Implements a Message Writer.

    Args:
        socket_stream (mysqlx.connection.SocketStream): `SocketStream` object.

    .. versionadded:: 8.0.21

    """

    def __init__(self, socket_stream: SocketType) -> None:
        self._stream: SocketType = socket_stream
        self._compressor: Optional[Compressor] = None

    def write_message(self, msg_type: int, msg: MessageType) -> None:
        """Write message.

        Args:
            msg_type (int): The message type.
            msg (mysqlx.protobuf.Message): MySQL X Protobuf Message.
        """
        msg_size = msg.byte_size(msg)
        if self._compressor and msg_size > _COMPRESSION_THRESHOLD:
            msg_str = encode_to_bytes(msg.serialize_to_string())
            header = struct.pack("<LB", msg_size + 1, msg_type)
            compressed = self._compressor.compress(b"".join([header, msg_str]))

            msg_first_fields = Message("Mysqlx.Connection.Compression")
            msg_first_fields["client_messages"] = msg_type
            msg_first_fields["uncompressed_size"] = msg_size + 5

            msg_payload = Message("Mysqlx.Connection.Compression")
            msg_payload["payload"] = compressed

            output = b"".join(
                [
                    encode_to_bytes(msg_first_fields.serialize_partial_to_string())[
                        :-2
                    ],
                    encode_to_bytes(msg_payload.serialize_partial_to_string()),
                ]
            )

            msg_comp_id = mysqlxpb_enum("Mysqlx.ClientMessages.Type.COMPRESSION")
            header = struct.pack("<LB", len(output) + 1, msg_comp_id)
            self._stream.sendall(b"".join([header, output]))
        else:
            msg_str = encode_to_bytes(msg.serialize_to_string())
            header = struct.pack("<LB", msg_size + 1, msg_type)
            self._stream.sendall(b"".join([header, msg_str]))

    def set_compression(self, algorithm: str) -> None:
        """Creates a :class:`mysqlx.protocol.Compressor` object based on the
        compression algorithm.

        Args:
            algorithm (str): Compression algorithm.
        """
        self._compressor = Compressor(algorithm) if algorithm else None


class Protocol:
    """Implements the MySQL X Protocol.

    Args:
        read (mysqlx.protocol.MessageReader): A Message Reader object.
        writer (mysqlx.protocol.MessageWriter): A Message Writer object.

    .. versionchanged:: 8.0.21
    """

    def __init__(self, reader: MessageReader, writer: MessageWriter) -> None:
        self._reader: MessageReader = reader
        self._writer: MessageWriter = writer
        self._compression_algorithm: Optional[str] = None
        self._warnings: List[str] = []

    @property
    def compression_algorithm(self) -> Optional[str]:
        """str: The compresion algorithm."""
        return self._compression_algorithm

    @staticmethod
    def _apply_filter(msg: MessageType, stmt: FilterableStatement) -> None:
        """Apply filter.

        Args:
            msg (mysqlx.protobuf.Message): The MySQL X Protobuf Message.
            stmt (Statement): A `Statement` based type object.
        """
        if stmt.has_where:
            msg["criteria"] = stmt.get_where_expr()
        if stmt.has_sort:
            msg["order"].extend(stmt.get_sort_expr())
        if stmt.has_group_by:
            msg["grouping"].extend(stmt.get_grouping())
        if stmt.has_having:
            msg["grouping_criteria"] = stmt.get_having()

    def _create_any(self, arg: Any) -> Optional[MessageType]:
        """Create any.

        Args:
            arg (object): Arbitrary object.

        Returns:
            mysqlx.protobuf.Message: MySQL X Protobuf Message.
        """
        if isinstance(arg, str):
            value = Message("Mysqlx.Datatypes.Scalar.String", value=arg)
            scalar = Message("Mysqlx.Datatypes.Scalar", type=8, v_string=value)
            return Message("Mysqlx.Datatypes.Any", type=1, scalar=scalar)
        if isinstance(arg, bool):
            return Message(
                "Mysqlx.Datatypes.Any", type=1, scalar=build_bool_scalar(arg)
            )
        if isinstance(arg, int):
            if arg < 0:
                return Message(
                    "Mysqlx.Datatypes.Any",
                    type=1,
                    scalar=build_int_scalar(arg),
                )
            return Message(
                "Mysqlx.Datatypes.Any",
                type=1,
                scalar=build_unsigned_int_scalar(arg),
            )
        if isinstance(arg, tuple) and len(arg) == 2:
            arg_key, arg_value = arg
            obj_fld = Message(
                "Mysqlx.Datatypes.Object.ObjectField",
                key=arg_key,
                value=self._create_any(arg_value),
            )
            obj = Message("Mysqlx.Datatypes.Object", fld=[obj_fld.get_message()])
            return Message("Mysqlx.Datatypes.Any", type=2, obj=obj)
        if isinstance(arg, dict) or (
            isinstance(arg, (list, tuple)) and isinstance(arg[0], dict)
        ):
            array_values = []
            for items in arg:
                obj_flds = []
                for key, value in items.items():
                    # Array can only handle Any types, Mysqlx.Datatypes.Any.obj
                    obj_fld = Message(
                        "Mysqlx.Datatypes.Object.ObjectField",
                        key=key,
                        value=self._create_any(value),
                    )
                    obj_flds.append(obj_fld.get_message())
                msg_obj = Message("Mysqlx.Datatypes.Object", fld=obj_flds)
                msg_any = Message("Mysqlx.Datatypes.Any", type=2, obj=msg_obj)
                array_values.append(msg_any.get_message())

            msg = Message("Mysqlx.Datatypes.Array")
            msg["value"] = array_values
            return Message("Mysqlx.Datatypes.Any", type=3, array=msg)
        if isinstance(arg, list):
            obj_flds = []
            for key, value in arg:
                obj_fld = Message(
                    "Mysqlx.Datatypes.Object.ObjectField",
                    key=key,
                    value=self._create_any(value),
                )
                obj_flds.append(obj_fld.get_message())
            msg_obj = Message("Mysqlx.Datatypes.Object", fld=obj_flds)
            msg_any = Message("Mysqlx.Datatypes.Any", type=2, obj=msg_obj)
            return msg_any

        return None

    def _get_binding_args(
        self, stmt: Union[FilterableStatement, SqlStatement], is_scalar: bool = True
    ) -> Union[List[None], List[Union[ProtobufMessageType, ProtobufMessageCextType]]]:
        """Returns the binding any/scalar.

        Args:
            stmt (Statement): A `Statement` based type object.
            is_scalar (bool): `True` to return scalar values.

        Raises:
            :class:`mysqlx.ProgrammingError`: If unable to find placeholder for
                                              parameter.

        Returns:
            list: A list of ``Any`` or ``Scalar`` objects.
        """

        def build_value(
            value: Any,
        ) -> Union[ProtobufMessageType, ProtobufMessageCextType]:
            if is_scalar:
                return build_scalar(value).get_message()
            return self._create_any(value).get_message()

        bindings = stmt.get_bindings()
        binding_map = stmt.get_binding_map()

        # If binding_map is None it's a SqlStatement object
        if binding_map is None:
            return [build_value(value) for value in bindings]

        count = len(binding_map)
        args: List[Any] = count * [None]
        if count != len(bindings):
            raise ProgrammingError(
                "The number of bind parameters and placeholders do not match"
            )
        for name, value in bindings.items():  # type: ignore[union-attr]
            if name not in binding_map:
                raise ProgrammingError(
                    f"Unable to find placeholder for parameter: {name}"
                )
            pos = binding_map[name]
            args[pos] = build_value(value)
        return args

    def _process_frame(self, msg: MessageType, result: ResultBaseType) -> None:
        """Process frame.

        Args:
            msg (mysqlx.protobuf.Message): A MySQL X Protobuf Message.
            result (Result): A `Result` based type object.
        """
        if msg["type"] == 1:
            warn_msg = Message.from_message("Mysqlx.Notice.Warning", msg["payload"])
            self._warnings.append(warn_msg.msg)
            logger.warning(
                "Protocol.process_frame Received Warning Notice code %s: %s",
                warn_msg.code,
                warn_msg.msg,
            )
            result.append_warning(warn_msg.level, warn_msg.code, warn_msg.msg)
        elif msg["type"] == 2:
            Message.from_message("Mysqlx.Notice.SessionVariableChanged", msg["payload"])
        elif msg["type"] == 3:
            sess_state_msg = Message.from_message(
                "Mysqlx.Notice.SessionStateChanged", msg["payload"]
            )
            if sess_state_msg["param"] == mysqlxpb_enum(
                "Mysqlx.Notice.SessionStateChanged.Parameter.GENERATED_DOCUMENT_IDS"
            ):
                result.set_generated_ids(
                    [
                        get_item_or_attr(
                            get_item_or_attr(value, "v_octets"), "value"
                        ).decode()
                        for value in sess_state_msg["value"]
                    ]
                )
            else:  # Following results are unitary and not a list
                sess_state_value = sess_state_msg["value"].pop()
                if sess_state_msg["param"] == mysqlxpb_enum(
                    "Mysqlx.Notice.SessionStateChanged.Parameter.ROWS_AFFECTED"
                ):
                    result.set_rows_affected(
                        get_item_or_attr(sess_state_value, "v_unsigned_int")
                    )
                elif sess_state_msg["param"] == mysqlxpb_enum(
                    "Mysqlx.Notice.SessionStateChanged.Parameter.GENERATED_INSERT_ID"
                ):
                    result.set_generated_insert_id(
                        get_item_or_attr(sess_state_value, "v_unsigned_int")
                    )

    def _read_message(self, result: ResultBaseType) -> Optional[MessageType]:
        """Read message.

        Args:
            result (Result): A `Result` based type object.
        """
        while True:
            try:
                msg = self._reader.read_message()
            except RuntimeError as err:
                warnings = repr(result.get_warnings())
                if warnings:
                    raise RuntimeError(f"{err} reason: {warnings}") from err
            if msg.type == "Mysqlx.Error":
                raise OperationalError(msg["msg"], msg["code"])
            if msg.type == "Mysqlx.Notice.Frame":
                try:
                    self._process_frame(msg, result)
                except (AttributeError, KeyError):
                    continue
            elif msg.type == "Mysqlx.Sql.StmtExecuteOk":
                return None
            elif msg.type == "Mysqlx.Resultset.FetchDone":
                result.set_closed(True)
            elif msg.type == "Mysqlx.Resultset.FetchDoneMoreResultsets":
                result.set_has_more_results(True)
            elif msg.type == "Mysqlx.Resultset.Row":
                result.set_has_data(True)
                break
            else:
                break
        return msg

    def set_compression(self, algorithm: str) -> None:
        """Sets the compression algorithm to be used by the compression
        object, for uplink and downlink.

        Args:
            algorithm (str): Algorithm to be used in compression/decompression.

        .. versionadded:: 8.0.21

        """
        self._compression_algorithm = algorithm
        self._reader.set_compression(algorithm)
        self._writer.set_compression(algorithm)

    def get_capabilites(self) -> MessageType:
        """Get capabilities.

        Returns:
            mysqlx.protobuf.Message: MySQL X Protobuf Message.
        """
        msg = Message("Mysqlx.Connection.CapabilitiesGet")
        self._writer.write_message(
            mysqlxpb_enum("Mysqlx.ClientMessages.Type.CON_CAPABILITIES_GET"),
            msg,
        )
        msg = self._reader.read_message()
        while msg.type == "Mysqlx.Notice.Frame":
            msg = self._reader.read_message()

        if msg.type == "Mysqlx.Error":
            raise OperationalError(msg["msg"], msg["code"])

        return msg

    def set_capabilities(self, **kwargs: Any) -> None:
        """Set capabilities.

        Args:
            **kwargs: Arbitrary keyword arguments.

        Returns:
            mysqlx.protobuf.Message: MySQL X Protobuf Message.
        """
        if not kwargs:
            return None
        capabilities = Message("Mysqlx.Connection.Capabilities")
        for key, value in kwargs.items():
            capability = Message("Mysqlx.Connection.Capability")
            capability["name"] = key
            if isinstance(value, dict):
                items = value
                obj_flds = []
                for item in items:
                    obj_fld = Message(
                        "Mysqlx.Datatypes.Object.ObjectField",
                        key=item,
                        value=self._create_any(items[item]),
                    )
                    obj_flds.append(obj_fld.get_message())
                msg_obj = Message("Mysqlx.Datatypes.Object", fld=obj_flds)
                msg_any = Message("Mysqlx.Datatypes.Any", type=2, obj=msg_obj)
                capability["value"] = msg_any.get_message()
            else:
                capability["value"] = self._create_any(value)

            capabilities["capabilities"].extend([capability.get_message()])
        msg = Message("Mysqlx.Connection.CapabilitiesSet")
        msg["capabilities"] = capabilities
        self._writer.write_message(
            mysqlxpb_enum("Mysqlx.ClientMessages.Type.CON_CAPABILITIES_SET"),
            msg,
        )

        try:
            return self.read_ok()
        except InterfaceError as err:
            # Skip capability "session_connect_attrs" error since
            # is only available on version >= 8.0.16
            if err.errno != 5002:
                raise
        return None

    def send_auth_start(
        self,
        method: str,
        auth_data: Optional[str] = None,
        initial_response: Optional[str] = None,
    ) -> None:
        """Send authenticate start.

        Args:
            method (str): Message method.
            auth_data (Optional[str]): Authentication data.
            initial_response (Optional[str]): Initial response.
        """
        msg = Message("Mysqlx.Session.AuthenticateStart")
        msg["mech_name"] = method
        if auth_data is not None:
            msg["auth_data"] = auth_data
        if initial_response is not None:
            msg["initial_response"] = initial_response
        self._writer.write_message(
            mysqlxpb_enum("Mysqlx.ClientMessages.Type.SESS_AUTHENTICATE_START"),
            msg,
        )

    def read_auth_continue(self) -> bytes:
        """Read authenticate continue.

        Raises:
            :class:`InterfaceError`: If the message type is not
                                     `Mysqlx.Session.AuthenticateContinue`

        Returns:
            str: The authentication data.
        """
        msg = self._reader.read_message()
        while msg.type == "Mysqlx.Notice.Frame":
            msg = self._reader.read_message()
        if msg.type != "Mysqlx.Session.AuthenticateContinue":
            raise InterfaceError(
                "Unexpected message encountered during authentication handshake"
            )
        return msg["auth_data"]

    def send_auth_continue(self, auth_data: str) -> None:
        """Send authenticate continue.

        Args:
            auth_data (str): Authentication data.
        """
        msg = Message("Mysqlx.Session.AuthenticateContinue", auth_data=auth_data)
        self._writer.write_message(
            mysqlxpb_enum("Mysqlx.ClientMessages.Type.SESS_AUTHENTICATE_CONTINUE"),
            msg,
        )

    def read_auth_ok(self) -> None:
        """Read authenticate OK.

        Raises:
            :class:`mysqlx.InterfaceError`: If message type is `Mysqlx.Error`.
        """
        while True:
            msg = self._reader.read_message()
            if msg.type == "Mysqlx.Session.AuthenticateOk":
                break
            if msg.type == "Mysqlx.Error":
                raise InterfaceError(msg.msg)

    def send_prepare_prepare(
        self,
        msg_type: str,
        msg: MessageType,
        stmt: Union[
            FindStatement,
            DeleteStatement,
            ModifyStatement,
            ReadStatement,
            RemoveStatement,
            UpdateStatement,
        ],
    ) -> None:
        """
        Send prepare statement.

        Args:
            msg_type (str): Message ID string.
            msg (mysqlx.protobuf.Message): MySQL X Protobuf Message.
            stmt (Statement): A `Statement` based type object.

        Raises:
            :class:`mysqlx.NotSupportedError`: If prepared statements are not
                                               supported.

        .. versionadded:: 8.0.16
        """
        if stmt.has_limit and msg.type != "Mysqlx.Crud.Insert":
            # Remove 'limit' from message by building a new one
            if msg.type == "Mysqlx.Crud.Find":
                _, msg = self.build_find(stmt)  # type: ignore[arg-type]
            elif msg.type == "Mysqlx.Crud.Update":
                _, msg = self.build_update(stmt)  # type: ignore[arg-type]
            elif msg.type == "Mysqlx.Crud.Delete":
                _, msg = self.build_delete(stmt)  # type: ignore[arg-type]
            else:
                raise ValueError(f"Invalid message type: {msg_type}")
            # Build 'limit_expr' message
            position = len(stmt.get_bindings())
            placeholder = mysqlxpb_enum("Mysqlx.Expr.Expr.Type.PLACEHOLDER")
            msg_limit_expr = Message("Mysqlx.Crud.LimitExpr")
            msg_limit_expr["row_count"] = Message(
                "Mysqlx.Expr.Expr", type=placeholder, position=position
            )
            if msg.type == "Mysqlx.Crud.Find":
                msg_limit_expr["offset"] = Message(
                    "Mysqlx.Expr.Expr", type=placeholder, position=position + 1
                )
            msg["limit_expr"] = msg_limit_expr

        oneof_type, oneof_op = CRUD_PREPARE_MAPPING[msg_type]
        msg_oneof = Message("Mysqlx.Prepare.Prepare.OneOfMessage")
        msg_oneof["type"] = mysqlxpb_enum(oneof_type)
        msg_oneof[oneof_op] = msg
        msg_prepare = Message("Mysqlx.Prepare.Prepare")
        msg_prepare["stmt_id"] = stmt.stmt_id
        msg_prepare["stmt"] = msg_oneof

        self._writer.write_message(
            mysqlxpb_enum("Mysqlx.ClientMessages.Type.PREPARE_PREPARE"),
            msg_prepare,
        )

        try:
            self.read_ok()
        except InterfaceError as err:
            raise NotSupportedError from err

    def send_prepare_execute(
        self, msg_type: str, msg: MessageType, stmt: FilterableStatement
    ) -> None:
        """
        Send execute statement.

        Args:
            msg_type (str): Message ID string.
            msg (mysqlx.protobuf.Message): MySQL X Protobuf Message.
            stmt (Statement): A `Statement` based type object.

        .. versionadded:: 8.0.16
        """
        oneof_type, oneof_op = CRUD_PREPARE_MAPPING[msg_type]
        msg_oneof = Message("Mysqlx.Prepare.Prepare.OneOfMessage")
        msg_oneof["type"] = mysqlxpb_enum(oneof_type)
        msg_oneof[oneof_op] = msg
        msg_execute = Message("Mysqlx.Prepare.Execute")
        msg_execute["stmt_id"] = stmt.stmt_id

        args = self._get_binding_args(stmt, is_scalar=False)
        if args:
            msg_execute["args"].extend(args)

        if stmt.has_limit:
            msg_execute["args"].extend(
                [
                    self._create_any(stmt.get_limit_row_count()).get_message(),
                    self._create_any(stmt.get_limit_offset()).get_message(),
                ]
            )

        self._writer.write_message(
            mysqlxpb_enum("Mysqlx.ClientMessages.Type.PREPARE_EXECUTE"),
            msg_execute,
        )

    def send_prepare_deallocate(self, stmt_id: int) -> None:
        """
        Send prepare deallocate statement.

        Args:
            stmt_id (int): Statement ID.

        .. versionadded:: 8.0.16
        """
        msg_dealloc = Message("Mysqlx.Prepare.Deallocate")
        msg_dealloc["stmt_id"] = stmt_id
        self._writer.write_message(
            mysqlxpb_enum("Mysqlx.ClientMessages.Type.PREPARE_DEALLOCATE"),
            msg_dealloc,
        )
        self.read_ok()

    def send_msg_without_ps(
        self,
        msg_type: str,
        msg: MessageType,
        stmt: Union[FilterableStatement, SqlStatement],
    ) -> None:
        """
        Send a message without prepared statements support.

        Args:
            msg_type (str): Message ID string.
            msg (mysqlx.protobuf.Message): MySQL X Protobuf Message.
            stmt (Statement): A `Statement` based type object.

        .. versionadded:: 8.0.16
        """
        if stmt.has_limit:
            msg_limit = Message("Mysqlx.Crud.Limit")
            msg_limit["row_count"] = stmt.get_limit_row_count()  # type: ignore[union-attr]
            if msg.type == "Mysqlx.Crud.Find":
                msg_limit["offset"] = stmt.get_limit_offset()  # type: ignore[union-attr]
            msg["limit"] = msg_limit
        is_scalar = msg_type != "Mysqlx.ClientMessages.Type.SQL_STMT_EXECUTE"
        args = self._get_binding_args(stmt, is_scalar=is_scalar)
        if args:
            msg["args"].extend(args)
        self.send_msg(msg_type, msg)

    def send_msg(self, msg_type: str, msg: MessageType) -> None:
        """
        Send a message.

        Args:
            msg_type (str): Message ID string.
            msg (mysqlx.protobuf.Message): MySQL X Protobuf Message.

        .. versionadded:: 8.0.16
        """
        self._writer.write_message(mysqlxpb_enum(msg_type), msg)

    def build_find(
        self, stmt: Union[FindStatement, ReadStatement]
    ) -> Tuple[str, MessageType]:
        """Build find/read message.

        Args:
            stmt (Statement): A :class:`mysqlx.ReadStatement` or
                              :class:`mysqlx.FindStatement` object.

        Returns:
            (tuple): Tuple containing:

                * `str`: Message ID string.
                * :class:`mysqlx.protobuf.Message`: MySQL X Protobuf Message.

        .. versionadded:: 8.0.16
        """
        data_model = mysqlxpb_enum(
            "Mysqlx.Crud.DataModel.DOCUMENT"
            if stmt.is_doc_based()
            else "Mysqlx.Crud.DataModel.TABLE"
        )
        collection = Message(
            "Mysqlx.Crud.Collection",
            name=stmt.target.name,
            schema=stmt.schema.name,
        )
        msg = Message("Mysqlx.Crud.Find", data_model=data_model, collection=collection)
        if stmt.has_projection:
            msg["projection"] = stmt.get_projection_expr()
        self._apply_filter(msg, stmt)

        if stmt.is_lock_exclusive():
            msg["locking"] = mysqlxpb_enum("Mysqlx.Crud.Find.RowLock.EXCLUSIVE_LOCK")
        elif stmt.is_lock_shared():
            msg["locking"] = mysqlxpb_enum("Mysqlx.Crud.Find.RowLock.SHARED_LOCK")

        if stmt.lock_contention.value > 0:
            msg["locking_options"] = stmt.lock_contention.value

        return "Mysqlx.ClientMessages.Type.CRUD_FIND", msg

    def build_update(
        self, stmt: Union[ModifyStatement, UpdateStatement]
    ) -> Tuple[str, MessageType]:
        """Build update message.

        Args:
            stmt (Statement): A :class:`mysqlx.ModifyStatement` or
                              :class:`mysqlx.UpdateStatement` object.

        Returns:
            (tuple): Tuple containing:

                * `str`: Message ID string.
                * :class:`mysqlx.protobuf.Message`: MySQL X Protobuf Message.

        .. versionadded:: 8.0.16
        """
        data_model = mysqlxpb_enum(
            "Mysqlx.Crud.DataModel.DOCUMENT"
            if stmt.is_doc_based()
            else "Mysqlx.Crud.DataModel.TABLE"
        )
        collection = Message(
            "Mysqlx.Crud.Collection",
            name=stmt.target.name,
            schema=stmt.schema.name,
        )
        msg = Message(
            "Mysqlx.Crud.Update", data_model=data_model, collection=collection
        )
        self._apply_filter(msg, stmt)
        for _, update_op in stmt.get_update_ops().items():
            operation = Message("Mysqlx.Crud.UpdateOperation")
            operation["operation"] = update_op.update_type
            operation["source"] = update_op.source
            if update_op.value is not None:
                operation["value"] = build_expr(update_op.value)
            msg["operation"].extend([operation.get_message()])

        return "Mysqlx.ClientMessages.Type.CRUD_UPDATE", msg

    def build_delete(
        self, stmt: Union[DeleteStatement, RemoveStatement]
    ) -> Tuple[str, MessageType]:
        """Build delete message.

        Args:
            stmt (Statement): A :class:`mysqlx.DeleteStatement` or
                              :class:`mysqlx.RemoveStatement` object.

        Returns:
            (tuple): Tuple containing:

                * `str`: Message ID string.
                * :class:`mysqlx.protobuf.Message`: MySQL X Protobuf Message.

        .. versionadded:: 8.0.16
        """
        data_model = mysqlxpb_enum(
            "Mysqlx.Crud.DataModel.DOCUMENT"
            if stmt.is_doc_based()
            else "Mysqlx.Crud.DataModel.TABLE"
        )
        collection = Message(
            "Mysqlx.Crud.Collection",
            name=stmt.target.name,
            schema=stmt.schema.name,
        )
        msg = Message(
            "Mysqlx.Crud.Delete", data_model=data_model, collection=collection
        )
        self._apply_filter(msg, stmt)
        return "Mysqlx.ClientMessages.Type.CRUD_DELETE", msg

    def build_execute_statement(
        self,
        namespace: str,
        stmt: Union[str, StatementType],
        fields: Optional[Dict[str, Any]] = None,
    ) -> Tuple[str, MessageType]:
        """Build execute statement.

        Args:
            namespace (str): The namespace.
            stmt (Statement): A `Statement` based type object.
            fields (Optional[dict]): The message fields.

        Returns:
            (tuple): Tuple containing:

                * `str`: Message ID string.
                * :class:`mysqlx.protobuf.Message`: MySQL X Protobuf Message.

        .. versionadded:: 8.0.16
        """
        msg = Message(
            "Mysqlx.Sql.StmtExecute",
            namespace=namespace,
            stmt=stmt,
            compact_metadata=False,
        )

        if fields:
            obj_flds = []
            for key, value in fields.items():
                obj_fld = Message(
                    "Mysqlx.Datatypes.Object.ObjectField",
                    key=key,
                    value=self._create_any(value),
                )
                obj_flds.append(obj_fld.get_message())
            msg_obj = Message("Mysqlx.Datatypes.Object", fld=obj_flds)
            msg_any = Message("Mysqlx.Datatypes.Any", type=2, obj=msg_obj)
            msg["args"] = [msg_any.get_message()]
        return "Mysqlx.ClientMessages.Type.SQL_STMT_EXECUTE", msg

    @staticmethod
    def build_insert(
        stmt: Union[AddStatement, InsertStatement]
    ) -> Tuple[str, MessageType]:
        """Build insert statement.

        Args:
            stmt (Statement): A :class:`mysqlx.AddStatement` or
                              :class:`mysqlx.InsertStatement` object.

        Returns:
            (tuple): Tuple containing:

                * `str`: Message ID string.
                * :class:`mysqlx.protobuf.Message`: MySQL X Protobuf Message.

        .. versionadded:: 8.0.16
        """
        data_model = mysqlxpb_enum(
            "Mysqlx.Crud.DataModel.DOCUMENT"
            if stmt.is_doc_based()
            else "Mysqlx.Crud.DataModel.TABLE"
        )
        collection = Message(
            "Mysqlx.Crud.Collection",
            name=stmt.target.name,
            schema=stmt.schema.name,
        )
        msg = Message(
            "Mysqlx.Crud.Insert", data_model=data_model, collection=collection
        )

        if hasattr(stmt, "_fields"):
            for field in stmt._fields:
                expr = ExprParser(
                    field, not stmt.is_doc_based()
                ).parse_table_insert_field()
                msg["projection"].extend([expr.get_message()])

        for value in stmt.get_values():
            row = Message("Mysqlx.Crud.Insert.TypedRow")
            if isinstance(value, list):
                for val in value:
                    row["field"].extend([build_expr(val).get_message()])
            else:
                row["field"].extend([build_expr(value).get_message()])
            msg["row"].extend([row.get_message()])

        if hasattr(stmt, "is_upsert"):
            msg["upsert"] = stmt.is_upsert()

        return "Mysqlx.ClientMessages.Type.CRUD_INSERT", msg

    def close_result(self, result: ResultBaseType) -> None:
        """Close the result.

        Args:
            result (Result): A `Result` based type object.

        Raises:
            :class:`mysqlx.OperationalError`: If message read is None.
        """
        msg = self._read_message(result)
        if msg is not None:
            raise OperationalError("Expected to close the result")

    def read_row(self, result: ResultBaseType) -> Optional[MessageType]:
        """Read row.

        Args:
            result (Result): A `Result` based type object.
        """
        msg = self._read_message(result)
        if msg is None:
            return None
        if msg.type == "Mysqlx.Resultset.Row":
            return msg
        self._reader.push_message(msg)
        return None

    def get_column_metadata(self, result: ResultBaseType) -> List[ColumnType]:
        """Returns column metadata.

        Args:
            result (Result): A `Result` based type object.

        Raises:
            :class:`mysqlx.InterfaceError`: If unexpected message.
        """
        columns = []
        while True:
            msg = self._read_message(result)
            if msg is None:
                break
            if msg.type == "Mysqlx.Resultset.Row":
                self._reader.push_message(msg)
                break
            if msg.type != "Mysqlx.Resultset.ColumnMetaData":
                raise InterfaceError("Unexpected msg type")
            col = Column(
                msg["type"],
                msg["catalog"],
                msg["schema"],
                msg["table"],
                msg["original_table"],
                msg["name"],
                msg["original_name"],
                msg.get("length", 21),
                msg.get("collation", 0),
                msg.get("fractional_digits", 0),
                msg.get("flags", 16),
                msg.get("content_type"),
            )
            columns.append(col)
        return columns

    def read_ok(self) -> None:
        """Read OK.

        Raises:
            :class:`mysqlx.InterfaceError`: If unexpected message.
        """
        msg = self._reader.read_message()
        if msg.type == "Mysqlx.Error":
            raise InterfaceError(f"Mysqlx.Error: {msg['msg']}", errno=msg["code"])
        if msg.type != "Mysqlx.Ok":
            raise InterfaceError("Unexpected message encountered")

    def send_connection_close(self) -> None:
        """Send connection close."""
        msg = Message("Mysqlx.Connection.Close")
        self._writer.write_message(
            mysqlxpb_enum("Mysqlx.ClientMessages.Type.CON_CLOSE"), msg
        )

    def send_close(self) -> None:
        """Send close."""
        msg = Message("Mysqlx.Session.Close")
        self._writer.write_message(
            mysqlxpb_enum("Mysqlx.ClientMessages.Type.SESS_CLOSE"), msg
        )

    def send_expect_open(self) -> None:
        """Send expectation."""
        cond_key = mysqlxpb_enum("Mysqlx.Expect.Open.Condition.Key.EXPECT_FIELD_EXIST")
        msg_oc = Message("Mysqlx.Expect.Open.Condition")
        msg_oc["condition_key"] = cond_key
        msg_oc["condition_value"] = "6.1"

        msg_eo = Message("Mysqlx.Expect.Open")
        msg_eo["cond"] = [msg_oc.get_message()]

        self._writer.write_message(
            mysqlxpb_enum("Mysqlx.ClientMessages.Type.EXPECT_OPEN"), msg_eo
        )

    def send_reset(self, keep_open: Optional[bool] = None) -> bool:
        """Send reset session message.

        Returns:
            boolean: ``True`` if the server will keep the session open,
                     otherwise ``False``.
        """
        msg = Message("Mysqlx.Session.Reset")
        if keep_open is None:
            try:
                # Send expectation: keep connection open
                self.send_expect_open()
                self.read_ok()
                keep_open = True
            except InterfaceError:
                # Expectation is unkown by this version of the server
                keep_open = False
        if keep_open:
            msg["keep_open"] = True
        self._writer.write_message(
            mysqlxpb_enum("Mysqlx.ClientMessages.Type.SESS_RESET"), msg
        )
        self.read_ok()
        if keep_open:
            return True
        return False

virt/lib/python3.9/site-packages/mysqlx/protocol 3.py
PrinceDisant/LostInNL
# Copyright (c) 2016, 2023, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

"""Implementation of the X protocol for MySQL servers."""

import struct
import zlib

from io import BytesIO
from typing import Any, Dict, List, Optional, Tuple, Union

try:
    import lz4.frame

    HAVE_LZ4 = True
except ImportError:
    HAVE_LZ4 = False

try:
    import zstandard as zstd

    HAVE_ZSTD = True
except ImportError:
    HAVE_ZSTD = False

from .errors import (
    InterfaceError,
    NotSupportedError,
    OperationalError,
    ProgrammingError,
)
from .expr import (
    ExprParser,
    build_bool_scalar,
    build_expr,
    build_int_scalar,
    build_scalar,
    build_unsigned_int_scalar,
)
from .helpers import encode_to_bytes, get_item_or_attr
from .logger import logger
from .protobuf import CRUD_PREPARE_MAPPING, SERVER_MESSAGES, Message, mysqlxpb_enum
from .result import Column
from .statement import (
    AddStatement,
    DeleteStatement,
    FilterableStatement,
    FindStatement,
    InsertStatement,
    ModifyStatement,
    ReadStatement,
    RemoveStatement,
    SqlStatement,
    UpdateStatement,
)
from .types import (
    ColumnType,
    MessageType,
    ProtobufMessageCextType,
    ProtobufMessageType,
    ResultBaseType,
    SocketType,
    StatementType,
    StrOrBytes,
)

_COMPRESSION_THRESHOLD = 1000


class Compressor:
    """Implements compression/decompression using `zstd_stream`, `lz4_message`
    and `deflate_stream` algorithms.

    Args:
        algorithm (str): Compression algorithm.

    .. versionadded:: 8.0.21

    """

    def __init__(self, algorithm: str) -> None:
        self._algorithm: str = algorithm
        self._compressobj: Any = None
        self._decompressobj: Any = None

        if algorithm == "zstd_stream":
            self._compressobj = zstd.ZstdCompressor()
            self._decompressobj = zstd.ZstdDecompressor()
        elif algorithm == "deflate_stream":
            self._compressobj = zlib.compressobj()
            self._decompressobj = zlib.decompressobj()

    def compress(self, data: StrOrBytes) -> bytes:
        """Compresses data and returns it.

        Args:
            data (str, bytes or buffer object): Data to be compressed.

        Returns:
            bytes: Compressed data.
        """
        if self._algorithm == "zstd_stream":
            return self._compressobj.compress(data)
        if self._algorithm == "lz4_message":
            with lz4.frame.LZ4FrameCompressor() as compressor:
                compressed = compressor.begin()
                compressed += compressor.compress(data)
                compressed += compressor.flush()
            return compressed

        # Using 'deflate_stream' algorithm
        compressed = self._compressobj.compress(data)
        compressed += self._compressobj.flush(zlib.Z_SYNC_FLUSH)
        return compressed

    def decompress(self, data: StrOrBytes) -> bytes:
        """Decompresses a frame of data and returns it as a string of bytes.

        Args:
            data (str, bytes or buffer object): Data to be compressed.

        Returns:
            bytes: Decompresssed data.
        """
        if self._algorithm == "zstd_stream":
            return self._decompressobj.decompress(data)
        if self._algorithm == "lz4_message":
            with lz4.frame.LZ4FrameDecompressor() as decompressor:
                decompressed = decompressor.decompress(data)
            return decompressed

        # Using 'deflate' algorithm
        decompressed = self._decompressobj.decompress(data)
        decompressed += self._decompressobj.flush(zlib.Z_SYNC_FLUSH)
        return decompressed


class MessageReader:
    """Implements a Message Reader.

    Args:
        socket_stream (mysqlx.connection.SocketStream): `SocketStream` object.

    .. versionadded:: 8.0.21
    """

    def __init__(self, socket_stream: SocketType) -> None:
        self._stream: SocketType = socket_stream
        self._compressor: Optional[Compressor] = None
        self._msg: MessageType = None
        self._msg_queue: List[Message] = []

    def _read_message(self) -> MessageType:
        """Reads X Protocol messages from the stream and returns a
        :class:`mysqlx.protobuf.Message` object.

        Raises:
            :class:`mysqlx.ProgrammingError`: If e connected server does not
                                              have the MySQL X protocol plugin
                                              enabled.

        Returns:
            mysqlx.protobuf.Message: MySQL X Protobuf Message.
        """
        if self._msg_queue:
            return self._msg_queue.pop(0)

        frame_size, frame_type = struct.unpack("<LB", self._stream.read(5))

        if frame_type == 10:
            raise ProgrammingError(
                "The connected server does not have the "
                "MySQL X protocol plugin enabled or "
                "protocol mismatch"
            )

        frame_payload = self._stream.read(frame_size - 1)
        if frame_type not in SERVER_MESSAGES:
            raise ValueError(f"Unknown message type: {frame_type}")

        # Do not parse empty notices, Message requires a type in payload
        if frame_type == 11 and frame_payload == b"":
            return self._read_message()

        frame_msg = Message.from_server_message(frame_type, frame_payload)

        if frame_type == 19:  # Mysqlx.ServerMessages.Type.COMPRESSION
            uncompressed_size = frame_msg["uncompressed_size"]
            stream = BytesIO(self._compressor.decompress(frame_msg["payload"]))
            bytes_processed = 0
            while bytes_processed < uncompressed_size:
                payload_size, msg_type = struct.unpack("<LB", stream.read(5))
                payload = stream.read(payload_size - 1)
                self._msg_queue.append(Message.from_server_message(msg_type, payload))
                bytes_processed += payload_size + 4
            return self._msg_queue.pop(0) if self._msg_queue else None

        return frame_msg

    def read_message(self) -> MessageType:
        """Read message.

        Returns:
            mysqlx.protobuf.Message: MySQL X Protobuf Message.
        """
        if self._msg is not None:
            msg = self._msg
            self._msg = None
            return msg
        return self._read_message()

    def push_message(self, msg: MessageType) -> None:
        """Push message.

        Args:
            msg (mysqlx.protobuf.Message): MySQL X Protobuf Message.

        Raises:
            :class:`mysqlx.OperationalError`: If message push slot is full.
        """
        if self._msg is not None:
            raise OperationalError("Message push slot is full")
        self._msg = msg

    def set_compression(self, algorithm: str) -> None:
        """Creates a :class:`mysqlx.protocol.Compressor` object based on the
        compression algorithm.

        Args:
            algorithm (str): Compression algorithm.

        .. versionadded:: 8.0.21

        """
        self._compressor = Compressor(algorithm) if algorithm else None


class MessageWriter:
    """Implements a Message Writer.

    Args:
        socket_stream (mysqlx.connection.SocketStream): `SocketStream` object.

    .. versionadded:: 8.0.21

    """

    def __init__(self, socket_stream: SocketType) -> None:
        self._stream: SocketType = socket_stream
        self._compressor: Optional[Compressor] = None

    def write_message(self, msg_type: int, msg: MessageType) -> None:
        """Write message.

        Args:
            msg_type (int): The message type.
            msg (mysqlx.protobuf.Message): MySQL X Protobuf Message.
        """
        msg_size = msg.byte_size(msg)
        if self._compressor and msg_size > _COMPRESSION_THRESHOLD:
            msg_str = encode_to_bytes(msg.serialize_to_string())
            header = struct.pack("<LB", msg_size + 1, msg_type)
            compressed = self._compressor.compress(b"".join([header, msg_str]))

            msg_first_fields = Message("Mysqlx.Connection.Compression")
            msg_first_fields["client_messages"] = msg_type
            msg_first_fields["uncompressed_size"] = msg_size + 5

            msg_payload = Message("Mysqlx.Connection.Compression")
            msg_payload["payload"] = compressed

            output = b"".join(
                [
                    encode_to_bytes(msg_first_fields.serialize_partial_to_string())[
                        :-2
                    ],
                    encode_to_bytes(msg_payload.serialize_partial_to_string()),
                ]
            )

            msg_comp_id = mysqlxpb_enum("Mysqlx.ClientMessages.Type.COMPRESSION")
            header = struct.pack("<LB", len(output) + 1, msg_comp_id)
            self._stream.sendall(b"".join([header, output]))
        else:
            msg_str = encode_to_bytes(msg.serialize_to_string())
            header = struct.pack("<LB", msg_size + 1, msg_type)
            self._stream.sendall(b"".join([header, msg_str]))

    def set_compression(self, algorithm: str) -> None:
        """Creates a :class:`mysqlx.protocol.Compressor` object based on the
        compression algorithm.

        Args:
            algorithm (str): Compression algorithm.
        """
        self._compressor = Compressor(algorithm) if algorithm else None


class Protocol:
    """Implements the MySQL X Protocol.

    Args:
        read (mysqlx.protocol.MessageReader): A Message Reader object.
        writer (mysqlx.protocol.MessageWriter): A Message Writer object.

    .. versionchanged:: 8.0.21
    """

    def __init__(self, reader: MessageReader, writer: MessageWriter) -> None:
        self._reader: MessageReader = reader
        self._writer: MessageWriter = writer
        self._compression_algorithm: Optional[str] = None
        self._warnings: List[str] = []

    @property
    def compression_algorithm(self) -> Optional[str]:
        """str: The compresion algorithm."""
        return self._compression_algorithm

    @staticmethod
    def _apply_filter(msg: MessageType, stmt: FilterableStatement) -> None:
        """Apply filter.

        Args:
            msg (mysqlx.protobuf.Message): The MySQL X Protobuf Message.
            stmt (Statement): A `Statement` based type object.
        """
        if stmt.has_where:
            msg["criteria"] = stmt.get_where_expr()
        if stmt.has_sort:
            msg["order"].extend(stmt.get_sort_expr())
        if stmt.has_group_by:
            msg["grouping"].extend(stmt.get_grouping())
        if stmt.has_having:
            msg["grouping_criteria"] = stmt.get_having()

    def _create_any(self, arg: Any) -> Optional[MessageType]:
        """Create any.

        Args:
            arg (object): Arbitrary object.

        Returns:
            mysqlx.protobuf.Message: MySQL X Protobuf Message.
        """
        if isinstance(arg, str):
            value = Message("Mysqlx.Datatypes.Scalar.String", value=arg)
            scalar = Message("Mysqlx.Datatypes.Scalar", type=8, v_string=value)
            return Message("Mysqlx.Datatypes.Any", type=1, scalar=scalar)
        if isinstance(arg, bool):
            return Message(
                "Mysqlx.Datatypes.Any", type=1, scalar=build_bool_scalar(arg)
            )
        if isinstance(arg, int):
            if arg < 0:
                return Message(
                    "Mysqlx.Datatypes.Any",
                    type=1,
                    scalar=build_int_scalar(arg),
                )
            return Message(
                "Mysqlx.Datatypes.Any",
                type=1,
                scalar=build_unsigned_int_scalar(arg),
            )
        if isinstance(arg, tuple) and len(arg) == 2:
            arg_key, arg_value = arg
            obj_fld = Message(
                "Mysqlx.Datatypes.Object.ObjectField",
                key=arg_key,
                value=self._create_any(arg_value),
            )
            obj = Message("Mysqlx.Datatypes.Object", fld=[obj_fld.get_message()])
            return Message("Mysqlx.Datatypes.Any", type=2, obj=obj)
        if isinstance(arg, dict) or (
            isinstance(arg, (list, tuple)) and isinstance(arg[0], dict)
        ):
            array_values = []
            for items in arg:
                obj_flds = []
                for key, value in items.items():
                    # Array can only handle Any types, Mysqlx.Datatypes.Any.obj
                    obj_fld = Message(
                        "Mysqlx.Datatypes.Object.ObjectField",
                        key=key,
                        value=self._create_any(value),
                    )
                    obj_flds.append(obj_fld.get_message())
                msg_obj = Message("Mysqlx.Datatypes.Object", fld=obj_flds)
                msg_any = Message("Mysqlx.Datatypes.Any", type=2, obj=msg_obj)
                array_values.append(msg_any.get_message())

            msg = Message("Mysqlx.Datatypes.Array")
            msg["value"] = array_values
            return Message("Mysqlx.Datatypes.Any", type=3, array=msg)
        if isinstance(arg, list):
            obj_flds = []
            for key, value in arg:
                obj_fld = Message(
                    "Mysqlx.Datatypes.Object.ObjectField",
                    key=key,
                    value=self._create_any(value),
                )
                obj_flds.append(obj_fld.get_message())
            msg_obj = Message("Mysqlx.Datatypes.Object", fld=obj_flds)
            msg_any = Message("Mysqlx.Datatypes.Any", type=2, obj=msg_obj)
            return msg_any

        return None

    def _get_binding_args(
        self, stmt: Union[FilterableStatement, SqlStatement], is_scalar: bool = True
    ) -> Union[List[None], List[Union[ProtobufMessageType, ProtobufMessageCextType]]]:
        """Returns the binding any/scalar.

        Args:
            stmt (Statement): A `Statement` based type object.
            is_scalar (bool): `True` to return scalar values.

        Raises:
            :class:`mysqlx.ProgrammingError`: If unable to find placeholder for
                                              parameter.

        Returns:
            list: A list of ``Any`` or ``Scalar`` objects.
        """

        def build_value(
            value: Any,
        ) -> Union[ProtobufMessageType, ProtobufMessageCextType]:
            if is_scalar:
                return build_scalar(value).get_message()
            return self._create_any(value).get_message()

        bindings = stmt.get_bindings()
        binding_map = stmt.get_binding_map()

        # If binding_map is None it's a SqlStatement object
        if binding_map is None:
            return [build_value(value) for value in bindings]

        count = len(binding_map)
        args: List[Any] = count * [None]
        if count != len(bindings):
            raise ProgrammingError(
                "The number of bind parameters and placeholders do not match"
            )
        for name, value in bindings.items():  # type: ignore[union-attr]
            if name not in binding_map:
                raise ProgrammingError(
                    f"Unable to find placeholder for parameter: {name}"
                )
            pos = binding_map[name]
            args[pos] = build_value(value)
        return args

    def _process_frame(self, msg: MessageType, result: ResultBaseType) -> None:
        """Process frame.

        Args:
            msg (mysqlx.protobuf.Message): A MySQL X Protobuf Message.
            result (Result): A `Result` based type object.
        """
        if msg["type"] == 1:
            warn_msg = Message.from_message("Mysqlx.Notice.Warning", msg["payload"])
            self._warnings.append(warn_msg.msg)
            logger.warning(
                "Protocol.process_frame Received Warning Notice code %s: %s",
                warn_msg.code,
                warn_msg.msg,
            )
            result.append_warning(warn_msg.level, warn_msg.code, warn_msg.msg)
        elif msg["type"] == 2:
            Message.from_message("Mysqlx.Notice.SessionVariableChanged", msg["payload"])
        elif msg["type"] == 3:
            sess_state_msg = Message.from_message(
                "Mysqlx.Notice.SessionStateChanged", msg["payload"]
            )
            if sess_state_msg["param"] == mysqlxpb_enum(
                "Mysqlx.Notice.SessionStateChanged.Parameter.GENERATED_DOCUMENT_IDS"
            ):
                result.set_generated_ids(
                    [
                        get_item_or_attr(
                            get_item_or_attr(value, "v_octets"), "value"
                        ).decode()
                        for value in sess_state_msg["value"]
                    ]
                )
            else:  # Following results are unitary and not a list
                sess_state_value = sess_state_msg["value"].pop()
                if sess_state_msg["param"] == mysqlxpb_enum(
                    "Mysqlx.Notice.SessionStateChanged.Parameter.ROWS_AFFECTED"
                ):
                    result.set_rows_affected(
                        get_item_or_attr(sess_state_value, "v_unsigned_int")
                    )
                elif sess_state_msg["param"] == mysqlxpb_enum(
                    "Mysqlx.Notice.SessionStateChanged.Parameter.GENERATED_INSERT_ID"
                ):
                    result.set_generated_insert_id(
                        get_item_or_attr(sess_state_value, "v_unsigned_int")
                    )

    def _read_message(self, result: ResultBaseType) -> Optional[MessageType]:
        """Read message.

        Args:
            result (Result): A `Result` based type object.
        """
        while True:
            try:
                msg = self._reader.read_message()
            except RuntimeError as err:
                warnings = repr(result.get_warnings())
                if warnings:
                    raise RuntimeError(f"{err} reason: {warnings}") from err
            if msg.type == "Mysqlx.Error":
                raise OperationalError(msg["msg"], msg["code"])
            if msg.type == "Mysqlx.Notice.Frame":
                try:
                    self._process_frame(msg, result)
                except (AttributeError, KeyError):
                    continue
            elif msg.type == "Mysqlx.Sql.StmtExecuteOk":
                return None
            elif msg.type == "Mysqlx.Resultset.FetchDone":
                result.set_closed(True)
            elif msg.type == "Mysqlx.Resultset.FetchDoneMoreResultsets":
                result.set_has_more_results(True)
            elif msg.type == "Mysqlx.Resultset.Row":
                result.set_has_data(True)
                break
            else:
                break
        return msg

    def set_compression(self, algorithm: str) -> None:
        """Sets the compression algorithm to be used by the compression
        object, for uplink and downlink.

        Args:
            algorithm (str): Algorithm to be used in compression/decompression.

        .. versionadded:: 8.0.21

        """
        self._compression_algorithm = algorithm
        self._reader.set_compression(algorithm)
        self._writer.set_compression(algorithm)

    def get_capabilites(self) -> MessageType:
        """Get capabilities.

        Returns:
            mysqlx.protobuf.Message: MySQL X Protobuf Message.
        """
        msg = Message("Mysqlx.Connection.CapabilitiesGet")
        self._writer.write_message(
            mysqlxpb_enum("Mysqlx.ClientMessages.Type.CON_CAPABILITIES_GET"),
            msg,
        )
        msg = self._reader.read_message()
        while msg.type == "Mysqlx.Notice.Frame":
            msg = self._reader.read_message()

        if msg.type == "Mysqlx.Error":
            raise OperationalError(msg["msg"], msg["code"])

        return msg

    def set_capabilities(self, **kwargs: Any) -> None:
        """Set capabilities.

        Args:
            **kwargs: Arbitrary keyword arguments.

        Returns:
            mysqlx.protobuf.Message: MySQL X Protobuf Message.
        """
        if not kwargs:
            return None
        capabilities = Message("Mysqlx.Connection.Capabilities")
        for key, value in kwargs.items():
            capability = Message("Mysqlx.Connection.Capability")
            capability["name"] = key
            if isinstance(value, dict):
                items = value
                obj_flds = []
                for item in items:
                    obj_fld = Message(
                        "Mysqlx.Datatypes.Object.ObjectField",
                        key=item,
                        value=self._create_any(items[item]),
                    )
                    obj_flds.append(obj_fld.get_message())
                msg_obj = Message("Mysqlx.Datatypes.Object", fld=obj_flds)
                msg_any = Message("Mysqlx.Datatypes.Any", type=2, obj=msg_obj)
                capability["value"] = msg_any.get_message()
            else:
                capability["value"] = self._create_any(value)

            capabilities["capabilities"].extend([capability.get_message()])
        msg = Message("Mysqlx.Connection.CapabilitiesSet")
        msg["capabilities"] = capabilities
        self._writer.write_message(
            mysqlxpb_enum("Mysqlx.ClientMessages.Type.CON_CAPABILITIES_SET"),
            msg,
        )

        try:
            return self.read_ok()
        except InterfaceError as err:
            # Skip capability "session_connect_attrs" error since
            # is only available on version >= 8.0.16
            if err.errno != 5002:
                raise
        return None

    def send_auth_start(
        self,
        method: str,
        auth_data: Optional[str] = None,
        initial_response: Optional[str] = None,
    ) -> None:
        """Send authenticate start.

        Args:
            method (str): Message method.
            auth_data (Optional[str]): Authentication data.
            initial_response (Optional[str]): Initial response.
        """
        msg = Message("Mysqlx.Session.AuthenticateStart")
        msg["mech_name"] = method
        if auth_data is not None:
            msg["auth_data"] = auth_data
        if initial_response is not None:
            msg["initial_response"] = initial_response
        self._writer.write_message(
            mysqlxpb_enum("Mysqlx.ClientMessages.Type.SESS_AUTHENTICATE_START"),
            msg,
        )

    def read_auth_continue(self) -> bytes:
        """Read authenticate continue.

        Raises:
            :class:`InterfaceError`: If the message type is not
                                     `Mysqlx.Session.AuthenticateContinue`

        Returns:
            str: The authentication data.
        """
        msg = self._reader.read_message()
        while msg.type == "Mysqlx.Notice.Frame":
            msg = self._reader.read_message()
        if msg.type != "Mysqlx.Session.AuthenticateContinue":
            raise InterfaceError(
                "Unexpected message encountered during authentication handshake"
            )
        return msg["auth_data"]

    def send_auth_continue(self, auth_data: str) -> None:
        """Send authenticate continue.

        Args:
            auth_data (str): Authentication data.
        """
        msg = Message("Mysqlx.Session.AuthenticateContinue", auth_data=auth_data)
        self._writer.write_message(
            mysqlxpb_enum("Mysqlx.ClientMessages.Type.SESS_AUTHENTICATE_CONTINUE"),
            msg,
        )

    def read_auth_ok(self) -> None:
        """Read authenticate OK.

        Raises:
            :class:`mysqlx.InterfaceError`: If message type is `Mysqlx.Error`.
        """
        while True:
            msg = self._reader.read_message()
            if msg.type == "Mysqlx.Session.AuthenticateOk":
                break
            if msg.type == "Mysqlx.Error":
                raise InterfaceError(msg.msg)

    def send_prepare_prepare(
        self,
        msg_type: str,
        msg: MessageType,
        stmt: Union[
            FindStatement,
            DeleteStatement,
            ModifyStatement,
            ReadStatement,
            RemoveStatement,
            UpdateStatement,
        ],
    ) -> None:
        """
        Send prepare statement.

        Args:
            msg_type (str): Message ID string.
            msg (mysqlx.protobuf.Message): MySQL X Protobuf Message.
            stmt (Statement): A `Statement` based type object.

        Raises:
            :class:`mysqlx.NotSupportedError`: If prepared statements are not
                                               supported.

        .. versionadded:: 8.0.16
        """
        if stmt.has_limit and msg.type != "Mysqlx.Crud.Insert":
            # Remove 'limit' from message by building a new one
            if msg.type == "Mysqlx.Crud.Find":
                _, msg = self.build_find(stmt)  # type: ignore[arg-type]
            elif msg.type == "Mysqlx.Crud.Update":
                _, msg = self.build_update(stmt)  # type: ignore[arg-type]
            elif msg.type == "Mysqlx.Crud.Delete":
                _, msg = self.build_delete(stmt)  # type: ignore[arg-type]
            else:
                raise ValueError(f"Invalid message type: {msg_type}")
            # Build 'limit_expr' message
            position = len(stmt.get_bindings())
            placeholder = mysqlxpb_enum("Mysqlx.Expr.Expr.Type.PLACEHOLDER")
            msg_limit_expr = Message("Mysqlx.Crud.LimitExpr")
            msg_limit_expr["row_count"] = Message(
                "Mysqlx.Expr.Expr", type=placeholder, position=position
            )
            if msg.type == "Mysqlx.Crud.Find":
                msg_limit_expr["offset"] = Message(
                    "Mysqlx.Expr.Expr", type=placeholder, position=position + 1
                )
            msg["limit_expr"] = msg_limit_expr

        oneof_type, oneof_op = CRUD_PREPARE_MAPPING[msg_type]
        msg_oneof = Message("Mysqlx.Prepare.Prepare.OneOfMessage")
        msg_oneof["type"] = mysqlxpb_enum(oneof_type)
        msg_oneof[oneof_op] = msg
        msg_prepare = Message("Mysqlx.Prepare.Prepare")
        msg_prepare["stmt_id"] = stmt.stmt_id
        msg_prepare["stmt"] = msg_oneof

        self._writer.write_message(
            mysqlxpb_enum("Mysqlx.ClientMessages.Type.PREPARE_PREPARE"),
            msg_prepare,
        )

        try:
            self.read_ok()
        except InterfaceError as err:
            raise NotSupportedError from err

    def send_prepare_execute(
        self, msg_type: str, msg: MessageType, stmt: FilterableStatement
    ) -> None:
        """
        Send execute statement.

        Args:
            msg_type (str): Message ID string.
            msg (mysqlx.protobuf.Message): MySQL X Protobuf Message.
            stmt (Statement): A `Statement` based type object.

        .. versionadded:: 8.0.16
        """
        oneof_type, oneof_op = CRUD_PREPARE_MAPPING[msg_type]
        msg_oneof = Message("Mysqlx.Prepare.Prepare.OneOfMessage")
        msg_oneof["type"] = mysqlxpb_enum(oneof_type)
        msg_oneof[oneof_op] = msg
        msg_execute = Message("Mysqlx.Prepare.Execute")
        msg_execute["stmt_id"] = stmt.stmt_id

        args = self._get_binding_args(stmt, is_scalar=False)
        if args:
            msg_execute["args"].extend(args)

        if stmt.has_limit:
            msg_execute["args"].extend(
                [
                    self._create_any(stmt.get_limit_row_count()).get_message(),
                    self._create_any(stmt.get_limit_offset()).get_message(),
                ]
            )

        self._writer.write_message(
            mysqlxpb_enum("Mysqlx.ClientMessages.Type.PREPARE_EXECUTE"),
            msg_execute,
        )

    def send_prepare_deallocate(self, stmt_id: int) -> None:
        """
        Send prepare deallocate statement.

        Args:
            stmt_id (int): Statement ID.

        .. versionadded:: 8.0.16
        """
        msg_dealloc = Message("Mysqlx.Prepare.Deallocate")
        msg_dealloc["stmt_id"] = stmt_id
        self._writer.write_message(
            mysqlxpb_enum("Mysqlx.ClientMessages.Type.PREPARE_DEALLOCATE"),
            msg_dealloc,
        )
        self.read_ok()

    def send_msg_without_ps(
        self,
        msg_type: str,
        msg: MessageType,
        stmt: Union[FilterableStatement, SqlStatement],
    ) -> None:
        """
        Send a message without prepared statements support.

        Args:
            msg_type (str): Message ID string.
            msg (mysqlx.protobuf.Message): MySQL X Protobuf Message.
            stmt (Statement): A `Statement` based type object.

        .. versionadded:: 8.0.16
        """
        if stmt.has_limit:
            msg_limit = Message("Mysqlx.Crud.Limit")
            msg_limit["row_count"] = stmt.get_limit_row_count()  # type: ignore[union-attr]
            if msg.type == "Mysqlx.Crud.Find":
                msg_limit["offset"] = stmt.get_limit_offset()  # type: ignore[union-attr]
            msg["limit"] = msg_limit
        is_scalar = msg_type != "Mysqlx.ClientMessages.Type.SQL_STMT_EXECUTE"
        args = self._get_binding_args(stmt, is_scalar=is_scalar)
        if args:
            msg["args"].extend(args)
        self.send_msg(msg_type, msg)

    def send_msg(self, msg_type: str, msg: MessageType) -> None:
        """
        Send a message.

        Args:
            msg_type (str): Message ID string.
            msg (mysqlx.protobuf.Message): MySQL X Protobuf Message.

        .. versionadded:: 8.0.16
        """
        self._writer.write_message(mysqlxpb_enum(msg_type), msg)

    def build_find(
        self, stmt: Union[FindStatement, ReadStatement]
    ) -> Tuple[str, MessageType]:
        """Build find/read message.

        Args:
            stmt (Statement): A :class:`mysqlx.ReadStatement` or
                              :class:`mysqlx.FindStatement` object.

        Returns:
            (tuple): Tuple containing:

                * `str`: Message ID string.
                * :class:`mysqlx.protobuf.Message`: MySQL X Protobuf Message.

        .. versionadded:: 8.0.16
        """
        data_model = mysqlxpb_enum(
            "Mysqlx.Crud.DataModel.DOCUMENT"
            if stmt.is_doc_based()
            else "Mysqlx.Crud.DataModel.TABLE"
        )
        collection = Message(
            "Mysqlx.Crud.Collection",
            name=stmt.target.name,
            schema=stmt.schema.name,
        )
        msg = Message("Mysqlx.Crud.Find", data_model=data_model, collection=collection)
        if stmt.has_projection:
            msg["projection"] = stmt.get_projection_expr()
        self._apply_filter(msg, stmt)

        if stmt.is_lock_exclusive():
            msg["locking"] = mysqlxpb_enum("Mysqlx.Crud.Find.RowLock.EXCLUSIVE_LOCK")
        elif stmt.is_lock_shared():
            msg["locking"] = mysqlxpb_enum("Mysqlx.Crud.Find.RowLock.SHARED_LOCK")

        if stmt.lock_contention.value > 0:
            msg["locking_options"] = stmt.lock_contention.value

        return "Mysqlx.ClientMessages.Type.CRUD_FIND", msg

    def build_update(
        self, stmt: Union[ModifyStatement, UpdateStatement]
    ) -> Tuple[str, MessageType]:
        """Build update message.

        Args:
            stmt (Statement): A :class:`mysqlx.ModifyStatement` or
                              :class:`mysqlx.UpdateStatement` object.

        Returns:
            (tuple): Tuple containing:

                * `str`: Message ID string.
                * :class:`mysqlx.protobuf.Message`: MySQL X Protobuf Message.

        .. versionadded:: 8.0.16
        """
        data_model = mysqlxpb_enum(
            "Mysqlx.Crud.DataModel.DOCUMENT"
            if stmt.is_doc_based()
            else "Mysqlx.Crud.DataModel.TABLE"
        )
        collection = Message(
            "Mysqlx.Crud.Collection",
            name=stmt.target.name,
            schema=stmt.schema.name,
        )
        msg = Message(
            "Mysqlx.Crud.Update", data_model=data_model, collection=collection
        )
        self._apply_filter(msg, stmt)
        for _, update_op in stmt.get_update_ops().items():
            operation = Message("Mysqlx.Crud.UpdateOperation")
            operation["operation"] = update_op.update_type
            operation["source"] = update_op.source
            if update_op.value is not None:
                operation["value"] = build_expr(update_op.value)
            msg["operation"].extend([operation.get_message()])

        return "Mysqlx.ClientMessages.Type.CRUD_UPDATE", msg

    def build_delete(
        self, stmt: Union[DeleteStatement, RemoveStatement]
    ) -> Tuple[str, MessageType]:
        """Build delete message.

        Args:
            stmt (Statement): A :class:`mysqlx.DeleteStatement` or
                              :class:`mysqlx.RemoveStatement` object.

        Returns:
            (tuple): Tuple containing:

                * `str`: Message ID string.
                * :class:`mysqlx.protobuf.Message`: MySQL X Protobuf Message.

        .. versionadded:: 8.0.16
        """
        data_model = mysqlxpb_enum(
            "Mysqlx.Crud.DataModel.DOCUMENT"
            if stmt.is_doc_based()
            else "Mysqlx.Crud.DataModel.TABLE"
        )
        collection = Message(
            "Mysqlx.Crud.Collection",
            name=stmt.target.name,
            schema=stmt.schema.name,
        )
        msg = Message(
            "Mysqlx.Crud.Delete", data_model=data_model, collection=collection
        )
        self._apply_filter(msg, stmt)
        return "Mysqlx.ClientMessages.Type.CRUD_DELETE", msg

    def build_execute_statement(
        self,
        namespace: str,
        stmt: Union[str, StatementType],
        fields: Optional[Dict[str, Any]] = None,
    ) -> Tuple[str, MessageType]:
        """Build execute statement.

        Args:
            namespace (str): The namespace.
            stmt (Statement): A `Statement` based type object.
            fields (Optional[dict]): The message fields.

        Returns:
            (tuple): Tuple containing:

                * `str`: Message ID string.
                * :class:`mysqlx.protobuf.Message`: MySQL X Protobuf Message.

        .. versionadded:: 8.0.16
        """
        msg = Message(
            "Mysqlx.Sql.StmtExecute",
            namespace=namespace,
            stmt=stmt,
            compact_metadata=False,
        )

        if fields:
            obj_flds = []
            for key, value in fields.items():
                obj_fld = Message(
                    "Mysqlx.Datatypes.Object.ObjectField",
                    key=key,
                    value=self._create_any(value),
                )
                obj_flds.append(obj_fld.get_message())
            msg_obj = Message("Mysqlx.Datatypes.Object", fld=obj_flds)
            msg_any = Message("Mysqlx.Datatypes.Any", type=2, obj=msg_obj)
            msg["args"] = [msg_any.get_message()]
        return "Mysqlx.ClientMessages.Type.SQL_STMT_EXECUTE", msg

    @staticmethod
    def build_insert(
        stmt: Union[AddStatement, InsertStatement]
    ) -> Tuple[str, MessageType]:
        """Build insert statement.

        Args:
            stmt (Statement): A :class:`mysqlx.AddStatement` or
                              :class:`mysqlx.InsertStatement` object.

        Returns:
            (tuple): Tuple containing:

                * `str`: Message ID string.
                * :class:`mysqlx.protobuf.Message`: MySQL X Protobuf Message.

        .. versionadded:: 8.0.16
        """
        data_model = mysqlxpb_enum(
            "Mysqlx.Crud.DataModel.DOCUMENT"
            if stmt.is_doc_based()
            else "Mysqlx.Crud.DataModel.TABLE"
        )
        collection = Message(
            "Mysqlx.Crud.Collection",
            name=stmt.target.name,
            schema=stmt.schema.name,
        )
        msg = Message(
            "Mysqlx.Crud.Insert", data_model=data_model, collection=collection
        )

        if hasattr(stmt, "_fields"):
            for field in stmt._fields:
                expr = ExprParser(
                    field, not stmt.is_doc_based()
                ).parse_table_insert_field()
                msg["projection"].extend([expr.get_message()])

        for value in stmt.get_values():
            row = Message("Mysqlx.Crud.Insert.TypedRow")
            if isinstance(value, list):
                for val in value:
                    row["field"].extend([build_expr(val).get_message()])
            else:
                row["field"].extend([build_expr(value).get_message()])
            msg["row"].extend([row.get_message()])

        if hasattr(stmt, "is_upsert"):
            msg["upsert"] = stmt.is_upsert()

        return "Mysqlx.ClientMessages.Type.CRUD_INSERT", msg

    def close_result(self, result: ResultBaseType) -> None:
        """Close the result.

        Args:
            result (Result): A `Result` based type object.

        Raises:
            :class:`mysqlx.OperationalError`: If message read is None.
        """
        msg = self._read_message(result)
        if msg is not None:
            raise OperationalError("Expected to close the result")

    def read_row(self, result: ResultBaseType) -> Optional[MessageType]:
        """Read row.

        Args:
            result (Result): A `Result` based type object.
        """
        msg = self._read_message(result)
        if msg is None:
            return None
        if msg.type == "Mysqlx.Resultset.Row":
            return msg
        self._reader.push_message(msg)
        return None

    def get_column_metadata(self, result: ResultBaseType) -> List[ColumnType]:
        """Returns column metadata.

        Args:
            result (Result): A `Result` based type object.

        Raises:
            :class:`mysqlx.InterfaceError`: If unexpected message.
        """
        columns = []
        while True:
            msg = self._read_message(result)
            if msg is None:
                break
            if msg.type == "Mysqlx.Resultset.Row":
                self._reader.push_message(msg)
                break
            if msg.type != "Mysqlx.Resultset.ColumnMetaData":
                raise InterfaceError("Unexpected msg type")
            col = Column(
                msg["type"],
                msg["catalog"],
                msg["schema"],
                msg["table"],
                msg["original_table"],
                msg["name"],
                msg["original_name"],
                msg.get("length", 21),
                msg.get("collation", 0),
                msg.get("fractional_digits", 0),
                msg.get("flags", 16),
                msg.get("content_type"),
            )
            columns.append(col)
        return columns

    def read_ok(self) -> None:
        """Read OK.

        Raises:
            :class:`mysqlx.InterfaceError`: If unexpected message.
        """
        msg = self._reader.read_message()
        if msg.type == "Mysqlx.Error":
            raise InterfaceError(f"Mysqlx.Error: {msg['msg']}", errno=msg["code"])
        if msg.type != "Mysqlx.Ok":
            raise InterfaceError("Unexpected message encountered")

    def send_connection_close(self) -> None:
        """Send connection close."""
        msg = Message("Mysqlx.Connection.Close")
        self._writer.write_message(
            mysqlxpb_enum("Mysqlx.ClientMessages.Type.CON_CLOSE"), msg
        )

    def send_close(self) -> None:
        """Send close."""
        msg = Message("Mysqlx.Session.Close")
        self._writer.write_message(
            mysqlxpb_enum("Mysqlx.ClientMessages.Type.SESS_CLOSE"), msg
        )

    def send_expect_open(self) -> None:
        """Send expectation."""
        cond_key = mysqlxpb_enum("Mysqlx.Expect.Open.Condition.Key.EXPECT_FIELD_EXIST")
        msg_oc = Message("Mysqlx.Expect.Open.Condition")
        msg_oc["condition_key"] = cond_key
        msg_oc["condition_value"] = "6.1"

        msg_eo = Message("Mysqlx.Expect.Open")
        msg_eo["cond"] = [msg_oc.get_message()]

        self._writer.write_message(
            mysqlxpb_enum("Mysqlx.ClientMessages.Type.EXPECT_OPEN"), msg_eo
        )

    def send_reset(self, keep_open: Optional[bool] = None) -> bool:
        """Send reset session message.

        Returns:
            boolean: ``True`` if the server will keep the session open,
                     otherwise ``False``.
        """
        msg = Message("Mysqlx.Session.Reset")
        if keep_open is None:
            try:
                # Send expectation: keep connection open
                self.send_expect_open()
                self.read_ok()
                keep_open = True
            except InterfaceError:
                # Expectation is unkown by this version of the server
                keep_open = False
        if keep_open:
            msg["keep_open"] = True
        self._writer.write_message(
            mysqlxpb_enum("Mysqlx.ClientMessages.Type.SESS_RESET"), msg
        )
        self.read_ok()
        if keep_open:
            return True
        return False

virt/lib/python3.9/site-packages/mysqlx/protocol.py
PrinceDisant/LostInNL
# Copyright (c) 2016, 2022, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

"""Implementation of the Result classes."""

from __future__ import annotations

import decimal
import struct
import sys

from datetime import datetime, timedelta
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

from .charsets import MYSQL_CHARACTER_SETS
from .dbdoc import DbDoc
from .helpers import decode_from_bytes, deprecated
from .types import ConnectionType, FieldTypes


# pylint: disable=missing-class-docstring,missing-function-docstring
def from_protobuf(column: Column, payload: bytes) -> Any:
    if len(payload) == 0:
        return None

    if column.get_type() == ColumnType.STRING:
        return decode_from_bytes(payload[:-1])  # Strip trailing char

    try:
        return ColumnProtoType.converter_map[column.get_proto_type()](payload)
    except KeyError as err:
        sys.stderr.write(f"{err}")
        sys.stderr.write(f"{payload.encode('hex')}")  # type: ignore[attr-defined]
        return None


def bytes_from_protobuf(payload: bytes) -> bytes:
    # Strip trailing char
    return payload[:-1]


def float_from_protobuf(payload: bytes) -> float:
    assert len(payload) == 4
    return struct.unpack("<f", payload)[0]


def double_from_protobuf(payload: bytes) -> float:
    assert len(payload) == 8
    return struct.unpack("<d", payload)[0]


def varint_from_protobuf_stream(payload: bytes) -> Tuple[int, bytes]:
    if len(payload) == 0:
        raise ValueError("Payload is empty")

    cur = 0
    i = 0
    shift = 0

    for item in payload:
        char = item if isinstance(item, int) else ord(item)  # type: ignore[arg-type]
        eos = (char & 0x80) == 0
        cur_bits = char & 0x7F
        cur_bits <<= shift
        i |= cur_bits
        if eos:
            return i, payload[cur + 1 :]
        cur += 1
        shift += 7

    raise EOFError("Payload too short")


def varint_from_protobuf(payload: bytes) -> int:
    i, payload = varint_from_protobuf_stream(payload)
    if len(payload) != 0:
        raise ValueError("Payload too long")

    return i


def varsint_from_protobuf(payload: bytes) -> int:
    i, payload = varint_from_protobuf_stream(payload)
    if len(payload) != 0:
        raise ValueError("Payload too long")

    # Zigzag encoded, revert it
    if i & 0x1:
        i = ~i
        i = i >> 1
        i |= 1 << 63
    else:
        i = i >> 1

    return i


def set_from_protobuf(payload: bytes) -> List[bytes]:
    set_pb: List = []
    while True:
        try:
            field_len, payload = varint_from_protobuf_stream(payload)
            if len(payload) < field_len:
                if len(payload) == 0 and field_len == 1 and len(set_pb) == 0:
                    # Special case for empty set
                    return []
                raise ValueError("Invalid Set encoding")

            set_pb.append(payload[:field_len])
            payload = payload[field_len:]
            if len(payload) == 0:
                # Done
                break
        except ValueError:
            break
    return set_pb


def decimal_from_protobuf(payload: bytes) -> decimal.Decimal:
    digits = []
    sign = None
    scale = payload[0] if isinstance(payload[0], int) else ord(payload[0])  # type: ignore[arg-type]
    payload = payload[1:]

    for item in payload:
        char = item if isinstance(item, int) else ord(item)  # type: ignore[arg-type]
        high_bcd = (char & 0xF0) >> 4
        low_bcd = char & 0x0F
        if high_bcd < 0x0A:
            digits.append(high_bcd)
            if low_bcd < 0x0A:
                digits.append(low_bcd)
            elif low_bcd == 0x0C:
                sign = 0
                break
            elif low_bcd == 0x0D:
                sign = 1
                break
            else:
                raise ValueError("Invalid BCD")
        elif high_bcd == 0x0C:
            sign = 0
            assert low_bcd == 0x00
            break
        elif high_bcd == 0x0D:
            sign = 1
            assert low_bcd == 0x00
            break
        else:
            raise ValueError(f"Invalid BCD: {high_bcd}")

    return decimal.Decimal((sign, digits, -scale))


def datetime_from_protobuf(payload: bytes) -> datetime:
    # A sequence of varints
    hour = 0
    minutes = 0
    seconds = 0
    useconds = 0
    year, payload = varint_from_protobuf_stream(payload)
    month, payload = varint_from_protobuf_stream(payload)
    day, payload = varint_from_protobuf_stream(payload)

    try:
        hour, payload = varint_from_protobuf_stream(payload)
        minutes, payload = varint_from_protobuf_stream(payload)
        seconds, payload = varint_from_protobuf_stream(payload)
        useconds, payload = varint_from_protobuf_stream(payload)
    except ValueError:
        pass

    return datetime(year, month, day, hour, minutes, seconds, useconds)


def time_from_protobuf(payload: bytes) -> timedelta:
    # A sequence of varints
    hour = 0
    minutes = 0
    seconds = 0
    useconds = 0
    negate = payload[0] == 1
    payload = payload[1:]

    try:
        hour, payload = varint_from_protobuf_stream(payload)
        minutes, payload = varint_from_protobuf_stream(payload)
        seconds, payload = varint_from_protobuf_stream(payload)
        useconds, payload = varint_from_protobuf_stream(payload)
    except ValueError:
        pass

    if negate:
        # Negate the first non-zero value
        if hour:
            hour *= -1
        elif minutes:
            minutes *= -1
        elif seconds:
            seconds *= -1
        elif useconds:
            useconds *= -1

    return timedelta(
        hours=hour, minutes=minutes, seconds=seconds, microseconds=useconds
    )


class Collations:
    UTF8_GENERAL_CI = 33


class ColumnType:
    BIT = 1
    TINYINT = 2
    SMALLINT = 3
    MEDIUMINT = 4
    INT = 5
    BIGINT = 6
    REAL = 7
    FLOAT = 8
    DECIMAL = 9
    NUMERIC = 10
    DOUBLE = 11
    JSON = 12
    STRING = 13
    BYTES = 14
    TIME = 15
    DATE = 16
    DATETIME = 17
    TIMESTAMP = 18
    SET = 19
    ENUM = 20
    GEOMETRY = 21
    XML = 22
    YEAR = 23
    CHAR = 24
    VARCHAR = 25
    BINARY = 26
    VARBINARY = 27
    TINYBLOB = 28
    BLOB = 29
    MEDIUMBLOB = 30
    LONGBLOB = 31
    TINYTEXT = 32
    TEXT = 33
    MEDIUMTEXT = 34
    LONGTEXT = 35

    @classmethod
    def to_string(cls, needle: Any) -> Optional[str]:
        for key, value in vars(cls).items():
            if value == needle:
                return key
        return None

    @classmethod
    def from_string(cls, key: str) -> Any:
        return getattr(cls, key.upper(), None)

    @classmethod
    def is_char(cls, col_type: int) -> bool:
        return col_type in (
            cls.CHAR,
            cls.VARCHAR,
        )

    @classmethod
    def is_binary(cls, col_type: int) -> bool:
        return col_type in (
            cls.BINARY,
            cls.VARBINARY,
        )

    @classmethod
    def is_text(cls, col_type: int) -> bool:
        return col_type in (
            cls.TEXT,
            cls.TINYTEXT,
            cls.MEDIUMTEXT,
            cls.LONGTEXT,
        )

    @classmethod
    def is_decimals(cls, col_type: int) -> bool:
        return col_type in (
            cls.REAL,
            cls.DOUBLE,
            cls.FLOAT,
            cls.DECIMAL,
            cls.NUMERIC,
        )

    @classmethod
    def is_numeric(cls, col_type: int) -> bool:
        return col_type in (
            cls.BIT,
            cls.TINYINT,
            cls.SMALLINT,
            cls.MEDIUMINT,
            cls.INT,
            cls.BIGINT,
        )

    @classmethod
    def is_finite_set(cls, col_type: int) -> bool:
        return col_type in (
            cls.SET,
            cls.ENUM,
        )


class ColumnProtoType:
    SINT = 1
    UINT = 2
    DOUBLE = 5
    FLOAT = 6
    BYTES = 7
    TIME = 10
    DATETIME = 12
    SET = 15
    ENUM = 16
    BIT = 17
    DECIMAL = 18

    converter_map: Dict[int, Callable[[bytes], Any]] = {
        SINT: varsint_from_protobuf,
        UINT: varint_from_protobuf,
        BYTES: bytes_from_protobuf,
        DATETIME: datetime_from_protobuf,
        TIME: time_from_protobuf,
        FLOAT: float_from_protobuf,
        DOUBLE: double_from_protobuf,
        BIT: varint_from_protobuf,
        SET: set_from_protobuf,
        ENUM: bytes_from_protobuf,
        DECIMAL: decimal_from_protobuf,
    }


class Flags:
    def __init__(self, value: int) -> None:
        self._allowed_flags: Dict[str, int] = {}
        self._flag_names: Dict[int, str] = {}
        for key, val in self.__class__.__dict__.items():
            if key.startswith("__"):
                continue
            if isinstance(val, int):
                self._allowed_flags[key] = val
                self._flag_names[val] = key
        self._value: int = value

    def __str__(self) -> str:
        mask = 1
        flag_names = []
        value = self._value

        for _ in range(0, 63):
            mask <<= 1
            flag = value & mask
            if flag:
                # We matched something, find the name for it
                try:
                    flag_names.append(self._flag_names[flag])
                except KeyError:
                    sys.stderr.write(f"{self._flag_names}")
                    sys.stderr.write(f"{self.__class__.__dict__}")

        return ",".join(flag_names)

    @property
    def value(self) -> int:
        return self._value

    @value.setter
    def value(self, val: int) -> None:
        self._value = val


class ColumnFlags(Flags):
    NOT_NULL = 0x0010
    PRIMARY_KEY = 0x0020
    UNIQUE_KEY = 0x0040
    MULTIPLE_KEY = 0x0080
    AUTO_INCREMENT = 0x0100


class DatetimeColumnFlags(ColumnFlags):
    TIMESTAMP = 0x0001


class UIntColumnFlags(ColumnFlags):
    ZEROFILL = 0x0001


class DoubleColumnFlags(ColumnFlags):
    UNSIGNED = 0x0001


class FloatColumnFlags(ColumnFlags):
    UNSIGNED = 0x0001


class BytesColumnFlags(ColumnFlags):
    RIGHT_PAD = 0x0001


class BytesContentType(ColumnFlags):
    GEOMETRY = 0x0001
    JSON = 0x0002
    XML = 0x0003


# pylint: enable=missing-class-docstring,missing-function-docstring


class Column:
    """Represents meta data for a table column.

    Args:
        col_type (int): The column type.
        catalog (str): The catalog.
        schema (str): The schema name.
        table (str): The table name.
        original_table (str): The original table name.
        name (str): The column name.
        original_name (str): The original table name.
        length (int): The column length,
        collation (str): The collation name.
        fractional_digits (int): The fractional digits.
        flags (int): The flags.
        content_type (int): The content type.

    .. versionchanged:: 8.0.12
    """

    def __init__(
        self,
        col_type: int,
        catalog: Optional[str] = None,
        schema: Optional[str] = None,
        table: Optional[str] = None,
        original_table: Optional[str] = None,
        name: Optional[str] = None,
        original_name: Optional[str] = None,
        length: Optional[int] = None,
        collation: Optional[int] = None,
        fractional_digits: Optional[int] = None,
        flags: Optional[int] = None,
        content_type: Optional[int] = None,
    ) -> None:
        self._schema: str = decode_from_bytes(schema)
        self._name: str = decode_from_bytes(name)
        self._original_name: str = decode_from_bytes(original_name)
        self._table: str = decode_from_bytes(table)
        self._original_table: str = decode_from_bytes(original_table)
        self._proto_type: int = col_type
        self._col_type: Optional[int] = None
        self._catalog: Optional[str] = catalog
        self._length: Optional[int] = length
        self._collation: Optional[int] = collation
        self._fractional_digits: Optional[int] = fractional_digits
        self._flags: Optional[int] = flags
        self._content_type: Optional[int] = content_type
        self._number_signed: bool = False
        self._is_padded: Union[bool, int] = False
        self._is_binary: bool = False
        self._is_bytes: bool = False
        self._collation_name: Optional[str] = None
        self._character_set_name: Optional[str] = None
        self._zero_fill: Optional[int] = None

        if self._collation > 0:
            if self._collation >= len(MYSQL_CHARACTER_SETS):
                raise ValueError(f"No mapping found for collation {self._collation}")
            info = MYSQL_CHARACTER_SETS[self._collation]
            self._character_set_name = info[0]
            self._collation_name = info[1]
            self._is_binary = (
                "binary" in self._collation_name or "_bin" in self._collation_name
            )
        self._map_type()
        self._is_bytes = self._col_type in (
            ColumnType.GEOMETRY,
            ColumnType.JSON,
            ColumnType.XML,
            ColumnType.BYTES,
            ColumnType.STRING,
        )

    def __str__(self) -> str:
        return str(
            {
                "col_type": self._col_type,
                "schema": self._schema,
                "table": self._table,
                "flags": str(self._flags),
            }
        )

    def _map_bytes(self) -> None:
        """Map bytes."""
        if self._content_type == BytesContentType.GEOMETRY:
            self._col_type = ColumnType.GEOMETRY
        elif self._content_type == BytesContentType.JSON:
            self._col_type = ColumnType.JSON
        elif self._content_type == BytesContentType.XML:
            self._col_type = ColumnType.XML
        elif self._is_binary:
            self._col_type = ColumnType.BYTES
        else:
            self._col_type = ColumnType.STRING
        self._is_padded = self._flags & 1

    def _map_datetime(self) -> None:
        """Map datetime."""
        if self._length == 10:
            self._col_type = ColumnType.DATE
        elif self._flags & DatetimeColumnFlags.TIMESTAMP > 0:
            self._col_type = ColumnType.TIMESTAMP
        elif self._length >= 19:
            self._col_type = ColumnType.DATETIME
        else:
            raise ValueError("Datetime mapping scenario unhandled")

    def _map_int_type(self) -> None:
        """Map int type."""
        if self._length <= 4:
            self._col_type = ColumnType.TINYINT
        elif self._length <= 6:
            self._col_type = ColumnType.SMALLINT
        elif self._length <= 9:
            self._col_type = ColumnType.MEDIUMINT
        elif self._length <= 11:
            self._col_type = ColumnType.INT
        else:
            self._col_type = ColumnType.BIGINT
        self._number_signed = True

    def _map_uint_type(self) -> None:
        """Map uint type."""
        if self._length <= 3:
            self._col_type = ColumnType.TINYINT
        elif self._length <= 5:
            self._col_type = ColumnType.SMALLINT
        elif self._length <= 8:
            self._col_type = ColumnType.MEDIUMINT
        elif self._length <= 10:
            self._col_type = ColumnType.INT
        else:
            self._col_type = ColumnType.BIGINT
        self._zero_fill = self._flags & 1

    def _map_type(self) -> None:
        """Map type."""
        if self._proto_type == ColumnProtoType.SINT:
            self._map_int_type()
        elif self._proto_type == ColumnProtoType.UINT:
            self._map_uint_type()
        elif self._proto_type == ColumnProtoType.FLOAT:
            self._col_type = ColumnType.FLOAT
            self._is_number_signed = (self._flags & FloatColumnFlags.UNSIGNED) == 0
        elif self._proto_type == ColumnProtoType.DECIMAL:
            self._col_type = ColumnType.DECIMAL
            self._is_number_signed = (self._flags & FloatColumnFlags.UNSIGNED) == 0
        elif self._proto_type == ColumnProtoType.DOUBLE:
            self._col_type = ColumnType.DOUBLE
            self._is_number_signed = (self._flags & FloatColumnFlags.UNSIGNED) == 0
        elif self._proto_type == ColumnProtoType.BYTES:
            self._map_bytes()
        elif self._proto_type == ColumnProtoType.TIME:
            self._col_type = ColumnType.TIME
        elif self._proto_type == ColumnProtoType.DATETIME:
            self._map_datetime()
        elif self._proto_type == ColumnProtoType.SET:
            self._col_type = ColumnType.SET
        elif self._proto_type == ColumnProtoType.ENUM:
            self._col_type = ColumnType.ENUM
        elif self._proto_type == ColumnProtoType.BIT:
            self._col_type = ColumnType.BIT
        else:
            raise ValueError(f"Unknown column type {self._proto_type}")

    @property
    def schema_name(self) -> str:
        """str: The schema name.

        .. versionadded:: 8.0.12
        """
        return self._schema

    @property
    def table_name(self) -> str:
        """str: The table name.

        .. versionadded:: 8.0.12
        """
        return self._original_table or self._table

    @property
    def table_label(self) -> str:
        """str: The table label.

        .. versionadded:: 8.0.12
        """
        return self._table or self._original_table

    @property
    def column_name(self) -> str:
        """str: The column name.

        .. versionadded:: 8.0.12
        """
        return self._original_name or self._name

    @property
    def column_label(self) -> str:
        """str: The column label.

        .. versionadded:: 8.0.12
        """
        return self._name or self._original_name

    @property
    def type(self) -> int:
        """int: The column type.

        .. versionadded:: 8.0.12
        """
        return self._col_type

    @property
    def length(self) -> int:
        """int. The column length.

        .. versionadded:: 8.0.12
        """
        return self._length

    @property
    def fractional_digits(self) -> int:
        """int: The column fractional digits.

        .. versionadded:: 8.0.12
        """
        return self._fractional_digits

    @property
    def collation_name(self) -> str:
        """str: The collation name.

        .. versionadded:: 8.0.12
        """
        return self._collation_name

    @property
    def character_set_name(self) -> str:
        """str: The character set name.

        .. versionadded:: 8.0.12
        """
        return self._character_set_name

    def get_schema_name(self) -> str:
        """Returns the schema name.

        Returns:
            str: The schema name.
        """
        return self._schema

    def get_table_name(self) -> str:
        """Returns the table name.

        Returns:
            str: The table name.
        """
        return self._original_table or self._table

    def get_table_label(self) -> str:
        """Returns the table label.

        Returns:
            str: The table label.
        """
        return self._table or self._original_table

    def get_column_name(self) -> str:
        """Returns the column name.

        Returns:
            str: The column name.
        """
        return self._original_name or self._name

    def get_column_label(self) -> str:
        """Returns the column label.

        Returns:
            str: The column label.
        """
        return self._name or self._original_name

    def get_proto_type(self) -> int:
        """Returns the column proto type.

        Returns:
            int: The column proto type.
        """
        return self._proto_type

    def get_type(self) -> int:
        """Returns the column type.

        Returns:
            int: The column type.
        """
        return self._col_type

    def get_length(self) -> int:
        """Returns the column length.

        Returns:
            int: The column length.
        """
        return self._length

    def get_fractional_digits(self) -> int:
        """Returns the column fractional digits.

        Returns:
            int: The column fractional digits.
        """
        return self._fractional_digits

    def get_collation_name(self) -> str:
        """Returns the collation name.

        Returns:
            str: The collation name.
        """
        return self._collation_name

    def get_character_set_name(self) -> str:
        """Returns the character set name.

        Returns:
            str: The character set name.
        """
        return self._character_set_name

    def is_number_signed(self) -> bool:
        """Returns `True` if is a number signed.

        Returns:
            bool: Returns `True` if is a number signed.
        """
        return self._number_signed

    def is_padded(self) -> Union[bool, int]:
        """Returns `True` if is padded.

        Returns:
            bool: Returns `True` if is padded.
        """
        return self._is_padded

    def is_bytes(self) -> bool:
        """Returns `True` if is bytes.

        Returns:
            bool: Returns `True` if is bytes.
        """
        return self._is_bytes


class Row:
    """Represents a row element returned from a SELECT query.

    Args:
        resultset (mysqlx.SqlResult or mysqlx.RowResult): The result set.
        fields (`list`): The list of fields.
    """

    def __init__(
        self, resultset: Union[BufferingResult, RowResult], fields: Sequence[FieldTypes]
    ) -> None:
        self._fields: Sequence[FieldTypes] = fields
        self._resultset: Union[BufferingResult, RowResult] = resultset

    def __repr__(self) -> str:
        return repr(self._fields)

    def __getitem__(self, index: Union[int, str]) -> Any:
        """Returns the value of a column by name or index.

        .. versionchanged:: 8.0.12
        """
        int_index = self._resultset.index_of(index) if isinstance(index, str) else index
        if int_index == -1 and isinstance(index, str):
            raise ValueError(f"Column name '{index}' not found")
        if int_index >= len(self._fields) or int_index < 0:
            raise IndexError("Index out of range")
        return self._fields[int_index]

    @deprecated("8.0.12")
    def get_string(self, str_index: str) -> str:
        """Returns the value using the column name.

        Args:
            str_index (str): The column name.

        .. deprecated:: 8.0.12
        """
        int_index = self._resultset.index_of(str_index)
        if int_index >= len(self._fields):
            raise IndexError("Argument out of range")
        if int_index == -1:
            raise ValueError(f"Column name '{str_index}' not found")
        return str(self._fields[int_index])


class BaseResult:
    """Provides base functionality for result objects.

    Args:
        connection (mysqlx.connection.Connection): The Connection object.
    """

    def __init__(self, connection: ConnectionType) -> None:
        self._connection: ConnectionType = connection
        self._closed: bool = False
        self._rows_affected: int = 0
        self._generated_id: int = -1
        self._generated_ids: List[int] = []
        self._warnings: List[Dict[str, Union[int, str]]] = []

        if connection is None:
            self._protocol = None
        else:
            self._protocol = connection.protocol
            connection.fetch_active_result()

    def get_affected_items_count(self) -> int:
        """Returns the number of affected items for the last operation.

        Returns:
            int: The number of affected items.
        """
        return self._rows_affected

    def get_warnings(self) -> List[Dict[str, Union[int, str]]]:
        """Returns the warnings.

        Returns:
            `list`: The list of warnings.
        """
        return self._warnings

    def get_warnings_count(self) -> int:
        """Returns the number of warnings.

        Returns:
            int: The number of warnings.
        """
        return len(self._warnings)

    def set_closed(self, flag: bool) -> None:
        """Sets if resultset fetch is done."""
        self._closed = flag

    def append_warning(self, level: int, code: int, msg: str) -> None:
        """Append a warning.

        Args:
            level (int): The warning level.
            code (int): The warning code.
            msg (str): The warning message.
        """
        self._warnings.append({"level": level, "code": code, "msg": msg})

    def set_generated_ids(self, generated_ids: List[int]) -> None:
        """Sets the generated ids."""
        self._generated_ids = generated_ids

    def set_generated_insert_id(self, generated_id: int) -> None:
        """Sets the generated insert id."""
        self._generated_id = generated_id

    def set_rows_affected(self, total: int) -> None:
        """Sets the number of rows affected."""
        self._rows_affected = total


class Result(BaseResult):
    """Allows retrieving information about non query operations performed on
    the database.

    Args:
        connection (mysqlx.connection.Connection): The Connection object.
                                                   ids (`list`): A list of IDs.
    """

    def __init__(
        self,
        connection: Optional[ConnectionType] = None,
        ids: Optional[List[int]] = None,
    ) -> None:
        super().__init__(connection)
        self._ids: Optional[List[int]] = ids

        if connection is not None:
            self._connection.close_result(self)

    def get_autoincrement_value(self) -> int:
        """Returns the last insert id auto generated.

        Returns:
            int: The last insert id.
        """
        return self._generated_id

    @deprecated("8.0.12")
    def get_document_id(self) -> Optional[int]:
        """Returns ID of the last document inserted into a collection.

        .. deprecated:: 8.0.12
        """
        if self._ids is None or len(self._ids) == 0:
            return None
        return self._ids[0]

    @deprecated("8.0.12")
    def get_generated_insert_id(self) -> int:
        """Returns the generated insert id.

        .. deprecated:: 8.0.12
        """
        return self._generated_id

    def get_generated_ids(self) -> List[int]:
        """Returns the generated ids."""
        return self._generated_ids


class BufferingResult(BaseResult):
    """Provides base functionality for buffering result objects.

    Args:
        connection (mysqlx.connection.Connection): The Connection object.
                                                   ids (`list`): A list of IDs.
    """

    def __init__(self, connection: ConnectionType) -> None:
        super().__init__(connection)
        self._columns: List[Column] = []
        self._has_data: bool = False
        self._has_more_results: bool = False
        self._items: List[Union[Row, DbDoc]] = []
        self._page_size: int = 0
        self._position: int = -1
        self._init_result()

    def __getitem__(self, index: int) -> Union[Row, DbDoc]:
        return self._items[index]

    @property
    def count(self) -> int:
        """int: The total of items."""
        return len(self._items)

    def _init_result(self) -> None:
        """Initialize the result."""
        self._columns = self._connection.get_column_metadata(self)
        self._has_more_data = len(self._columns) > 0
        self._items = []
        self._page_size = 20
        self._position = -1
        self._connection.set_active_result(self if self._has_more_data else None)

    def _read_item(self, dumping: bool) -> Optional[Union[Row, DbDoc]]:
        """Read item.

        Args:
            dumping (bool): `True` for dumping.

        Returns:
            :class:`mysqlx.Row`: A `Row` object.
        """
        row = self._connection.read_row(self)
        if row is None:
            return None
        item = [None] * len(row["field"])
        if not dumping:
            for key in range(len(row["field"])):
                column = self._columns[key]
                item[key] = from_protobuf(column, row["field"][key])
        return Row(self, item)

    def _page_in_items(self) -> Union[bool, int]:
        """Reads the page items.

        Returns:
            int: Total items read.
        """
        if self._closed:
            return False

        count = 0
        for _ in range(self._page_size):
            item = self._read_item(False)
            if item is None:
                break
            self._items.append(item)
            count += 1
        return count

    def index_of(self, col_name: str) -> int:
        """Returns the index of the column.

        Returns:
            int: The index of the column.
        """
        index = 0
        for col in self._columns:
            if col.get_column_label() == col_name:
                return index
            index += 1
        return -1

    def fetch_one(self) -> Optional[Union[Row, DbDoc]]:
        """Fetch one item.

        Returns:
            :class:`mysqlx.Row` or :class:`mysqlx.DbDoc`: one result item.
        """
        if self._closed:
            return None

        return self._read_item(False)

    def fetch_all(self) -> List[Union[Row, DbDoc]]:
        """Fetch all items.

        Returns:
            `list`: The list of items of :class:`mysqlx.DbDoc` or
                    :class:`mysqlx.Row`.
        """
        while True:
            if not self._page_in_items():
                break
        return self._items

    def set_has_data(self, flag: bool) -> None:
        """Sets if result has data.

        Args:
            flag (bool): `True` if result has data.
        """
        self._has_data = flag

    def set_has_more_results(self, flag: bool) -> None:
        """Sets if has more results.

        Args:
            flag (bool): `True` if has more results.
        """
        self._has_more_results = flag


class RowResult(BufferingResult):
    """Allows traversing the Row objects returned by a Table.select operation.

    Args:
        connection (mysqlx.connection.Connection): The Connection object.
    """

    @property
    def columns(self) -> List[Column]:
        """`list`: The list of columns."""
        return self._columns

    def get_columns(self) -> List[Column]:
        """Returns the list of columns.

        Returns:
            `list`: The list of columns.

        .. versionadded:: 8.0.12
        """
        return self._columns


class SqlResult(RowResult):
    """Represents a result from a SQL statement.

    Args:
        connection (mysqlx.connection.Connection): The Connection object.
    """

    def get_autoincrement_value(self) -> int:
        """Returns the identifier for the last record inserted.

        Returns:
            str: The identifier of the last record inserted.
        """
        return self._generated_id

    def next_result(self) -> bool:
        """Process the next result.

        Returns:
            bool: Returns `True` if the fetch is done.
        """
        if self._closed:
            return False
        self._has_more_results = False
        self._init_result()
        return True

    def has_data(self) -> bool:
        """Returns True if result has data.

        Returns:
            bool: Returns `True` if result has data.

        .. versionadded:: 8.0.12
        """
        return self._has_data


class DocResult(BufferingResult):
    """Allows traversing the DbDoc objects returned by a Collection.find
    operation.

    Args:
        connection (mysqlx.connection.Connection): The Connection object.
    """

    def _read_item(self, dumping: bool) -> DbDoc:
        """Read item.

        Args:
            dumping (bool): `True` for dumping.

        Returns:
            :class:`mysqlx.DbDoc`: A `DbDoc` object.
        """
        row = super()._read_item(dumping)
        if row is None:
            return None
        return DbDoc(decode_from_bytes(row[0]))  # type: ignore[index]

virt/lib/python3.9/site-packages/mysqlx/result 2.py
PrinceDisant/LostInNL
# Copyright (c) 2016, 2022, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

"""Implementation of the Result classes."""

from __future__ import annotations

import decimal
import struct
import sys

from datetime import datetime, timedelta
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

from .charsets import MYSQL_CHARACTER_SETS
from .dbdoc import DbDoc
from .helpers import decode_from_bytes, deprecated
from .types import ConnectionType, FieldTypes


# pylint: disable=missing-class-docstring,missing-function-docstring
def from_protobuf(column: Column, payload: bytes) -> Any:
    if len(payload) == 0:
        return None

    if column.get_type() == ColumnType.STRING:
        return decode_from_bytes(payload[:-1])  # Strip trailing char

    try:
        return ColumnProtoType.converter_map[column.get_proto_type()](payload)
    except KeyError as err:
        sys.stderr.write(f"{err}")
        sys.stderr.write(f"{payload.encode('hex')}")  # type: ignore[attr-defined]
        return None


def bytes_from_protobuf(payload: bytes) -> bytes:
    # Strip trailing char
    return payload[:-1]


def float_from_protobuf(payload: bytes) -> float:
    assert len(payload) == 4
    return struct.unpack("<f", payload)[0]


def double_from_protobuf(payload: bytes) -> float:
    assert len(payload) == 8
    return struct.unpack("<d", payload)[0]


def varint_from_protobuf_stream(payload: bytes) -> Tuple[int, bytes]:
    if len(payload) == 0:
        raise ValueError("Payload is empty")

    cur = 0
    i = 0
    shift = 0

    for item in payload:
        char = item if isinstance(item, int) else ord(item)  # type: ignore[arg-type]
        eos = (char & 0x80) == 0
        cur_bits = char & 0x7F
        cur_bits <<= shift
        i |= cur_bits
        if eos:
            return i, payload[cur + 1 :]
        cur += 1
        shift += 7

    raise EOFError("Payload too short")


def varint_from_protobuf(payload: bytes) -> int:
    i, payload = varint_from_protobuf_stream(payload)
    if len(payload) != 0:
        raise ValueError("Payload too long")

    return i


def varsint_from_protobuf(payload: bytes) -> int:
    i, payload = varint_from_protobuf_stream(payload)
    if len(payload) != 0:
        raise ValueError("Payload too long")

    # Zigzag encoded, revert it
    if i & 0x1:
        i = ~i
        i = i >> 1
        i |= 1 << 63
    else:
        i = i >> 1

    return i


def set_from_protobuf(payload: bytes) -> List[bytes]:
    set_pb: List = []
    while True:
        try:
            field_len, payload = varint_from_protobuf_stream(payload)
            if len(payload) < field_len:
                if len(payload) == 0 and field_len == 1 and len(set_pb) == 0:
                    # Special case for empty set
                    return []
                raise ValueError("Invalid Set encoding")

            set_pb.append(payload[:field_len])
            payload = payload[field_len:]
            if len(payload) == 0:
                # Done
                break
        except ValueError:
            break
    return set_pb


def decimal_from_protobuf(payload: bytes) -> decimal.Decimal:
    digits = []
    sign = None
    scale = payload[0] if isinstance(payload[0], int) else ord(payload[0])  # type: ignore[arg-type]
    payload = payload[1:]

    for item in payload:
        char = item if isinstance(item, int) else ord(item)  # type: ignore[arg-type]
        high_bcd = (char & 0xF0) >> 4
        low_bcd = char & 0x0F
        if high_bcd < 0x0A:
            digits.append(high_bcd)
            if low_bcd < 0x0A:
                digits.append(low_bcd)
            elif low_bcd == 0x0C:
                sign = 0
                break
            elif low_bcd == 0x0D:
                sign = 1
                break
            else:
                raise ValueError("Invalid BCD")
        elif high_bcd == 0x0C:
            sign = 0
            assert low_bcd == 0x00
            break
        elif high_bcd == 0x0D:
            sign = 1
            assert low_bcd == 0x00
            break
        else:
            raise ValueError(f"Invalid BCD: {high_bcd}")

    return decimal.Decimal((sign, digits, -scale))


def datetime_from_protobuf(payload: bytes) -> datetime:
    # A sequence of varints
    hour = 0
    minutes = 0
    seconds = 0
    useconds = 0
    year, payload = varint_from_protobuf_stream(payload)
    month, payload = varint_from_protobuf_stream(payload)
    day, payload = varint_from_protobuf_stream(payload)

    try:
        hour, payload = varint_from_protobuf_stream(payload)
        minutes, payload = varint_from_protobuf_stream(payload)
        seconds, payload = varint_from_protobuf_stream(payload)
        useconds, payload = varint_from_protobuf_stream(payload)
    except ValueError:
        pass

    return datetime(year, month, day, hour, minutes, seconds, useconds)


def time_from_protobuf(payload: bytes) -> timedelta:
    # A sequence of varints
    hour = 0
    minutes = 0
    seconds = 0
    useconds = 0
    negate = payload[0] == 1
    payload = payload[1:]

    try:
        hour, payload = varint_from_protobuf_stream(payload)
        minutes, payload = varint_from_protobuf_stream(payload)
        seconds, payload = varint_from_protobuf_stream(payload)
        useconds, payload = varint_from_protobuf_stream(payload)
    except ValueError:
        pass

    if negate:
        # Negate the first non-zero value
        if hour:
            hour *= -1
        elif minutes:
            minutes *= -1
        elif seconds:
            seconds *= -1
        elif useconds:
            useconds *= -1

    return timedelta(
        hours=hour, minutes=minutes, seconds=seconds, microseconds=useconds
    )


class Collations:
    UTF8_GENERAL_CI = 33


class ColumnType:
    BIT = 1
    TINYINT = 2
    SMALLINT = 3
    MEDIUMINT = 4
    INT = 5
    BIGINT = 6
    REAL = 7
    FLOAT = 8
    DECIMAL = 9
    NUMERIC = 10
    DOUBLE = 11
    JSON = 12
    STRING = 13
    BYTES = 14
    TIME = 15
    DATE = 16
    DATETIME = 17
    TIMESTAMP = 18
    SET = 19
    ENUM = 20
    GEOMETRY = 21
    XML = 22
    YEAR = 23
    CHAR = 24
    VARCHAR = 25
    BINARY = 26
    VARBINARY = 27
    TINYBLOB = 28
    BLOB = 29
    MEDIUMBLOB = 30
    LONGBLOB = 31
    TINYTEXT = 32
    TEXT = 33
    MEDIUMTEXT = 34
    LONGTEXT = 35

    @classmethod
    def to_string(cls, needle: Any) -> Optional[str]:
        for key, value in vars(cls).items():
            if value == needle:
                return key
        return None

    @classmethod
    def from_string(cls, key: str) -> Any:
        return getattr(cls, key.upper(), None)

    @classmethod
    def is_char(cls, col_type: int) -> bool:
        return col_type in (
            cls.CHAR,
            cls.VARCHAR,
        )

    @classmethod
    def is_binary(cls, col_type: int) -> bool:
        return col_type in (
            cls.BINARY,
            cls.VARBINARY,
        )

    @classmethod
    def is_text(cls, col_type: int) -> bool:
        return col_type in (
            cls.TEXT,
            cls.TINYTEXT,
            cls.MEDIUMTEXT,
            cls.LONGTEXT,
        )

    @classmethod
    def is_decimals(cls, col_type: int) -> bool:
        return col_type in (
            cls.REAL,
            cls.DOUBLE,
            cls.FLOAT,
            cls.DECIMAL,
            cls.NUMERIC,
        )

    @classmethod
    def is_numeric(cls, col_type: int) -> bool:
        return col_type in (
            cls.BIT,
            cls.TINYINT,
            cls.SMALLINT,
            cls.MEDIUMINT,
            cls.INT,
            cls.BIGINT,
        )

    @classmethod
    def is_finite_set(cls, col_type: int) -> bool:
        return col_type in (
            cls.SET,
            cls.ENUM,
        )


class ColumnProtoType:
    SINT = 1
    UINT = 2
    DOUBLE = 5
    FLOAT = 6
    BYTES = 7
    TIME = 10
    DATETIME = 12
    SET = 15
    ENUM = 16
    BIT = 17
    DECIMAL = 18

    converter_map: Dict[int, Callable[[bytes], Any]] = {
        SINT: varsint_from_protobuf,
        UINT: varint_from_protobuf,
        BYTES: bytes_from_protobuf,
        DATETIME: datetime_from_protobuf,
        TIME: time_from_protobuf,
        FLOAT: float_from_protobuf,
        DOUBLE: double_from_protobuf,
        BIT: varint_from_protobuf,
        SET: set_from_protobuf,
        ENUM: bytes_from_protobuf,
        DECIMAL: decimal_from_protobuf,
    }


class Flags:
    def __init__(self, value: int) -> None:
        self._allowed_flags: Dict[str, int] = {}
        self._flag_names: Dict[int, str] = {}
        for key, val in self.__class__.__dict__.items():
            if key.startswith("__"):
                continue
            if isinstance(val, int):
                self._allowed_flags[key] = val
                self._flag_names[val] = key
        self._value: int = value

    def __str__(self) -> str:
        mask = 1
        flag_names = []
        value = self._value

        for _ in range(0, 63):
            mask <<= 1
            flag = value & mask
            if flag:
                # We matched something, find the name for it
                try:
                    flag_names.append(self._flag_names[flag])
                except KeyError:
                    sys.stderr.write(f"{self._flag_names}")
                    sys.stderr.write(f"{self.__class__.__dict__}")

        return ",".join(flag_names)

    @property
    def value(self) -> int:
        return self._value

    @value.setter
    def value(self, val: int) -> None:
        self._value = val


class ColumnFlags(Flags):
    NOT_NULL = 0x0010
    PRIMARY_KEY = 0x0020
    UNIQUE_KEY = 0x0040
    MULTIPLE_KEY = 0x0080
    AUTO_INCREMENT = 0x0100


class DatetimeColumnFlags(ColumnFlags):
    TIMESTAMP = 0x0001


class UIntColumnFlags(ColumnFlags):
    ZEROFILL = 0x0001


class DoubleColumnFlags(ColumnFlags):
    UNSIGNED = 0x0001


class FloatColumnFlags(ColumnFlags):
    UNSIGNED = 0x0001


class BytesColumnFlags(ColumnFlags):
    RIGHT_PAD = 0x0001


class BytesContentType(ColumnFlags):
    GEOMETRY = 0x0001
    JSON = 0x0002
    XML = 0x0003


# pylint: enable=missing-class-docstring,missing-function-docstring


class Column:
    """Represents meta data for a table column.

    Args:
        col_type (int): The column type.
        catalog (str): The catalog.
        schema (str): The schema name.
        table (str): The table name.
        original_table (str): The original table name.
        name (str): The column name.
        original_name (str): The original table name.
        length (int): The column length,
        collation (str): The collation name.
        fractional_digits (int): The fractional digits.
        flags (int): The flags.
        content_type (int): The content type.

    .. versionchanged:: 8.0.12
    """

    def __init__(
        self,
        col_type: int,
        catalog: Optional[str] = None,
        schema: Optional[str] = None,
        table: Optional[str] = None,
        original_table: Optional[str] = None,
        name: Optional[str] = None,
        original_name: Optional[str] = None,
        length: Optional[int] = None,
        collation: Optional[int] = None,
        fractional_digits: Optional[int] = None,
        flags: Optional[int] = None,
        content_type: Optional[int] = None,
    ) -> None:
        self._schema: str = decode_from_bytes(schema)
        self._name: str = decode_from_bytes(name)
        self._original_name: str = decode_from_bytes(original_name)
        self._table: str = decode_from_bytes(table)
        self._original_table: str = decode_from_bytes(original_table)
        self._proto_type: int = col_type
        self._col_type: Optional[int] = None
        self._catalog: Optional[str] = catalog
        self._length: Optional[int] = length
        self._collation: Optional[int] = collation
        self._fractional_digits: Optional[int] = fractional_digits
        self._flags: Optional[int] = flags
        self._content_type: Optional[int] = content_type
        self._number_signed: bool = False
        self._is_padded: Union[bool, int] = False
        self._is_binary: bool = False
        self._is_bytes: bool = False
        self._collation_name: Optional[str] = None
        self._character_set_name: Optional[str] = None
        self._zero_fill: Optional[int] = None

        if self._collation > 0:
            if self._collation >= len(MYSQL_CHARACTER_SETS):
                raise ValueError(f"No mapping found for collation {self._collation}")
            info = MYSQL_CHARACTER_SETS[self._collation]
            self._character_set_name = info[0]
            self._collation_name = info[1]
            self._is_binary = (
                "binary" in self._collation_name or "_bin" in self._collation_name
            )
        self._map_type()
        self._is_bytes = self._col_type in (
            ColumnType.GEOMETRY,
            ColumnType.JSON,
            ColumnType.XML,
            ColumnType.BYTES,
            ColumnType.STRING,
        )

    def __str__(self) -> str:
        return str(
            {
                "col_type": self._col_type,
                "schema": self._schema,
                "table": self._table,
                "flags": str(self._flags),
            }
        )

    def _map_bytes(self) -> None:
        """Map bytes."""
        if self._content_type == BytesContentType.GEOMETRY:
            self._col_type = ColumnType.GEOMETRY
        elif self._content_type == BytesContentType.JSON:
            self._col_type = ColumnType.JSON
        elif self._content_type == BytesContentType.XML:
            self._col_type = ColumnType.XML
        elif self._is_binary:
            self._col_type = ColumnType.BYTES
        else:
            self._col_type = ColumnType.STRING
        self._is_padded = self._flags & 1

    def _map_datetime(self) -> None:
        """Map datetime."""
        if self._length == 10:
            self._col_type = ColumnType.DATE
        elif self._flags & DatetimeColumnFlags.TIMESTAMP > 0:
            self._col_type = ColumnType.TIMESTAMP
        elif self._length >= 19:
            self._col_type = ColumnType.DATETIME
        else:
            raise ValueError("Datetime mapping scenario unhandled")

    def _map_int_type(self) -> None:
        """Map int type."""
        if self._length <= 4:
            self._col_type = ColumnType.TINYINT
        elif self._length <= 6:
            self._col_type = ColumnType.SMALLINT
        elif self._length <= 9:
            self._col_type = ColumnType.MEDIUMINT
        elif self._length <= 11:
            self._col_type = ColumnType.INT
        else:
            self._col_type = ColumnType.BIGINT
        self._number_signed = True

    def _map_uint_type(self) -> None:
        """Map uint type."""
        if self._length <= 3:
            self._col_type = ColumnType.TINYINT
        elif self._length <= 5:
            self._col_type = ColumnType.SMALLINT
        elif self._length <= 8:
            self._col_type = ColumnType.MEDIUMINT
        elif self._length <= 10:
            self._col_type = ColumnType.INT
        else:
            self._col_type = ColumnType.BIGINT
        self._zero_fill = self._flags & 1

    def _map_type(self) -> None:
        """Map type."""
        if self._proto_type == ColumnProtoType.SINT:
            self._map_int_type()
        elif self._proto_type == ColumnProtoType.UINT:
            self._map_uint_type()
        elif self._proto_type == ColumnProtoType.FLOAT:
            self._col_type = ColumnType.FLOAT
            self._is_number_signed = (self._flags & FloatColumnFlags.UNSIGNED) == 0
        elif self._proto_type == ColumnProtoType.DECIMAL:
            self._col_type = ColumnType.DECIMAL
            self._is_number_signed = (self._flags & FloatColumnFlags.UNSIGNED) == 0
        elif self._proto_type == ColumnProtoType.DOUBLE:
            self._col_type = ColumnType.DOUBLE
            self._is_number_signed = (self._flags & FloatColumnFlags.UNSIGNED) == 0
        elif self._proto_type == ColumnProtoType.BYTES:
            self._map_bytes()
        elif self._proto_type == ColumnProtoType.TIME:
            self._col_type = ColumnType.TIME
        elif self._proto_type == ColumnProtoType.DATETIME:
            self._map_datetime()
        elif self._proto_type == ColumnProtoType.SET:
            self._col_type = ColumnType.SET
        elif self._proto_type == ColumnProtoType.ENUM:
            self._col_type = ColumnType.ENUM
        elif self._proto_type == ColumnProtoType.BIT:
            self._col_type = ColumnType.BIT
        else:
            raise ValueError(f"Unknown column type {self._proto_type}")

    @property
    def schema_name(self) -> str:
        """str: The schema name.

        .. versionadded:: 8.0.12
        """
        return self._schema

    @property
    def table_name(self) -> str:
        """str: The table name.

        .. versionadded:: 8.0.12
        """
        return self._original_table or self._table

    @property
    def table_label(self) -> str:
        """str: The table label.

        .. versionadded:: 8.0.12
        """
        return self._table or self._original_table

    @property
    def column_name(self) -> str:
        """str: The column name.

        .. versionadded:: 8.0.12
        """
        return self._original_name or self._name

    @property
    def column_label(self) -> str:
        """str: The column label.

        .. versionadded:: 8.0.12
        """
        return self._name or self._original_name

    @property
    def type(self) -> int:
        """int: The column type.

        .. versionadded:: 8.0.12
        """
        return self._col_type

    @property
    def length(self) -> int:
        """int. The column length.

        .. versionadded:: 8.0.12
        """
        return self._length

    @property
    def fractional_digits(self) -> int:
        """int: The column fractional digits.

        .. versionadded:: 8.0.12
        """
        return self._fractional_digits

    @property
    def collation_name(self) -> str:
        """str: The collation name.

        .. versionadded:: 8.0.12
        """
        return self._collation_name

    @property
    def character_set_name(self) -> str:
        """str: The character set name.

        .. versionadded:: 8.0.12
        """
        return self._character_set_name

    def get_schema_name(self) -> str:
        """Returns the schema name.

        Returns:
            str: The schema name.
        """
        return self._schema

    def get_table_name(self) -> str:
        """Returns the table name.

        Returns:
            str: The table name.
        """
        return self._original_table or self._table

    def get_table_label(self) -> str:
        """Returns the table label.

        Returns:
            str: The table label.
        """
        return self._table or self._original_table

    def get_column_name(self) -> str:
        """Returns the column name.

        Returns:
            str: The column name.
        """
        return self._original_name or self._name

    def get_column_label(self) -> str:
        """Returns the column label.

        Returns:
            str: The column label.
        """
        return self._name or self._original_name

    def get_proto_type(self) -> int:
        """Returns the column proto type.

        Returns:
            int: The column proto type.
        """
        return self._proto_type

    def get_type(self) -> int:
        """Returns the column type.

        Returns:
            int: The column type.
        """
        return self._col_type

    def get_length(self) -> int:
        """Returns the column length.

        Returns:
            int: The column length.
        """
        return self._length

    def get_fractional_digits(self) -> int:
        """Returns the column fractional digits.

        Returns:
            int: The column fractional digits.
        """
        return self._fractional_digits

    def get_collation_name(self) -> str:
        """Returns the collation name.

        Returns:
            str: The collation name.
        """
        return self._collation_name

    def get_character_set_name(self) -> str:
        """Returns the character set name.

        Returns:
            str: The character set name.
        """
        return self._character_set_name

    def is_number_signed(self) -> bool:
        """Returns `True` if is a number signed.

        Returns:
            bool: Returns `True` if is a number signed.
        """
        return self._number_signed

    def is_padded(self) -> Union[bool, int]:
        """Returns `True` if is padded.

        Returns:
            bool: Returns `True` if is padded.
        """
        return self._is_padded

    def is_bytes(self) -> bool:
        """Returns `True` if is bytes.

        Returns:
            bool: Returns `True` if is bytes.
        """
        return self._is_bytes


class Row:
    """Represents a row element returned from a SELECT query.

    Args:
        resultset (mysqlx.SqlResult or mysqlx.RowResult): The result set.
        fields (`list`): The list of fields.
    """

    def __init__(
        self, resultset: Union[BufferingResult, RowResult], fields: Sequence[FieldTypes]
    ) -> None:
        self._fields: Sequence[FieldTypes] = fields
        self._resultset: Union[BufferingResult, RowResult] = resultset

    def __repr__(self) -> str:
        return repr(self._fields)

    def __getitem__(self, index: Union[int, str]) -> Any:
        """Returns the value of a column by name or index.

        .. versionchanged:: 8.0.12
        """
        int_index = self._resultset.index_of(index) if isinstance(index, str) else index
        if int_index == -1 and isinstance(index, str):
            raise ValueError(f"Column name '{index}' not found")
        if int_index >= len(self._fields) or int_index < 0:
            raise IndexError("Index out of range")
        return self._fields[int_index]

    @deprecated("8.0.12")
    def get_string(self, str_index: str) -> str:
        """Returns the value using the column name.

        Args:
            str_index (str): The column name.

        .. deprecated:: 8.0.12
        """
        int_index = self._resultset.index_of(str_index)
        if int_index >= len(self._fields):
            raise IndexError("Argument out of range")
        if int_index == -1:
            raise ValueError(f"Column name '{str_index}' not found")
        return str(self._fields[int_index])


class BaseResult:
    """Provides base functionality for result objects.

    Args:
        connection (mysqlx.connection.Connection): The Connection object.
    """

    def __init__(self, connection: ConnectionType) -> None:
        self._connection: ConnectionType = connection
        self._closed: bool = False
        self._rows_affected: int = 0
        self._generated_id: int = -1
        self._generated_ids: List[int] = []
        self._warnings: List[Dict[str, Union[int, str]]] = []

        if connection is None:
            self._protocol = None
        else:
            self._protocol = connection.protocol
            connection.fetch_active_result()

    def get_affected_items_count(self) -> int:
        """Returns the number of affected items for the last operation.

        Returns:
            int: The number of affected items.
        """
        return self._rows_affected

    def get_warnings(self) -> List[Dict[str, Union[int, str]]]:
        """Returns the warnings.

        Returns:
            `list`: The list of warnings.
        """
        return self._warnings

    def get_warnings_count(self) -> int:
        """Returns the number of warnings.

        Returns:
            int: The number of warnings.
        """
        return len(self._warnings)

    def set_closed(self, flag: bool) -> None:
        """Sets if resultset fetch is done."""
        self._closed = flag

    def append_warning(self, level: int, code: int, msg: str) -> None:
        """Append a warning.

        Args:
            level (int): The warning level.
            code (int): The warning code.
            msg (str): The warning message.
        """
        self._warnings.append({"level": level, "code": code, "msg": msg})

    def set_generated_ids(self, generated_ids: List[int]) -> None:
        """Sets the generated ids."""
        self._generated_ids = generated_ids

    def set_generated_insert_id(self, generated_id: int) -> None:
        """Sets the generated insert id."""
        self._generated_id = generated_id

    def set_rows_affected(self, total: int) -> None:
        """Sets the number of rows affected."""
        self._rows_affected = total


class Result(BaseResult):
    """Allows retrieving information about non query operations performed on
    the database.

    Args:
        connection (mysqlx.connection.Connection): The Connection object.
                                                   ids (`list`): A list of IDs.
    """

    def __init__(
        self,
        connection: Optional[ConnectionType] = None,
        ids: Optional[List[int]] = None,
    ) -> None:
        super().__init__(connection)
        self._ids: Optional[List[int]] = ids

        if connection is not None:
            self._connection.close_result(self)

    def get_autoincrement_value(self) -> int:
        """Returns the last insert id auto generated.

        Returns:
            int: The last insert id.
        """
        return self._generated_id

    @deprecated("8.0.12")
    def get_document_id(self) -> Optional[int]:
        """Returns ID of the last document inserted into a collection.

        .. deprecated:: 8.0.12
        """
        if self._ids is None or len(self._ids) == 0:
            return None
        return self._ids[0]

    @deprecated("8.0.12")
    def get_generated_insert_id(self) -> int:
        """Returns the generated insert id.

        .. deprecated:: 8.0.12
        """
        return self._generated_id

    def get_generated_ids(self) -> List[int]:
        """Returns the generated ids."""
        return self._generated_ids


class BufferingResult(BaseResult):
    """Provides base functionality for buffering result objects.

    Args:
        connection (mysqlx.connection.Connection): The Connection object.
                                                   ids (`list`): A list of IDs.
    """

    def __init__(self, connection: ConnectionType) -> None:
        super().__init__(connection)
        self._columns: List[Column] = []
        self._has_data: bool = False
        self._has_more_results: bool = False
        self._items: List[Union[Row, DbDoc]] = []
        self._page_size: int = 0
        self._position: int = -1
        self._init_result()

    def __getitem__(self, index: int) -> Union[Row, DbDoc]:
        return self._items[index]

    @property
    def count(self) -> int:
        """int: The total of items."""
        return len(self._items)

    def _init_result(self) -> None:
        """Initialize the result."""
        self._columns = self._connection.get_column_metadata(self)
        self._has_more_data = len(self._columns) > 0
        self._items = []
        self._page_size = 20
        self._position = -1
        self._connection.set_active_result(self if self._has_more_data else None)

    def _read_item(self, dumping: bool) -> Optional[Union[Row, DbDoc]]:
        """Read item.

        Args:
            dumping (bool): `True` for dumping.

        Returns:
            :class:`mysqlx.Row`: A `Row` object.
        """
        row = self._connection.read_row(self)
        if row is None:
            return None
        item = [None] * len(row["field"])
        if not dumping:
            for key in range(len(row["field"])):
                column = self._columns[key]
                item[key] = from_protobuf(column, row["field"][key])
        return Row(self, item)

    def _page_in_items(self) -> Union[bool, int]:
        """Reads the page items.

        Returns:
            int: Total items read.
        """
        if self._closed:
            return False

        count = 0
        for _ in range(self._page_size):
            item = self._read_item(False)
            if item is None:
                break
            self._items.append(item)
            count += 1
        return count

    def index_of(self, col_name: str) -> int:
        """Returns the index of the column.

        Returns:
            int: The index of the column.
        """
        index = 0
        for col in self._columns:
            if col.get_column_label() == col_name:
                return index
            index += 1
        return -1

    def fetch_one(self) -> Optional[Union[Row, DbDoc]]:
        """Fetch one item.

        Returns:
            :class:`mysqlx.Row` or :class:`mysqlx.DbDoc`: one result item.
        """
        if self._closed:
            return None

        return self._read_item(False)

    def fetch_all(self) -> List[Union[Row, DbDoc]]:
        """Fetch all items.

        Returns:
            `list`: The list of items of :class:`mysqlx.DbDoc` or
                    :class:`mysqlx.Row`.
        """
        while True:
            if not self._page_in_items():
                break
        return self._items

    def set_has_data(self, flag: bool) -> None:
        """Sets if result has data.

        Args:
            flag (bool): `True` if result has data.
        """
        self._has_data = flag

    def set_has_more_results(self, flag: bool) -> None:
        """Sets if has more results.

        Args:
            flag (bool): `True` if has more results.
        """
        self._has_more_results = flag


class RowResult(BufferingResult):
    """Allows traversing the Row objects returned by a Table.select operation.

    Args:
        connection (mysqlx.connection.Connection): The Connection object.
    """

    @property
    def columns(self) -> List[Column]:
        """`list`: The list of columns."""
        return self._columns

    def get_columns(self) -> List[Column]:
        """Returns the list of columns.

        Returns:
            `list`: The list of columns.

        .. versionadded:: 8.0.12
        """
        return self._columns


class SqlResult(RowResult):
    """Represents a result from a SQL statement.

    Args:
        connection (mysqlx.connection.Connection): The Connection object.
    """

    def get_autoincrement_value(self) -> int:
        """Returns the identifier for the last record inserted.

        Returns:
            str: The identifier of the last record inserted.
        """
        return self._generated_id

    def next_result(self) -> bool:
        """Process the next result.

        Returns:
            bool: Returns `True` if the fetch is done.
        """
        if self._closed:
            return False
        self._has_more_results = False
        self._init_result()
        return True

    def has_data(self) -> bool:
        """Returns True if result has data.

        Returns:
            bool: Returns `True` if result has data.

        .. versionadded:: 8.0.12
        """
        return self._has_data


class DocResult(BufferingResult):
    """Allows traversing the DbDoc objects returned by a Collection.find
    operation.

    Args:
        connection (mysqlx.connection.Connection): The Connection object.
    """

    def _read_item(self, dumping: bool) -> DbDoc:
        """Read item.

        Args:
            dumping (bool): `True` for dumping.

        Returns:
            :class:`mysqlx.DbDoc`: A `DbDoc` object.
        """
        row = super()._read_item(dumping)
        if row is None:
            return None
        return DbDoc(decode_from_bytes(row[0]))  # type: ignore[index]

virt/lib/python3.9/site-packages/mysqlx/result 3.py
PrinceDisant/LostInNL
# Copyright (c) 2016, 2022, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

"""Implementation of the Result classes."""

from __future__ import annotations

import decimal
import struct
import sys

from datetime import datetime, timedelta
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union

from .charsets import MYSQL_CHARACTER_SETS
from .dbdoc import DbDoc
from .helpers import decode_from_bytes, deprecated
from .types import ConnectionType, FieldTypes


# pylint: disable=missing-class-docstring,missing-function-docstring
def from_protobuf(column: Column, payload: bytes) -> Any:
    if len(payload) == 0:
        return None

    if column.get_type() == ColumnType.STRING:
        return decode_from_bytes(payload[:-1])  # Strip trailing char

    try:
        return ColumnProtoType.converter_map[column.get_proto_type()](payload)
    except KeyError as err:
        sys.stderr.write(f"{err}")
        sys.stderr.write(f"{payload.encode('hex')}")  # type: ignore[attr-defined]
        return None


def bytes_from_protobuf(payload: bytes) -> bytes:
    # Strip trailing char
    return payload[:-1]


def float_from_protobuf(payload: bytes) -> float:
    assert len(payload) == 4
    return struct.unpack("<f", payload)[0]


def double_from_protobuf(payload: bytes) -> float:
    assert len(payload) == 8
    return struct.unpack("<d", payload)[0]


def varint_from_protobuf_stream(payload: bytes) -> Tuple[int, bytes]:
    if len(payload) == 0:
        raise ValueError("Payload is empty")

    cur = 0
    i = 0
    shift = 0

    for item in payload:
        char = item if isinstance(item, int) else ord(item)  # type: ignore[arg-type]
        eos = (char & 0x80) == 0
        cur_bits = char & 0x7F
        cur_bits <<= shift
        i |= cur_bits
        if eos:
            return i, payload[cur + 1 :]
        cur += 1
        shift += 7

    raise EOFError("Payload too short")


def varint_from_protobuf(payload: bytes) -> int:
    i, payload = varint_from_protobuf_stream(payload)
    if len(payload) != 0:
        raise ValueError("Payload too long")

    return i


def varsint_from_protobuf(payload: bytes) -> int:
    i, payload = varint_from_protobuf_stream(payload)
    if len(payload) != 0:
        raise ValueError("Payload too long")

    # Zigzag encoded, revert it
    if i & 0x1:
        i = ~i
        i = i >> 1
        i |= 1 << 63
    else:
        i = i >> 1

    return i


def set_from_protobuf(payload: bytes) -> List[bytes]:
    set_pb: List = []
    while True:
        try:
            field_len, payload = varint_from_protobuf_stream(payload)
            if len(payload) < field_len:
                if len(payload) == 0 and field_len == 1 and len(set_pb) == 0:
                    # Special case for empty set
                    return []
                raise ValueError("Invalid Set encoding")

            set_pb.append(payload[:field_len])
            payload = payload[field_len:]
            if len(payload) == 0:
                # Done
                break
        except ValueError:
            break
    return set_pb


def decimal_from_protobuf(payload: bytes) -> decimal.Decimal:
    digits = []
    sign = None
    scale = payload[0] if isinstance(payload[0], int) else ord(payload[0])  # type: ignore[arg-type]
    payload = payload[1:]

    for item in payload:
        char = item if isinstance(item, int) else ord(item)  # type: ignore[arg-type]
        high_bcd = (char & 0xF0) >> 4
        low_bcd = char & 0x0F
        if high_bcd < 0x0A:
            digits.append(high_bcd)
            if low_bcd < 0x0A:
                digits.append(low_bcd)
            elif low_bcd == 0x0C:
                sign = 0
                break
            elif low_bcd == 0x0D:
                sign = 1
                break
            else:
                raise ValueError("Invalid BCD")
        elif high_bcd == 0x0C:
            sign = 0
            assert low_bcd == 0x00
            break
        elif high_bcd == 0x0D:
            sign = 1
            assert low_bcd == 0x00
            break
        else:
            raise ValueError(f"Invalid BCD: {high_bcd}")

    return decimal.Decimal((sign, digits, -scale))


def datetime_from_protobuf(payload: bytes) -> datetime:
    # A sequence of varints
    hour = 0
    minutes = 0
    seconds = 0
    useconds = 0
    year, payload = varint_from_protobuf_stream(payload)
    month, payload = varint_from_protobuf_stream(payload)
    day, payload = varint_from_protobuf_stream(payload)

    try:
        hour, payload = varint_from_protobuf_stream(payload)
        minutes, payload = varint_from_protobuf_stream(payload)
        seconds, payload = varint_from_protobuf_stream(payload)
        useconds, payload = varint_from_protobuf_stream(payload)
    except ValueError:
        pass

    return datetime(year, month, day, hour, minutes, seconds, useconds)


def time_from_protobuf(payload: bytes) -> timedelta:
    # A sequence of varints
    hour = 0
    minutes = 0
    seconds = 0
    useconds = 0
    negate = payload[0] == 1
    payload = payload[1:]

    try:
        hour, payload = varint_from_protobuf_stream(payload)
        minutes, payload = varint_from_protobuf_stream(payload)
        seconds, payload = varint_from_protobuf_stream(payload)
        useconds, payload = varint_from_protobuf_stream(payload)
    except ValueError:
        pass

    if negate:
        # Negate the first non-zero value
        if hour:
            hour *= -1
        elif minutes:
            minutes *= -1
        elif seconds:
            seconds *= -1
        elif useconds:
            useconds *= -1

    return timedelta(
        hours=hour, minutes=minutes, seconds=seconds, microseconds=useconds
    )


class Collations:
    UTF8_GENERAL_CI = 33


class ColumnType:
    BIT = 1
    TINYINT = 2
    SMALLINT = 3
    MEDIUMINT = 4
    INT = 5
    BIGINT = 6
    REAL = 7
    FLOAT = 8
    DECIMAL = 9
    NUMERIC = 10
    DOUBLE = 11
    JSON = 12
    STRING = 13
    BYTES = 14
    TIME = 15
    DATE = 16
    DATETIME = 17
    TIMESTAMP = 18
    SET = 19
    ENUM = 20
    GEOMETRY = 21
    XML = 22
    YEAR = 23
    CHAR = 24
    VARCHAR = 25
    BINARY = 26
    VARBINARY = 27
    TINYBLOB = 28
    BLOB = 29
    MEDIUMBLOB = 30
    LONGBLOB = 31
    TINYTEXT = 32
    TEXT = 33
    MEDIUMTEXT = 34
    LONGTEXT = 35

    @classmethod
    def to_string(cls, needle: Any) -> Optional[str]:
        for key, value in vars(cls).items():
            if value == needle:
                return key
        return None

    @classmethod
    def from_string(cls, key: str) -> Any:
        return getattr(cls, key.upper(), None)

    @classmethod
    def is_char(cls, col_type: int) -> bool:
        return col_type in (
            cls.CHAR,
            cls.VARCHAR,
        )

    @classmethod
    def is_binary(cls, col_type: int) -> bool:
        return col_type in (
            cls.BINARY,
            cls.VARBINARY,
        )

    @classmethod
    def is_text(cls, col_type: int) -> bool:
        return col_type in (
            cls.TEXT,
            cls.TINYTEXT,
            cls.MEDIUMTEXT,
            cls.LONGTEXT,
        )

    @classmethod
    def is_decimals(cls, col_type: int) -> bool:
        return col_type in (
            cls.REAL,
            cls.DOUBLE,
            cls.FLOAT,
            cls.DECIMAL,
            cls.NUMERIC,
        )

    @classmethod
    def is_numeric(cls, col_type: int) -> bool:
        return col_type in (
            cls.BIT,
            cls.TINYINT,
            cls.SMALLINT,
            cls.MEDIUMINT,
            cls.INT,
            cls.BIGINT,
        )

    @classmethod
    def is_finite_set(cls, col_type: int) -> bool:
        return col_type in (
            cls.SET,
            cls.ENUM,
        )


class ColumnProtoType:
    SINT = 1
    UINT = 2
    DOUBLE = 5
    FLOAT = 6
    BYTES = 7
    TIME = 10
    DATETIME = 12
    SET = 15
    ENUM = 16
    BIT = 17
    DECIMAL = 18

    converter_map: Dict[int, Callable[[bytes], Any]] = {
        SINT: varsint_from_protobuf,
        UINT: varint_from_protobuf,
        BYTES: bytes_from_protobuf,
        DATETIME: datetime_from_protobuf,
        TIME: time_from_protobuf,
        FLOAT: float_from_protobuf,
        DOUBLE: double_from_protobuf,
        BIT: varint_from_protobuf,
        SET: set_from_protobuf,
        ENUM: bytes_from_protobuf,
        DECIMAL: decimal_from_protobuf,
    }


class Flags:
    def __init__(self, value: int) -> None:
        self._allowed_flags: Dict[str, int] = {}
        self._flag_names: Dict[int, str] = {}
        for key, val in self.__class__.__dict__.items():
            if key.startswith("__"):
                continue
            if isinstance(val, int):
                self._allowed_flags[key] = val
                self._flag_names[val] = key
        self._value: int = value

    def __str__(self) -> str:
        mask = 1
        flag_names = []
        value = self._value

        for _ in range(0, 63):
            mask <<= 1
            flag = value & mask
            if flag:
                # We matched something, find the name for it
                try:
                    flag_names.append(self._flag_names[flag])
                except KeyError:
                    sys.stderr.write(f"{self._flag_names}")
                    sys.stderr.write(f"{self.__class__.__dict__}")

        return ",".join(flag_names)

    @property
    def value(self) -> int:
        return self._value

    @value.setter
    def value(self, val: int) -> None:
        self._value = val


class ColumnFlags(Flags):
    NOT_NULL = 0x0010
    PRIMARY_KEY = 0x0020
    UNIQUE_KEY = 0x0040
    MULTIPLE_KEY = 0x0080
    AUTO_INCREMENT = 0x0100


class DatetimeColumnFlags(ColumnFlags):
    TIMESTAMP = 0x0001


class UIntColumnFlags(ColumnFlags):
    ZEROFILL = 0x0001


class DoubleColumnFlags(ColumnFlags):
    UNSIGNED = 0x0001


class FloatColumnFlags(ColumnFlags):
    UNSIGNED = 0x0001


class BytesColumnFlags(ColumnFlags):
    RIGHT_PAD = 0x0001


class BytesContentType(ColumnFlags):
    GEOMETRY = 0x0001
    JSON = 0x0002
    XML = 0x0003


# pylint: enable=missing-class-docstring,missing-function-docstring


class Column:
    """Represents meta data for a table column.

    Args:
        col_type (int): The column type.
        catalog (str): The catalog.
        schema (str): The schema name.
        table (str): The table name.
        original_table (str): The original table name.
        name (str): The column name.
        original_name (str): The original table name.
        length (int): The column length,
        collation (str): The collation name.
        fractional_digits (int): The fractional digits.
        flags (int): The flags.
        content_type (int): The content type.

    .. versionchanged:: 8.0.12
    """

    def __init__(
        self,
        col_type: int,
        catalog: Optional[str] = None,
        schema: Optional[str] = None,
        table: Optional[str] = None,
        original_table: Optional[str] = None,
        name: Optional[str] = None,
        original_name: Optional[str] = None,
        length: Optional[int] = None,
        collation: Optional[int] = None,
        fractional_digits: Optional[int] = None,
        flags: Optional[int] = None,
        content_type: Optional[int] = None,
    ) -> None:
        self._schema: str = decode_from_bytes(schema)
        self._name: str = decode_from_bytes(name)
        self._original_name: str = decode_from_bytes(original_name)
        self._table: str = decode_from_bytes(table)
        self._original_table: str = decode_from_bytes(original_table)
        self._proto_type: int = col_type
        self._col_type: Optional[int] = None
        self._catalog: Optional[str] = catalog
        self._length: Optional[int] = length
        self._collation: Optional[int] = collation
        self._fractional_digits: Optional[int] = fractional_digits
        self._flags: Optional[int] = flags
        self._content_type: Optional[int] = content_type
        self._number_signed: bool = False
        self._is_padded: Union[bool, int] = False
        self._is_binary: bool = False
        self._is_bytes: bool = False
        self._collation_name: Optional[str] = None
        self._character_set_name: Optional[str] = None
        self._zero_fill: Optional[int] = None

        if self._collation > 0:
            if self._collation >= len(MYSQL_CHARACTER_SETS):
                raise ValueError(f"No mapping found for collation {self._collation}")
            info = MYSQL_CHARACTER_SETS[self._collation]
            self._character_set_name = info[0]
            self._collation_name = info[1]
            self._is_binary = (
                "binary" in self._collation_name or "_bin" in self._collation_name
            )
        self._map_type()
        self._is_bytes = self._col_type in (
            ColumnType.GEOMETRY,
            ColumnType.JSON,
            ColumnType.XML,
            ColumnType.BYTES,
            ColumnType.STRING,
        )

    def __str__(self) -> str:
        return str(
            {
                "col_type": self._col_type,
                "schema": self._schema,
                "table": self._table,
                "flags": str(self._flags),
            }
        )

    def _map_bytes(self) -> None:
        """Map bytes."""
        if self._content_type == BytesContentType.GEOMETRY:
            self._col_type = ColumnType.GEOMETRY
        elif self._content_type == BytesContentType.JSON:
            self._col_type = ColumnType.JSON
        elif self._content_type == BytesContentType.XML:
            self._col_type = ColumnType.XML
        elif self._is_binary:
            self._col_type = ColumnType.BYTES
        else:
            self._col_type = ColumnType.STRING
        self._is_padded = self._flags & 1

    def _map_datetime(self) -> None:
        """Map datetime."""
        if self._length == 10:
            self._col_type = ColumnType.DATE
        elif self._flags & DatetimeColumnFlags.TIMESTAMP > 0:
            self._col_type = ColumnType.TIMESTAMP
        elif self._length >= 19:
            self._col_type = ColumnType.DATETIME
        else:
            raise ValueError("Datetime mapping scenario unhandled")

    def _map_int_type(self) -> None:
        """Map int type."""
        if self._length <= 4:
            self._col_type = ColumnType.TINYINT
        elif self._length <= 6:
            self._col_type = ColumnType.SMALLINT
        elif self._length <= 9:
            self._col_type = ColumnType.MEDIUMINT
        elif self._length <= 11:
            self._col_type = ColumnType.INT
        else:
            self._col_type = ColumnType.BIGINT
        self._number_signed = True

    def _map_uint_type(self) -> None:
        """Map uint type."""
        if self._length <= 3:
            self._col_type = ColumnType.TINYINT
        elif self._length <= 5:
            self._col_type = ColumnType.SMALLINT
        elif self._length <= 8:
            self._col_type = ColumnType.MEDIUMINT
        elif self._length <= 10:
            self._col_type = ColumnType.INT
        else:
            self._col_type = ColumnType.BIGINT
        self._zero_fill = self._flags & 1

    def _map_type(self) -> None:
        """Map type."""
        if self._proto_type == ColumnProtoType.SINT:
            self._map_int_type()
        elif self._proto_type == ColumnProtoType.UINT:
            self._map_uint_type()
        elif self._proto_type == ColumnProtoType.FLOAT:
            self._col_type = ColumnType.FLOAT
            self._is_number_signed = (self._flags & FloatColumnFlags.UNSIGNED) == 0
        elif self._proto_type == ColumnProtoType.DECIMAL:
            self._col_type = ColumnType.DECIMAL
            self._is_number_signed = (self._flags & FloatColumnFlags.UNSIGNED) == 0
        elif self._proto_type == ColumnProtoType.DOUBLE:
            self._col_type = ColumnType.DOUBLE
            self._is_number_signed = (self._flags & FloatColumnFlags.UNSIGNED) == 0
        elif self._proto_type == ColumnProtoType.BYTES:
            self._map_bytes()
        elif self._proto_type == ColumnProtoType.TIME:
            self._col_type = ColumnType.TIME
        elif self._proto_type == ColumnProtoType.DATETIME:
            self._map_datetime()
        elif self._proto_type == ColumnProtoType.SET:
            self._col_type = ColumnType.SET
        elif self._proto_type == ColumnProtoType.ENUM:
            self._col_type = ColumnType.ENUM
        elif self._proto_type == ColumnProtoType.BIT:
            self._col_type = ColumnType.BIT
        else:
            raise ValueError(f"Unknown column type {self._proto_type}")

    @property
    def schema_name(self) -> str:
        """str: The schema name.

        .. versionadded:: 8.0.12
        """
        return self._schema

    @property
    def table_name(self) -> str:
        """str: The table name.

        .. versionadded:: 8.0.12
        """
        return self._original_table or self._table

    @property
    def table_label(self) -> str:
        """str: The table label.

        .. versionadded:: 8.0.12
        """
        return self._table or self._original_table

    @property
    def column_name(self) -> str:
        """str: The column name.

        .. versionadded:: 8.0.12
        """
        return self._original_name or self._name

    @property
    def column_label(self) -> str:
        """str: The column label.

        .. versionadded:: 8.0.12
        """
        return self._name or self._original_name

    @property
    def type(self) -> int:
        """int: The column type.

        .. versionadded:: 8.0.12
        """
        return self._col_type

    @property
    def length(self) -> int:
        """int. The column length.

        .. versionadded:: 8.0.12
        """
        return self._length

    @property
    def fractional_digits(self) -> int:
        """int: The column fractional digits.

        .. versionadded:: 8.0.12
        """
        return self._fractional_digits

    @property
    def collation_name(self) -> str:
        """str: The collation name.

        .. versionadded:: 8.0.12
        """
        return self._collation_name

    @property
    def character_set_name(self) -> str:
        """str: The character set name.

        .. versionadded:: 8.0.12
        """
        return self._character_set_name

    def get_schema_name(self) -> str:
        """Returns the schema name.

        Returns:
            str: The schema name.
        """
        return self._schema

    def get_table_name(self) -> str:
        """Returns the table name.

        Returns:
            str: The table name.
        """
        return self._original_table or self._table

    def get_table_label(self) -> str:
        """Returns the table label.

        Returns:
            str: The table label.
        """
        return self._table or self._original_table

    def get_column_name(self) -> str:
        """Returns the column name.

        Returns:
            str: The column name.
        """
        return self._original_name or self._name

    def get_column_label(self) -> str:
        """Returns the column label.

        Returns:
            str: The column label.
        """
        return self._name or self._original_name

    def get_proto_type(self) -> int:
        """Returns the column proto type.

        Returns:
            int: The column proto type.
        """
        return self._proto_type

    def get_type(self) -> int:
        """Returns the column type.

        Returns:
            int: The column type.
        """
        return self._col_type

    def get_length(self) -> int:
        """Returns the column length.

        Returns:
            int: The column length.
        """
        return self._length

    def get_fractional_digits(self) -> int:
        """Returns the column fractional digits.

        Returns:
            int: The column fractional digits.
        """
        return self._fractional_digits

    def get_collation_name(self) -> str:
        """Returns the collation name.

        Returns:
            str: The collation name.
        """
        return self._collation_name

    def get_character_set_name(self) -> str:
        """Returns the character set name.

        Returns:
            str: The character set name.
        """
        return self._character_set_name

    def is_number_signed(self) -> bool:
        """Returns `True` if is a number signed.

        Returns:
            bool: Returns `True` if is a number signed.
        """
        return self._number_signed

    def is_padded(self) -> Union[bool, int]:
        """Returns `True` if is padded.

        Returns:
            bool: Returns `True` if is padded.
        """
        return self._is_padded

    def is_bytes(self) -> bool:
        """Returns `True` if is bytes.

        Returns:
            bool: Returns `True` if is bytes.
        """
        return self._is_bytes


class Row:
    """Represents a row element returned from a SELECT query.

    Args:
        resultset (mysqlx.SqlResult or mysqlx.RowResult): The result set.
        fields (`list`): The list of fields.
    """

    def __init__(
        self, resultset: Union[BufferingResult, RowResult], fields: Sequence[FieldTypes]
    ) -> None:
        self._fields: Sequence[FieldTypes] = fields
        self._resultset: Union[BufferingResult, RowResult] = resultset

    def __repr__(self) -> str:
        return repr(self._fields)

    def __getitem__(self, index: Union[int, str]) -> Any:
        """Returns the value of a column by name or index.

        .. versionchanged:: 8.0.12
        """
        int_index = self._resultset.index_of(index) if isinstance(index, str) else index
        if int_index == -1 and isinstance(index, str):
            raise ValueError(f"Column name '{index}' not found")
        if int_index >= len(self._fields) or int_index < 0:
            raise IndexError("Index out of range")
        return self._fields[int_index]

    @deprecated("8.0.12")
    def get_string(self, str_index: str) -> str:
        """Returns the value using the column name.

        Args:
            str_index (str): The column name.

        .. deprecated:: 8.0.12
        """
        int_index = self._resultset.index_of(str_index)
        if int_index >= len(self._fields):
            raise IndexError("Argument out of range")
        if int_index == -1:
            raise ValueError(f"Column name '{str_index}' not found")
        return str(self._fields[int_index])


class BaseResult:
    """Provides base functionality for result objects.

    Args:
        connection (mysqlx.connection.Connection): The Connection object.
    """

    def __init__(self, connection: ConnectionType) -> None:
        self._connection: ConnectionType = connection
        self._closed: bool = False
        self._rows_affected: int = 0
        self._generated_id: int = -1
        self._generated_ids: List[int] = []
        self._warnings: List[Dict[str, Union[int, str]]] = []

        if connection is None:
            self._protocol = None
        else:
            self._protocol = connection.protocol
            connection.fetch_active_result()

    def get_affected_items_count(self) -> int:
        """Returns the number of affected items for the last operation.

        Returns:
            int: The number of affected items.
        """
        return self._rows_affected

    def get_warnings(self) -> List[Dict[str, Union[int, str]]]:
        """Returns the warnings.

        Returns:
            `list`: The list of warnings.
        """
        return self._warnings

    def get_warnings_count(self) -> int:
        """Returns the number of warnings.

        Returns:
            int: The number of warnings.
        """
        return len(self._warnings)

    def set_closed(self, flag: bool) -> None:
        """Sets if resultset fetch is done."""
        self._closed = flag

    def append_warning(self, level: int, code: int, msg: str) -> None:
        """Append a warning.

        Args:
            level (int): The warning level.
            code (int): The warning code.
            msg (str): The warning message.
        """
        self._warnings.append({"level": level, "code": code, "msg": msg})

    def set_generated_ids(self, generated_ids: List[int]) -> None:
        """Sets the generated ids."""
        self._generated_ids = generated_ids

    def set_generated_insert_id(self, generated_id: int) -> None:
        """Sets the generated insert id."""
        self._generated_id = generated_id

    def set_rows_affected(self, total: int) -> None:
        """Sets the number of rows affected."""
        self._rows_affected = total


class Result(BaseResult):
    """Allows retrieving information about non query operations performed on
    the database.

    Args:
        connection (mysqlx.connection.Connection): The Connection object.
                                                   ids (`list`): A list of IDs.
    """

    def __init__(
        self,
        connection: Optional[ConnectionType] = None,
        ids: Optional[List[int]] = None,
    ) -> None:
        super().__init__(connection)
        self._ids: Optional[List[int]] = ids

        if connection is not None:
            self._connection.close_result(self)

    def get_autoincrement_value(self) -> int:
        """Returns the last insert id auto generated.

        Returns:
            int: The last insert id.
        """
        return self._generated_id

    @deprecated("8.0.12")
    def get_document_id(self) -> Optional[int]:
        """Returns ID of the last document inserted into a collection.

        .. deprecated:: 8.0.12
        """
        if self._ids is None or len(self._ids) == 0:
            return None
        return self._ids[0]

    @deprecated("8.0.12")
    def get_generated_insert_id(self) -> int:
        """Returns the generated insert id.

        .. deprecated:: 8.0.12
        """
        return self._generated_id

    def get_generated_ids(self) -> List[int]:
        """Returns the generated ids."""
        return self._generated_ids


class BufferingResult(BaseResult):
    """Provides base functionality for buffering result objects.

    Args:
        connection (mysqlx.connection.Connection): The Connection object.
                                                   ids (`list`): A list of IDs.
    """

    def __init__(self, connection: ConnectionType) -> None:
        super().__init__(connection)
        self._columns: List[Column] = []
        self._has_data: bool = False
        self._has_more_results: bool = False
        self._items: List[Union[Row, DbDoc]] = []
        self._page_size: int = 0
        self._position: int = -1
        self._init_result()

    def __getitem__(self, index: int) -> Union[Row, DbDoc]:
        return self._items[index]

    @property
    def count(self) -> int:
        """int: The total of items."""
        return len(self._items)

    def _init_result(self) -> None:
        """Initialize the result."""
        self._columns = self._connection.get_column_metadata(self)
        self._has_more_data = len(self._columns) > 0
        self._items = []
        self._page_size = 20
        self._position = -1
        self._connection.set_active_result(self if self._has_more_data else None)

    def _read_item(self, dumping: bool) -> Optional[Union[Row, DbDoc]]:
        """Read item.

        Args:
            dumping (bool): `True` for dumping.

        Returns:
            :class:`mysqlx.Row`: A `Row` object.
        """
        row = self._connection.read_row(self)
        if row is None:
            return None
        item = [None] * len(row["field"])
        if not dumping:
            for key in range(len(row["field"])):
                column = self._columns[key]
                item[key] = from_protobuf(column, row["field"][key])
        return Row(self, item)

    def _page_in_items(self) -> Union[bool, int]:
        """Reads the page items.

        Returns:
            int: Total items read.
        """
        if self._closed:
            return False

        count = 0
        for _ in range(self._page_size):
            item = self._read_item(False)
            if item is None:
                break
            self._items.append(item)
            count += 1
        return count

    def index_of(self, col_name: str) -> int:
        """Returns the index of the column.

        Returns:
            int: The index of the column.
        """
        index = 0
        for col in self._columns:
            if col.get_column_label() == col_name:
                return index
            index += 1
        return -1

    def fetch_one(self) -> Optional[Union[Row, DbDoc]]:
        """Fetch one item.

        Returns:
            :class:`mysqlx.Row` or :class:`mysqlx.DbDoc`: one result item.
        """
        if self._closed:
            return None

        return self._read_item(False)

    def fetch_all(self) -> List[Union[Row, DbDoc]]:
        """Fetch all items.

        Returns:
            `list`: The list of items of :class:`mysqlx.DbDoc` or
                    :class:`mysqlx.Row`.
        """
        while True:
            if not self._page_in_items():
                break
        return self._items

    def set_has_data(self, flag: bool) -> None:
        """Sets if result has data.

        Args:
            flag (bool): `True` if result has data.
        """
        self._has_data = flag

    def set_has_more_results(self, flag: bool) -> None:
        """Sets if has more results.

        Args:
            flag (bool): `True` if has more results.
        """
        self._has_more_results = flag


class RowResult(BufferingResult):
    """Allows traversing the Row objects returned by a Table.select operation.

    Args:
        connection (mysqlx.connection.Connection): The Connection object.
    """

    @property
    def columns(self) -> List[Column]:
        """`list`: The list of columns."""
        return self._columns

    def get_columns(self) -> List[Column]:
        """Returns the list of columns.

        Returns:
            `list`: The list of columns.

        .. versionadded:: 8.0.12
        """
        return self._columns


class SqlResult(RowResult):
    """Represents a result from a SQL statement.

    Args:
        connection (mysqlx.connection.Connection): The Connection object.
    """

    def get_autoincrement_value(self) -> int:
        """Returns the identifier for the last record inserted.

        Returns:
            str: The identifier of the last record inserted.
        """
        return self._generated_id

    def next_result(self) -> bool:
        """Process the next result.

        Returns:
            bool: Returns `True` if the fetch is done.
        """
        if self._closed:
            return False
        self._has_more_results = False
        self._init_result()
        return True

    def has_data(self) -> bool:
        """Returns True if result has data.

        Returns:
            bool: Returns `True` if result has data.

        .. versionadded:: 8.0.12
        """
        return self._has_data


class DocResult(BufferingResult):
    """Allows traversing the DbDoc objects returned by a Collection.find
    operation.

    Args:
        connection (mysqlx.connection.Connection): The Connection object.
    """

    def _read_item(self, dumping: bool) -> DbDoc:
        """Read item.

        Args:
            dumping (bool): `True` for dumping.

        Returns:
            :class:`mysqlx.DbDoc`: A `DbDoc` object.
        """
        row = super()._read_item(dumping)
        if row is None:
            return None
        return DbDoc(decode_from_bytes(row[0]))  # type: ignore[index]

virt/lib/python3.9/site-packages/mysqlx/result.py
PrinceDisant/LostInNL
# Copyright (c) 2016, 2022, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

# mypy: disable-error-code="return-value"

"""Implementation of Statements."""

from __future__ import annotations

import copy
import json
import warnings

from typing import Any, Dict, Iterable, List, Optional, Tuple, Union

from .constants import LockContention
from .dbdoc import DbDoc
from .errors import NotSupportedError, ProgrammingError
from .expr import ExprParser
from .helpers import deprecated
from .protobuf import mysqlxpb_enum
from .result import DocResult, Result, RowResult, SqlResult
from .types import (
    ConnectionType,
    DatabaseTargetType,
    MessageType,
    ProtobufMessageCextType,
    ProtobufMessageType,
    SchemaType,
)

ERR_INVALID_INDEX_NAME = 'The given index name "{}" is not valid'


class Expr:
    """Expression wrapper."""

    def __init__(self, expr: Any) -> None:
        self.expr: Any = expr


def flexible_params(*values: Any) -> Union[List, Tuple]:
    """Parse flexible parameters."""
    if len(values) == 1 and isinstance(values[0], (list, tuple)):
        return values[0]
    return values


def is_quoted_identifier(identifier: str, sql_mode: str = "") -> bool:
    """Check if the given identifier is quoted.

    Args:
        identifier (string): Identifier to check.
        sql_mode (Optional[string]): SQL mode.

    Returns:
        `True` if the identifier has backtick quotes, and False otherwise.
    """
    if "ANSI_QUOTES" in sql_mode:
        return (identifier[0] == "`" and identifier[-1] == "`") or (
            identifier[0] == '"' and identifier[-1] == '"'
        )
    return identifier[0] == "`" and identifier[-1] == "`"


def quote_identifier(identifier: str, sql_mode: str = "") -> str:
    """Quote the given identifier with backticks, converting backticks (`) in
    the identifier name with the correct escape sequence (``).

    Args:
        identifier (string): Identifier to quote.
        sql_mode (Optional[string]): SQL mode.

    Returns:
        A string with the identifier quoted with backticks.
    """
    if len(identifier) == 0:
        return "``"
    if "ANSI_QUOTES" in sql_mode:
        quoted = identifier.replace('"', '""')
        return f'"{quoted}"'
    quoted = identifier.replace("`", "``")
    return f"`{quoted}`"


def quote_multipart_identifier(identifiers: Iterable[str], sql_mode: str = "") -> str:
    """Quote the given multi-part identifier with backticks.

    Args:
        identifiers (iterable): List of identifiers to quote.
        sql_mode (Optional[string]): SQL mode.

    Returns:
        A string with the multi-part identifier quoted with backticks.
    """
    return ".".join(
        [quote_identifier(identifier, sql_mode) for identifier in identifiers]
    )


def parse_table_name(
    default_schema: str, table_name: str, sql_mode: str = ""
) -> Tuple[str, str]:
    """Parse table name.

    Args:
        default_schema (str): The default schema.
        table_name (str): The table name.
        sql_mode(Optional[str]): The SQL mode.

    Returns:
        str: The parsed table name.
    """
    quote = '"' if "ANSI_QUOTES" in sql_mode else "`"
    delimiter = f".{quote}" if quote in table_name else "."
    temp = table_name.split(delimiter, 1)
    return (
        default_schema if len(temp) == 1 else temp[0].strip(quote),
        temp[-1].strip(quote),
    )


class Statement:
    """Provides base functionality for statement objects.

    Args:
        target (object): The target database object, it can be
                         :class:`mysqlx.Collection` or :class:`mysqlx.Table`.
        doc_based (bool): `True` if it is document based.
    """

    def __init__(self, target: DatabaseTargetType, doc_based: bool = True) -> None:
        self._target: DatabaseTargetType = target
        self._doc_based: bool = doc_based
        self._connection: Optional[ConnectionType] = (
            target.get_connection() if target else None
        )
        self._stmt_id: Optional[int] = None
        self._exec_counter: int = 0
        self._changed: bool = True
        self._prepared: bool = False
        self._deallocate_prepare_execute: bool = False

    @property
    def target(self) -> DatabaseTargetType:
        """object: The database object target."""
        return self._target

    @property
    def schema(self) -> SchemaType:
        """:class:`mysqlx.Schema`: The Schema object."""
        return self._target.schema

    @property
    def stmt_id(self) -> int:
        """Returns this statement ID.

        Returns:
            int: The statement ID.
        """
        return self._stmt_id

    @stmt_id.setter
    def stmt_id(self, value: int) -> None:
        self._stmt_id = value

    @property
    def exec_counter(self) -> int:
        """int: The number of times this statement was executed."""
        return self._exec_counter

    @property
    def changed(self) -> bool:
        """bool: `True` if this statement has changes."""
        return self._changed

    @changed.setter
    def changed(self, value: bool) -> None:
        self._changed = value

    @property
    def prepared(self) -> bool:
        """bool: `True` if this statement has been prepared."""
        return self._prepared

    @prepared.setter
    def prepared(self, value: bool) -> None:
        self._prepared = value

    @property
    def repeated(self) -> bool:
        """bool: `True` if this statement was executed more than once."""
        return self._exec_counter > 1

    @property
    def deallocate_prepare_execute(self) -> bool:
        """bool: `True` to deallocate + prepare + execute statement."""
        return self._deallocate_prepare_execute

    @deallocate_prepare_execute.setter
    def deallocate_prepare_execute(self, value: bool) -> None:
        self._deallocate_prepare_execute = value

    def is_doc_based(self) -> bool:
        """Check if it is document based.

        Returns:
            bool: `True` if it is document based.
        """
        return self._doc_based

    def increment_exec_counter(self) -> None:
        """Increments the number of times this statement has been executed."""
        self._exec_counter += 1

    def reset_exec_counter(self) -> None:
        """Resets the number of times this statement has been executed."""
        self._exec_counter = 0

    def execute(self) -> Any:
        """Execute the statement.

        Raises:
           NotImplementedError: This method must be implemented.
        """
        raise NotImplementedError


class FilterableStatement(Statement):
    """A statement to be used with filterable statements.

    Args:
        target (object): The target database object, it can be
                         :class:`mysqlx.Collection` or :class:`mysqlx.Table`.
        doc_based (Optional[bool]): `True` if it is document based
                                    (default: `True`).
        condition (Optional[str]): Sets the search condition to filter
                                   documents or records.
    """

    def __init__(
        self,
        target: DatabaseTargetType,
        doc_based: bool = True,
        condition: Optional[str] = None,
    ) -> None:
        super().__init__(target=target, doc_based=doc_based)
        self._binding_map: Dict[str, Any] = {}
        self._bindings: Union[Dict[str, Any], List] = {}
        self._having: Optional[MessageType] = None
        self._grouping_str: str = ""
        self._grouping: Optional[
            List[Union[ProtobufMessageType, ProtobufMessageCextType]]
        ] = None
        self._limit_offset: int = 0
        self._limit_row_count: int = None
        self._projection_str: str = ""
        self._projection_expr: Optional[
            List[Union[ProtobufMessageType, ProtobufMessageCextType]]
        ] = None
        self._sort_str: str = ""
        self._sort_expr: Optional[
            List[Union[ProtobufMessageType, ProtobufMessageCextType]]
        ] = None
        self._where_str: str = ""
        self._where_expr: MessageType = None
        self.has_bindings: bool = False
        self.has_limit: bool = False
        self.has_group_by: bool = False
        self.has_having: bool = False
        self.has_projection: bool = False
        self.has_sort: bool = False
        self.has_where: bool = False
        if condition:
            self._set_where(condition)

    def _bind_single(self, obj: Union[DbDoc, Dict[str, Any], str]) -> None:
        """Bind single object.

        Args:
            obj (:class:`mysqlx.DbDoc` or str): DbDoc or JSON string object.

        Raises:
            :class:`mysqlx.ProgrammingError`: If invalid JSON string to bind.
            ValueError: If JSON loaded is not a dictionary.
        """
        if isinstance(obj, dict):
            self.bind(DbDoc(obj).as_str())
        elif isinstance(obj, DbDoc):
            self.bind(obj.as_str())
        elif isinstance(obj, str):
            try:
                res = json.loads(obj)
                if not isinstance(res, dict):
                    raise ValueError
            except ValueError as err:
                raise ProgrammingError("Invalid JSON string to bind") from err
            for key in res.keys():
                self.bind(key, res[key])
        else:
            raise ProgrammingError("Invalid JSON string or object to bind")

    def _sort(self, *clauses: str) -> FilterableStatement:
        """Sets the sorting criteria.

        Args:
            *clauses: The expression strings defining the sort criteria.

        Returns:
            mysqlx.FilterableStatement: FilterableStatement object.
        """
        self.has_sort = True
        self._sort_str = ",".join(flexible_params(*clauses))
        self._sort_expr = ExprParser(
            self._sort_str, not self._doc_based
        ).parse_order_spec()
        self._changed = True
        return self

    def _set_where(self, condition: str) -> FilterableStatement:
        """Sets the search condition to filter.

        Args:
            condition (str): Sets the search condition to filter documents or
                             records.

        Returns:
            mysqlx.FilterableStatement: FilterableStatement object.
        """
        self.has_where = True
        self._where_str = condition
        try:
            expr = ExprParser(condition, not self._doc_based)
            self._where_expr = expr.expr()
        except ValueError as err:
            raise ProgrammingError("Invalid condition") from err
        self._binding_map = expr.placeholder_name_to_position
        self._changed = True
        return self

    def _set_group_by(self, *fields: str) -> None:
        """Set group by.

        Args:
            *fields: List of fields.
        """
        fields = flexible_params(*fields)
        self.has_group_by = True
        self._grouping_str = ",".join(fields)
        self._grouping = ExprParser(
            self._grouping_str, not self._doc_based
        ).parse_expr_list()
        self._changed = True

    def _set_having(self, condition: str) -> None:
        """Set having.

        Args:
            condition (str): The condition.
        """
        self.has_having = True
        self._having = ExprParser(condition, not self._doc_based).expr()
        self._changed = True

    def _set_projection(self, *fields: str) -> FilterableStatement:
        """Set the projection.

        Args:
            *fields: List of fields.

        Returns:
            :class:`mysqlx.FilterableStatement`: Returns self.
        """
        fields = flexible_params(*fields)
        self.has_projection = True
        self._projection_str = ",".join(fields)
        self._projection_expr = ExprParser(
            self._projection_str, not self._doc_based
        ).parse_table_select_projection()
        self._changed = True
        return self

    def get_binding_map(self) -> Dict[str, Any]:
        """Returns the binding map dictionary.

        Returns:
            dict: The binding map dictionary.
        """
        return self._binding_map

    def get_bindings(self) -> Union[Dict[str, Any], List]:
        """Returns the bindings list.

        Returns:
            `list`: The bindings list.
        """
        return self._bindings

    def get_grouping(self) -> List[Union[ProtobufMessageType, ProtobufMessageCextType]]:
        """Returns the grouping expression list.

        Returns:
            `list`: The grouping expression list.
        """
        return self._grouping

    def get_having(self) -> MessageType:
        """Returns the having expression.

        Returns:
            object: The having expression.
        """
        return self._having

    def get_limit_row_count(self) -> int:
        """Returns the limit row count.

        Returns:
            int: The limit row count.
        """
        return self._limit_row_count

    def get_limit_offset(self) -> int:
        """Returns the limit offset.

        Returns:
            int: The limit offset.
        """
        return self._limit_offset

    def get_where_expr(self) -> MessageType:
        """Returns the where expression.

        Returns:
            object: The where expression.
        """
        return self._where_expr

    def get_projection_expr(
        self,
    ) -> List[Union[ProtobufMessageType, ProtobufMessageCextType]]:
        """Returns the projection expression.

        Returns:
            object: The projection expression.
        """
        return self._projection_expr

    def get_sort_expr(
        self,
    ) -> List[Union[ProtobufMessageType, ProtobufMessageCextType]]:
        """Returns the sort expression.

        Returns:
            object: The sort expression.
        """
        return self._sort_expr

    @deprecated("8.0.12")
    def where(self, condition: str) -> FilterableStatement:
        """Sets the search condition to filter.

        Args:
            condition (str): Sets the search condition to filter documents or
                             records.

        Returns:
            mysqlx.FilterableStatement: FilterableStatement object.

        .. deprecated:: 8.0.12
        """
        return self._set_where(condition)

    @deprecated("8.0.12")
    def sort(self, *clauses: str) -> FilterableStatement:
        """Sets the sorting criteria.

        Args:
            *clauses: The expression strings defining the sort criteria.

        Returns:
            mysqlx.FilterableStatement: FilterableStatement object.

        .. deprecated:: 8.0.12
        """
        return self._sort(*clauses)

    def limit(
        self, row_count: int, offset: Optional[int] = None
    ) -> FilterableStatement:
        """Sets the maximum number of items to be returned.

        Args:
            row_count (int): The maximum number of items.

        Returns:
            mysqlx.FilterableStatement: FilterableStatement object.

        Raises:
            ValueError: If ``row_count`` is not a positive integer.

        .. versionchanged:: 8.0.12
           The usage of ``offset`` was deprecated.
        """
        if not isinstance(row_count, int) or row_count < 0:
            raise ValueError("The 'row_count' value must be a positive integer")
        if not self.has_limit:
            self._changed = bool(self._exec_counter == 0)
            self._deallocate_prepare_execute = bool(not self._exec_counter == 0)

        self._limit_row_count = row_count
        self.has_limit = True
        if offset:
            self.offset(offset)
            warnings.warn(
                "'limit(row_count, offset)' is deprecated, please "
                "use 'offset(offset)' to set the number of items to "
                "skip",
                category=DeprecationWarning,
            )
        return self

    def offset(self, offset: int) -> FilterableStatement:
        """Sets the number of items to skip.

        Args:
            offset (int): The number of items to skip.

        Returns:
            mysqlx.FilterableStatement: FilterableStatement object.

        Raises:
            ValueError: If ``offset`` is not a positive integer.

        .. versionadded:: 8.0.12
        """
        if not isinstance(offset, int) or offset < 0:
            raise ValueError("The 'offset' value must be a positive integer")
        self._limit_offset = offset
        return self

    def bind(self, *args: Any) -> FilterableStatement:
        """Binds value(s) to a specific placeholder(s).

        Args:
            *args: The name of the placeholder and the value to bind.
                   A :class:`mysqlx.DbDoc` object or a JSON string
                   representation can be used.

        Returns:
            mysqlx.FilterableStatement: FilterableStatement object.

        Raises:
            ProgrammingError: If the number of arguments is invalid.
        """
        self.has_bindings = True
        count = len(args)
        if count == 1:
            self._bind_single(args[0])
        elif count == 2:
            self._bindings[args[0]] = args[1]
        else:
            raise ProgrammingError("Invalid number of arguments to bind")
        return self

    def execute(self) -> Any:
        """Execute the statement.

        Raises:
           NotImplementedError: This method must be implemented.
        """
        raise NotImplementedError


class SqlStatement(Statement):
    """A statement for SQL execution.

    Args:
        connection (mysqlx.connection.Connection): Connection object.
        sql (string): The sql statement to be executed.
    """

    def __init__(self, connection: ConnectionType, sql: str) -> None:
        super().__init__(target=None, doc_based=False)
        self._connection: ConnectionType = connection
        self._sql: str = sql
        self._binding_map: Optional[Dict[str, Any]] = None
        self._bindings: Union[List, Tuple] = []
        self.has_bindings: bool = False
        self.has_limit: bool = False

    @property
    def sql(self) -> str:
        """string: The SQL text statement."""
        return self._sql

    def get_binding_map(self) -> Dict[str, Any]:
        """Returns the binding map dictionary.

        Returns:
            dict: The binding map dictionary.
        """
        return self._binding_map

    def get_bindings(self) -> Union[Tuple, List]:
        """Returns the bindings list.

        Returns:
            `list`: The bindings list.
        """
        return self._bindings

    def bind(self, *args: Any) -> SqlStatement:
        """Binds value(s) to a specific placeholder(s).

        Args:
            *args: The value(s) to bind.

        Returns:
            mysqlx.SqlStatement: SqlStatement object.
        """
        if len(args) == 0:
            raise ProgrammingError("Invalid number of arguments to bind")
        self.has_bindings = True
        bindings = flexible_params(*args)
        if isinstance(bindings, (list, tuple)):
            self._bindings = bindings
        else:
            self._bindings.append(bindings)
        return self

    def execute(self) -> SqlResult:
        """Execute the statement.

        Returns:
            mysqlx.SqlResult: SqlResult object.
        """
        return self._connection.send_sql(self)


class WriteStatement(Statement):
    """Provide common write operation attributes."""

    def __init__(self, target: DatabaseTargetType, doc_based: bool) -> None:
        super().__init__(target, doc_based)
        self._values: List[
            Union[
                int,
                str,
                DbDoc,
                Dict[str, Any],
                List[Optional[Union[str, int, float, ExprParser, Dict[str, Any]]]],
            ]
        ] = []

    def get_values(
        self,
    ) -> List[
        Union[
            int,
            str,
            DbDoc,
            Dict[str, Any],
            List[Optional[Union[str, int, float, ExprParser, Dict[str, Any]]]],
        ]
    ]:
        """Returns the list of values.

        Returns:
            `list`: The list of values.
        """
        return self._values

    def execute(self) -> Any:
        """Execute the statement.

        Raises:
           NotImplementedError: This method must be implemented.
        """
        raise NotImplementedError


class AddStatement(WriteStatement):
    """A statement for document addition on a collection.

    Args:
        collection (mysqlx.Collection): The Collection object.
    """

    def __init__(self, collection: DatabaseTargetType) -> None:
        super().__init__(collection, True)
        self._upsert: bool = False
        self.ids: List = []

    def is_upsert(self) -> bool:
        """Returns `True` if it's an upsert.

        Returns:
            bool: `True` if it's an upsert.
        """
        return self._upsert

    def upsert(self, value: bool = True) -> AddStatement:
        """Sets the upset flag to the boolean of the value provided.
        Setting of this flag allows updating of the matched rows/documents
        with the provided value.

        Args:
            value (optional[bool]): Set or unset the upsert flag.
        """
        self._upsert = value
        return self

    def add(self, *values: DbDoc) -> AddStatement:
        """Adds a list of documents into a collection.

        Args:
            *values: The documents to be added into the collection.

        Returns:
            mysqlx.AddStatement: AddStatement object.
        """
        for val in flexible_params(*values):
            if isinstance(val, DbDoc):
                self._values.append(val)
            else:
                self._values.append(DbDoc(val))
        return self

    def execute(self) -> Result:
        """Execute the statement.

        Returns:
            mysqlx.Result: Result object.
        """
        if len(self._values) == 0:
            return Result()

        return self._connection.send_insert(self)


class UpdateSpec:
    """Update specification class implementation.

    Args:
        update_type (int): The update type.
        source (str): The source.
        value (Optional[str]): The value.

    Raises:
        ProgrammingError: If `source` is invalid.
    """

    def __init__(self, update_type: int, source: str, value: Any = None) -> None:
        if update_type == mysqlxpb_enum("Mysqlx.Crud.UpdateOperation.UpdateType.SET"):
            self._table_set(source, value)
        else:
            self.update_type: int = update_type
            try:
                self.source: Any = ExprParser(source, False).document_field().identifier
            except ValueError as err:
                raise ProgrammingError(f"{err}") from err
            self.value: Any = value

    def _table_set(self, source: str, value: Any) -> None:
        """Table set.

        Args:
            source (str): The source.
            value (str): The value.
        """
        self.update_type = mysqlxpb_enum("Mysqlx.Crud.UpdateOperation.UpdateType.SET")
        self.source = ExprParser(source, True).parse_table_update_field()
        self.value = value


class ModifyStatement(FilterableStatement):
    """A statement for document update operations on a Collection.

    Args:
        collection (mysqlx.Collection): The Collection object.
        condition (str): Sets the search condition to identify the documents
                         to be modified.

    .. versionchanged:: 8.0.12
       The ``condition`` parameter is now mandatory.
    """

    def __init__(self, collection: DatabaseTargetType, condition: str) -> None:
        super().__init__(target=collection, condition=condition)
        self._update_ops: Dict[str, Any] = {}

    def sort(self, *clauses: str) -> ModifyStatement:
        """Sets the sorting criteria.

        Args:
            *clauses: The expression strings defining the sort criteria.

        Returns:
            mysqlx.ModifyStatement: ModifyStatement object.
        """
        return self._sort(*clauses)

    def get_update_ops(self) -> Dict[str, Any]:
        """Returns the list of update operations.

        Returns:
            `list`: The list of update operations.
        """
        return self._update_ops

    def set(self, doc_path: str, value: Any) -> ModifyStatement:
        """Sets or updates attributes on documents in a collection.

        Args:
            doc_path (string): The document path of the item to be set.
            value (string): The value to be set on the specified attribute.

        Returns:
            mysqlx.ModifyStatement: ModifyStatement object.
        """
        self._update_ops[doc_path] = UpdateSpec(
            mysqlxpb_enum("Mysqlx.Crud.UpdateOperation.UpdateType.ITEM_SET"),
            doc_path,
            value,
        )
        self._changed = True
        return self

    @deprecated("8.0.12")
    def change(self, doc_path: str, value: Any) -> ModifyStatement:
        """Add an update to the statement setting the field, if it exists at
        the document path, to the given value.

        Args:
            doc_path (string): The document path of the item to be set.
            value (object): The value to be set on the specified attribute.

        Returns:
            mysqlx.ModifyStatement: ModifyStatement object.

        .. deprecated:: 8.0.12
        """
        self._update_ops[doc_path] = UpdateSpec(
            mysqlxpb_enum("Mysqlx.Crud.UpdateOperation.UpdateType.ITEM_REPLACE"),
            doc_path,
            value,
        )
        self._changed = True
        return self

    def unset(self, *doc_paths: str) -> ModifyStatement:
        """Removes attributes from documents in a collection.

        Args:
            doc_paths (list): The list of document paths of the attributes to be
                              removed.

        Returns:
            mysqlx.ModifyStatement: ModifyStatement object.
        """
        for item in flexible_params(*doc_paths):
            self._update_ops[item] = UpdateSpec(
                mysqlxpb_enum("Mysqlx.Crud.UpdateOperation.UpdateType.ITEM_REMOVE"),
                item,
            )
        self._changed = True
        return self

    def array_insert(self, field: str, value: Any) -> ModifyStatement:
        """Insert a value into the specified array in documents of a
        collection.

        Args:
            field (string): A document path that identifies the array attribute
                            and position where the value will be inserted.
            value (object): The value to be inserted.

        Returns:
            mysqlx.ModifyStatement: ModifyStatement object.
        """
        self._update_ops[field] = UpdateSpec(
            mysqlxpb_enum("Mysqlx.Crud.UpdateOperation.UpdateType.ARRAY_INSERT"),
            field,
            value,
        )
        self._changed = True
        return self

    def array_append(self, doc_path: str, value: Any) -> ModifyStatement:
        """Inserts a value into a specific position in an array attribute in
        documents of a collection.

        Args:
            doc_path (string): A document path that identifies the array
                               attribute and position where the value will be
                               inserted.
            value (object): The value to be inserted.

        Returns:
            mysqlx.ModifyStatement: ModifyStatement object.
        """
        self._update_ops[doc_path] = UpdateSpec(
            mysqlxpb_enum("Mysqlx.Crud.UpdateOperation.UpdateType.ARRAY_APPEND"),
            doc_path,
            value,
        )
        self._changed = True
        return self

    def patch(self, doc: Union[Dict, DbDoc, ExprParser, str]) -> ModifyStatement:
        """Takes a :class:`mysqlx.DbDoc`, string JSON format or a dict with the
        changes and applies it on all matching documents.

        Args:
            doc (object): A generic document (DbDoc), string in JSON format or
                          dict, with the changes to apply to the matching
                          documents.

        Returns:
            mysqlx.ModifyStatement: ModifyStatement object.
        """
        if doc is None:
            doc = ""
        if not isinstance(doc, (ExprParser, dict, DbDoc, str)):
            raise ProgrammingError(
                "Invalid data for update operation on document collection table"
            )
        self._update_ops["patch"] = UpdateSpec(
            mysqlxpb_enum("Mysqlx.Crud.UpdateOperation.UpdateType.MERGE_PATCH"),
            "$",
            doc.expr() if isinstance(doc, ExprParser) else doc,
        )
        self._changed = True
        return self

    def execute(self) -> Result:
        """Execute the statement.

        Returns:
            mysqlx.Result: Result object.

        Raises:
            ProgrammingError: If condition was not set.
        """
        if not self.has_where:
            raise ProgrammingError("No condition was found for modify")
        return self._connection.send_update(self)


class ReadStatement(FilterableStatement):
    """Provide base functionality for Read operations

    Args:
        target (object): The target database object, it can be
                         :class:`mysqlx.Collection` or :class:`mysqlx.Table`.
        doc_based (Optional[bool]): `True` if it is document based
                                    (default: `True`).
        condition (Optional[str]): Sets the search condition to filter
                                   documents or records.
    """

    def __init__(
        self,
        target: DatabaseTargetType,
        doc_based: bool = True,
        condition: Optional[str] = None,
    ) -> None:
        super().__init__(target, doc_based, condition)
        self._lock_exclusive: bool = False
        self._lock_shared: bool = False
        self._lock_contention: LockContention = LockContention.DEFAULT

    @property
    def lock_contention(self) -> LockContention:
        """:class:`mysqlx.LockContention`: The lock contention value."""
        return self._lock_contention

    def _set_lock_contention(self, lock_contention: LockContention) -> None:
        """Set the lock contention.

        Args:
            lock_contention (:class:`mysqlx.LockContention`): Lock contention.

        Raises:
            ProgrammingError: If is an invalid lock contention value.
        """
        try:
            # Check if is a valid lock contention value
            _ = LockContention(lock_contention.value)
        except ValueError as err:
            raise ProgrammingError(
                "Invalid lock contention mode. Use 'NOWAIT' or 'SKIP_LOCKED'"
            ) from err
        self._lock_contention = lock_contention

    def is_lock_exclusive(self) -> bool:
        """Returns `True` if is `EXCLUSIVE LOCK`.

        Returns:
            bool: `True` if is `EXCLUSIVE LOCK`.
        """
        return self._lock_exclusive

    def is_lock_shared(self) -> bool:
        """Returns `True` if is `SHARED LOCK`.

        Returns:
            bool: `True` if is `SHARED LOCK`.
        """
        return self._lock_shared

    def lock_shared(
        self, lock_contention: LockContention = LockContention.DEFAULT
    ) -> ReadStatement:
        """Execute a read operation with `SHARED LOCK`. Only one lock can be
           active at a time.

        Args:
            lock_contention (:class:`mysqlx.LockContention`): Lock contention.
        """
        self._lock_exclusive = False
        self._lock_shared = True
        self._set_lock_contention(lock_contention)
        return self

    def lock_exclusive(
        self, lock_contention: LockContention = LockContention.DEFAULT
    ) -> ReadStatement:
        """Execute a read operation with `EXCLUSIVE LOCK`. Only one lock can be
           active at a time.

        Args:
            lock_contention (:class:`mysqlx.LockContention`): Lock contention.
        """
        self._lock_exclusive = True
        self._lock_shared = False
        self._set_lock_contention(lock_contention)
        return self

    def group_by(self, *fields: str) -> ReadStatement:
        """Sets a grouping criteria for the resultset.

        Args:
            *fields: The string expressions identifying the grouping criteria.

        Returns:
            mysqlx.ReadStatement: ReadStatement object.
        """
        self._set_group_by(*fields)
        return self

    def having(self, condition: str) -> ReadStatement:
        """Sets a condition for records to be considered in agregate function
        operations.

        Args:
            condition (string): A condition on the agregate functions used on
                                the grouping criteria.

        Returns:
            mysqlx.ReadStatement: ReadStatement object.
        """
        self._set_having(condition)
        return self

    def execute(self) -> Union[DocResult, RowResult]:
        """Execute the statement.

        Returns:
            mysqlx.Result: Result object.
        """
        return self._connection.send_find(self)


class FindStatement(ReadStatement):
    """A statement document selection on a Collection.

    Args:
        collection (mysqlx.Collection): The Collection object.
        condition (Optional[str]): An optional expression to identify the
                                   documents to be retrieved. If not specified
                                   all the documents will be included on the
                                   result unless a limit is set.
    """

    def __init__(
        self, collection: DatabaseTargetType, condition: Optional[str] = None
    ) -> None:
        super().__init__(collection, True, condition)

    def fields(self, *fields: str) -> FindStatement:
        """Sets a document field filter.

        Args:
            *fields: The string expressions identifying the fields to be
                     extracted.

        Returns:
            mysqlx.FindStatement: FindStatement object.
        """
        return self._set_projection(*fields)

    def sort(self, *clauses: str) -> FindStatement:
        """Sets the sorting criteria.

        Args:
            *clauses: The expression strings defining the sort criteria.

        Returns:
            mysqlx.FindStatement: FindStatement object.
        """
        return self._sort(*clauses)


class SelectStatement(ReadStatement):
    """A statement for record retrieval operations on a Table.

    Args:
        table (mysqlx.Table): The Table object.
        *fields: The fields to be retrieved.
    """

    def __init__(self, table: DatabaseTargetType, *fields: str) -> None:
        super().__init__(table, False)
        self._set_projection(*fields)

    def where(self, condition: str) -> SelectStatement:
        """Sets the search condition to filter.

        Args:
            condition (str): Sets the search condition to filter records.

        Returns:
            mysqlx.SelectStatement: SelectStatement object.
        """
        return self._set_where(condition)

    def order_by(self, *clauses: str) -> SelectStatement:
        """Sets the order by criteria.

        Args:
            *clauses: The expression strings defining the order by criteria.

        Returns:
            mysqlx.SelectStatement: SelectStatement object.
        """
        return self._sort(*clauses)

    def get_sql(self) -> str:
        """Returns the generated SQL.

        Returns:
            str: The generated SQL.
        """
        where = f" WHERE {self._where_str}" if self.has_where else ""
        group_by = f" GROUP BY {self._grouping_str}" if self.has_group_by else ""
        having = f" HAVING {self._having}" if self.has_having else ""
        order_by = f" ORDER BY {self._sort_str}" if self.has_sort else ""
        limit = (
            f" LIMIT {self._limit_row_count} OFFSET {self._limit_offset}"
            if self.has_limit
            else ""
        )
        stmt = (
            f"SELECT {self._projection_str or '*'} "
            f"FROM {self.schema.name}.{self.target.name}"
            f"{where}{group_by}{having}{order_by}{limit}"
        )
        return stmt


class InsertStatement(WriteStatement):
    """A statement for insert operations on Table.

    Args:
        table (mysqlx.Table): The Table object.
        *fields: The fields to be inserted.
    """

    def __init__(self, table: DatabaseTargetType, *fields: Any) -> None:
        super().__init__(table, False)
        self._fields: Union[List, Tuple] = flexible_params(*fields)

    def values(self, *values: Any) -> InsertStatement:
        """Set the values to be inserted.

        Args:
            *values: The values of the columns to be inserted.

        Returns:
            mysqlx.InsertStatement: InsertStatement object.
        """
        self._values.append(list(flexible_params(*values)))
        return self

    def execute(self) -> Result:
        """Execute the statement.

        Returns:
            mysqlx.Result: Result object.
        """
        return self._connection.send_insert(self)


class UpdateStatement(FilterableStatement):
    """A statement for record update operations on a Table.

    Args:
        table (mysqlx.Table): The Table object.

    .. versionchanged:: 8.0.12
       The ``fields`` parameters were removed.
    """

    def __init__(self, table: DatabaseTargetType) -> None:
        super().__init__(target=table, doc_based=False)
        self._update_ops: Dict[str, Any] = {}

    def where(self, condition: str) -> UpdateStatement:
        """Sets the search condition to filter.

        Args:
            condition (str): Sets the search condition to filter records.

        Returns:
            mysqlx.UpdateStatement: UpdateStatement object.
        """
        return self._set_where(condition)

    def order_by(self, *clauses: str) -> UpdateStatement:
        """Sets the order by criteria.

        Args:
            *clauses: The expression strings defining the order by criteria.

        Returns:
            mysqlx.UpdateStatement: UpdateStatement object.
        """
        return self._sort(*clauses)

    def get_update_ops(self) -> Dict[str, Any]:
        """Returns the list of update operations.

        Returns:
            `list`: The list of update operations.
        """
        return self._update_ops

    def set(self, field: str, value: Any) -> UpdateStatement:
        """Updates the column value on records in a table.

        Args:
            field (string): The column name to be updated.
            value (object): The value to be set on the specified column.

        Returns:
            mysqlx.UpdateStatement: UpdateStatement object.
        """
        self._update_ops[field] = UpdateSpec(
            mysqlxpb_enum("Mysqlx.Crud.UpdateOperation.UpdateType.SET"),
            field,
            value,
        )
        self._changed = True
        return self

    def execute(self) -> Result:
        """Execute the statement.

        Returns:
            mysqlx.Result: Result object

        Raises:
            ProgrammingError: If condition was not set.
        """
        if not self.has_where:
            raise ProgrammingError("No condition was found for update")
        return self._connection.send_update(self)


class RemoveStatement(FilterableStatement):
    """A statement for document removal from a collection.

    Args:
        collection (mysqlx.Collection): The Collection object.
        condition (str): Sets the search condition to identify the documents
                         to be removed.

    .. versionchanged:: 8.0.12
       The ``condition`` parameter was added.
    """

    def __init__(self, collection: DatabaseTargetType, condition: str) -> None:
        super().__init__(target=collection, condition=condition)

    def sort(self, *clauses: str) -> RemoveStatement:
        """Sets the sorting criteria.

        Args:
            *clauses: The expression strings defining the sort criteria.

        Returns:
            mysqlx.FindStatement: FindStatement object.
        """
        return self._sort(*clauses)

    def execute(self) -> Result:
        """Execute the statement.

        Returns:
            mysqlx.Result: Result object.

        Raises:
            ProgrammingError: If condition was not set.
        """
        if not self.has_where:
            raise ProgrammingError("No condition was found for remove")
        return self._connection.send_delete(self)


class DeleteStatement(FilterableStatement):
    """A statement that drops a table.

    Args:
        table (mysqlx.Table): The Table object.

    .. versionchanged:: 8.0.12
       The ``condition`` parameter was removed.
    """

    def __init__(self, table: DatabaseTargetType) -> None:
        super().__init__(target=table, doc_based=False)

    def where(self, condition: str) -> DeleteStatement:
        """Sets the search condition to filter.

        Args:
            condition (str): Sets the search condition to filter records.

        Returns:
            mysqlx.DeleteStatement: DeleteStatement object.
        """
        return self._set_where(condition)

    def order_by(self, *clauses: str) -> DeleteStatement:
        """Sets the order by criteria.

        Args:
            *clauses: The expression strings defining the order by criteria.

        Returns:
            mysqlx.DeleteStatement: DeleteStatement object.
        """
        return self._sort(*clauses)

    def execute(self) -> Result:
        """Execute the statement.

        Returns:
            mysqlx.Result: Result object.

        Raises:
            ProgrammingError: If condition was not set.
        """
        if not self.has_where:
            raise ProgrammingError("No condition was found for delete")
        return self._connection.send_delete(self)


class CreateCollectionIndexStatement(Statement):
    """A statement that creates an index on a collection.

    Args:
        collection (mysqlx.Collection): Collection.
        index_name (string): Index name.
        index_desc (dict): A dictionary containing the fields members that
                           constraints the index to be created. It must have
                           the form as shown in the following::

                               {"fields": [{"field": member_path,
                                            "type": member_type,
                                            "required": member_required,
                                            "collation": collation,
                                            "options": options,
                                            "srid": srid},
                                            # {... more members,
                                            #      repeated as many times
                                            #      as needed}
                                            ],
                                "type": type}
    """

    def __init__(
        self,
        collection: DatabaseTargetType,
        index_name: str,
        index_desc: Dict[str, Any],
    ) -> None:
        super().__init__(target=collection)
        self._index_desc: Dict[str, Any] = copy.deepcopy(index_desc)
        self._index_name: str = index_name
        self._fields_desc: List[Dict[str, Any]] = self._index_desc.pop("fields", [])

    def execute(self) -> Result:
        """Execute the statement.

        Returns:
            mysqlx.Result: Result object.
        """
        # Validate index name is a valid identifier
        if self._index_name is None:
            raise ProgrammingError(ERR_INVALID_INDEX_NAME.format(self._index_name))
        try:
            parsed_ident = ExprParser(self._index_name).expr().get_message()

            # The message is type dict when the Protobuf cext is used
            if isinstance(parsed_ident, dict):
                if parsed_ident["type"] != mysqlxpb_enum("Mysqlx.Expr.Expr.Type.IDENT"):
                    raise ProgrammingError(
                        ERR_INVALID_INDEX_NAME.format(self._index_name)
                    )
            else:
                if parsed_ident.type != mysqlxpb_enum("Mysqlx.Expr.Expr.Type.IDENT"):
                    raise ProgrammingError(
                        ERR_INVALID_INDEX_NAME.format(self._index_name)
                    )

        except (ValueError, AttributeError) as err:
            raise ProgrammingError(
                ERR_INVALID_INDEX_NAME.format(self._index_name)
            ) from err

        # Validate members that constraint the index
        if not self._fields_desc:
            raise ProgrammingError(
                "Required member 'fields' not found in the given index "
                f"description: {self._index_desc}"
            )

        if not isinstance(self._fields_desc, list):
            raise ProgrammingError("Required member 'fields' must contain a list")
        args: Dict[str, Any] = {}
        args["name"] = self._index_name
        args["collection"] = self._target.name
        args["schema"] = self._target.schema.name
        if "type" in self._index_desc:
            args["type"] = self._index_desc.pop("type")
        else:
            args["type"] = "INDEX"
        args["unique"] = self._index_desc.pop("unique", False)
        # Currently unique indexes are not supported:
        if args["unique"]:
            raise NotSupportedError("Unique indexes are not supported.")
        args["constraint"] = []

        if self._index_desc:
            raise ProgrammingError(f"Unidentified fields: {self._index_desc}")

        try:
            for field_desc in self._fields_desc:
                constraint = {}
                constraint["member"] = field_desc.pop("field")
                constraint["type"] = field_desc.pop("type")
                constraint["required"] = field_desc.pop("required", False)
                constraint["array"] = field_desc.pop("array", False)
                if not isinstance(constraint["required"], bool):
                    raise TypeError("Field member 'required' must be Boolean")
                if not isinstance(constraint["array"], bool):
                    raise TypeError("Field member 'array' must be Boolean")
                if args["type"].upper() == "SPATIAL" and not constraint["required"]:
                    raise ProgrammingError(
                        "Field member 'required' must be set to 'True' when "
                        "index type is set to 'SPATIAL'"
                    )
                if args["type"].upper() == "INDEX" and constraint["type"] == "GEOJSON":
                    raise ProgrammingError(
                        "Index 'type' must be set to 'SPATIAL' when field "
                        "type is set to 'GEOJSON'"
                    )
                if "collation" in field_desc:
                    if not constraint["type"].upper().startswith("TEXT"):
                        raise ProgrammingError(
                            "The 'collation' member can only be used when "
                            "field type is set to "
                            f"'{constraint['type'].upper()}'"
                        )
                    constraint["collation"] = field_desc.pop("collation")
                # "options" and "srid" fields in IndexField can be
                # present only if "type" is set to "GEOJSON"
                if "options" in field_desc:
                    if constraint["type"].upper() != "GEOJSON":
                        raise ProgrammingError(
                            "The 'options' member can only be used when "
                            "index type is set to 'GEOJSON'"
                        )
                    constraint["options"] = field_desc.pop("options")
                if "srid" in field_desc:
                    if constraint["type"].upper() != "GEOJSON":
                        raise ProgrammingError(
                            "The 'srid' member can only be used when index "
                            "type is set to 'GEOJSON'"
                        )
                    constraint["srid"] = field_desc.pop("srid")
                args["constraint"].append(constraint)
        except KeyError as err:
            raise ProgrammingError(
                f"Required inner member {err} not found in constraint: {field_desc}"
            ) from err

        for field_desc in self._fields_desc:
            if field_desc:
                raise ProgrammingError(f"Unidentified inner fields: {field_desc}")

        return self._connection.execute_nonquery(
            "mysqlx", "create_collection_index", True, args
        )

virt/lib/python3.9/site-packages/mysqlx/statement 2.py
PrinceDisant/LostInNL
# Copyright (c) 2016, 2022, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

# mypy: disable-error-code="return-value"

"""Implementation of Statements."""

from __future__ import annotations

import copy
import json
import warnings

from typing import Any, Dict, Iterable, List, Optional, Tuple, Union

from .constants import LockContention
from .dbdoc import DbDoc
from .errors import NotSupportedError, ProgrammingError
from .expr import ExprParser
from .helpers import deprecated
from .protobuf import mysqlxpb_enum
from .result import DocResult, Result, RowResult, SqlResult
from .types import (
    ConnectionType,
    DatabaseTargetType,
    MessageType,
    ProtobufMessageCextType,
    ProtobufMessageType,
    SchemaType,
)

ERR_INVALID_INDEX_NAME = 'The given index name "{}" is not valid'


class Expr:
    """Expression wrapper."""

    def __init__(self, expr: Any) -> None:
        self.expr: Any = expr


def flexible_params(*values: Any) -> Union[List, Tuple]:
    """Parse flexible parameters."""
    if len(values) == 1 and isinstance(values[0], (list, tuple)):
        return values[0]
    return values


def is_quoted_identifier(identifier: str, sql_mode: str = "") -> bool:
    """Check if the given identifier is quoted.

    Args:
        identifier (string): Identifier to check.
        sql_mode (Optional[string]): SQL mode.

    Returns:
        `True` if the identifier has backtick quotes, and False otherwise.
    """
    if "ANSI_QUOTES" in sql_mode:
        return (identifier[0] == "`" and identifier[-1] == "`") or (
            identifier[0] == '"' and identifier[-1] == '"'
        )
    return identifier[0] == "`" and identifier[-1] == "`"


def quote_identifier(identifier: str, sql_mode: str = "") -> str:
    """Quote the given identifier with backticks, converting backticks (`) in
    the identifier name with the correct escape sequence (``).

    Args:
        identifier (string): Identifier to quote.
        sql_mode (Optional[string]): SQL mode.

    Returns:
        A string with the identifier quoted with backticks.
    """
    if len(identifier) == 0:
        return "``"
    if "ANSI_QUOTES" in sql_mode:
        quoted = identifier.replace('"', '""')
        return f'"{quoted}"'
    quoted = identifier.replace("`", "``")
    return f"`{quoted}`"


def quote_multipart_identifier(identifiers: Iterable[str], sql_mode: str = "") -> str:
    """Quote the given multi-part identifier with backticks.

    Args:
        identifiers (iterable): List of identifiers to quote.
        sql_mode (Optional[string]): SQL mode.

    Returns:
        A string with the multi-part identifier quoted with backticks.
    """
    return ".".join(
        [quote_identifier(identifier, sql_mode) for identifier in identifiers]
    )


def parse_table_name(
    default_schema: str, table_name: str, sql_mode: str = ""
) -> Tuple[str, str]:
    """Parse table name.

    Args:
        default_schema (str): The default schema.
        table_name (str): The table name.
        sql_mode(Optional[str]): The SQL mode.

    Returns:
        str: The parsed table name.
    """
    quote = '"' if "ANSI_QUOTES" in sql_mode else "`"
    delimiter = f".{quote}" if quote in table_name else "."
    temp = table_name.split(delimiter, 1)
    return (
        default_schema if len(temp) == 1 else temp[0].strip(quote),
        temp[-1].strip(quote),
    )


class Statement:
    """Provides base functionality for statement objects.

    Args:
        target (object): The target database object, it can be
                         :class:`mysqlx.Collection` or :class:`mysqlx.Table`.
        doc_based (bool): `True` if it is document based.
    """

    def __init__(self, target: DatabaseTargetType, doc_based: bool = True) -> None:
        self._target: DatabaseTargetType = target
        self._doc_based: bool = doc_based
        self._connection: Optional[ConnectionType] = (
            target.get_connection() if target else None
        )
        self._stmt_id: Optional[int] = None
        self._exec_counter: int = 0
        self._changed: bool = True
        self._prepared: bool = False
        self._deallocate_prepare_execute: bool = False

    @property
    def target(self) -> DatabaseTargetType:
        """object: The database object target."""
        return self._target

    @property
    def schema(self) -> SchemaType:
        """:class:`mysqlx.Schema`: The Schema object."""
        return self._target.schema

    @property
    def stmt_id(self) -> int:
        """Returns this statement ID.

        Returns:
            int: The statement ID.
        """
        return self._stmt_id

    @stmt_id.setter
    def stmt_id(self, value: int) -> None:
        self._stmt_id = value

    @property
    def exec_counter(self) -> int:
        """int: The number of times this statement was executed."""
        return self._exec_counter

    @property
    def changed(self) -> bool:
        """bool: `True` if this statement has changes."""
        return self._changed

    @changed.setter
    def changed(self, value: bool) -> None:
        self._changed = value

    @property
    def prepared(self) -> bool:
        """bool: `True` if this statement has been prepared."""
        return self._prepared

    @prepared.setter
    def prepared(self, value: bool) -> None:
        self._prepared = value

    @property
    def repeated(self) -> bool:
        """bool: `True` if this statement was executed more than once."""
        return self._exec_counter > 1

    @property
    def deallocate_prepare_execute(self) -> bool:
        """bool: `True` to deallocate + prepare + execute statement."""
        return self._deallocate_prepare_execute

    @deallocate_prepare_execute.setter
    def deallocate_prepare_execute(self, value: bool) -> None:
        self._deallocate_prepare_execute = value

    def is_doc_based(self) -> bool:
        """Check if it is document based.

        Returns:
            bool: `True` if it is document based.
        """
        return self._doc_based

    def increment_exec_counter(self) -> None:
        """Increments the number of times this statement has been executed."""
        self._exec_counter += 1

    def reset_exec_counter(self) -> None:
        """Resets the number of times this statement has been executed."""
        self._exec_counter = 0

    def execute(self) -> Any:
        """Execute the statement.

        Raises:
           NotImplementedError: This method must be implemented.
        """
        raise NotImplementedError


class FilterableStatement(Statement):
    """A statement to be used with filterable statements.

    Args:
        target (object): The target database object, it can be
                         :class:`mysqlx.Collection` or :class:`mysqlx.Table`.
        doc_based (Optional[bool]): `True` if it is document based
                                    (default: `True`).
        condition (Optional[str]): Sets the search condition to filter
                                   documents or records.
    """

    def __init__(
        self,
        target: DatabaseTargetType,
        doc_based: bool = True,
        condition: Optional[str] = None,
    ) -> None:
        super().__init__(target=target, doc_based=doc_based)
        self._binding_map: Dict[str, Any] = {}
        self._bindings: Union[Dict[str, Any], List] = {}
        self._having: Optional[MessageType] = None
        self._grouping_str: str = ""
        self._grouping: Optional[
            List[Union[ProtobufMessageType, ProtobufMessageCextType]]
        ] = None
        self._limit_offset: int = 0
        self._limit_row_count: int = None
        self._projection_str: str = ""
        self._projection_expr: Optional[
            List[Union[ProtobufMessageType, ProtobufMessageCextType]]
        ] = None
        self._sort_str: str = ""
        self._sort_expr: Optional[
            List[Union[ProtobufMessageType, ProtobufMessageCextType]]
        ] = None
        self._where_str: str = ""
        self._where_expr: MessageType = None
        self.has_bindings: bool = False
        self.has_limit: bool = False
        self.has_group_by: bool = False
        self.has_having: bool = False
        self.has_projection: bool = False
        self.has_sort: bool = False
        self.has_where: bool = False
        if condition:
            self._set_where(condition)

    def _bind_single(self, obj: Union[DbDoc, Dict[str, Any], str]) -> None:
        """Bind single object.

        Args:
            obj (:class:`mysqlx.DbDoc` or str): DbDoc or JSON string object.

        Raises:
            :class:`mysqlx.ProgrammingError`: If invalid JSON string to bind.
            ValueError: If JSON loaded is not a dictionary.
        """
        if isinstance(obj, dict):
            self.bind(DbDoc(obj).as_str())
        elif isinstance(obj, DbDoc):
            self.bind(obj.as_str())
        elif isinstance(obj, str):
            try:
                res = json.loads(obj)
                if not isinstance(res, dict):
                    raise ValueError
            except ValueError as err:
                raise ProgrammingError("Invalid JSON string to bind") from err
            for key in res.keys():
                self.bind(key, res[key])
        else:
            raise ProgrammingError("Invalid JSON string or object to bind")

    def _sort(self, *clauses: str) -> FilterableStatement:
        """Sets the sorting criteria.

        Args:
            *clauses: The expression strings defining the sort criteria.

        Returns:
            mysqlx.FilterableStatement: FilterableStatement object.
        """
        self.has_sort = True
        self._sort_str = ",".join(flexible_params(*clauses))
        self._sort_expr = ExprParser(
            self._sort_str, not self._doc_based
        ).parse_order_spec()
        self._changed = True
        return self

    def _set_where(self, condition: str) -> FilterableStatement:
        """Sets the search condition to filter.

        Args:
            condition (str): Sets the search condition to filter documents or
                             records.

        Returns:
            mysqlx.FilterableStatement: FilterableStatement object.
        """
        self.has_where = True
        self._where_str = condition
        try:
            expr = ExprParser(condition, not self._doc_based)
            self._where_expr = expr.expr()
        except ValueError as err:
            raise ProgrammingError("Invalid condition") from err
        self._binding_map = expr.placeholder_name_to_position
        self._changed = True
        return self

    def _set_group_by(self, *fields: str) -> None:
        """Set group by.

        Args:
            *fields: List of fields.
        """
        fields = flexible_params(*fields)
        self.has_group_by = True
        self._grouping_str = ",".join(fields)
        self._grouping = ExprParser(
            self._grouping_str, not self._doc_based
        ).parse_expr_list()
        self._changed = True

    def _set_having(self, condition: str) -> None:
        """Set having.

        Args:
            condition (str): The condition.
        """
        self.has_having = True
        self._having = ExprParser(condition, not self._doc_based).expr()
        self._changed = True

    def _set_projection(self, *fields: str) -> FilterableStatement:
        """Set the projection.

        Args:
            *fields: List of fields.

        Returns:
            :class:`mysqlx.FilterableStatement`: Returns self.
        """
        fields = flexible_params(*fields)
        self.has_projection = True
        self._projection_str = ",".join(fields)
        self._projection_expr = ExprParser(
            self._projection_str, not self._doc_based
        ).parse_table_select_projection()
        self._changed = True
        return self

    def get_binding_map(self) -> Dict[str, Any]:
        """Returns the binding map dictionary.

        Returns:
            dict: The binding map dictionary.
        """
        return self._binding_map

    def get_bindings(self) -> Union[Dict[str, Any], List]:
        """Returns the bindings list.

        Returns:
            `list`: The bindings list.
        """
        return self._bindings

    def get_grouping(self) -> List[Union[ProtobufMessageType, ProtobufMessageCextType]]:
        """Returns the grouping expression list.

        Returns:
            `list`: The grouping expression list.
        """
        return self._grouping

    def get_having(self) -> MessageType:
        """Returns the having expression.

        Returns:
            object: The having expression.
        """
        return self._having

    def get_limit_row_count(self) -> int:
        """Returns the limit row count.

        Returns:
            int: The limit row count.
        """
        return self._limit_row_count

    def get_limit_offset(self) -> int:
        """Returns the limit offset.

        Returns:
            int: The limit offset.
        """
        return self._limit_offset

    def get_where_expr(self) -> MessageType:
        """Returns the where expression.

        Returns:
            object: The where expression.
        """
        return self._where_expr

    def get_projection_expr(
        self,
    ) -> List[Union[ProtobufMessageType, ProtobufMessageCextType]]:
        """Returns the projection expression.

        Returns:
            object: The projection expression.
        """
        return self._projection_expr

    def get_sort_expr(
        self,
    ) -> List[Union[ProtobufMessageType, ProtobufMessageCextType]]:
        """Returns the sort expression.

        Returns:
            object: The sort expression.
        """
        return self._sort_expr

    @deprecated("8.0.12")
    def where(self, condition: str) -> FilterableStatement:
        """Sets the search condition to filter.

        Args:
            condition (str): Sets the search condition to filter documents or
                             records.

        Returns:
            mysqlx.FilterableStatement: FilterableStatement object.

        .. deprecated:: 8.0.12
        """
        return self._set_where(condition)

    @deprecated("8.0.12")
    def sort(self, *clauses: str) -> FilterableStatement:
        """Sets the sorting criteria.

        Args:
            *clauses: The expression strings defining the sort criteria.

        Returns:
            mysqlx.FilterableStatement: FilterableStatement object.

        .. deprecated:: 8.0.12
        """
        return self._sort(*clauses)

    def limit(
        self, row_count: int, offset: Optional[int] = None
    ) -> FilterableStatement:
        """Sets the maximum number of items to be returned.

        Args:
            row_count (int): The maximum number of items.

        Returns:
            mysqlx.FilterableStatement: FilterableStatement object.

        Raises:
            ValueError: If ``row_count`` is not a positive integer.

        .. versionchanged:: 8.0.12
           The usage of ``offset`` was deprecated.
        """
        if not isinstance(row_count, int) or row_count < 0:
            raise ValueError("The 'row_count' value must be a positive integer")
        if not self.has_limit:
            self._changed = bool(self._exec_counter == 0)
            self._deallocate_prepare_execute = bool(not self._exec_counter == 0)

        self._limit_row_count = row_count
        self.has_limit = True
        if offset:
            self.offset(offset)
            warnings.warn(
                "'limit(row_count, offset)' is deprecated, please "
                "use 'offset(offset)' to set the number of items to "
                "skip",
                category=DeprecationWarning,
            )
        return self

    def offset(self, offset: int) -> FilterableStatement:
        """Sets the number of items to skip.

        Args:
            offset (int): The number of items to skip.

        Returns:
            mysqlx.FilterableStatement: FilterableStatement object.

        Raises:
            ValueError: If ``offset`` is not a positive integer.

        .. versionadded:: 8.0.12
        """
        if not isinstance(offset, int) or offset < 0:
            raise ValueError("The 'offset' value must be a positive integer")
        self._limit_offset = offset
        return self

    def bind(self, *args: Any) -> FilterableStatement:
        """Binds value(s) to a specific placeholder(s).

        Args:
            *args: The name of the placeholder and the value to bind.
                   A :class:`mysqlx.DbDoc` object or a JSON string
                   representation can be used.

        Returns:
            mysqlx.FilterableStatement: FilterableStatement object.

        Raises:
            ProgrammingError: If the number of arguments is invalid.
        """
        self.has_bindings = True
        count = len(args)
        if count == 1:
            self._bind_single(args[0])
        elif count == 2:
            self._bindings[args[0]] = args[1]
        else:
            raise ProgrammingError("Invalid number of arguments to bind")
        return self

    def execute(self) -> Any:
        """Execute the statement.

        Raises:
           NotImplementedError: This method must be implemented.
        """
        raise NotImplementedError


class SqlStatement(Statement):
    """A statement for SQL execution.

    Args:
        connection (mysqlx.connection.Connection): Connection object.
        sql (string): The sql statement to be executed.
    """

    def __init__(self, connection: ConnectionType, sql: str) -> None:
        super().__init__(target=None, doc_based=False)
        self._connection: ConnectionType = connection
        self._sql: str = sql
        self._binding_map: Optional[Dict[str, Any]] = None
        self._bindings: Union[List, Tuple] = []
        self.has_bindings: bool = False
        self.has_limit: bool = False

    @property
    def sql(self) -> str:
        """string: The SQL text statement."""
        return self._sql

    def get_binding_map(self) -> Dict[str, Any]:
        """Returns the binding map dictionary.

        Returns:
            dict: The binding map dictionary.
        """
        return self._binding_map

    def get_bindings(self) -> Union[Tuple, List]:
        """Returns the bindings list.

        Returns:
            `list`: The bindings list.
        """
        return self._bindings

    def bind(self, *args: Any) -> SqlStatement:
        """Binds value(s) to a specific placeholder(s).

        Args:
            *args: The value(s) to bind.

        Returns:
            mysqlx.SqlStatement: SqlStatement object.
        """
        if len(args) == 0:
            raise ProgrammingError("Invalid number of arguments to bind")
        self.has_bindings = True
        bindings = flexible_params(*args)
        if isinstance(bindings, (list, tuple)):
            self._bindings = bindings
        else:
            self._bindings.append(bindings)
        return self

    def execute(self) -> SqlResult:
        """Execute the statement.

        Returns:
            mysqlx.SqlResult: SqlResult object.
        """
        return self._connection.send_sql(self)


class WriteStatement(Statement):
    """Provide common write operation attributes."""

    def __init__(self, target: DatabaseTargetType, doc_based: bool) -> None:
        super().__init__(target, doc_based)
        self._values: List[
            Union[
                int,
                str,
                DbDoc,
                Dict[str, Any],
                List[Optional[Union[str, int, float, ExprParser, Dict[str, Any]]]],
            ]
        ] = []

    def get_values(
        self,
    ) -> List[
        Union[
            int,
            str,
            DbDoc,
            Dict[str, Any],
            List[Optional[Union[str, int, float, ExprParser, Dict[str, Any]]]],
        ]
    ]:
        """Returns the list of values.

        Returns:
            `list`: The list of values.
        """
        return self._values

    def execute(self) -> Any:
        """Execute the statement.

        Raises:
           NotImplementedError: This method must be implemented.
        """
        raise NotImplementedError


class AddStatement(WriteStatement):
    """A statement for document addition on a collection.

    Args:
        collection (mysqlx.Collection): The Collection object.
    """

    def __init__(self, collection: DatabaseTargetType) -> None:
        super().__init__(collection, True)
        self._upsert: bool = False
        self.ids: List = []

    def is_upsert(self) -> bool:
        """Returns `True` if it's an upsert.

        Returns:
            bool: `True` if it's an upsert.
        """
        return self._upsert

    def upsert(self, value: bool = True) -> AddStatement:
        """Sets the upset flag to the boolean of the value provided.
        Setting of this flag allows updating of the matched rows/documents
        with the provided value.

        Args:
            value (optional[bool]): Set or unset the upsert flag.
        """
        self._upsert = value
        return self

    def add(self, *values: DbDoc) -> AddStatement:
        """Adds a list of documents into a collection.

        Args:
            *values: The documents to be added into the collection.

        Returns:
            mysqlx.AddStatement: AddStatement object.
        """
        for val in flexible_params(*values):
            if isinstance(val, DbDoc):
                self._values.append(val)
            else:
                self._values.append(DbDoc(val))
        return self

    def execute(self) -> Result:
        """Execute the statement.

        Returns:
            mysqlx.Result: Result object.
        """
        if len(self._values) == 0:
            return Result()

        return self._connection.send_insert(self)


class UpdateSpec:
    """Update specification class implementation.

    Args:
        update_type (int): The update type.
        source (str): The source.
        value (Optional[str]): The value.

    Raises:
        ProgrammingError: If `source` is invalid.
    """

    def __init__(self, update_type: int, source: str, value: Any = None) -> None:
        if update_type == mysqlxpb_enum("Mysqlx.Crud.UpdateOperation.UpdateType.SET"):
            self._table_set(source, value)
        else:
            self.update_type: int = update_type
            try:
                self.source: Any = ExprParser(source, False).document_field().identifier
            except ValueError as err:
                raise ProgrammingError(f"{err}") from err
            self.value: Any = value

    def _table_set(self, source: str, value: Any) -> None:
        """Table set.

        Args:
            source (str): The source.
            value (str): The value.
        """
        self.update_type = mysqlxpb_enum("Mysqlx.Crud.UpdateOperation.UpdateType.SET")
        self.source = ExprParser(source, True).parse_table_update_field()
        self.value = value


class ModifyStatement(FilterableStatement):
    """A statement for document update operations on a Collection.

    Args:
        collection (mysqlx.Collection): The Collection object.
        condition (str): Sets the search condition to identify the documents
                         to be modified.

    .. versionchanged:: 8.0.12
       The ``condition`` parameter is now mandatory.
    """

    def __init__(self, collection: DatabaseTargetType, condition: str) -> None:
        super().__init__(target=collection, condition=condition)
        self._update_ops: Dict[str, Any] = {}

    def sort(self, *clauses: str) -> ModifyStatement:
        """Sets the sorting criteria.

        Args:
            *clauses: The expression strings defining the sort criteria.

        Returns:
            mysqlx.ModifyStatement: ModifyStatement object.
        """
        return self._sort(*clauses)

    def get_update_ops(self) -> Dict[str, Any]:
        """Returns the list of update operations.

        Returns:
            `list`: The list of update operations.
        """
        return self._update_ops

    def set(self, doc_path: str, value: Any) -> ModifyStatement:
        """Sets or updates attributes on documents in a collection.

        Args:
            doc_path (string): The document path of the item to be set.
            value (string): The value to be set on the specified attribute.

        Returns:
            mysqlx.ModifyStatement: ModifyStatement object.
        """
        self._update_ops[doc_path] = UpdateSpec(
            mysqlxpb_enum("Mysqlx.Crud.UpdateOperation.UpdateType.ITEM_SET"),
            doc_path,
            value,
        )
        self._changed = True
        return self

    @deprecated("8.0.12")
    def change(self, doc_path: str, value: Any) -> ModifyStatement:
        """Add an update to the statement setting the field, if it exists at
        the document path, to the given value.

        Args:
            doc_path (string): The document path of the item to be set.
            value (object): The value to be set on the specified attribute.

        Returns:
            mysqlx.ModifyStatement: ModifyStatement object.

        .. deprecated:: 8.0.12
        """
        self._update_ops[doc_path] = UpdateSpec(
            mysqlxpb_enum("Mysqlx.Crud.UpdateOperation.UpdateType.ITEM_REPLACE"),
            doc_path,
            value,
        )
        self._changed = True
        return self

    def unset(self, *doc_paths: str) -> ModifyStatement:
        """Removes attributes from documents in a collection.

        Args:
            doc_paths (list): The list of document paths of the attributes to be
                              removed.

        Returns:
            mysqlx.ModifyStatement: ModifyStatement object.
        """
        for item in flexible_params(*doc_paths):
            self._update_ops[item] = UpdateSpec(
                mysqlxpb_enum("Mysqlx.Crud.UpdateOperation.UpdateType.ITEM_REMOVE"),
                item,
            )
        self._changed = True
        return self

    def array_insert(self, field: str, value: Any) -> ModifyStatement:
        """Insert a value into the specified array in documents of a
        collection.

        Args:
            field (string): A document path that identifies the array attribute
                            and position where the value will be inserted.
            value (object): The value to be inserted.

        Returns:
            mysqlx.ModifyStatement: ModifyStatement object.
        """
        self._update_ops[field] = UpdateSpec(
            mysqlxpb_enum("Mysqlx.Crud.UpdateOperation.UpdateType.ARRAY_INSERT"),
            field,
            value,
        )
        self._changed = True
        return self

    def array_append(self, doc_path: str, value: Any) -> ModifyStatement:
        """Inserts a value into a specific position in an array attribute in
        documents of a collection.

        Args:
            doc_path (string): A document path that identifies the array
                               attribute and position where the value will be
                               inserted.
            value (object): The value to be inserted.

        Returns:
            mysqlx.ModifyStatement: ModifyStatement object.
        """
        self._update_ops[doc_path] = UpdateSpec(
            mysqlxpb_enum("Mysqlx.Crud.UpdateOperation.UpdateType.ARRAY_APPEND"),
            doc_path,
            value,
        )
        self._changed = True
        return self

    def patch(self, doc: Union[Dict, DbDoc, ExprParser, str]) -> ModifyStatement:
        """Takes a :class:`mysqlx.DbDoc`, string JSON format or a dict with the
        changes and applies it on all matching documents.

        Args:
            doc (object): A generic document (DbDoc), string in JSON format or
                          dict, with the changes to apply to the matching
                          documents.

        Returns:
            mysqlx.ModifyStatement: ModifyStatement object.
        """
        if doc is None:
            doc = ""
        if not isinstance(doc, (ExprParser, dict, DbDoc, str)):
            raise ProgrammingError(
                "Invalid data for update operation on document collection table"
            )
        self._update_ops["patch"] = UpdateSpec(
            mysqlxpb_enum("Mysqlx.Crud.UpdateOperation.UpdateType.MERGE_PATCH"),
            "$",
            doc.expr() if isinstance(doc, ExprParser) else doc,
        )
        self._changed = True
        return self

    def execute(self) -> Result:
        """Execute the statement.

        Returns:
            mysqlx.Result: Result object.

        Raises:
            ProgrammingError: If condition was not set.
        """
        if not self.has_where:
            raise ProgrammingError("No condition was found for modify")
        return self._connection.send_update(self)


class ReadStatement(FilterableStatement):
    """Provide base functionality for Read operations

    Args:
        target (object): The target database object, it can be
                         :class:`mysqlx.Collection` or :class:`mysqlx.Table`.
        doc_based (Optional[bool]): `True` if it is document based
                                    (default: `True`).
        condition (Optional[str]): Sets the search condition to filter
                                   documents or records.
    """

    def __init__(
        self,
        target: DatabaseTargetType,
        doc_based: bool = True,
        condition: Optional[str] = None,
    ) -> None:
        super().__init__(target, doc_based, condition)
        self._lock_exclusive: bool = False
        self._lock_shared: bool = False
        self._lock_contention: LockContention = LockContention.DEFAULT

    @property
    def lock_contention(self) -> LockContention:
        """:class:`mysqlx.LockContention`: The lock contention value."""
        return self._lock_contention

    def _set_lock_contention(self, lock_contention: LockContention) -> None:
        """Set the lock contention.

        Args:
            lock_contention (:class:`mysqlx.LockContention`): Lock contention.

        Raises:
            ProgrammingError: If is an invalid lock contention value.
        """
        try:
            # Check if is a valid lock contention value
            _ = LockContention(lock_contention.value)
        except ValueError as err:
            raise ProgrammingError(
                "Invalid lock contention mode. Use 'NOWAIT' or 'SKIP_LOCKED'"
            ) from err
        self._lock_contention = lock_contention

    def is_lock_exclusive(self) -> bool:
        """Returns `True` if is `EXCLUSIVE LOCK`.

        Returns:
            bool: `True` if is `EXCLUSIVE LOCK`.
        """
        return self._lock_exclusive

    def is_lock_shared(self) -> bool:
        """Returns `True` if is `SHARED LOCK`.

        Returns:
            bool: `True` if is `SHARED LOCK`.
        """
        return self._lock_shared

    def lock_shared(
        self, lock_contention: LockContention = LockContention.DEFAULT
    ) -> ReadStatement:
        """Execute a read operation with `SHARED LOCK`. Only one lock can be
           active at a time.

        Args:
            lock_contention (:class:`mysqlx.LockContention`): Lock contention.
        """
        self._lock_exclusive = False
        self._lock_shared = True
        self._set_lock_contention(lock_contention)
        return self

    def lock_exclusive(
        self, lock_contention: LockContention = LockContention.DEFAULT
    ) -> ReadStatement:
        """Execute a read operation with `EXCLUSIVE LOCK`. Only one lock can be
           active at a time.

        Args:
            lock_contention (:class:`mysqlx.LockContention`): Lock contention.
        """
        self._lock_exclusive = True
        self._lock_shared = False
        self._set_lock_contention(lock_contention)
        return self

    def group_by(self, *fields: str) -> ReadStatement:
        """Sets a grouping criteria for the resultset.

        Args:
            *fields: The string expressions identifying the grouping criteria.

        Returns:
            mysqlx.ReadStatement: ReadStatement object.
        """
        self._set_group_by(*fields)
        return self

    def having(self, condition: str) -> ReadStatement:
        """Sets a condition for records to be considered in agregate function
        operations.

        Args:
            condition (string): A condition on the agregate functions used on
                                the grouping criteria.

        Returns:
            mysqlx.ReadStatement: ReadStatement object.
        """
        self._set_having(condition)
        return self

    def execute(self) -> Union[DocResult, RowResult]:
        """Execute the statement.

        Returns:
            mysqlx.Result: Result object.
        """
        return self._connection.send_find(self)


class FindStatement(ReadStatement):
    """A statement document selection on a Collection.

    Args:
        collection (mysqlx.Collection): The Collection object.
        condition (Optional[str]): An optional expression to identify the
                                   documents to be retrieved. If not specified
                                   all the documents will be included on the
                                   result unless a limit is set.
    """

    def __init__(
        self, collection: DatabaseTargetType, condition: Optional[str] = None
    ) -> None:
        super().__init__(collection, True, condition)

    def fields(self, *fields: str) -> FindStatement:
        """Sets a document field filter.

        Args:
            *fields: The string expressions identifying the fields to be
                     extracted.

        Returns:
            mysqlx.FindStatement: FindStatement object.
        """
        return self._set_projection(*fields)

    def sort(self, *clauses: str) -> FindStatement:
        """Sets the sorting criteria.

        Args:
            *clauses: The expression strings defining the sort criteria.

        Returns:
            mysqlx.FindStatement: FindStatement object.
        """
        return self._sort(*clauses)


class SelectStatement(ReadStatement):
    """A statement for record retrieval operations on a Table.

    Args:
        table (mysqlx.Table): The Table object.
        *fields: The fields to be retrieved.
    """

    def __init__(self, table: DatabaseTargetType, *fields: str) -> None:
        super().__init__(table, False)
        self._set_projection(*fields)

    def where(self, condition: str) -> SelectStatement:
        """Sets the search condition to filter.

        Args:
            condition (str): Sets the search condition to filter records.

        Returns:
            mysqlx.SelectStatement: SelectStatement object.
        """
        return self._set_where(condition)

    def order_by(self, *clauses: str) -> SelectStatement:
        """Sets the order by criteria.

        Args:
            *clauses: The expression strings defining the order by criteria.

        Returns:
            mysqlx.SelectStatement: SelectStatement object.
        """
        return self._sort(*clauses)

    def get_sql(self) -> str:
        """Returns the generated SQL.

        Returns:
            str: The generated SQL.
        """
        where = f" WHERE {self._where_str}" if self.has_where else ""
        group_by = f" GROUP BY {self._grouping_str}" if self.has_group_by else ""
        having = f" HAVING {self._having}" if self.has_having else ""
        order_by = f" ORDER BY {self._sort_str}" if self.has_sort else ""
        limit = (
            f" LIMIT {self._limit_row_count} OFFSET {self._limit_offset}"
            if self.has_limit
            else ""
        )
        stmt = (
            f"SELECT {self._projection_str or '*'} "
            f"FROM {self.schema.name}.{self.target.name}"
            f"{where}{group_by}{having}{order_by}{limit}"
        )
        return stmt


class InsertStatement(WriteStatement):
    """A statement for insert operations on Table.

    Args:
        table (mysqlx.Table): The Table object.
        *fields: The fields to be inserted.
    """

    def __init__(self, table: DatabaseTargetType, *fields: Any) -> None:
        super().__init__(table, False)
        self._fields: Union[List, Tuple] = flexible_params(*fields)

    def values(self, *values: Any) -> InsertStatement:
        """Set the values to be inserted.

        Args:
            *values: The values of the columns to be inserted.

        Returns:
            mysqlx.InsertStatement: InsertStatement object.
        """
        self._values.append(list(flexible_params(*values)))
        return self

    def execute(self) -> Result:
        """Execute the statement.

        Returns:
            mysqlx.Result: Result object.
        """
        return self._connection.send_insert(self)


class UpdateStatement(FilterableStatement):
    """A statement for record update operations on a Table.

    Args:
        table (mysqlx.Table): The Table object.

    .. versionchanged:: 8.0.12
       The ``fields`` parameters were removed.
    """

    def __init__(self, table: DatabaseTargetType) -> None:
        super().__init__(target=table, doc_based=False)
        self._update_ops: Dict[str, Any] = {}

    def where(self, condition: str) -> UpdateStatement:
        """Sets the search condition to filter.

        Args:
            condition (str): Sets the search condition to filter records.

        Returns:
            mysqlx.UpdateStatement: UpdateStatement object.
        """
        return self._set_where(condition)

    def order_by(self, *clauses: str) -> UpdateStatement:
        """Sets the order by criteria.

        Args:
            *clauses: The expression strings defining the order by criteria.

        Returns:
            mysqlx.UpdateStatement: UpdateStatement object.
        """
        return self._sort(*clauses)

    def get_update_ops(self) -> Dict[str, Any]:
        """Returns the list of update operations.

        Returns:
            `list`: The list of update operations.
        """
        return self._update_ops

    def set(self, field: str, value: Any) -> UpdateStatement:
        """Updates the column value on records in a table.

        Args:
            field (string): The column name to be updated.
            value (object): The value to be set on the specified column.

        Returns:
            mysqlx.UpdateStatement: UpdateStatement object.
        """
        self._update_ops[field] = UpdateSpec(
            mysqlxpb_enum("Mysqlx.Crud.UpdateOperation.UpdateType.SET"),
            field,
            value,
        )
        self._changed = True
        return self

    def execute(self) -> Result:
        """Execute the statement.

        Returns:
            mysqlx.Result: Result object

        Raises:
            ProgrammingError: If condition was not set.
        """
        if not self.has_where:
            raise ProgrammingError("No condition was found for update")
        return self._connection.send_update(self)


class RemoveStatement(FilterableStatement):
    """A statement for document removal from a collection.

    Args:
        collection (mysqlx.Collection): The Collection object.
        condition (str): Sets the search condition to identify the documents
                         to be removed.

    .. versionchanged:: 8.0.12
       The ``condition`` parameter was added.
    """

    def __init__(self, collection: DatabaseTargetType, condition: str) -> None:
        super().__init__(target=collection, condition=condition)

    def sort(self, *clauses: str) -> RemoveStatement:
        """Sets the sorting criteria.

        Args:
            *clauses: The expression strings defining the sort criteria.

        Returns:
            mysqlx.FindStatement: FindStatement object.
        """
        return self._sort(*clauses)

    def execute(self) -> Result:
        """Execute the statement.

        Returns:
            mysqlx.Result: Result object.

        Raises:
            ProgrammingError: If condition was not set.
        """
        if not self.has_where:
            raise ProgrammingError("No condition was found for remove")
        return self._connection.send_delete(self)


class DeleteStatement(FilterableStatement):
    """A statement that drops a table.

    Args:
        table (mysqlx.Table): The Table object.

    .. versionchanged:: 8.0.12
       The ``condition`` parameter was removed.
    """

    def __init__(self, table: DatabaseTargetType) -> None:
        super().__init__(target=table, doc_based=False)

    def where(self, condition: str) -> DeleteStatement:
        """Sets the search condition to filter.

        Args:
            condition (str): Sets the search condition to filter records.

        Returns:
            mysqlx.DeleteStatement: DeleteStatement object.
        """
        return self._set_where(condition)

    def order_by(self, *clauses: str) -> DeleteStatement:
        """Sets the order by criteria.

        Args:
            *clauses: The expression strings defining the order by criteria.

        Returns:
            mysqlx.DeleteStatement: DeleteStatement object.
        """
        return self._sort(*clauses)

    def execute(self) -> Result:
        """Execute the statement.

        Returns:
            mysqlx.Result: Result object.

        Raises:
            ProgrammingError: If condition was not set.
        """
        if not self.has_where:
            raise ProgrammingError("No condition was found for delete")
        return self._connection.send_delete(self)


class CreateCollectionIndexStatement(Statement):
    """A statement that creates an index on a collection.

    Args:
        collection (mysqlx.Collection): Collection.
        index_name (string): Index name.
        index_desc (dict): A dictionary containing the fields members that
                           constraints the index to be created. It must have
                           the form as shown in the following::

                               {"fields": [{"field": member_path,
                                            "type": member_type,
                                            "required": member_required,
                                            "collation": collation,
                                            "options": options,
                                            "srid": srid},
                                            # {... more members,
                                            #      repeated as many times
                                            #      as needed}
                                            ],
                                "type": type}
    """

    def __init__(
        self,
        collection: DatabaseTargetType,
        index_name: str,
        index_desc: Dict[str, Any],
    ) -> None:
        super().__init__(target=collection)
        self._index_desc: Dict[str, Any] = copy.deepcopy(index_desc)
        self._index_name: str = index_name
        self._fields_desc: List[Dict[str, Any]] = self._index_desc.pop("fields", [])

    def execute(self) -> Result:
        """Execute the statement.

        Returns:
            mysqlx.Result: Result object.
        """
        # Validate index name is a valid identifier
        if self._index_name is None:
            raise ProgrammingError(ERR_INVALID_INDEX_NAME.format(self._index_name))
        try:
            parsed_ident = ExprParser(self._index_name).expr().get_message()

            # The message is type dict when the Protobuf cext is used
            if isinstance(parsed_ident, dict):
                if parsed_ident["type"] != mysqlxpb_enum("Mysqlx.Expr.Expr.Type.IDENT"):
                    raise ProgrammingError(
                        ERR_INVALID_INDEX_NAME.format(self._index_name)
                    )
            else:
                if parsed_ident.type != mysqlxpb_enum("Mysqlx.Expr.Expr.Type.IDENT"):
                    raise ProgrammingError(
                        ERR_INVALID_INDEX_NAME.format(self._index_name)
                    )

        except (ValueError, AttributeError) as err:
            raise ProgrammingError(
                ERR_INVALID_INDEX_NAME.format(self._index_name)
            ) from err

        # Validate members that constraint the index
        if not self._fields_desc:
            raise ProgrammingError(
                "Required member 'fields' not found in the given index "
                f"description: {self._index_desc}"
            )

        if not isinstance(self._fields_desc, list):
            raise ProgrammingError("Required member 'fields' must contain a list")
        args: Dict[str, Any] = {}
        args["name"] = self._index_name
        args["collection"] = self._target.name
        args["schema"] = self._target.schema.name
        if "type" in self._index_desc:
            args["type"] = self._index_desc.pop("type")
        else:
            args["type"] = "INDEX"
        args["unique"] = self._index_desc.pop("unique", False)
        # Currently unique indexes are not supported:
        if args["unique"]:
            raise NotSupportedError("Unique indexes are not supported.")
        args["constraint"] = []

        if self._index_desc:
            raise ProgrammingError(f"Unidentified fields: {self._index_desc}")

        try:
            for field_desc in self._fields_desc:
                constraint = {}
                constraint["member"] = field_desc.pop("field")
                constraint["type"] = field_desc.pop("type")
                constraint["required"] = field_desc.pop("required", False)
                constraint["array"] = field_desc.pop("array", False)
                if not isinstance(constraint["required"], bool):
                    raise TypeError("Field member 'required' must be Boolean")
                if not isinstance(constraint["array"], bool):
                    raise TypeError("Field member 'array' must be Boolean")
                if args["type"].upper() == "SPATIAL" and not constraint["required"]:
                    raise ProgrammingError(
                        "Field member 'required' must be set to 'True' when "
                        "index type is set to 'SPATIAL'"
                    )
                if args["type"].upper() == "INDEX" and constraint["type"] == "GEOJSON":
                    raise ProgrammingError(
                        "Index 'type' must be set to 'SPATIAL' when field "
                        "type is set to 'GEOJSON'"
                    )
                if "collation" in field_desc:
                    if not constraint["type"].upper().startswith("TEXT"):
                        raise ProgrammingError(
                            "The 'collation' member can only be used when "
                            "field type is set to "
                            f"'{constraint['type'].upper()}'"
                        )
                    constraint["collation"] = field_desc.pop("collation")
                # "options" and "srid" fields in IndexField can be
                # present only if "type" is set to "GEOJSON"
                if "options" in field_desc:
                    if constraint["type"].upper() != "GEOJSON":
                        raise ProgrammingError(
                            "The 'options' member can only be used when "
                            "index type is set to 'GEOJSON'"
                        )
                    constraint["options"] = field_desc.pop("options")
                if "srid" in field_desc:
                    if constraint["type"].upper() != "GEOJSON":
                        raise ProgrammingError(
                            "The 'srid' member can only be used when index "
                            "type is set to 'GEOJSON'"
                        )
                    constraint["srid"] = field_desc.pop("srid")
                args["constraint"].append(constraint)
        except KeyError as err:
            raise ProgrammingError(
                f"Required inner member {err} not found in constraint: {field_desc}"
            ) from err

        for field_desc in self._fields_desc:
            if field_desc:
                raise ProgrammingError(f"Unidentified inner fields: {field_desc}")

        return self._connection.execute_nonquery(
            "mysqlx", "create_collection_index", True, args
        )

virt/lib/python3.9/site-packages/mysqlx/statement 3.py
PrinceDisant/LostInNL
# Copyright (c) 2016, 2022, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an
# additional permission to link the program and your derivative works
# with the separately licensed software that they have included with
# MySQL.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of MySQL Connector/Python, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# http://oss.oracle.com/licenses/universal-foss-exception.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

# mypy: disable-error-code="return-value"

"""Implementation of Statements."""

from __future__ import annotations

import copy
import json
import warnings

from typing import Any, Dict, Iterable, List, Optional, Tuple, Union

from .constants import LockContention
from .dbdoc import DbDoc
from .errors import NotSupportedError, ProgrammingError
from .expr import ExprParser
from .helpers import deprecated
from .protobuf import mysqlxpb_enum
from .result import DocResult, Result, RowResult, SqlResult
from .types import (
    ConnectionType,
    DatabaseTargetType,
    MessageType,
    ProtobufMessageCextType,
    ProtobufMessageType,
    SchemaType,
)

ERR_INVALID_INDEX_NAME = 'The given index name "{}" is not valid'


class Expr:
    """Expression wrapper."""

    def __init__(self, expr: Any) -> None:
        self.expr: Any = expr


def flexible_params(*values: Any) -> Union[List, Tuple]:
    """Parse flexible parameters."""
    if len(values) == 1 and isinstance(values[0], (list, tuple)):
        return values[0]
    return values


def is_quoted_identifier(identifier: str, sql_mode: str = "") -> bool:
    """Check if the given identifier is quoted.

    Args:
        identifier (string): Identifier to check.
        sql_mode (Optional[string]): SQL mode.

    Returns:
        `True` if the identifier has backtick quotes, and False otherwise.
    """
    if "ANSI_QUOTES" in sql_mode:
        return (identifier[0] == "`" and identifier[-1] == "`") or (
            identifier[0] == '"' and identifier[-1] == '"'
        )
    return identifier[0] == "`" and identifier[-1] == "`"


def quote_identifier(identifier: str, sql_mode: str = "") -> str:
    """Quote the given identifier with backticks, converting backticks (`) in
    the identifier name with the correct escape sequence (``).

    Args:
        identifier (string): Identifier to quote.
        sql_mode (Optional[string]): SQL mode.

    Returns:
        A string with the identifier quoted with backticks.
    """
    if len(identifier) == 0:
        return "``"
    if "ANSI_QUOTES" in sql_mode:
        quoted = identifier.replace('"', '""')
        return f'"{quoted}"'
    quoted = identifier.replace("`", "``")
    return f"`{quoted}`"


def quote_multipart_identifier(identifiers: Iterable[str], sql_mode: str = "") -> str:
    """Quote the given multi-part identifier with backticks.

    Args:
        identifiers (iterable): List of identifiers to quote.
        sql_mode (Optional[string]): SQL mode.

    Returns:
        A string with the multi-part identifier quoted with backticks.
    """
    return ".".join(
        [quote_identifier(identifier, sql_mode) for identifier in identifiers]
    )


def parse_table_name(
    default_schema: str, table_name: str, sql_mode: str = ""
) -> Tuple[str, str]:
    """Parse table name.

    Args:
        default_schema (str): The default schema.
        table_name (str): The table name.
        sql_mode(Optional[str]): The SQL mode.

    Returns:
        str: The parsed table name.
    """
    quote = '"' if "ANSI_QUOTES" in sql_mode else "`"
    delimiter = f".{quote}" if quote in table_name else "."
    temp = table_name.split(delimiter, 1)
    return (
        default_schema if len(temp) == 1 else temp[0].strip(quote),
        temp[-1].strip(quote),
    )


class Statement:
    """Provides base functionality for statement objects.

    Args:
        target (object): The target database object, it can be
                         :class:`mysqlx.Collection` or :class:`mysqlx.Table`.
        doc_based (bool): `True` if it is document based.
    """

    def __init__(self, target: DatabaseTargetType, doc_based: bool = True) -> None:
        self._target: DatabaseTargetType = target
        self._doc_based: bool = doc_based
        self._connection: Optional[ConnectionType] = (
            target.get_connection() if target else None
        )
        self._stmt_id: Optional[int] = None
        self._exec_counter: int = 0
        self._changed: bool = True
        self._prepared: bool = False
        self._deallocate_prepare_execute: bool = False

    @property
    def target(self) -> DatabaseTargetType:
        """object: The database object target."""
        return self._target

    @property
    def schema(self) -> SchemaType:
        """:class:`mysqlx.Schema`: The Schema object."""
        return self._target.schema

    @property
    def stmt_id(self) -> int:
        """Returns this statement ID.

        Returns:
            int: The statement ID.
        """
        return self._stmt_id

    @stmt_id.setter
    def stmt_id(self, value: int) -> None:
        self._stmt_id = value

    @property
    def exec_counter(self) -> int:
        """int: The number of times this statement was executed."""
        return self._exec_counter

    @property
    def changed(self) -> bool:
        """bool: `True` if this statement has changes."""
        return self._changed

    @changed.setter
    def changed(self, value: bool) -> None:
        self._changed = value

    @property
    def prepared(self) -> bool:
        """bool: `True` if this statement has been prepared."""
        return self._prepared

    @prepared.setter
    def prepared(self, value: bool) -> None:
        self._prepared = value

    @property
    def repeated(self) -> bool:
        """bool: `True` if this statement was executed more than once."""
        return self._exec_counter > 1

    @property
    def deallocate_prepare_execute(self) -> bool:
        """bool: `True` to deallocate + prepare + execute statement."""
        return self._deallocate_prepare_execute

    @deallocate_prepare_execute.setter
    def deallocate_prepare_execute(self, value: bool) -> None:
        self._deallocate_prepare_execute = value

    def is_doc_based(self) -> bool:
        """Check if it is document based.

        Returns:
            bool: `True` if it is document based.
        """
        return self._doc_based

    def increment_exec_counter(self) -> None:
        """Increments the number of times this statement has been executed."""
        self._exec_counter += 1

    def reset_exec_counter(self) -> None:
        """Resets the number of times this statement has been executed."""
        self._exec_counter = 0

    def execute(self) -> Any:
        """Execute the statement.

        Raises:
           NotImplementedError: This method must be implemented.
        """
        raise NotImplementedError


class FilterableStatement(Statement):
    """A statement to be used with filterable statements.

    Args:
        target (object): The target database object, it can be
                         :class:`mysqlx.Collection` or :class:`mysqlx.Table`.
        doc_based (Optional[bool]): `True` if it is document based
                                    (default: `True`).
        condition (Optional[str]): Sets the search condition to filter
                                   documents or records.
    """

    def __init__(
        self,
        target: DatabaseTargetType,
        doc_based: bool = True,
        condition: Optional[str] = None,
    ) -> None:
        super().__init__(target=target, doc_based=doc_based)
        self._binding_map: Dict[str, Any] = {}
        self._bindings: Union[Dict[str, Any], List] = {}
        self._having: Optional[MessageType] = None
        self._grouping_str: str = ""
        self._grouping: Optional[
            List[Union[ProtobufMessageType, ProtobufMessageCextType]]
        ] = None
        self._limit_offset: int = 0
        self._limit_row_count: int = None
        self._projection_str: str = ""
        self._projection_expr: Optional[
            List[Union[ProtobufMessageType, ProtobufMessageCextType]]
        ] = None
        self._sort_str: str = ""
        self._sort_expr: Optional[
            List[Union[ProtobufMessageType, ProtobufMessageCextType]]
        ] = None
        self._where_str: str = ""
        self._where_expr: MessageType = None
        self.has_bindings: bool = False
        self.has_limit: bool = False
        self.has_group_by: bool = False
        self.has_having: bool = False
        self.has_projection: bool = False
        self.has_sort: bool = False
        self.has_where: bool = False
        if condition:
            self._set_where(condition)

    def _bind_single(self, obj: Union[DbDoc, Dict[str, Any], str]) -> None:
        """Bind single object.

        Args:
            obj (:class:`mysqlx.DbDoc` or str): DbDoc or JSON string object.

        Raises:
            :class:`mysqlx.ProgrammingError`: If invalid JSON string to bind.
            ValueError: If JSON loaded is not a dictionary.
        """
        if isinstance(obj, dict):
            self.bind(DbDoc(obj).as_str())
        elif isinstance(obj, DbDoc):
            self.bind(obj.as_str())
        elif isinstance(obj, str):
            try:
                res = json.loads(obj)
                if not isinstance(res, dict):
                    raise ValueError
            except ValueError as err:
                raise ProgrammingError("Invalid JSON string to bind") from err
            for key in res.keys():
                self.bind(key, res[key])
        else:
            raise ProgrammingError("Invalid JSON string or object to bind")

    def _sort(self, *clauses: str) -> FilterableStatement:
        """Sets the sorting criteria.

        Args:
            *clauses: The expression strings defining the sort criteria.

        Returns:
            mysqlx.FilterableStatement: FilterableStatement object.
        """
        self.has_sort = True
        self._sort_str = ",".join(flexible_params(*clauses))
        self._sort_expr = ExprParser(
            self._sort_str, not self._doc_based
        ).parse_order_spec()
        self._changed = True
        return self

    def _set_where(self, condition: str) -> FilterableStatement:
        """Sets the search condition to filter.

        Args:
            condition (str): Sets the search condition to filter documents or
                             records.

        Returns:
            mysqlx.FilterableStatement: FilterableStatement object.
        """
        self.has_where = True
        self._where_str = condition
        try:
            expr = ExprParser(condition, not self._doc_based)
            self._where_expr = expr.expr()
        except ValueError as err:
            raise ProgrammingError("Invalid condition") from err
        self._binding_map = expr.placeholder_name_to_position
        self._changed = True
        return self

    def _set_group_by(self, *fields: str) -> None:
        """Set group by.

        Args:
            *fields: List of fields.
        """
        fields = flexible_params(*fields)
        self.has_group_by = True
        self._grouping_str = ",".join(fields)
        self._grouping = ExprParser(
            self._grouping_str, not self._doc_based
        ).parse_expr_list()
        self._changed = True

    def _set_having(self, condition: str) -> None:
        """Set having.

        Args:
            condition (str): The condition.
        """
        self.has_having = True
        self._having = ExprParser(condition, not self._doc_based).expr()
        self._changed = True

    def _set_projection(self, *fields: str) -> FilterableStatement:
        """Set the projection.

        Args:
            *fields: List of fields.

        Returns:
            :class:`mysqlx.FilterableStatement`: Returns self.
        """
        fields = flexible_params(*fields)
        self.has_projection = True
        self._projection_str = ",".join(fields)
        self._projection_expr = ExprParser(
            self._projection_str, not self._doc_based
        ).parse_table_select_projection()
        self._changed = True
        return self

    def get_binding_map(self) -> Dict[str, Any]:
        """Returns the binding map dictionary.

        Returns:
            dict: The binding map dictionary.
        """
        return self._binding_map

    def get_bindings(self) -> Union[Dict[str, Any], List]:
        """Returns the bindings list.

        Returns:
            `list`: The bindings list.
        """
        return self._bindings

    def get_grouping(self) -> List[Union[ProtobufMessageType, ProtobufMessageCextType]]:
        """Returns the grouping expression list.

        Returns:
            `list`: The grouping expression list.
        """
        return self._grouping

    def get_having(self) -> MessageType:
        """Returns the having expression.

        Returns:
            object: The having expression.
        """
        return self._having

    def get_limit_row_count(self) -> int:
        """Returns the limit row count.

        Returns:
            int: The limit row count.
        """
        return self._limit_row_count

    def get_limit_offset(self) -> int:
        """Returns the limit offset.

        Returns:
            int: The limit offset.
        """
        return self._limit_offset

    def get_where_expr(self) -> MessageType:
        """Returns the where expression.

        Returns:
            object: The where expression.
        """
        return self._where_expr

    def get_projection_expr(
        self,
    ) -> List[Union[ProtobufMessageType, ProtobufMessageCextType]]:
        """Returns the projection expression.

        Returns:
            object: The projection expression.
        """
        return self._projection_expr

    def get_sort_expr(
        self,
    ) -> List[Union[ProtobufMessageType, ProtobufMessageCextType]]:
        """Returns the sort expression.

        Returns:
            object: The sort expression.
        """
        return self._sort_expr

    @deprecated("8.0.12")
    def where(self, condition: str) -> FilterableStatement:
        """Sets the search condition to filter.

        Args:
            condition (str): Sets the search condition to filter documents or
                             records.

        Returns:
            mysqlx.FilterableStatement: FilterableStatement object.

        .. deprecated:: 8.0.12
        """
        return self._set_where(condition)

    @deprecated("8.0.12")
    def sort(self, *clauses: str) -> FilterableStatement:
        """Sets the sorting criteria.

        Args:
            *clauses: The expression strings defining the sort criteria.

        Returns:
            mysqlx.FilterableStatement: FilterableStatement object.

        .. deprecated:: 8.0.12
        """
        return self._sort(*clauses)

    def limit(
        self, row_count: int, offset: Optional[int] = None
    ) -> FilterableStatement:
        """Sets the maximum number of items to be returned.

        Args:
            row_count (int): The maximum number of items.

        Returns:
            mysqlx.FilterableStatement: FilterableStatement object.

        Raises:
            ValueError: If ``row_count`` is not a positive integer.

        .. versionchanged:: 8.0.12
           The usage of ``offset`` was deprecated.
        """
        if not isinstance(row_count, int) or row_count < 0:
            raise ValueError("The 'row_count' value must be a positive integer")
        if not self.has_limit:
            self._changed = bool(self._exec_counter == 0)
            self._deallocate_prepare_execute = bool(not self._exec_counter == 0)

        self._limit_row_count = row_count
        self.has_limit = True
        if offset:
            self.offset(offset)
            warnings.warn(
                "'limit(row_count, offset)' is deprecated, please "
                "use 'offset(offset)' to set the number of items to "
                "skip",
                category=DeprecationWarning,
            )
        return self

    def offset(self, offset: int) -> FilterableStatement:
        """Sets the number of items to skip.

        Args:
            offset (int): The number of items to skip.

        Returns:
            mysqlx.FilterableStatement: FilterableStatement object.

        Raises:
            ValueError: If ``offset`` is not a positive integer.

        .. versionadded:: 8.0.12
        """
        if not isinstance(offset, int) or offset < 0:
            raise ValueError("The 'offset' value must be a positive integer")
        self._limit_offset = offset
        return self

    def bind(self, *args: Any) -> FilterableStatement:
        """Binds value(s) to a specific placeholder(s).

        Args:
            *args: The name of the placeholder and the value to bind.
                   A :class:`mysqlx.DbDoc` object or a JSON string
                   representation can be used.

        Returns:
            mysqlx.FilterableStatement: FilterableStatement object.

        Raises:
            ProgrammingError: If the number of arguments is invalid.
        """
        self.has_bindings = True
        count = len(args)
        if count == 1:
            self._bind_single(args[0])
        elif count == 2:
            self._bindings[args[0]] = args[1]
        else:
            raise ProgrammingError("Invalid number of arguments to bind")
        return self

    def execute(self) -> Any:
        """Execute the statement.

        Raises:
           NotImplementedError: This method must be implemented.
        """
        raise NotImplementedError


class SqlStatement(Statement):
    """A statement for SQL execution.

    Args:
        connection (mysqlx.connection.Connection): Connection object.
        sql (string): The sql statement to be executed.
    """

    def __init__(self, connection: ConnectionType, sql: str) -> None:
        super().__init__(target=None, doc_based=False)
        self._connection: ConnectionType = connection
        self._sql: str = sql
        self._binding_map: Optional[Dict[str, Any]] = None
        self._bindings: Union[List, Tuple] = []
        self.has_bindings: bool = False
        self.has_limit: bool = False

    @property
    def sql(self) -> str:
        """string: The SQL text statement."""
        return self._sql

    def get_binding_map(self) -> Dict[str, Any]:
        """Returns the binding map dictionary.

        Returns:
            dict: The binding map dictionary.
        """
        return self._binding_map

    def get_bindings(self) -> Union[Tuple, List]:
        """Returns the bindings list.

        Returns:
            `list`: The bindings list.
        """
        return self._bindings

    def bind(self, *args: Any) -> SqlStatement:
        """Binds value(s) to a specific placeholder(s).

        Args:
            *args: The value(s) to bind.

        Returns:
            mysqlx.SqlStatement: SqlStatement object.
        """
        if len(args) == 0:
            raise ProgrammingError("Invalid number of arguments to bind")
        self.has_bindings = True
        bindings = flexible_params(*args)
        if isinstance(bindings, (list, tuple)):
            self._bindings = bindings
        else:
            self._bindings.append(bindings)
        return self

    def execute(self) -> SqlResult:
        """Execute the statement.

        Returns:
            mysqlx.SqlResult: SqlResult object.
        """
        return self._connection.send_sql(self)


class WriteStatement(Statement):
    """Provide common write operation attributes."""

    def __init__(self, target: DatabaseTargetType, doc_based: bool) -> None:
        super().__init__(target, doc_based)
        self._values: List[
            Union[
                int,
                str,
                DbDoc,
                Dict[str, Any],
                List[Optional[Union[str, int, float, ExprParser, Dict[str, Any]]]],
            ]
        ] = []

    def get_values(
        self,
    ) -> List[
        Union[
            int,
            str,
            DbDoc,
            Dict[str, Any],
            List[Optional[Union[str, int, float, ExprParser, Dict[str, Any]]]],
        ]
    ]:
        """Returns the list of values.

        Returns:
            `list`: The list of values.
        """
        return self._values

    def execute(self) -> Any:
        """Execute the statement.

        Raises:
           NotImplementedError: This method must be implemented.
        """
        raise NotImplementedError


class AddStatement(WriteStatement):
    """A statement for document addition on a collection.

    Args:
        collection (mysqlx.Collection): The Collection object.
    """

    def __init__(self, collection: DatabaseTargetType) -> None:
        super().__init__(collection, True)
        self._upsert: bool = False
        self.ids: List = []

    def is_upsert(self) -> bool:
        """Returns `True` if it's an upsert.

        Returns:
            bool: `True` if it's an upsert.
        """
        return self._upsert

    def upsert(self, value: bool = True) -> AddStatement:
        """Sets the upset flag to the boolean of the value provided.
        Setting of this flag allows updating of the matched rows/documents
        with the provided value.

        Args:
            value (optional[bool]): Set or unset the upsert flag.
        """
        self._upsert = value
        return self

    def add(self, *values: DbDoc) -> AddStatement:
        """Adds a list of documents into a collection.

        Args:
            *values: The documents to be added into the collection.

        Returns:
            mysqlx.AddStatement: AddStatement object.
        """
        for val in flexible_params(*values):
            if isinstance(val, DbDoc):
                self._values.append(val)
            else:
                self._values.append(DbDoc(val))
        return self

    def execute(self) -> Result:
        """Execute the statement.

        Returns:
            mysqlx.Result: Result object.
        """
        if len(self._values) == 0:
            return Result()

        return self._connection.send_insert(self)


class UpdateSpec:
    """Update specification class implementation.

    Args:
        update_type (int): The update type.
        source (str): The source.
        value (Optional[str]): The value.

    Raises:
        ProgrammingError: If `source` is invalid.
    """

    def __init__(self, update_type: int, source: str, value: Any = None) -> None:
        if update_type == mysqlxpb_enum("Mysqlx.Crud.UpdateOperation.UpdateType.SET"):
            self._table_set(source, value)
        else:
            self.update_type: int = update_type
            try:
                self.source: Any = ExprParser(source, False).document_field().identifier
            except ValueError as err:
                raise ProgrammingError(f"{err}") from err
            self.value: Any = value

    def _table_set(self, source: str, value: Any) -> None:
        """Table set.

        Args:
            source (str): The source.
            value (str): The value.
        """
        self.update_type = mysqlxpb_enum("Mysqlx.Crud.UpdateOperation.UpdateType.SET")
        self.source = ExprParser(source, True).parse_table_update_field()
        self.value = value


class ModifyStatement(FilterableStatement):
    """A statement for document update operations on a Collection.

    Args:
        collection (mysqlx.Collection): The Collection object.
        condition (str): Sets the search condition to identify the documents
                         to be modified.

    .. versionchanged:: 8.0.12
       The ``condition`` parameter is now mandatory.
    """

    def __init__(self, collection: DatabaseTargetType, condition: str) -> None:
        super().__init__(target=collection, condition=condition)
        self._update_ops: Dict[str, Any] = {}

    def sort(self, *clauses: str) -> ModifyStatement:
        """Sets the sorting criteria.

        Args:
            *clauses: The expression strings defining the sort criteria.

        Returns:
            mysqlx.ModifyStatement: ModifyStatement object.
        """
        return self._sort(*clauses)

    def get_update_ops(self) -> Dict[str, Any]:
        """Returns the list of update operations.

        Returns:
            `list`: The list of update operations.
        """
        return self._update_ops

    def set(self, doc_path: str, value: Any) -> ModifyStatement:
        """Sets or updates attributes on documents in a collection.

        Args:
            doc_path (string): The document path of the item to be set.
            value (string): The value to be set on the specified attribute.

        Returns:
            mysqlx.ModifyStatement: ModifyStatement object.
        """
        self._update_ops[doc_path] = UpdateSpec(
            mysqlxpb_enum("Mysqlx.Crud.UpdateOperation.UpdateType.ITEM_SET"),
            doc_path,
            value,
        )
        self._changed = True
        return self

    @deprecated("8.0.12")
    def change(self, doc_path: str, value: Any) -> ModifyStatement:
        """Add an update to the statement setting the field, if it exists at
        the document path, to the given value.

        Args:
            doc_path (string): The document path of the item to be set.
            value (object): The value to be set on the specified attribute.

        Returns:
            mysqlx.ModifyStatement: ModifyStatement object.

        .. deprecated:: 8.0.12
        """
        self._update_ops[doc_path] = UpdateSpec(
            mysqlxpb_enum("Mysqlx.Crud.UpdateOperation.UpdateType.ITEM_REPLACE"),
            doc_path,
            value,
        )
        self._changed = True
        return self

    def unset(self, *doc_paths: str) -> ModifyStatement:
        """Removes attributes from documents in a collection.

        Args:
            doc_paths (list): The list of document paths of the attributes to be
                              removed.

        Returns:
            mysqlx.ModifyStatement: ModifyStatement object.
        """
        for item in flexible_params(*doc_paths):
            self._update_ops[item] = UpdateSpec(
                mysqlxpb_enum("Mysqlx.Crud.UpdateOperation.UpdateType.ITEM_REMOVE"),
                item,
            )
        self._changed = True
        return self

    def array_insert(self, field: str, value: Any) -> ModifyStatement:
        """Insert a value into the specified array in documents of a
        collection.

        Args:
            field (string): A document path that identifies the array attribute
                            and position where the value will be inserted.
            value (object): The value to be inserted.

        Returns:
            mysqlx.ModifyStatement: ModifyStatement object.
        """
        self._update_ops[field] = UpdateSpec(
            mysqlxpb_enum("Mysqlx.Crud.UpdateOperation.UpdateType.ARRAY_INSERT"),
            field,
            value,
        )
        self._changed = True
        return self

    def array_append(self, doc_path: str, value: Any) -> ModifyStatement:
        """Inserts a value into a specific position in an array attribute in
        documents of a collection.

        Args:
            doc_path (string): A document path that identifies the array
                               attribute and position where the value will be
                               inserted.
            value (object): The value to be inserted.

        Returns:
            mysqlx.ModifyStatement: ModifyStatement object.
        """
        self._update_ops[doc_path] = UpdateSpec(
            mysqlxpb_enum("Mysqlx.Crud.UpdateOperation.UpdateType.ARRAY_APPEND"),
            doc_path,
            value,
        )
        self._changed = True
        return self

    def patch(self, doc: Union[Dict, DbDoc, ExprParser, str]) -> ModifyStatement:
        """Takes a :class:`mysqlx.DbDoc`, string JSON format or a dict with the
        changes and applies it on all matching documents.

        Args:
            doc (object): A generic document (DbDoc), string in JSON format or
                          dict, with the changes to apply to the matching
                          documents.

        Returns:
            mysqlx.ModifyStatement: ModifyStatement object.
        """
        if doc is None:
            doc = ""
        if not isinstance(doc, (ExprParser, dict, DbDoc, str)):
            raise ProgrammingError(
                "Invalid data for update operation on document collection table"
            )
        self._update_ops["patch"] = UpdateSpec(
            mysqlxpb_enum("Mysqlx.Crud.UpdateOperation.UpdateType.MERGE_PATCH"),
            "$",
            doc.expr() if isinstance(doc, ExprParser) else doc,
        )
        self._changed = True
        return self

    def execute(self) -> Result:
        """Execute the statement.

        Returns:
            mysqlx.Result: Result object.

        Raises:
            ProgrammingError: If condition was not set.
        """
        if not self.has_where:
            raise ProgrammingError("No condition was found for modify")
        return self._connection.send_update(self)


class ReadStatement(FilterableStatement):
    """Provide base functionality for Read operations

    Args:
        target (object): The target database object, it can be
                         :class:`mysqlx.Collection` or :class:`mysqlx.Table`.
        doc_based (Optional[bool]): `True` if it is document based
                                    (default: `True`).
        condition (Optional[str]): Sets the search condition to filter
                                   documents or records.
    """

    def __init__(
        self,
        target: DatabaseTargetType,
        doc_based: bool = True,
        condition: Optional[str] = None,
    ) -> None:
        super().__init__(target, doc_based, condition)
        self._lock_exclusive: bool = False
        self._lock_shared: bool = False
        self._lock_contention: LockContention = LockContention.DEFAULT

    @property
    def lock_contention(self) -> LockContention:
        """:class:`mysqlx.LockContention`: The lock contention value."""
        return self._lock_contention

    def _set_lock_contention(self, lock_contention: LockContention) -> None:
        """Set the lock contention.

        Args:
            lock_contention (:class:`mysqlx.LockContention`): Lock contention.

        Raises:
            ProgrammingError: If is an invalid lock contention value.
        """
        try:
            # Check if is a valid lock contention value
            _ = LockContention(lock_contention.value)
        except ValueError as err:
            raise ProgrammingError(
                "Invalid lock contention mode. Use 'NOWAIT' or 'SKIP_LOCKED'"
            ) from err
        self._lock_contention = lock_contention

    def is_lock_exclusive(self) -> bool:
        """Returns `True` if is `EXCLUSIVE LOCK`.

        Returns:
            bool: `True` if is `EXCLUSIVE LOCK`.
        """
        return self._lock_exclusive

    def is_lock_shared(self) -> bool:
        """Returns `True` if is `SHARED LOCK`.

        Returns:
            bool: `True` if is `SHARED LOCK`.
        """
        return self._lock_shared

    def lock_shared(
        self, lock_contention: LockContention = LockContention.DEFAULT
    ) -> ReadStatement:
        """Execute a read operation with `SHARED LOCK`. Only one lock can be
           active at a time.

        Args:
            lock_contention (:class:`mysqlx.LockContention`): Lock contention.
        """
        self._lock_exclusive = False
        self._lock_shared = True
        self._set_lock_contention(lock_contention)
        return self

    def lock_exclusive(
        self, lock_contention: LockContention = LockContention.DEFAULT
    ) -> ReadStatement:
        """Execute a read operation with `EXCLUSIVE LOCK`. Only one lock can be
           active at a time.

        Args:
            lock_contention (:class:`mysqlx.LockContention`): Lock contention.
        """
        self._lock_exclusive = True
        self._lock_shared = False
        self._set_lock_contention(lock_contention)
        return self

    def group_by(self, *fields: str) -> ReadStatement:
        """Sets a grouping criteria for the resultset.

        Args:
            *fields: The string expressions identifying the grouping criteria.

        Returns:
            mysqlx.ReadStatement: ReadStatement object.
        """
        self._set_group_by(*fields)
        return self

    def having(self, condition: str) -> ReadStatement:
        """Sets a condition for records to be considered in agregate function
        operations.

        Args:
            condition (string): A condition on the agregate functions used on
                                the grouping criteria.

        Returns:
            mysqlx.ReadStatement: ReadStatement object.
        """
        self._set_having(condition)
        return self

    def execute(self) -> Union[DocResult, RowResult]:
        """Execute the statement.

        Returns:
            mysqlx.Result: Result object.
        """
        return self._connection.send_find(self)


class FindStatement(ReadStatement):
    """A statement document selection on a Collection.

    Args:
        collection (mysqlx.Collection): The Collection object.
        condition (Optional[str]): An optional expression to identify the
                                   documents to be retrieved. If not specified
                                   all the documents will be included on the
                                   result unless a limit is set.
    """

    def __init__(
        self, collection: DatabaseTargetType, condition: Optional[str] = None
    ) -> None:
        super().__init__(collection, True, condition)

    def fields(self, *fields: str) -> FindStatement:
        """Sets a document field filter.

        Args:
            *fields: The string expressions identifying the fields to be
                     extracted.

        Returns:
            mysqlx.FindStatement: FindStatement object.
        """
        return self._set_projection(*fields)

    def sort(self, *clauses: str) -> FindStatement:
        """Sets the sorting criteria.

        Args:
            *clauses: The expression strings defining the sort criteria.

        Returns:
            mysqlx.FindStatement: FindStatement object.
        """
        return self._sort(*clauses)


class SelectStatement(ReadStatement):
    """A statement for record retrieval operations on a Table.

    Args:
        table (mysqlx.Table): The Table object.
        *fields: The fields to be retrieved.
    """

    def __init__(self, table: DatabaseTargetType, *fields: str) -> None:
        super().__init__(table, False)
        self._set_projection(*fields)

    def where(self, condition: str) -> SelectStatement:
        """Sets the search condition to filter.

        Args:
            condition (str): Sets the search condition to filter records.

        Returns:
            mysqlx.SelectStatement: SelectStatement object.
        """
        return self._set_where(condition)

    def order_by(self, *clauses: str) -> SelectStatement:
        """Sets the order by criteria.

        Args:
            *clauses: The expression strings defining the order by criteria.

        Returns:
            mysqlx.SelectStatement: SelectStatement object.
        """
        return self._sort(*clauses)

    def get_sql(self) -> str:
        """Returns the generated SQL.

        Returns:
            str: The generated SQL.
        """
        where = f" WHERE {self._where_str}" if self.has_where else ""
        group_by = f" GROUP BY {self._grouping_str}" if self.has_group_by else ""
        having = f" HAVING {self._having}" if self.has_having else ""
        order_by = f" ORDER BY {self._sort_str}" if self.has_sort else ""
        limit = (
            f" LIMIT {self._limit_row_count} OFFSET {self._limit_offset}"
            if self.has_limit
            else ""
        )
        stmt = (
            f"SELECT {self._projection_str or '*'} "
            f"FROM {self.schema.name}.{self.target.name}"
            f"{where}{group_by}{having}{order_by}{limit}"
        )
        return stmt


class InsertStatement(WriteStatement):
    """A statement for insert operations on Table.

    Args:
        table (mysqlx.Table): The Table object.
        *fields: The fields to be inserted.
    """

    def __init__(self, table: DatabaseTargetType, *fields: Any) -> None:
        super().__init__(table, False)
        self._fields: Union[List, Tuple] = flexible_params(*fields)

    def values(self, *values: Any) -> InsertStatement:
        """Set the values to be inserted.

        Args:
            *values: The values of the columns to be inserted.

        Returns:
            mysqlx.InsertStatement: InsertStatement object.
        """
        self._values.append(list(flexible_params(*values)))
        return self

    def execute(self) -> Result:
        """Execute the statement.

        Returns:
            mysqlx.Result: Result object.
        """
        return self._connection.send_insert(self)


class UpdateStatement(FilterableStatement):
    """A statement for record update operations on a Table.

    Args:
        table (mysqlx.Table): The Table object.

    .. versionchanged:: 8.0.12
       The ``fields`` parameters were removed.
    """

    def __init__(self, table: DatabaseTargetType) -> None:
        super().__init__(target=table, doc_based=False)
        self._update_ops: Dict[str, Any] = {}

    def where(self, condition: str) -> UpdateStatement:
        """Sets the search condition to filter.

        Args:
            condition (str): Sets the search condition to filter records.

        Returns:
            mysqlx.UpdateStatement: UpdateStatement object.
        """
        return self._set_where(condition)

    def order_by(self, *clauses: str) -> UpdateStatement:
        """Sets the order by criteria.

        Args:
            *clauses: The expression strings defining the order by criteria.

        Returns:
            mysqlx.UpdateStatement: UpdateStatement object.
        """
        return self._sort(*clauses)

    def get_update_ops(self) -> Dict[str, Any]:
        """Returns the list of update operations.

        Returns:
            `list`: The list of update operations.
        """
        return self._update_ops

    def set(self, field: str, value: Any) -> UpdateStatement:
        """Updates the column value on records in a table.

        Args:
            field (string): The column name to be updated.
            value (object): The value to be set on the specified column.

        Returns:
            mysqlx.UpdateStatement: UpdateStatement object.
        """
        self._update_ops[field] = UpdateSpec(
            mysqlxpb_enum("Mysqlx.Crud.UpdateOperation.UpdateType.SET"),
            field,
            value,
        )
        self._changed = True
        return self

    def execute(self) -> Result:
        """Execute the statement.

        Returns:
            mysqlx.Result: Result object

        Raises:
            ProgrammingError: If condition was not set.
        """
        if not self.has_where:
            raise ProgrammingError("No condition was found for update")
        return self._connection.send_update(self)


class RemoveStatement(FilterableStatement):
    """A statement for document removal from a collection.

    Args:
        collection (mysqlx.Collection): The Collection object.
        condition (str): Sets the search condition to identify the documents
                         to be removed.

    .. versionchanged:: 8.0.12
       The ``condition`` parameter was added.
    """

    def __init__(self, collection: DatabaseTargetType, condition: str) -> None:
        super().__init__(target=collection, condition=condition)

    def sort(self, *clauses: str) -> RemoveStatement:
        """Sets the sorting criteria.

        Args:
            *clauses: The expression strings defining the sort criteria.

        Returns:
            mysqlx.FindStatement: FindStatement object.
        """
        return self._sort(*clauses)

    def execute(self) -> Result:
        """Execute the statement.

        Returns:
            mysqlx.Result: Result object.

        Raises:
            ProgrammingError: If condition was not set.
        """
        if not self.has_where:
            raise ProgrammingError("No condition was found for remove")
        return self._connection.send_delete(self)


class DeleteStatement(FilterableStatement):
    """A statement that drops a table.

    Args:
        table (mysqlx.Table): The Table object.

    .. versionchanged:: 8.0.12
       The ``condition`` parameter was removed.
    """

    def __init__(self, table: DatabaseTargetType) -> None:
        super().__init__(target=table, doc_based=False)

    def where(self, condition: str) -> DeleteStatement:
        """Sets the search condition to filter.

        Args:
            condition (str): Sets the search condition to filter records.

        Returns:
            mysqlx.DeleteStatement: DeleteStatement object.
        """
        return self._set_where(condition)

    def order_by(self, *clauses: str) -> DeleteStatement:
        """Sets the order by criteria.

        Args:
            *clauses: The expression strings defining the order by criteria.

        Returns:
            mysqlx.DeleteStatement: DeleteStatement object.
        """
        return self._sort(*clauses)

    def execute(self) -> Result:
        """Execute the statement.

        Returns:
            mysqlx.Result: Result object.

        Raises:
            ProgrammingError: If condition was not set.
        """
        if not self.has_where:
            raise ProgrammingError("No condition was found for delete")
        return self._connection.send_delete(self)


class CreateCollectionIndexStatement(Statement):
    """A statement that creates an index on a collection.

    Args:
        collection (mysqlx.Collection): Collection.
        index_name (string): Index name.
        index_desc (dict): A dictionary containing the fields members that
                           constraints the index to be created. It must have
                           the form as shown in the following::

                               {"fields": [{"field": member_path,
                                            "type": member_type,
                                            "required": member_required,
                                            "collation": collation,
                                            "options": options,
                                            "srid": srid},
                                            # {... more members,
                                            #      repeated as many times
                                            #      as needed}
                                            ],
                                "type": type}
    """

    def __init__(
        self,
        collection: DatabaseTargetType,
        index_name: str,
        index_desc: Dict[str, Any],
    ) -> None:
        super().__init__(target=collection)
        self._index_desc: Dict[str, Any] = copy.deepcopy(index_desc)
        self._index_name: str = index_name
        self._fields_desc: List[Dict[str, Any]] = self._index_desc.pop("fields", [])

    def execute(self) -> Result:
        """Execute the statement.

        Returns:
            mysqlx.Result: Result object.
        """
        # Validate index name is a valid identifier
        if self._index_name is None:
            raise ProgrammingError(ERR_INVALID_INDEX_NAME.format(self._index_name))
        try:
            parsed_ident = ExprParser(self._index_name).expr().get_message()

            # The message is type dict when the Protobuf cext is used
            if isinstance(parsed_ident, dict):
                if parsed_ident["type"] != mysqlxpb_enum("Mysqlx.Expr.Expr.Type.IDENT"):
                    raise ProgrammingError(
                        ERR_INVALID_INDEX_NAME.format(self._index_name)
                    )
            else:
                if parsed_ident.type != mysqlxpb_enum("Mysqlx.Expr.Expr.Type.IDENT"):
                    raise ProgrammingError(
                        ERR_INVALID_INDEX_NAME.format(self._index_name)
                    )

        except (ValueError, AttributeError) as err:
            raise ProgrammingError(
                ERR_INVALID_INDEX_NAME.format(self._index_name)
            ) from err

        # Validate members that constraint the index
        if not self._fields_desc:
            raise ProgrammingError(
                "Required member 'fields' not found in the given index "
                f"description: {self._index_desc}"
            )

        if not isinstance(self._fields_desc, list):
            raise ProgrammingError("Required member 'fields' must contain a list")
        args: Dict[str, Any] = {}
        args["name"] = self._index_name
        args["collection"] = self._target.name
        args["schema"] = self._target.schema.name
        if "type" in self._index_desc:
            args["type"] = self._index_desc.pop("type")
        else:
            args["type"] = "INDEX"
        args["unique"] = self._index_desc.pop("unique", False)
        # Currently unique indexes are not supported:
        if args["unique"]:
            raise NotSupportedError("Unique indexes are not supported.")
        args["constraint"] = []

        if self._index_desc:
            raise ProgrammingError(f"Unidentified fields: {self._index_desc}")

        try:
            for field_desc in self._fields_desc:
                constraint = {}
                constraint["member"] = field_desc.pop("field")
                constraint["type"] = field_desc.pop("type")
                constraint["required"] = field_desc.pop("required", False)
                constraint["array"] = field_desc.pop("array", False)
                if not isinstance(constraint["required"], bool):
                    raise TypeError("Field member 'required' must be Boolean")
                if not isinstance(constraint["array"], bool):
                    raise TypeError("Field member 'array' must be Boolean")
                if args["type"].upper() == "SPATIAL" and not constraint["required"]:
                    raise ProgrammingError(
                        "Field member 'required' must be set to 'True' when "
                        "index type is set to 'SPATIAL'"
                    )
                if args["type"].upper() == "INDEX" and constraint["type"] == "GEOJSON":
                    raise ProgrammingError(
                        "Index 'type' must be set to 'SPATIAL' when field "
                        "type is set to 'GEOJSON'"
                    )
                if "collation" in field_desc:
                    if not constraint["type"].upper().startswith("TEXT"):
                        raise ProgrammingError(
                            "The 'collation' member can only be used when "
                            "field type is set to "
                            f"'{constraint['type'].upper()}'"
                        )
                    constraint["collation"] = field_desc.pop("collation")
                # "options" and "srid" fields in IndexField can be
                # present only if "type" is set to "GEOJSON"
                if "options" in field_desc:
                    if constraint["type"].upper() != "GEOJSON":
                        raise ProgrammingError(
                            "The 'options' member can only be used when "
                            "index type is set to 'GEOJSON'"
                        )
                    constraint["options"] = field_desc.pop("options")
                if "srid" in field_desc:
                    if constraint["type"].upper() != "GEOJSON":
                        raise ProgrammingError(
                            "The 'srid' member can only be used when index "
                            "type is set to 'GEOJSON'"
                        )
                    constraint["srid"] = field_desc.pop("srid")
                args["constraint"].append(constraint)
        except KeyError as err:
            raise ProgrammingError(
                f"Required inner member {err} not found in constraint: {field_desc}"
            ) from err

        for field_desc in self._fields_desc:
            if field_desc:
                raise ProgrammingError(f"Unidentified inner fields: {field_desc}")

        return self._connection.execute_nonquery(
            "mysqlx", "create_collection_index", True, args
        )

virt/lib/python3.9/site-packages/mysqlx/statement.py
PrinceDisant/LostInNL
"""
Type hint aliases hub
"""

import typing

from datetime import datetime, timedelta
from decimal import Decimal
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Type, Union

if hasattr(typing, "TypeAlias"):
    # pylint: disable=no-name-in-module
    from typing import TypeAlias  # type: ignore[attr-defined]
else:
    try:
        from typing_extensions import TypeAlias
    except ModuleNotFoundError:
        # pylint: disable=reimported
        from typing import Any as TypeAlias


if TYPE_CHECKING:
    from google.protobuf.message import Message as ProtoMessage

    # pylint: disable=redefined-builtin
    from .connection import Connection, Session, SocketStream
    from .crud import DatabaseObject, Schema
    from .dbdoc import DbDoc
    from .errors import (
        DatabaseError,
        DataError,
        Error,
        IntegrityError,
        InterfaceError,
        InternalError,
        NotSupportedError,
        OperationalError,
        PoolError,
        ProgrammingError,
        TimeoutError,
    )
    from .expr import ExprParser
    from .protobuf import Message as XdevMessage
    from .result import BaseResult, Column
    from .statement import Statement


StrOrBytes = Union[str, bytes]

BuildScalarTypes = Optional[Union[str, bytes, bool, int, float]]
BuildExprTypes = Union[
    "XdevMessage", "ExprParser", Dict[str, Any], "DbDoc", List, Tuple, BuildScalarTypes
]
ColumnType: TypeAlias = "Column"
ConnectionType: TypeAlias = "Connection"
DatabaseTargetType: TypeAlias = "DatabaseObject"
ErrorClassTypes = Union[
    Type["Error"],
    Type["InterfaceError"],
    Type["DatabaseError"],
    Type["InternalError"],
    Type["OperationalError"],
    Type["ProgrammingError"],
    Type["IntegrityError"],
    Type["DataError"],
    Type["NotSupportedError"],
    Type["PoolError"],
    Type["TimeoutError"],
]
ErrorTypes = Union[
    "Error",
    "InterfaceError",
    "DatabaseError",
    "InternalError",
    "OperationalError",
    "ProgrammingError",
    "IntegrityError",
    "DataError",
    "NotSupportedError",
    "PoolError",
    "TimeoutError",
]
EscapeTypes = Optional[Union[int, float, Decimal, StrOrBytes]]
FieldTypes = Optional[Union[int, float, str, bytes, Decimal, datetime, timedelta]]
MessageType: TypeAlias = "XdevMessage"
ProtobufMessageType: TypeAlias = "ProtoMessage"
ProtobufMessageCextType = Dict[str, Any]
ResultBaseType: TypeAlias = "BaseResult"
SchemaType: TypeAlias = "Schema"
SessionType: TypeAlias = "Session"
SocketType: TypeAlias = "SocketStream"
StatementType: TypeAlias = "Statement"

virt/lib/python3.9/site-packages/mysqlx/types.py
PrinceDisant/LostInNL
from typing import Union

from .align import AlignMethod
from .cells import cell_len, set_cell_size
from .console import Console, ConsoleOptions, RenderResult
from .jupyter import JupyterMixin
from .measure import Measurement
from .style import Style
from .text import Text


class Rule(JupyterMixin):
    """A console renderable to draw a horizontal rule (line).

    Args:
        title (Union[str, Text], optional): Text to render in the rule. Defaults to "".
        characters (str, optional): Character(s) used to draw the line. Defaults to "─".
        style (StyleType, optional): Style of Rule. Defaults to "rule.line".
        end (str, optional): Character at end of Rule. defaults to "\\\\n"
        align (str, optional): How to align the title, one of "left", "center", or "right". Defaults to "center".
    """

    def __init__(
        self,
        title: Union[str, Text] = "",
        *,
        characters: str = "─",
        style: Union[str, Style] = "rule.line",
        end: str = "\n",
        align: AlignMethod = "center",
    ) -> None:
        if cell_len(characters) < 1:
            raise ValueError(
                "'characters' argument must have a cell width of at least 1"
            )
        if align not in ("left", "center", "right"):
            raise ValueError(
                f'invalid value for align, expected "left", "center", "right" (not {align!r})'
            )
        self.title = title
        self.characters = characters
        self.style = style
        self.end = end
        self.align = align

    def __repr__(self) -> str:
        return f"Rule({self.title!r}, {self.characters!r})"

    def __rich_console__(
        self, console: Console, options: ConsoleOptions
    ) -> RenderResult:
        width = options.max_width

        characters = (
            "-"
            if (options.ascii_only and not self.characters.isascii())
            else self.characters
        )

        chars_len = cell_len(characters)
        if not self.title:
            yield self._rule_line(chars_len, width)
            return

        if isinstance(self.title, Text):
            title_text = self.title
        else:
            title_text = console.render_str(self.title, style="rule.text")

        title_text.plain = title_text.plain.replace("\n", " ")
        title_text.expand_tabs()

        required_space = 4 if self.align == "center" else 2
        truncate_width = max(0, width - required_space)
        if not truncate_width:
            yield self._rule_line(chars_len, width)
            return

        rule_text = Text(end=self.end)
        if self.align == "center":
            title_text.truncate(truncate_width, overflow="ellipsis")
            side_width = (width - cell_len(title_text.plain)) // 2
            left = Text(characters * (side_width // chars_len + 1))
            left.truncate(side_width - 1)
            right_length = width - cell_len(left.plain) - cell_len(title_text.plain)
            right = Text(characters * (side_width // chars_len + 1))
            right.truncate(right_length)
            rule_text.append(left.plain + " ", self.style)
            rule_text.append(title_text)
            rule_text.append(" " + right.plain, self.style)
        elif self.align == "left":
            title_text.truncate(truncate_width, overflow="ellipsis")
            rule_text.append(title_text)
            rule_text.append(" ")
            rule_text.append(characters * (width - rule_text.cell_len), self.style)
        elif self.align == "right":
            title_text.truncate(truncate_width, overflow="ellipsis")
            rule_text.append(characters * (width - title_text.cell_len - 1), self.style)
            rule_text.append(" ")
            rule_text.append(title_text)

        rule_text.plain = set_cell_size(rule_text.plain, width)
        yield rule_text

    def _rule_line(self, chars_len: int, width: int) -> Text:
        rule_text = Text(self.characters * ((width // chars_len) + 1), self.style)
        rule_text.truncate(width)
        rule_text.plain = set_cell_size(rule_text.plain, width)
        return rule_text

    def __rich_measure__(
        self, console: Console, options: ConsoleOptions
    ) -> Measurement:
        return Measurement(1, 1)


if __name__ == "__main__":  # pragma: no cover
    import sys

    from pip._vendor.rich.console import Console

    try:
        text = sys.argv[1]
    except IndexError:
        text = "Hello, World"
    console = Console()
    console.print(Rule(title=text))

    console = Console()
    console.print(Rule("foo"), width=4)

virt/lib/python3.9/site-packages/pip/_vendor/rich/rule.py
PrinceDisant/LostInNL
import abc
import collections
import collections.abc
import functools
import inspect
import operator
import sys
import types as _types
import typing
import warnings

__all__ = [
    # Super-special typing primitives.
    'Any',
    'ClassVar',
    'Concatenate',
    'Final',
    'LiteralString',
    'ParamSpec',
    'ParamSpecArgs',
    'ParamSpecKwargs',
    'Self',
    'Type',
    'TypeVar',
    'TypeVarTuple',
    'Unpack',

    # ABCs (from collections.abc).
    'Awaitable',
    'AsyncIterator',
    'AsyncIterable',
    'Coroutine',
    'AsyncGenerator',
    'AsyncContextManager',
    'Buffer',
    'ChainMap',

    # Concrete collection types.
    'ContextManager',
    'Counter',
    'Deque',
    'DefaultDict',
    'NamedTuple',
    'OrderedDict',
    'TypedDict',

    # Structural checks, a.k.a. protocols.
    'SupportsAbs',
    'SupportsBytes',
    'SupportsComplex',
    'SupportsFloat',
    'SupportsIndex',
    'SupportsInt',
    'SupportsRound',

    # One-off things.
    'Annotated',
    'assert_never',
    'assert_type',
    'clear_overloads',
    'dataclass_transform',
    'deprecated',
    'Doc',
    'get_overloads',
    'final',
    'get_args',
    'get_origin',
    'get_original_bases',
    'get_protocol_members',
    'get_type_hints',
    'IntVar',
    'is_protocol',
    'is_typeddict',
    'Literal',
    'NewType',
    'overload',
    'override',
    'Protocol',
    'reveal_type',
    'runtime',
    'runtime_checkable',
    'Text',
    'TypeAlias',
    'TypeAliasType',
    'TypeGuard',
    'TYPE_CHECKING',
    'Never',
    'NoReturn',
    'Required',
    'NotRequired',

    # Pure aliases, have always been in typing
    'AbstractSet',
    'AnyStr',
    'BinaryIO',
    'Callable',
    'Collection',
    'Container',
    'Dict',
    'ForwardRef',
    'FrozenSet',
    'Generator',
    'Generic',
    'Hashable',
    'IO',
    'ItemsView',
    'Iterable',
    'Iterator',
    'KeysView',
    'List',
    'Mapping',
    'MappingView',
    'Match',
    'MutableMapping',
    'MutableSequence',
    'MutableSet',
    'Optional',
    'Pattern',
    'Reversible',
    'Sequence',
    'Set',
    'Sized',
    'TextIO',
    'Tuple',
    'Union',
    'ValuesView',
    'cast',
    'no_type_check',
    'no_type_check_decorator',
]

# for backward compatibility
PEP_560 = True
GenericMeta = type

# The functions below are modified copies of typing internal helpers.
# They are needed by _ProtocolMeta and they provide support for PEP 646.


class _Sentinel:
    def __repr__(self):
        return "<sentinel>"


_marker = _Sentinel()


def _check_generic(cls, parameters, elen=_marker):
    """Check correct count for parameters of a generic cls (internal helper).
    This gives a nice error message in case of count mismatch.
    """
    if not elen:
        raise TypeError(f"{cls} is not a generic class")
    if elen is _marker:
        if not hasattr(cls, "__parameters__") or not cls.__parameters__:
            raise TypeError(f"{cls} is not a generic class")
        elen = len(cls.__parameters__)
    alen = len(parameters)
    if alen != elen:
        if hasattr(cls, "__parameters__"):
            parameters = [p for p in cls.__parameters__ if not _is_unpack(p)]
            num_tv_tuples = sum(isinstance(p, TypeVarTuple) for p in parameters)
            if (num_tv_tuples > 0) and (alen >= elen - num_tv_tuples):
                return
        raise TypeError(f"Too {'many' if alen > elen else 'few'} parameters for {cls};"
                        f" actual {alen}, expected {elen}")


if sys.version_info >= (3, 10):
    def _should_collect_from_parameters(t):
        return isinstance(
            t, (typing._GenericAlias, _types.GenericAlias, _types.UnionType)
        )
elif sys.version_info >= (3, 9):
    def _should_collect_from_parameters(t):
        return isinstance(t, (typing._GenericAlias, _types.GenericAlias))
else:
    def _should_collect_from_parameters(t):
        return isinstance(t, typing._GenericAlias) and not t._special


def _collect_type_vars(types, typevar_types=None):
    """Collect all type variable contained in types in order of
    first appearance (lexicographic order). For example::

        _collect_type_vars((T, List[S, T])) == (T, S)
    """
    if typevar_types is None:
        typevar_types = typing.TypeVar
    tvars = []
    for t in types:
        if (
            isinstance(t, typevar_types) and
            t not in tvars and
            not _is_unpack(t)
        ):
            tvars.append(t)
        if _should_collect_from_parameters(t):
            tvars.extend([t for t in t.__parameters__ if t not in tvars])
    return tuple(tvars)


NoReturn = typing.NoReturn

# Some unconstrained type variables.  These are used by the container types.
# (These are not for export.)
T = typing.TypeVar('T')  # Any type.
KT = typing.TypeVar('KT')  # Key type.
VT = typing.TypeVar('VT')  # Value type.
T_co = typing.TypeVar('T_co', covariant=True)  # Any type covariant containers.
T_contra = typing.TypeVar('T_contra', contravariant=True)  # Ditto contravariant.


if sys.version_info >= (3, 11):
    from typing import Any
else:

    class _AnyMeta(type):
        def __instancecheck__(self, obj):
            if self is Any:
                raise TypeError("typing_extensions.Any cannot be used with isinstance()")
            return super().__instancecheck__(obj)

        def __repr__(self):
            if self is Any:
                return "typing_extensions.Any"
            return super().__repr__()

    class Any(metaclass=_AnyMeta):
        """Special type indicating an unconstrained type.
        - Any is compatible with every type.
        - Any assumed to have all methods.
        - All values assumed to be instances of Any.
        Note that all the above statements are true from the point of view of
        static type checkers. At runtime, Any should not be used with instance
        checks.
        """
        def __new__(cls, *args, **kwargs):
            if cls is Any:
                raise TypeError("Any cannot be instantiated")
            return super().__new__(cls, *args, **kwargs)


ClassVar = typing.ClassVar


class _ExtensionsSpecialForm(typing._SpecialForm, _root=True):
    def __repr__(self):
        return 'typing_extensions.' + self._name


Final = typing.Final

if sys.version_info >= (3, 11):
    final = typing.final
else:
    # @final exists in 3.8+, but we backport it for all versions
    # before 3.11 to keep support for the __final__ attribute.
    # See https://bugs.python.org/issue46342
    def final(f):
        """This decorator can be used to indicate to type checkers that
        the decorated method cannot be overridden, and decorated class
        cannot be subclassed. For example:

            class Base:
                @final
                def done(self) -> None:
                    ...
            class Sub(Base):
                def done(self) -> None:  # Error reported by type checker
                    ...
            @final
            class Leaf:
                ...
            class Other(Leaf):  # Error reported by type checker
                ...

        There is no runtime checking of these properties. The decorator
        sets the ``__final__`` attribute to ``True`` on the decorated object
        to allow runtime introspection.
        """
        try:
            f.__final__ = True
        except (AttributeError, TypeError):
            # Skip the attribute silently if it is not writable.
            # AttributeError happens if the object has __slots__ or a
            # read-only property, TypeError if it's a builtin class.
            pass
        return f


def IntVar(name):
    return typing.TypeVar(name)


# A Literal bug was fixed in 3.11.0, 3.10.1 and 3.9.8
if sys.version_info >= (3, 10, 1):
    Literal = typing.Literal
else:
    def _flatten_literal_params(parameters):
        """An internal helper for Literal creation: flatten Literals among parameters"""
        params = []
        for p in parameters:
            if isinstance(p, _LiteralGenericAlias):
                params.extend(p.__args__)
            else:
                params.append(p)
        return tuple(params)

    def _value_and_type_iter(params):
        for p in params:
            yield p, type(p)

    class _LiteralGenericAlias(typing._GenericAlias, _root=True):
        def __eq__(self, other):
            if not isinstance(other, _LiteralGenericAlias):
                return NotImplemented
            these_args_deduped = set(_value_and_type_iter(self.__args__))
            other_args_deduped = set(_value_and_type_iter(other.__args__))
            return these_args_deduped == other_args_deduped

        def __hash__(self):
            return hash(frozenset(_value_and_type_iter(self.__args__)))

    class _LiteralForm(_ExtensionsSpecialForm, _root=True):
        def __init__(self, doc: str):
            self._name = 'Literal'
            self._doc = self.__doc__ = doc

        def __getitem__(self, parameters):
            if not isinstance(parameters, tuple):
                parameters = (parameters,)

            parameters = _flatten_literal_params(parameters)

            val_type_pairs = list(_value_and_type_iter(parameters))
            try:
                deduped_pairs = set(val_type_pairs)
            except TypeError:
                # unhashable parameters
                pass
            else:
                # similar logic to typing._deduplicate on Python 3.9+
                if len(deduped_pairs) < len(val_type_pairs):
                    new_parameters = []
                    for pair in val_type_pairs:
                        if pair in deduped_pairs:
                            new_parameters.append(pair[0])
                            deduped_pairs.remove(pair)
                    assert not deduped_pairs, deduped_pairs
                    parameters = tuple(new_parameters)

            return _LiteralGenericAlias(self, parameters)

    Literal = _LiteralForm(doc="""\
                           A type that can be used to indicate to type checkers
                           that the corresponding value has a value literally equivalent
                           to the provided parameter. For example:

                               var: Literal[4] = 4

                           The type checker understands that 'var' is literally equal to
                           the value 4 and no other value.

                           Literal[...] cannot be subclassed. There is no runtime
                           checking verifying that the parameter is actually a value
                           instead of a type.""")


_overload_dummy = typing._overload_dummy


if hasattr(typing, "get_overloads"):  # 3.11+
    overload = typing.overload
    get_overloads = typing.get_overloads
    clear_overloads = typing.clear_overloads
else:
    # {module: {qualname: {firstlineno: func}}}
    _overload_registry = collections.defaultdict(
        functools.partial(collections.defaultdict, dict)
    )

    def overload(func):
        """Decorator for overloaded functions/methods.

        In a stub file, place two or more stub definitions for the same
        function in a row, each decorated with @overload.  For example:

        @overload
        def utf8(value: None) -> None: ...
        @overload
        def utf8(value: bytes) -> bytes: ...
        @overload
        def utf8(value: str) -> bytes: ...

        In a non-stub file (i.e. a regular .py file), do the same but
        follow it with an implementation.  The implementation should *not*
        be decorated with @overload.  For example:

        @overload
        def utf8(value: None) -> None: ...
        @overload
        def utf8(value: bytes) -> bytes: ...
        @overload
        def utf8(value: str) -> bytes: ...
        def utf8(value):
            # implementation goes here

        The overloads for a function can be retrieved at runtime using the
        get_overloads() function.
        """
        # classmethod and staticmethod
        f = getattr(func, "__func__", func)
        try:
            _overload_registry[f.__module__][f.__qualname__][
                f.__code__.co_firstlineno
            ] = func
        except AttributeError:
            # Not a normal function; ignore.
            pass
        return _overload_dummy

    def get_overloads(func):
        """Return all defined overloads for *func* as a sequence."""
        # classmethod and staticmethod
        f = getattr(func, "__func__", func)
        if f.__module__ not in _overload_registry:
            return []
        mod_dict = _overload_registry[f.__module__]
        if f.__qualname__ not in mod_dict:
            return []
        return list(mod_dict[f.__qualname__].values())

    def clear_overloads():
        """Clear all overloads in the registry."""
        _overload_registry.clear()


# This is not a real generic class.  Don't use outside annotations.
Type = typing.Type

# Various ABCs mimicking those in collections.abc.
# A few are simply re-exported for completeness.
Awaitable = typing.Awaitable
Coroutine = typing.Coroutine
AsyncIterable = typing.AsyncIterable
AsyncIterator = typing.AsyncIterator
Deque = typing.Deque
ContextManager = typing.ContextManager
AsyncContextManager = typing.AsyncContextManager
DefaultDict = typing.DefaultDict
OrderedDict = typing.OrderedDict
Counter = typing.Counter
ChainMap = typing.ChainMap
AsyncGenerator = typing.AsyncGenerator
Text = typing.Text
TYPE_CHECKING = typing.TYPE_CHECKING


_PROTO_ALLOWLIST = {
    'collections.abc': [
        'Callable', 'Awaitable', 'Iterable', 'Iterator', 'AsyncIterable',
        'Hashable', 'Sized', 'Container', 'Collection', 'Reversible', 'Buffer',
    ],
    'contextlib': ['AbstractContextManager', 'AbstractAsyncContextManager'],
    'typing_extensions': ['Buffer'],
}


_EXCLUDED_ATTRS = {
    "__abstractmethods__", "__annotations__", "__weakref__", "_is_protocol",
    "_is_runtime_protocol", "__dict__", "__slots__", "__parameters__",
    "__orig_bases__", "__module__", "_MutableMapping__marker", "__doc__",
    "__subclasshook__", "__orig_class__", "__init__", "__new__",
    "__protocol_attrs__", "__callable_proto_members_only__",
}

if sys.version_info >= (3, 9):
    _EXCLUDED_ATTRS.add("__class_getitem__")

if sys.version_info >= (3, 12):
    _EXCLUDED_ATTRS.add("__type_params__")

_EXCLUDED_ATTRS = frozenset(_EXCLUDED_ATTRS)


def _get_protocol_attrs(cls):
    attrs = set()
    for base in cls.__mro__[:-1]:  # without object
        if base.__name__ in {'Protocol', 'Generic'}:
            continue
        annotations = getattr(base, '__annotations__', {})
        for attr in (*base.__dict__, *annotations):
            if (not attr.startswith('_abc_') and attr not in _EXCLUDED_ATTRS):
                attrs.add(attr)
    return attrs


def _caller(depth=2):
    try:
        return sys._getframe(depth).f_globals.get('__name__', '__main__')
    except (AttributeError, ValueError):  # For platforms without _getframe()
        return None


# The performance of runtime-checkable protocols is significantly improved on Python 3.12,
# so we backport the 3.12 version of Protocol to Python <=3.11
if sys.version_info >= (3, 12):
    Protocol = typing.Protocol
else:
    def _allow_reckless_class_checks(depth=3):
        """Allow instance and class checks for special stdlib modules.
        The abc and functools modules indiscriminately call isinstance() and
        issubclass() on the whole MRO of a user class, which may contain protocols.
        """
        return _caller(depth) in {'abc', 'functools', None}

    def _no_init(self, *args, **kwargs):
        if type(self)._is_protocol:
            raise TypeError('Protocols cannot be instantiated')

    # Inheriting from typing._ProtocolMeta isn't actually desirable,
    # but is necessary to allow typing.Protocol and typing_extensions.Protocol
    # to mix without getting TypeErrors about "metaclass conflict"
    class _ProtocolMeta(type(typing.Protocol)):
        # This metaclass is somewhat unfortunate,
        # but is necessary for several reasons...
        #
        # NOTE: DO NOT call super() in any methods in this class
        # That would call the methods on typing._ProtocolMeta on Python 3.8-3.11
        # and those are slow
        def __new__(mcls, name, bases, namespace, **kwargs):
            if name == "Protocol" and len(bases) < 2:
                pass
            elif {Protocol, typing.Protocol} & set(bases):
                for base in bases:
                    if not (
                        base in {object, typing.Generic, Protocol, typing.Protocol}
                        or base.__name__ in _PROTO_ALLOWLIST.get(base.__module__, [])
                        or is_protocol(base)
                    ):
                        raise TypeError(
                            f"Protocols can only inherit from other protocols, "
                            f"got {base!r}"
                        )
            return abc.ABCMeta.__new__(mcls, name, bases, namespace, **kwargs)

        def __init__(cls, *args, **kwargs):
            abc.ABCMeta.__init__(cls, *args, **kwargs)
            if getattr(cls, "_is_protocol", False):
                cls.__protocol_attrs__ = _get_protocol_attrs(cls)
                # PEP 544 prohibits using issubclass()
                # with protocols that have non-method members.
                cls.__callable_proto_members_only__ = all(
                    callable(getattr(cls, attr, None)) for attr in cls.__protocol_attrs__
                )

        def __subclasscheck__(cls, other):
            if cls is Protocol:
                return type.__subclasscheck__(cls, other)
            if (
                getattr(cls, '_is_protocol', False)
                and not _allow_reckless_class_checks()
            ):
                if not isinstance(other, type):
                    # Same error message as for issubclass(1, int).
                    raise TypeError('issubclass() arg 1 must be a class')
                if (
                    not cls.__callable_proto_members_only__
                    and cls.__dict__.get("__subclasshook__") is _proto_hook
                ):
                    raise TypeError(
                        "Protocols with non-method members don't support issubclass()"
                    )
                if not getattr(cls, '_is_runtime_protocol', False):
                    raise TypeError(
                        "Instance and class checks can only be used with "
                        "@runtime_checkable protocols"
                    )
            return abc.ABCMeta.__subclasscheck__(cls, other)

        def __instancecheck__(cls, instance):
            # We need this method for situations where attributes are
            # assigned in __init__.
            if cls is Protocol:
                return type.__instancecheck__(cls, instance)
            if not getattr(cls, "_is_protocol", False):
                # i.e., it's a concrete subclass of a protocol
                return abc.ABCMeta.__instancecheck__(cls, instance)

            if (
                not getattr(cls, '_is_runtime_protocol', False) and
                not _allow_reckless_class_checks()
            ):
                raise TypeError("Instance and class checks can only be used with"
                                " @runtime_checkable protocols")

            if abc.ABCMeta.__instancecheck__(cls, instance):
                return True

            for attr in cls.__protocol_attrs__:
                try:
                    val = inspect.getattr_static(instance, attr)
                except AttributeError:
                    break
                if val is None and callable(getattr(cls, attr, None)):
                    break
            else:
                return True

            return False

        def __eq__(cls, other):
            # Hack so that typing.Generic.__class_getitem__
            # treats typing_extensions.Protocol
            # as equivalent to typing.Protocol
            if abc.ABCMeta.__eq__(cls, other) is True:
                return True
            return cls is Protocol and other is typing.Protocol

        # This has to be defined, or the abc-module cache
        # complains about classes with this metaclass being unhashable,
        # if we define only __eq__!
        def __hash__(cls) -> int:
            return type.__hash__(cls)

    @classmethod
    def _proto_hook(cls, other):
        if not cls.__dict__.get('_is_protocol', False):
            return NotImplemented

        for attr in cls.__protocol_attrs__:
            for base in other.__mro__:
                # Check if the members appears in the class dictionary...
                if attr in base.__dict__:
                    if base.__dict__[attr] is None:
                        return NotImplemented
                    break

                # ...or in annotations, if it is a sub-protocol.
                annotations = getattr(base, '__annotations__', {})
                if (
                    isinstance(annotations, collections.abc.Mapping)
                    and attr in annotations
                    and is_protocol(other)
                ):
                    break
            else:
                return NotImplemented
        return True

    class Protocol(typing.Generic, metaclass=_ProtocolMeta):
        __doc__ = typing.Protocol.__doc__
        __slots__ = ()
        _is_protocol = True
        _is_runtime_protocol = False

        def __init_subclass__(cls, *args, **kwargs):
            super().__init_subclass__(*args, **kwargs)

            # Determine if this is a protocol or a concrete subclass.
            if not cls.__dict__.get('_is_protocol', False):
                cls._is_protocol = any(b is Protocol for b in cls.__bases__)

            # Set (or override) the protocol subclass hook.
            if '__subclasshook__' not in cls.__dict__:
                cls.__subclasshook__ = _proto_hook

            # Prohibit instantiation for protocol classes
            if cls._is_protocol and cls.__init__ is Protocol.__init__:
                cls.__init__ = _no_init


# The "runtime" alias exists for backwards compatibility.
runtime = runtime_checkable = typing.runtime_checkable


# Our version of runtime-checkable protocols is faster on Python 3.8-3.11
if sys.version_info >= (3, 12):
    SupportsInt = typing.SupportsInt
    SupportsFloat = typing.SupportsFloat
    SupportsComplex = typing.SupportsComplex
    SupportsBytes = typing.SupportsBytes
    SupportsIndex = typing.SupportsIndex
    SupportsAbs = typing.SupportsAbs
    SupportsRound = typing.SupportsRound
else:
    @runtime_checkable
    class SupportsInt(Protocol):
        """An ABC with one abstract method __int__."""
        __slots__ = ()

        @abc.abstractmethod
        def __int__(self) -> int:
            pass

    @runtime_checkable
    class SupportsFloat(Protocol):
        """An ABC with one abstract method __float__."""
        __slots__ = ()

        @abc.abstractmethod
        def __float__(self) -> float:
            pass

    @runtime_checkable
    class SupportsComplex(Protocol):
        """An ABC with one abstract method __complex__."""
        __slots__ = ()

        @abc.abstractmethod
        def __complex__(self) -> complex:
            pass

    @runtime_checkable
    class SupportsBytes(Protocol):
        """An ABC with one abstract method __bytes__."""
        __slots__ = ()

        @abc.abstractmethod
        def __bytes__(self) -> bytes:
            pass

    @runtime_checkable
    class SupportsIndex(Protocol):
        __slots__ = ()

        @abc.abstractmethod
        def __index__(self) -> int:
            pass

    @runtime_checkable
    class SupportsAbs(Protocol[T_co]):
        """
        An ABC with one abstract method __abs__ that is covariant in its return type.
        """
        __slots__ = ()

        @abc.abstractmethod
        def __abs__(self) -> T_co:
            pass

    @runtime_checkable
    class SupportsRound(Protocol[T_co]):
        """
        An ABC with one abstract method __round__ that is covariant in its return type.
        """
        __slots__ = ()

        @abc.abstractmethod
        def __round__(self, ndigits: int = 0) -> T_co:
            pass


def _ensure_subclassable(mro_entries):
    def inner(func):
        if sys.implementation.name == "pypy" and sys.version_info < (3, 9):
            cls_dict = {
                "__call__": staticmethod(func),
                "__mro_entries__": staticmethod(mro_entries)
            }
            t = type(func.__name__, (), cls_dict)
            return functools.update_wrapper(t(), func)
        else:
            func.__mro_entries__ = mro_entries
            return func
    return inner


if sys.version_info >= (3, 13):
    # The standard library TypedDict in Python 3.8 does not store runtime information
    # about which (if any) keys are optional.  See https://bugs.python.org/issue38834
    # The standard library TypedDict in Python 3.9.0/1 does not honour the "total"
    # keyword with old-style TypedDict().  See https://bugs.python.org/issue42059
    # The standard library TypedDict below Python 3.11 does not store runtime
    # information about optional and required keys when using Required or NotRequired.
    # Generic TypedDicts are also impossible using typing.TypedDict on Python <3.11.
    # Aaaand on 3.12 we add __orig_bases__ to TypedDict
    # to enable better runtime introspection.
    # On 3.13 we deprecate some odd ways of creating TypedDicts.
    TypedDict = typing.TypedDict
    _TypedDictMeta = typing._TypedDictMeta
    is_typeddict = typing.is_typeddict
else:
    # 3.10.0 and later
    _TAKES_MODULE = "module" in inspect.signature(typing._type_check).parameters

    class _TypedDictMeta(type):
        def __new__(cls, name, bases, ns, total=True):
            """Create new typed dict class object.

            This method is called when TypedDict is subclassed,
            or when TypedDict is instantiated. This way
            TypedDict supports all three syntax forms described in its docstring.
            Subclasses and instances of TypedDict return actual dictionaries.
            """
            for base in bases:
                if type(base) is not _TypedDictMeta and base is not typing.Generic:
                    raise TypeError('cannot inherit from both a TypedDict type '
                                    'and a non-TypedDict base class')

            if any(issubclass(b, typing.Generic) for b in bases):
                generic_base = (typing.Generic,)
            else:
                generic_base = ()

            # typing.py generally doesn't let you inherit from plain Generic, unless
            # the name of the class happens to be "Protocol"
            tp_dict = type.__new__(_TypedDictMeta, "Protocol", (*generic_base, dict), ns)
            tp_dict.__name__ = name
            if tp_dict.__qualname__ == "Protocol":
                tp_dict.__qualname__ = name

            if not hasattr(tp_dict, '__orig_bases__'):
                tp_dict.__orig_bases__ = bases

            annotations = {}
            own_annotations = ns.get('__annotations__', {})
            msg = "TypedDict('Name', {f0: t0, f1: t1, ...}); each t must be a type"
            if _TAKES_MODULE:
                own_annotations = {
                    n: typing._type_check(tp, msg, module=tp_dict.__module__)
                    for n, tp in own_annotations.items()
                }
            else:
                own_annotations = {
                    n: typing._type_check(tp, msg)
                    for n, tp in own_annotations.items()
                }
            required_keys = set()
            optional_keys = set()

            for base in bases:
                annotations.update(base.__dict__.get('__annotations__', {}))
                required_keys.update(base.__dict__.get('__required_keys__', ()))
                optional_keys.update(base.__dict__.get('__optional_keys__', ()))

            annotations.update(own_annotations)
            for annotation_key, annotation_type in own_annotations.items():
                annotation_origin = get_origin(annotation_type)
                if annotation_origin is Annotated:
                    annotation_args = get_args(annotation_type)
                    if annotation_args:
                        annotation_type = annotation_args[0]
                        annotation_origin = get_origin(annotation_type)

                if annotation_origin is Required:
                    required_keys.add(annotation_key)
                elif annotation_origin is NotRequired:
                    optional_keys.add(annotation_key)
                elif total:
                    required_keys.add(annotation_key)
                else:
                    optional_keys.add(annotation_key)

            tp_dict.__annotations__ = annotations
            tp_dict.__required_keys__ = frozenset(required_keys)
            tp_dict.__optional_keys__ = frozenset(optional_keys)
            if not hasattr(tp_dict, '__total__'):
                tp_dict.__total__ = total
            return tp_dict

        __call__ = dict  # static method

        def __subclasscheck__(cls, other):
            # Typed dicts are only for static structural subtyping.
            raise TypeError('TypedDict does not support instance and class checks')

        __instancecheck__ = __subclasscheck__

    _TypedDict = type.__new__(_TypedDictMeta, 'TypedDict', (), {})

    @_ensure_subclassable(lambda bases: (_TypedDict,))
    def TypedDict(typename, fields=_marker, /, *, total=True, **kwargs):
        """A simple typed namespace. At runtime it is equivalent to a plain dict.

        TypedDict creates a dictionary type such that a type checker will expect all
        instances to have a certain set of keys, where each key is
        associated with a value of a consistent type. This expectation
        is not checked at runtime.

        Usage::

            class Point2D(TypedDict):
                x: int
                y: int
                label: str

            a: Point2D = {'x': 1, 'y': 2, 'label': 'good'}  # OK
            b: Point2D = {'z': 3, 'label': 'bad'}           # Fails type check

            assert Point2D(x=1, y=2, label='first') == dict(x=1, y=2, label='first')

        The type info can be accessed via the Point2D.__annotations__ dict, and
        the Point2D.__required_keys__ and Point2D.__optional_keys__ frozensets.
        TypedDict supports an additional equivalent form::

            Point2D = TypedDict('Point2D', {'x': int, 'y': int, 'label': str})

        By default, all keys must be present in a TypedDict. It is possible
        to override this by specifying totality::

            class Point2D(TypedDict, total=False):
                x: int
                y: int

        This means that a Point2D TypedDict can have any of the keys omitted. A type
        checker is only expected to support a literal False or True as the value of
        the total argument. True is the default, and makes all items defined in the
        class body be required.

        The Required and NotRequired special forms can also be used to mark
        individual keys as being required or not required::

            class Point2D(TypedDict):
                x: int  # the "x" key must always be present (Required is the default)
                y: NotRequired[int]  # the "y" key can be omitted

        See PEP 655 for more details on Required and NotRequired.
        """
        if fields is _marker or fields is None:
            if fields is _marker:
                deprecated_thing = "Failing to pass a value for the 'fields' parameter"
            else:
                deprecated_thing = "Passing `None` as the 'fields' parameter"

            example = f"`{typename} = TypedDict({typename!r}, {{}})`"
            deprecation_msg = (
                f"{deprecated_thing} is deprecated and will be disallowed in "
                "Python 3.15. To create a TypedDict class with 0 fields "
                "using the functional syntax, pass an empty dictionary, e.g. "
            ) + example + "."
            warnings.warn(deprecation_msg, DeprecationWarning, stacklevel=2)
            fields = kwargs
        elif kwargs:
            raise TypeError("TypedDict takes either a dict or keyword arguments,"
                            " but not both")
        if kwargs:
            warnings.warn(
                "The kwargs-based syntax for TypedDict definitions is deprecated "
                "in Python 3.11, will be removed in Python 3.13, and may not be "
                "understood by third-party type checkers.",
                DeprecationWarning,
                stacklevel=2,
            )

        ns = {'__annotations__': dict(fields)}
        module = _caller()
        if module is not None:
            # Setting correct module is necessary to make typed dict classes pickleable.
            ns['__module__'] = module

        td = _TypedDictMeta(typename, (), ns, total=total)
        td.__orig_bases__ = (TypedDict,)
        return td

    if hasattr(typing, "_TypedDictMeta"):
        _TYPEDDICT_TYPES = (typing._TypedDictMeta, _TypedDictMeta)
    else:
        _TYPEDDICT_TYPES = (_TypedDictMeta,)

    def is_typeddict(tp):
        """Check if an annotation is a TypedDict class

        For example::
            class Film(TypedDict):
                title: str
                year: int

            is_typeddict(Film)  # => True
            is_typeddict(Union[list, str])  # => False
        """
        # On 3.8, this would otherwise return True
        if hasattr(typing, "TypedDict") and tp is typing.TypedDict:
            return False
        return isinstance(tp, _TYPEDDICT_TYPES)


if hasattr(typing, "assert_type"):
    assert_type = typing.assert_type

else:
    def assert_type(val, typ, /):
        """Assert (to the type checker) that the value is of the given type.

        When the type checker encounters a call to assert_type(), it
        emits an error if the value is not of the specified type::

            def greet(name: str) -> None:
                assert_type(name, str)  # ok
                assert_type(name, int)  # type checker error

        At runtime this returns the first argument unchanged and otherwise
        does nothing.
        """
        return val


if hasattr(typing, "Required"):  # 3.11+
    get_type_hints = typing.get_type_hints
else:  # <=3.10
    # replaces _strip_annotations()
    def _strip_extras(t):
        """Strips Annotated, Required and NotRequired from a given type."""
        if isinstance(t, _AnnotatedAlias):
            return _strip_extras(t.__origin__)
        if hasattr(t, "__origin__") and t.__origin__ in (Required, NotRequired):
            return _strip_extras(t.__args__[0])
        if isinstance(t, typing._GenericAlias):
            stripped_args = tuple(_strip_extras(a) for a in t.__args__)
            if stripped_args == t.__args__:
                return t
            return t.copy_with(stripped_args)
        if hasattr(_types, "GenericAlias") and isinstance(t, _types.GenericAlias):
            stripped_args = tuple(_strip_extras(a) for a in t.__args__)
            if stripped_args == t.__args__:
                return t
            return _types.GenericAlias(t.__origin__, stripped_args)
        if hasattr(_types, "UnionType") and isinstance(t, _types.UnionType):
            stripped_args = tuple(_strip_extras(a) for a in t.__args__)
            if stripped_args == t.__args__:
                return t
            return functools.reduce(operator.or_, stripped_args)

        return t

    def get_type_hints(obj, globalns=None, localns=None, include_extras=False):
        """Return type hints for an object.

        This is often the same as obj.__annotations__, but it handles
        forward references encoded as string literals, adds Optional[t] if a
        default value equal to None is set and recursively replaces all
        'Annotated[T, ...]', 'Required[T]' or 'NotRequired[T]' with 'T'
        (unless 'include_extras=True').

        The argument may be a module, class, method, or function. The annotations
        are returned as a dictionary. For classes, annotations include also
        inherited members.

        TypeError is raised if the argument is not of a type that can contain
        annotations, and an empty dictionary is returned if no annotations are
        present.

        BEWARE -- the behavior of globalns and localns is counterintuitive
        (unless you are familiar with how eval() and exec() work).  The
        search order is locals first, then globals.

        - If no dict arguments are passed, an attempt is made to use the
          globals from obj (or the respective module's globals for classes),
          and these are also used as the locals.  If the object does not appear
          to have globals, an empty dictionary is used.

        - If one dict argument is passed, it is used for both globals and
          locals.

        - If two dict arguments are passed, they specify globals and
          locals, respectively.
        """
        if hasattr(typing, "Annotated"):  # 3.9+
            hint = typing.get_type_hints(
                obj, globalns=globalns, localns=localns, include_extras=True
            )
        else:  # 3.8
            hint = typing.get_type_hints(obj, globalns=globalns, localns=localns)
        if include_extras:
            return hint
        return {k: _strip_extras(t) for k, t in hint.items()}


# Python 3.9+ has PEP 593 (Annotated)
if hasattr(typing, 'Annotated'):
    Annotated = typing.Annotated
    # Not exported and not a public API, but needed for get_origin() and get_args()
    # to work.
    _AnnotatedAlias = typing._AnnotatedAlias
# 3.8
else:
    class _AnnotatedAlias(typing._GenericAlias, _root=True):
        """Runtime representation of an annotated type.

        At its core 'Annotated[t, dec1, dec2, ...]' is an alias for the type 't'
        with extra annotations. The alias behaves like a normal typing alias,
        instantiating is the same as instantiating the underlying type, binding
        it to types is also the same.
        """
        def __init__(self, origin, metadata):
            if isinstance(origin, _AnnotatedAlias):
                metadata = origin.__metadata__ + metadata
                origin = origin.__origin__
            super().__init__(origin, origin)
            self.__metadata__ = metadata

        def copy_with(self, params):
            assert len(params) == 1
            new_type = params[0]
            return _AnnotatedAlias(new_type, self.__metadata__)

        def __repr__(self):
            return (f"typing_extensions.Annotated[{typing._type_repr(self.__origin__)}, "
                    f"{', '.join(repr(a) for a in self.__metadata__)}]")

        def __reduce__(self):
            return operator.getitem, (
                Annotated, (self.__origin__,) + self.__metadata__
            )

        def __eq__(self, other):
            if not isinstance(other, _AnnotatedAlias):
                return NotImplemented
            if self.__origin__ != other.__origin__:
                return False
            return self.__metadata__ == other.__metadata__

        def __hash__(self):
            return hash((self.__origin__, self.__metadata__))

    class Annotated:
        """Add context specific metadata to a type.

        Example: Annotated[int, runtime_check.Unsigned] indicates to the
        hypothetical runtime_check module that this type is an unsigned int.
        Every other consumer of this type can ignore this metadata and treat
        this type as int.

        The first argument to Annotated must be a valid type (and will be in
        the __origin__ field), the remaining arguments are kept as a tuple in
        the __extra__ field.

        Details:

        - It's an error to call `Annotated` with less than two arguments.
        - Nested Annotated are flattened::

            Annotated[Annotated[T, Ann1, Ann2], Ann3] == Annotated[T, Ann1, Ann2, Ann3]

        - Instantiating an annotated type is equivalent to instantiating the
        underlying type::

            Annotated[C, Ann1](5) == C(5)

        - Annotated can be used as a generic type alias::

            Optimized = Annotated[T, runtime.Optimize()]
            Optimized[int] == Annotated[int, runtime.Optimize()]

            OptimizedList = Annotated[List[T], runtime.Optimize()]
            OptimizedList[int] == Annotated[List[int], runtime.Optimize()]
        """

        __slots__ = ()

        def __new__(cls, *args, **kwargs):
            raise TypeError("Type Annotated cannot be instantiated.")

        @typing._tp_cache
        def __class_getitem__(cls, params):
            if not isinstance(params, tuple) or len(params) < 2:
                raise TypeError("Annotated[...] should be used "
                                "with at least two arguments (a type and an "
                                "annotation).")
            allowed_special_forms = (ClassVar, Final)
            if get_origin(params[0]) in allowed_special_forms:
                origin = params[0]
            else:
                msg = "Annotated[t, ...]: t must be a type."
                origin = typing._type_check(params[0], msg)
            metadata = tuple(params[1:])
            return _AnnotatedAlias(origin, metadata)

        def __init_subclass__(cls, *args, **kwargs):
            raise TypeError(
                f"Cannot subclass {cls.__module__}.Annotated"
            )

# Python 3.8 has get_origin() and get_args() but those implementations aren't
# Annotated-aware, so we can't use those. Python 3.9's versions don't support
# ParamSpecArgs and ParamSpecKwargs, so only Python 3.10's versions will do.
if sys.version_info[:2] >= (3, 10):
    get_origin = typing.get_origin
    get_args = typing.get_args
# 3.8-3.9
else:
    try:
        # 3.9+
        from typing import _BaseGenericAlias
    except ImportError:
        _BaseGenericAlias = typing._GenericAlias
    try:
        # 3.9+
        from typing import GenericAlias as _typing_GenericAlias
    except ImportError:
        _typing_GenericAlias = typing._GenericAlias

    def get_origin(tp):
        """Get the unsubscripted version of a type.

        This supports generic types, Callable, Tuple, Union, Literal, Final, ClassVar
        and Annotated. Return None for unsupported types. Examples::

            get_origin(Literal[42]) is Literal
            get_origin(int) is None
            get_origin(ClassVar[int]) is ClassVar
            get_origin(Generic) is Generic
            get_origin(Generic[T]) is Generic
            get_origin(Union[T, int]) is Union
            get_origin(List[Tuple[T, T]][int]) == list
            get_origin(P.args) is P
        """
        if isinstance(tp, _AnnotatedAlias):
            return Annotated
        if isinstance(tp, (typing._GenericAlias, _typing_GenericAlias, _BaseGenericAlias,
                           ParamSpecArgs, ParamSpecKwargs)):
            return tp.__origin__
        if tp is typing.Generic:
            return typing.Generic
        return None

    def get_args(tp):
        """Get type arguments with all substitutions performed.

        For unions, basic simplifications used by Union constructor are performed.
        Examples::
            get_args(Dict[str, int]) == (str, int)
            get_args(int) == ()
            get_args(Union[int, Union[T, int], str][int]) == (int, str)
            get_args(Union[int, Tuple[T, int]][str]) == (int, Tuple[str, int])
            get_args(Callable[[], T][int]) == ([], int)
        """
        if isinstance(tp, _AnnotatedAlias):
            return (tp.__origin__,) + tp.__metadata__
        if isinstance(tp, (typing._GenericAlias, _typing_GenericAlias)):
            if getattr(tp, "_special", False):
                return ()
            res = tp.__args__
            if get_origin(tp) is collections.abc.Callable and res[0] is not Ellipsis:
                res = (list(res[:-1]), res[-1])
            return res
        return ()


# 3.10+
if hasattr(typing, 'TypeAlias'):
    TypeAlias = typing.TypeAlias
# 3.9
elif sys.version_info[:2] >= (3, 9):
    @_ExtensionsSpecialForm
    def TypeAlias(self, parameters):
        """Special marker indicating that an assignment should
        be recognized as a proper type alias definition by type
        checkers.

        For example::

            Predicate: TypeAlias = Callable[..., bool]

        It's invalid when used anywhere except as in the example above.
        """
        raise TypeError(f"{self} is not subscriptable")
# 3.8
else:
    TypeAlias = _ExtensionsSpecialForm(
        'TypeAlias',
        doc="""Special marker indicating that an assignment should
        be recognized as a proper type alias definition by type
        checkers.

        For example::

            Predicate: TypeAlias = Callable[..., bool]

        It's invalid when used anywhere except as in the example
        above."""
    )


def _set_default(type_param, default):
    if isinstance(default, (tuple, list)):
        type_param.__default__ = tuple((typing._type_check(d, "Default must be a type")
                                        for d in default))
    elif default != _marker:
        if isinstance(type_param, ParamSpec) and default is ...:  # ... not valid <3.11
            type_param.__default__ = default
        else:
            type_param.__default__ = typing._type_check(default, "Default must be a type")
    else:
        type_param.__default__ = None


def _set_module(typevarlike):
    # for pickling:
    def_mod = _caller(depth=3)
    if def_mod != 'typing_extensions':
        typevarlike.__module__ = def_mod


class _DefaultMixin:
    """Mixin for TypeVarLike defaults."""

    __slots__ = ()
    __init__ = _set_default


# Classes using this metaclass must provide a _backported_typevarlike ClassVar
class _TypeVarLikeMeta(type):
    def __instancecheck__(cls, __instance: Any) -> bool:
        return isinstance(__instance, cls._backported_typevarlike)


# Add default and infer_variance parameters from PEP 696 and 695
class TypeVar(metaclass=_TypeVarLikeMeta):
    """Type variable."""

    _backported_typevarlike = typing.TypeVar

    def __new__(cls, name, *constraints, bound=None,
                covariant=False, contravariant=False,
                default=_marker, infer_variance=False):
        if hasattr(typing, "TypeAliasType"):
            # PEP 695 implemented (3.12+), can pass infer_variance to typing.TypeVar
            typevar = typing.TypeVar(name, *constraints, bound=bound,
                                     covariant=covariant, contravariant=contravariant,
                                     infer_variance=infer_variance)
        else:
            typevar = typing.TypeVar(name, *constraints, bound=bound,
                                     covariant=covariant, contravariant=contravariant)
            if infer_variance and (covariant or contravariant):
                raise ValueError("Variance cannot be specified with infer_variance.")
            typevar.__infer_variance__ = infer_variance
        _set_default(typevar, default)
        _set_module(typevar)
        return typevar

    def __init_subclass__(cls) -> None:
        raise TypeError(f"type '{__name__}.TypeVar' is not an acceptable base type")


# Python 3.10+ has PEP 612
if hasattr(typing, 'ParamSpecArgs'):
    ParamSpecArgs = typing.ParamSpecArgs
    ParamSpecKwargs = typing.ParamSpecKwargs
# 3.8-3.9
else:
    class _Immutable:
        """Mixin to indicate that object should not be copied."""
        __slots__ = ()

        def __copy__(self):
            return self

        def __deepcopy__(self, memo):
            return self

    class ParamSpecArgs(_Immutable):
        """The args for a ParamSpec object.

        Given a ParamSpec object P, P.args is an instance of ParamSpecArgs.

        ParamSpecArgs objects have a reference back to their ParamSpec:

        P.args.__origin__ is P

        This type is meant for runtime introspection and has no special meaning to
        static type checkers.
        """
        def __init__(self, origin):
            self.__origin__ = origin

        def __repr__(self):
            return f"{self.__origin__.__name__}.args"

        def __eq__(self, other):
            if not isinstance(other, ParamSpecArgs):
                return NotImplemented
            return self.__origin__ == other.__origin__

    class ParamSpecKwargs(_Immutable):
        """The kwargs for a ParamSpec object.

        Given a ParamSpec object P, P.kwargs is an instance of ParamSpecKwargs.

        ParamSpecKwargs objects have a reference back to their ParamSpec:

        P.kwargs.__origin__ is P

        This type is meant for runtime introspection and has no special meaning to
        static type checkers.
        """
        def __init__(self, origin):
            self.__origin__ = origin

        def __repr__(self):
            return f"{self.__origin__.__name__}.kwargs"

        def __eq__(self, other):
            if not isinstance(other, ParamSpecKwargs):
                return NotImplemented
            return self.__origin__ == other.__origin__

# 3.10+
if hasattr(typing, 'ParamSpec'):

    # Add default parameter - PEP 696
    class ParamSpec(metaclass=_TypeVarLikeMeta):
        """Parameter specification."""

        _backported_typevarlike = typing.ParamSpec

        def __new__(cls, name, *, bound=None,
                    covariant=False, contravariant=False,
                    infer_variance=False, default=_marker):
            if hasattr(typing, "TypeAliasType"):
                # PEP 695 implemented, can pass infer_variance to typing.TypeVar
                paramspec = typing.ParamSpec(name, bound=bound,
                                             covariant=covariant,
                                             contravariant=contravariant,
                                             infer_variance=infer_variance)
            else:
                paramspec = typing.ParamSpec(name, bound=bound,
                                             covariant=covariant,
                                             contravariant=contravariant)
                paramspec.__infer_variance__ = infer_variance

            _set_default(paramspec, default)
            _set_module(paramspec)
            return paramspec

        def __init_subclass__(cls) -> None:
            raise TypeError(f"type '{__name__}.ParamSpec' is not an acceptable base type")

# 3.8-3.9
else:

    # Inherits from list as a workaround for Callable checks in Python < 3.9.2.
    class ParamSpec(list, _DefaultMixin):
        """Parameter specification variable.

        Usage::

           P = ParamSpec('P')

        Parameter specification variables exist primarily for the benefit of static
        type checkers.  They are used to forward the parameter types of one
        callable to another callable, a pattern commonly found in higher order
        functions and decorators.  They are only valid when used in ``Concatenate``,
        or s the first argument to ``Callable``. In Python 3.10 and higher,
        they are also supported in user-defined Generics at runtime.
        See class Generic for more information on generic types.  An
        example for annotating a decorator::

           T = TypeVar('T')
           P = ParamSpec('P')

           def add_logging(f: Callable[P, T]) -> Callable[P, T]:
               '''A type-safe decorator to add logging to a function.'''
               def inner(*args: P.args, **kwargs: P.kwargs) -> T:
                   logging.info(f'{f.__name__} was called')
                   return f(*args, **kwargs)
               return inner

           @add_logging
           def add_two(x: float, y: float) -> float:
               '''Add two numbers together.'''
               return x + y

        Parameter specification variables defined with covariant=True or
        contravariant=True can be used to declare covariant or contravariant
        generic types.  These keyword arguments are valid, but their actual semantics
        are yet to be decided.  See PEP 612 for details.

        Parameter specification variables can be introspected. e.g.:

           P.__name__ == 'T'
           P.__bound__ == None
           P.__covariant__ == False
           P.__contravariant__ == False

        Note that only parameter specification variables defined in global scope can
        be pickled.
        """

        # Trick Generic __parameters__.
        __class__ = typing.TypeVar

        @property
        def args(self):
            return ParamSpecArgs(self)

        @property
        def kwargs(self):
            return ParamSpecKwargs(self)

        def __init__(self, name, *, bound=None, covariant=False, contravariant=False,
                     infer_variance=False, default=_marker):
            super().__init__([self])
            self.__name__ = name
            self.__covariant__ = bool(covariant)
            self.__contravariant__ = bool(contravariant)
            self.__infer_variance__ = bool(infer_variance)
            if bound:
                self.__bound__ = typing._type_check(bound, 'Bound must be a type.')
            else:
                self.__bound__ = None
            _DefaultMixin.__init__(self, default)

            # for pickling:
            def_mod = _caller()
            if def_mod != 'typing_extensions':
                self.__module__ = def_mod

        def __repr__(self):
            if self.__infer_variance__:
                prefix = ''
            elif self.__covariant__:
                prefix = '+'
            elif self.__contravariant__:
                prefix = '-'
            else:
                prefix = '~'
            return prefix + self.__name__

        def __hash__(self):
            return object.__hash__(self)

        def __eq__(self, other):
            return self is other

        def __reduce__(self):
            return self.__name__

        # Hack to get typing._type_check to pass.
        def __call__(self, *args, **kwargs):
            pass


# 3.8-3.9
if not hasattr(typing, 'Concatenate'):
    # Inherits from list as a workaround for Callable checks in Python < 3.9.2.
    class _ConcatenateGenericAlias(list):

        # Trick Generic into looking into this for __parameters__.
        __class__ = typing._GenericAlias

        # Flag in 3.8.
        _special = False

        def __init__(self, origin, args):
            super().__init__(args)
            self.__origin__ = origin
            self.__args__ = args

        def __repr__(self):
            _type_repr = typing._type_repr
            return (f'{_type_repr(self.__origin__)}'
                    f'[{", ".join(_type_repr(arg) for arg in self.__args__)}]')

        def __hash__(self):
            return hash((self.__origin__, self.__args__))

        # Hack to get typing._type_check to pass in Generic.
        def __call__(self, *args, **kwargs):
            pass

        @property
        def __parameters__(self):
            return tuple(
                tp for tp in self.__args__ if isinstance(tp, (typing.TypeVar, ParamSpec))
            )


# 3.8-3.9
@typing._tp_cache
def _concatenate_getitem(self, parameters):
    if parameters == ():
        raise TypeError("Cannot take a Concatenate of no types.")
    if not isinstance(parameters, tuple):
        parameters = (parameters,)
    if not isinstance(parameters[-1], ParamSpec):
        raise TypeError("The last parameter to Concatenate should be a "
                        "ParamSpec variable.")
    msg = "Concatenate[arg, ...]: each arg must be a type."
    parameters = tuple(typing._type_check(p, msg) for p in parameters)
    return _ConcatenateGenericAlias(self, parameters)


# 3.10+
if hasattr(typing, 'Concatenate'):
    Concatenate = typing.Concatenate
    _ConcatenateGenericAlias = typing._ConcatenateGenericAlias  # noqa: F811
# 3.9
elif sys.version_info[:2] >= (3, 9):
    @_ExtensionsSpecialForm
    def Concatenate(self, parameters):
        """Used in conjunction with ``ParamSpec`` and ``Callable`` to represent a
        higher order function which adds, removes or transforms parameters of a
        callable.

        For example::

           Callable[Concatenate[int, P], int]

        See PEP 612 for detailed information.
        """
        return _concatenate_getitem(self, parameters)
# 3.8
else:
    class _ConcatenateForm(_ExtensionsSpecialForm, _root=True):
        def __getitem__(self, parameters):
            return _concatenate_getitem(self, parameters)

    Concatenate = _ConcatenateForm(
        'Concatenate',
        doc="""Used in conjunction with ``ParamSpec`` and ``Callable`` to represent a
        higher order function which adds, removes or transforms parameters of a
        callable.

        For example::

           Callable[Concatenate[int, P], int]

        See PEP 612 for detailed information.
        """)

# 3.10+
if hasattr(typing, 'TypeGuard'):
    TypeGuard = typing.TypeGuard
# 3.9
elif sys.version_info[:2] >= (3, 9):
    @_ExtensionsSpecialForm
    def TypeGuard(self, parameters):
        """Special typing form used to annotate the return type of a user-defined
        type guard function.  ``TypeGuard`` only accepts a single type argument.
        At runtime, functions marked this way should return a boolean.

        ``TypeGuard`` aims to benefit *type narrowing* -- a technique used by static
        type checkers to determine a more precise type of an expression within a
        program's code flow.  Usually type narrowing is done by analyzing
        conditional code flow and applying the narrowing to a block of code.  The
        conditional expression here is sometimes referred to as a "type guard".

        Sometimes it would be convenient to use a user-defined boolean function
        as a type guard.  Such a function should use ``TypeGuard[...]`` as its
        return type to alert static type checkers to this intention.

        Using  ``-> TypeGuard`` tells the static type checker that for a given
        function:

        1. The return value is a boolean.
        2. If the return value is ``True``, the type of its argument
        is the type inside ``TypeGuard``.

        For example::

            def is_str(val: Union[str, float]):
                # "isinstance" type guard
                if isinstance(val, str):
                    # Type of ``val`` is narrowed to ``str``
                    ...
                else:
                    # Else, type of ``val`` is narrowed to ``float``.
                    ...

        Strict type narrowing is not enforced -- ``TypeB`` need not be a narrower
        form of ``TypeA`` (it can even be a wider form) and this may lead to
        type-unsafe results.  The main reason is to allow for things like
        narrowing ``List[object]`` to ``List[str]`` even though the latter is not
        a subtype of the former, since ``List`` is invariant.  The responsibility of
        writing type-safe type guards is left to the user.

        ``TypeGuard`` also works with type variables.  For more information, see
        PEP 647 (User-Defined Type Guards).
        """
        item = typing._type_check(parameters, f'{self} accepts only a single type.')
        return typing._GenericAlias(self, (item,))
# 3.8
else:
    class _TypeGuardForm(_ExtensionsSpecialForm, _root=True):
        def __getitem__(self, parameters):
            item = typing._type_check(parameters,
                                      f'{self._name} accepts only a single type')
            return typing._GenericAlias(self, (item,))

    TypeGuard = _TypeGuardForm(
        'TypeGuard',
        doc="""Special typing form used to annotate the return type of a user-defined
        type guard function.  ``TypeGuard`` only accepts a single type argument.
        At runtime, functions marked this way should return a boolean.

        ``TypeGuard`` aims to benefit *type narrowing* -- a technique used by static
        type checkers to determine a more precise type of an expression within a
        program's code flow.  Usually type narrowing is done by analyzing
        conditional code flow and applying the narrowing to a block of code.  The
        conditional expression here is sometimes referred to as a "type guard".

        Sometimes it would be convenient to use a user-defined boolean function
        as a type guard.  Such a function should use ``TypeGuard[...]`` as its
        return type to alert static type checkers to this intention.

        Using  ``-> TypeGuard`` tells the static type checker that for a given
        function:

        1. The return value is a boolean.
        2. If the return value is ``True``, the type of its argument
        is the type inside ``TypeGuard``.

        For example::

            def is_str(val: Union[str, float]):
                # "isinstance" type guard
                if isinstance(val, str):
                    # Type of ``val`` is narrowed to ``str``
                    ...
                else:
                    # Else, type of ``val`` is narrowed to ``float``.
                    ...

        Strict type narrowing is not enforced -- ``TypeB`` need not be a narrower
        form of ``TypeA`` (it can even be a wider form) and this may lead to
        type-unsafe results.  The main reason is to allow for things like
        narrowing ``List[object]`` to ``List[str]`` even though the latter is not
        a subtype of the former, since ``List`` is invariant.  The responsibility of
        writing type-safe type guards is left to the user.

        ``TypeGuard`` also works with type variables.  For more information, see
        PEP 647 (User-Defined Type Guards).
        """)


# Vendored from cpython typing._SpecialFrom
class _SpecialForm(typing._Final, _root=True):
    __slots__ = ('_name', '__doc__', '_getitem')

    def __init__(self, getitem):
        self._getitem = getitem
        self._name = getitem.__name__
        self.__doc__ = getitem.__doc__

    def __getattr__(self, item):
        if item in {'__name__', '__qualname__'}:
            return self._name

        raise AttributeError(item)

    def __mro_entries__(self, bases):
        raise TypeError(f"Cannot subclass {self!r}")

    def __repr__(self):
        return f'typing_extensions.{self._name}'

    def __reduce__(self):
        return self._name

    def __call__(self, *args, **kwds):
        raise TypeError(f"Cannot instantiate {self!r}")

    def __or__(self, other):
        return typing.Union[self, other]

    def __ror__(self, other):
        return typing.Union[other, self]

    def __instancecheck__(self, obj):
        raise TypeError(f"{self} cannot be used with isinstance()")

    def __subclasscheck__(self, cls):
        raise TypeError(f"{self} cannot be used with issubclass()")

    @typing._tp_cache
    def __getitem__(self, parameters):
        return self._getitem(self, parameters)


if hasattr(typing, "LiteralString"):  # 3.11+
    LiteralString = typing.LiteralString
else:
    @_SpecialForm
    def LiteralString(self, params):
        """Represents an arbitrary literal string.

        Example::

          from typing_extensions import LiteralString

          def query(sql: LiteralString) -> ...:
              ...

          query("SELECT * FROM table")  # ok
          query(f"SELECT * FROM {input()}")  # not ok

        See PEP 675 for details.

        """
        raise TypeError(f"{self} is not subscriptable")


if hasattr(typing, "Self"):  # 3.11+
    Self = typing.Self
else:
    @_SpecialForm
    def Self(self, params):
        """Used to spell the type of "self" in classes.

        Example::

          from typing import Self

          class ReturnsSelf:
              def parse(self, data: bytes) -> Self:
                  ...
                  return self

        """

        raise TypeError(f"{self} is not subscriptable")


if hasattr(typing, "Never"):  # 3.11+
    Never = typing.Never
else:
    @_SpecialForm
    def Never(self, params):
        """The bottom type, a type that has no members.

        This can be used to define a function that should never be
        called, or a function that never returns::

            from typing_extensions import Never

            def never_call_me(arg: Never) -> None:
                pass

            def int_or_str(arg: int | str) -> None:
                never_call_me(arg)  # type checker error
                match arg:
                    case int():
                        print("It's an int")
                    case str():
                        print("It's a str")
                    case _:
                        never_call_me(arg)  # ok, arg is of type Never

        """

        raise TypeError(f"{self} is not subscriptable")


if hasattr(typing, 'Required'):  # 3.11+
    Required = typing.Required
    NotRequired = typing.NotRequired
elif sys.version_info[:2] >= (3, 9):  # 3.9-3.10
    @_ExtensionsSpecialForm
    def Required(self, parameters):
        """A special typing construct to mark a key of a total=False TypedDict
        as required. For example:

            class Movie(TypedDict, total=False):
                title: Required[str]
                year: int

            m = Movie(
                title='The Matrix',  # typechecker error if key is omitted
                year=1999,
            )

        There is no runtime checking that a required key is actually provided
        when instantiating a related TypedDict.
        """
        item = typing._type_check(parameters, f'{self._name} accepts only a single type.')
        return typing._GenericAlias(self, (item,))

    @_ExtensionsSpecialForm
    def NotRequired(self, parameters):
        """A special typing construct to mark a key of a TypedDict as
        potentially missing. For example:

            class Movie(TypedDict):
                title: str
                year: NotRequired[int]

            m = Movie(
                title='The Matrix',  # typechecker error if key is omitted
                year=1999,
            )
        """
        item = typing._type_check(parameters, f'{self._name} accepts only a single type.')
        return typing._GenericAlias(self, (item,))

else:  # 3.8
    class _RequiredForm(_ExtensionsSpecialForm, _root=True):
        def __getitem__(self, parameters):
            item = typing._type_check(parameters,
                                      f'{self._name} accepts only a single type.')
            return typing._GenericAlias(self, (item,))

    Required = _RequiredForm(
        'Required',
        doc="""A special typing construct to mark a key of a total=False TypedDict
        as required. For example:

            class Movie(TypedDict, total=False):
                title: Required[str]
                year: int

            m = Movie(
                title='The Matrix',  # typechecker error if key is omitted
                year=1999,
            )

        There is no runtime checking that a required key is actually provided
        when instantiating a related TypedDict.
        """)
    NotRequired = _RequiredForm(
        'NotRequired',
        doc="""A special typing construct to mark a key of a TypedDict as
        potentially missing. For example:

            class Movie(TypedDict):
                title: str
                year: NotRequired[int]

            m = Movie(
                title='The Matrix',  # typechecker error if key is omitted
                year=1999,
            )
        """)


_UNPACK_DOC = """\
Type unpack operator.

The type unpack operator takes the child types from some container type,
such as `tuple[int, str]` or a `TypeVarTuple`, and 'pulls them out'. For
example:

  # For some generic class `Foo`:
  Foo[Unpack[tuple[int, str]]]  # Equivalent to Foo[int, str]

  Ts = TypeVarTuple('Ts')
  # Specifies that `Bar` is generic in an arbitrary number of types.
  # (Think of `Ts` as a tuple of an arbitrary number of individual
  #  `TypeVar`s, which the `Unpack` is 'pulling out' directly into the
  #  `Generic[]`.)
  class Bar(Generic[Unpack[Ts]]): ...
  Bar[int]  # Valid
  Bar[int, str]  # Also valid

From Python 3.11, this can also be done using the `*` operator:

    Foo[*tuple[int, str]]
    class Bar(Generic[*Ts]): ...

The operator can also be used along with a `TypedDict` to annotate
`**kwargs` in a function signature. For instance:

  class Movie(TypedDict):
    name: str
    year: int

  # This function expects two keyword arguments - *name* of type `str` and
  # *year* of type `int`.
  def foo(**kwargs: Unpack[Movie]): ...

Note that there is only some runtime checking of this operator. Not
everything the runtime allows may be accepted by static type checkers.

For more information, see PEP 646 and PEP 692.
"""


if sys.version_info >= (3, 12):  # PEP 692 changed the repr of Unpack[]
    Unpack = typing.Unpack

    def _is_unpack(obj):
        return get_origin(obj) is Unpack

elif sys.version_info[:2] >= (3, 9):  # 3.9+
    class _UnpackSpecialForm(_ExtensionsSpecialForm, _root=True):
        def __init__(self, getitem):
            super().__init__(getitem)
            self.__doc__ = _UNPACK_DOC

    class _UnpackAlias(typing._GenericAlias, _root=True):
        __class__ = typing.TypeVar

    @_UnpackSpecialForm
    def Unpack(self, parameters):
        item = typing._type_check(parameters, f'{self._name} accepts only a single type.')
        return _UnpackAlias(self, (item,))

    def _is_unpack(obj):
        return isinstance(obj, _UnpackAlias)

else:  # 3.8
    class _UnpackAlias(typing._GenericAlias, _root=True):
        __class__ = typing.TypeVar

    class _UnpackForm(_ExtensionsSpecialForm, _root=True):
        def __getitem__(self, parameters):
            item = typing._type_check(parameters,
                                      f'{self._name} accepts only a single type.')
            return _UnpackAlias(self, (item,))

    Unpack = _UnpackForm('Unpack', doc=_UNPACK_DOC)

    def _is_unpack(obj):
        return isinstance(obj, _UnpackAlias)


if hasattr(typing, "TypeVarTuple"):  # 3.11+

    # Add default parameter - PEP 696
    class TypeVarTuple(metaclass=_TypeVarLikeMeta):
        """Type variable tuple."""

        _backported_typevarlike = typing.TypeVarTuple

        def __new__(cls, name, *, default=_marker):
            tvt = typing.TypeVarTuple(name)
            _set_default(tvt, default)
            _set_module(tvt)
            return tvt

        def __init_subclass__(self, *args, **kwds):
            raise TypeError("Cannot subclass special typing classes")

else:  # <=3.10
    class TypeVarTuple(_DefaultMixin):
        """Type variable tuple.

        Usage::

            Ts = TypeVarTuple('Ts')

        In the same way that a normal type variable is a stand-in for a single
        type such as ``int``, a type variable *tuple* is a stand-in for a *tuple*
        type such as ``Tuple[int, str]``.

        Type variable tuples can be used in ``Generic`` declarations.
        Consider the following example::

            class Array(Generic[*Ts]): ...

        The ``Ts`` type variable tuple here behaves like ``tuple[T1, T2]``,
        where ``T1`` and ``T2`` are type variables. To use these type variables
        as type parameters of ``Array``, we must *unpack* the type variable tuple using
        the star operator: ``*Ts``. The signature of ``Array`` then behaves
        as if we had simply written ``class Array(Generic[T1, T2]): ...``.
        In contrast to ``Generic[T1, T2]``, however, ``Generic[*Shape]`` allows
        us to parameterise the class with an *arbitrary* number of type parameters.

        Type variable tuples can be used anywhere a normal ``TypeVar`` can.
        This includes class definitions, as shown above, as well as function
        signatures and variable annotations::

            class Array(Generic[*Ts]):

                def __init__(self, shape: Tuple[*Ts]):
                    self._shape: Tuple[*Ts] = shape

                def get_shape(self) -> Tuple[*Ts]:
                    return self._shape

            shape = (Height(480), Width(640))
            x: Array[Height, Width] = Array(shape)
            y = abs(x)  # Inferred type is Array[Height, Width]
            z = x + x   #        ...    is Array[Height, Width]
            x.get_shape()  #     ...    is tuple[Height, Width]

        """

        # Trick Generic __parameters__.
        __class__ = typing.TypeVar

        def __iter__(self):
            yield self.__unpacked__

        def __init__(self, name, *, default=_marker):
            self.__name__ = name
            _DefaultMixin.__init__(self, default)

            # for pickling:
            def_mod = _caller()
            if def_mod != 'typing_extensions':
                self.__module__ = def_mod

            self.__unpacked__ = Unpack[self]

        def __repr__(self):
            return self.__name__

        def __hash__(self):
            return object.__hash__(self)

        def __eq__(self, other):
            return self is other

        def __reduce__(self):
            return self.__name__

        def __init_subclass__(self, *args, **kwds):
            if '_root' not in kwds:
                raise TypeError("Cannot subclass special typing classes")


if hasattr(typing, "reveal_type"):  # 3.11+
    reveal_type = typing.reveal_type
else:  # <=3.10
    def reveal_type(obj: T, /) -> T:
        """Reveal the inferred type of a variable.

        When a static type checker encounters a call to ``reveal_type()``,
        it will emit the inferred type of the argument::

            x: int = 1
            reveal_type(x)

        Running a static type checker (e.g., ``mypy``) on this example
        will produce output similar to 'Revealed type is "builtins.int"'.

        At runtime, the function prints the runtime type of the
        argument and returns it unchanged.

        """
        print(f"Runtime type is {type(obj).__name__!r}", file=sys.stderr)
        return obj


if hasattr(typing, "assert_never"):  # 3.11+
    assert_never = typing.assert_never
else:  # <=3.10
    def assert_never(arg: Never, /) -> Never:
        """Assert to the type checker that a line of code is unreachable.

        Example::

            def int_or_str(arg: int | str) -> None:
                match arg:
                    case int():
                        print("It's an int")
                    case str():
                        print("It's a str")
                    case _:
                        assert_never(arg)

        If a type checker finds that a call to assert_never() is
        reachable, it will emit an error.

        At runtime, this throws an exception when called.

        """
        raise AssertionError("Expected code to be unreachable")


if sys.version_info >= (3, 12):  # 3.12+
    # dataclass_transform exists in 3.11 but lacks the frozen_default parameter
    dataclass_transform = typing.dataclass_transform
else:  # <=3.11
    def dataclass_transform(
        *,
        eq_default: bool = True,
        order_default: bool = False,
        kw_only_default: bool = False,
        frozen_default: bool = False,
        field_specifiers: typing.Tuple[
            typing.Union[typing.Type[typing.Any], typing.Callable[..., typing.Any]],
            ...
        ] = (),
        **kwargs: typing.Any,
    ) -> typing.Callable[[T], T]:
        """Decorator that marks a function, class, or metaclass as providing
        dataclass-like behavior.

        Example:

            from typing_extensions import dataclass_transform

            _T = TypeVar("_T")

            # Used on a decorator function
            @dataclass_transform()
            def create_model(cls: type[_T]) -> type[_T]:
                ...
                return cls

            @create_model
            class CustomerModel:
                id: int
                name: str

            # Used on a base class
            @dataclass_transform()
            class ModelBase: ...

            class CustomerModel(ModelBase):
                id: int
                name: str

            # Used on a metaclass
            @dataclass_transform()
            class ModelMeta(type): ...

            class ModelBase(metaclass=ModelMeta): ...

            class CustomerModel(ModelBase):
                id: int
                name: str

        Each of the ``CustomerModel`` classes defined in this example will now
        behave similarly to a dataclass created with the ``@dataclasses.dataclass``
        decorator. For example, the type checker will synthesize an ``__init__``
        method.

        The arguments to this decorator can be used to customize this behavior:
        - ``eq_default`` indicates whether the ``eq`` parameter is assumed to be
          True or False if it is omitted by the caller.
        - ``order_default`` indicates whether the ``order`` parameter is
          assumed to be True or False if it is omitted by the caller.
        - ``kw_only_default`` indicates whether the ``kw_only`` parameter is
          assumed to be True or False if it is omitted by the caller.
        - ``frozen_default`` indicates whether the ``frozen`` parameter is
          assumed to be True or False if it is omitted by the caller.
        - ``field_specifiers`` specifies a static list of supported classes
          or functions that describe fields, similar to ``dataclasses.field()``.

        At runtime, this decorator records its arguments in the
        ``__dataclass_transform__`` attribute on the decorated object.

        See PEP 681 for details.

        """
        def decorator(cls_or_fn):
            cls_or_fn.__dataclass_transform__ = {
                "eq_default": eq_default,
                "order_default": order_default,
                "kw_only_default": kw_only_default,
                "frozen_default": frozen_default,
                "field_specifiers": field_specifiers,
                "kwargs": kwargs,
            }
            return cls_or_fn
        return decorator


if hasattr(typing, "override"):  # 3.12+
    override = typing.override
else:  # <=3.11
    _F = typing.TypeVar("_F", bound=typing.Callable[..., typing.Any])

    def override(arg: _F, /) -> _F:
        """Indicate that a method is intended to override a method in a base class.

        Usage:

            class Base:
                def method(self) -> None: ...
                    pass

            class Child(Base):
                @override
                def method(self) -> None:
                    super().method()

        When this decorator is applied to a method, the type checker will
        validate that it overrides a method with the same name on a base class.
        This helps prevent bugs that may occur when a base class is changed
        without an equivalent change to a child class.

        There is no runtime checking of these properties. The decorator
        sets the ``__override__`` attribute to ``True`` on the decorated object
        to allow runtime introspection.

        See PEP 698 for details.

        """
        try:
            arg.__override__ = True
        except (AttributeError, TypeError):
            # Skip the attribute silently if it is not writable.
            # AttributeError happens if the object has __slots__ or a
            # read-only property, TypeError if it's a builtin class.
            pass
        return arg


if hasattr(typing, "deprecated"):
    deprecated = typing.deprecated
else:
    _T = typing.TypeVar("_T")

    def deprecated(
        msg: str,
        /,
        *,
        category: typing.Optional[typing.Type[Warning]] = DeprecationWarning,
        stacklevel: int = 1,
    ) -> typing.Callable[[_T], _T]:
        """Indicate that a class, function or overload is deprecated.

        Usage:

            @deprecated("Use B instead")
            class A:
                pass

            @deprecated("Use g instead")
            def f():
                pass

            @overload
            @deprecated("int support is deprecated")
            def g(x: int) -> int: ...
            @overload
            def g(x: str) -> int: ...

        When this decorator is applied to an object, the type checker
        will generate a diagnostic on usage of the deprecated object.

        The warning specified by ``category`` will be emitted on use
        of deprecated objects. For functions, that happens on calls;
        for classes, on instantiation. If the ``category`` is ``None``,
        no warning is emitted. The ``stacklevel`` determines where the
        warning is emitted. If it is ``1`` (the default), the warning
        is emitted at the direct caller of the deprecated object; if it
        is higher, it is emitted further up the stack.

        The decorator sets the ``__deprecated__``
        attribute on the decorated object to the deprecation message
        passed to the decorator. If applied to an overload, the decorator
        must be after the ``@overload`` decorator for the attribute to
        exist on the overload as returned by ``get_overloads()``.

        See PEP 702 for details.

        """
        def decorator(arg: _T, /) -> _T:
            if category is None:
                arg.__deprecated__ = msg
                return arg
            elif isinstance(arg, type):
                original_new = arg.__new__
                has_init = arg.__init__ is not object.__init__

                @functools.wraps(original_new)
                def __new__(cls, *args, **kwargs):
                    warnings.warn(msg, category=category, stacklevel=stacklevel + 1)
                    if original_new is not object.__new__:
                        return original_new(cls, *args, **kwargs)
                    # Mirrors a similar check in object.__new__.
                    elif not has_init and (args or kwargs):
                        raise TypeError(f"{cls.__name__}() takes no arguments")
                    else:
                        return original_new(cls)

                arg.__new__ = staticmethod(__new__)
                arg.__deprecated__ = __new__.__deprecated__ = msg
                return arg
            elif callable(arg):
                @functools.wraps(arg)
                def wrapper(*args, **kwargs):
                    warnings.warn(msg, category=category, stacklevel=stacklevel + 1)
                    return arg(*args, **kwargs)

                arg.__deprecated__ = wrapper.__deprecated__ = msg
                return wrapper
            else:
                raise TypeError(
                    "@deprecated decorator with non-None category must be applied to "
                    f"a class or callable, not {arg!r}"
                )

        return decorator


# We have to do some monkey patching to deal with the dual nature of
# Unpack/TypeVarTuple:
# - We want Unpack to be a kind of TypeVar so it gets accepted in
#   Generic[Unpack[Ts]]
# - We want it to *not* be treated as a TypeVar for the purposes of
#   counting generic parameters, so that when we subscript a generic,
#   the runtime doesn't try to substitute the Unpack with the subscripted type.
if not hasattr(typing, "TypeVarTuple"):
    typing._collect_type_vars = _collect_type_vars
    typing._check_generic = _check_generic


# Backport typing.NamedTuple as it exists in Python 3.13.
# In 3.11, the ability to define generic `NamedTuple`s was supported.
# This was explicitly disallowed in 3.9-3.10, and only half-worked in <=3.8.
# On 3.12, we added __orig_bases__ to call-based NamedTuples
# On 3.13, we deprecated kwargs-based NamedTuples
if sys.version_info >= (3, 13):
    NamedTuple = typing.NamedTuple
else:
    def _make_nmtuple(name, types, module, defaults=()):
        fields = [n for n, t in types]
        annotations = {n: typing._type_check(t, f"field {n} annotation must be a type")
                       for n, t in types}
        nm_tpl = collections.namedtuple(name, fields,
                                        defaults=defaults, module=module)
        nm_tpl.__annotations__ = nm_tpl.__new__.__annotations__ = annotations
        # The `_field_types` attribute was removed in 3.9;
        # in earlier versions, it is the same as the `__annotations__` attribute
        if sys.version_info < (3, 9):
            nm_tpl._field_types = annotations
        return nm_tpl

    _prohibited_namedtuple_fields = typing._prohibited
    _special_namedtuple_fields = frozenset({'__module__', '__name__', '__annotations__'})

    class _NamedTupleMeta(type):
        def __new__(cls, typename, bases, ns):
            assert _NamedTuple in bases
            for base in bases:
                if base is not _NamedTuple and base is not typing.Generic:
                    raise TypeError(
                        'can only inherit from a NamedTuple type and Generic')
            bases = tuple(tuple if base is _NamedTuple else base for base in bases)
            types = ns.get('__annotations__', {})
            default_names = []
            for field_name in types:
                if field_name in ns:
                    default_names.append(field_name)
                elif default_names:
                    raise TypeError(f"Non-default namedtuple field {field_name} "
                                    f"cannot follow default field"
                                    f"{'s' if len(default_names) > 1 else ''} "
                                    f"{', '.join(default_names)}")
            nm_tpl = _make_nmtuple(
                typename, types.items(),
                defaults=[ns[n] for n in default_names],
                module=ns['__module__']
            )
            nm_tpl.__bases__ = bases
            if typing.Generic in bases:
                if hasattr(typing, '_generic_class_getitem'):  # 3.12+
                    nm_tpl.__class_getitem__ = classmethod(typing._generic_class_getitem)
                else:
                    class_getitem = typing.Generic.__class_getitem__.__func__
                    nm_tpl.__class_getitem__ = classmethod(class_getitem)
            # update from user namespace without overriding special namedtuple attributes
            for key in ns:
                if key in _prohibited_namedtuple_fields:
                    raise AttributeError("Cannot overwrite NamedTuple attribute " + key)
                elif key not in _special_namedtuple_fields and key not in nm_tpl._fields:
                    setattr(nm_tpl, key, ns[key])
            if typing.Generic in bases:
                nm_tpl.__init_subclass__()
            return nm_tpl

    _NamedTuple = type.__new__(_NamedTupleMeta, 'NamedTuple', (), {})

    def _namedtuple_mro_entries(bases):
        assert NamedTuple in bases
        return (_NamedTuple,)

    @_ensure_subclassable(_namedtuple_mro_entries)
    def NamedTuple(typename, fields=_marker, /, **kwargs):
        """Typed version of namedtuple.

        Usage::

            class Employee(NamedTuple):
                name: str
                id: int

        This is equivalent to::

            Employee = collections.namedtuple('Employee', ['name', 'id'])

        The resulting class has an extra __annotations__ attribute, giving a
        dict that maps field names to types.  (The field names are also in
        the _fields attribute, which is part of the namedtuple API.)
        An alternative equivalent functional syntax is also accepted::

            Employee = NamedTuple('Employee', [('name', str), ('id', int)])
        """
        if fields is _marker:
            if kwargs:
                deprecated_thing = "Creating NamedTuple classes using keyword arguments"
                deprecation_msg = (
                    "{name} is deprecated and will be disallowed in Python {remove}. "
                    "Use the class-based or functional syntax instead."
                )
            else:
                deprecated_thing = "Failing to pass a value for the 'fields' parameter"
                example = f"`{typename} = NamedTuple({typename!r}, [])`"
                deprecation_msg = (
                    "{name} is deprecated and will be disallowed in Python {remove}. "
                    "To create a NamedTuple class with 0 fields "
                    "using the functional syntax, "
                    "pass an empty list, e.g. "
                ) + example + "."
        elif fields is None:
            if kwargs:
                raise TypeError(
                    "Cannot pass `None` as the 'fields' parameter "
                    "and also specify fields using keyword arguments"
                )
            else:
                deprecated_thing = "Passing `None` as the 'fields' parameter"
                example = f"`{typename} = NamedTuple({typename!r}, [])`"
                deprecation_msg = (
                    "{name} is deprecated and will be disallowed in Python {remove}. "
                    "To create a NamedTuple class with 0 fields "
                    "using the functional syntax, "
                    "pass an empty list, e.g. "
                ) + example + "."
        elif kwargs:
            raise TypeError("Either list of fields or keywords"
                            " can be provided to NamedTuple, not both")
        if fields is _marker or fields is None:
            warnings.warn(
                deprecation_msg.format(name=deprecated_thing, remove="3.15"),
                DeprecationWarning,
                stacklevel=2,
            )
            fields = kwargs.items()
        nt = _make_nmtuple(typename, fields, module=_caller())
        nt.__orig_bases__ = (NamedTuple,)
        return nt


if hasattr(collections.abc, "Buffer"):
    Buffer = collections.abc.Buffer
else:
    class Buffer(abc.ABC):
        """Base class for classes that implement the buffer protocol.

        The buffer protocol allows Python objects to expose a low-level
        memory buffer interface. Before Python 3.12, it is not possible
        to implement the buffer protocol in pure Python code, or even
        to check whether a class implements the buffer protocol. In
        Python 3.12 and higher, the ``__buffer__`` method allows access
        to the buffer protocol from Python code, and the
        ``collections.abc.Buffer`` ABC allows checking whether a class
        implements the buffer protocol.

        To indicate support for the buffer protocol in earlier versions,
        inherit from this ABC, either in a stub file or at runtime,
        or use ABC registration. This ABC provides no methods, because
        there is no Python-accessible methods shared by pre-3.12 buffer
        classes. It is useful primarily for static checks.

        """

    # As a courtesy, register the most common stdlib buffer classes.
    Buffer.register(memoryview)
    Buffer.register(bytearray)
    Buffer.register(bytes)


# Backport of types.get_original_bases, available on 3.12+ in CPython
if hasattr(_types, "get_original_bases"):
    get_original_bases = _types.get_original_bases
else:
    def get_original_bases(cls, /):
        """Return the class's "original" bases prior to modification by `__mro_entries__`.

        Examples::

            from typing import TypeVar, Generic
            from typing_extensions import NamedTuple, TypedDict

            T = TypeVar("T")
            class Foo(Generic[T]): ...
            class Bar(Foo[int], float): ...
            class Baz(list[str]): ...
            Eggs = NamedTuple("Eggs", [("a", int), ("b", str)])
            Spam = TypedDict("Spam", {"a": int, "b": str})

            assert get_original_bases(Bar) == (Foo[int], float)
            assert get_original_bases(Baz) == (list[str],)
            assert get_original_bases(Eggs) == (NamedTuple,)
            assert get_original_bases(Spam) == (TypedDict,)
            assert get_original_bases(int) == (object,)
        """
        try:
            return cls.__dict__.get("__orig_bases__", cls.__bases__)
        except AttributeError:
            raise TypeError(
                f'Expected an instance of type, not {type(cls).__name__!r}'
            ) from None


# NewType is a class on Python 3.10+, making it pickleable
# The error message for subclassing instances of NewType was improved on 3.11+
if sys.version_info >= (3, 11):
    NewType = typing.NewType
else:
    class NewType:
        """NewType creates simple unique types with almost zero
        runtime overhead. NewType(name, tp) is considered a subtype of tp
        by static type checkers. At runtime, NewType(name, tp) returns
        a dummy callable that simply returns its argument. Usage::
            UserId = NewType('UserId', int)
            def name_by_id(user_id: UserId) -> str:
                ...
            UserId('user')          # Fails type check
            name_by_id(42)          # Fails type check
            name_by_id(UserId(42))  # OK
            num = UserId(5) + 1     # type: int
        """

        def __call__(self, obj):
            return obj

        def __init__(self, name, tp):
            self.__qualname__ = name
            if '.' in name:
                name = name.rpartition('.')[-1]
            self.__name__ = name
            self.__supertype__ = tp
            def_mod = _caller()
            if def_mod != 'typing_extensions':
                self.__module__ = def_mod

        def __mro_entries__(self, bases):
            # We defined __mro_entries__ to get a better error message
            # if a user attempts to subclass a NewType instance. bpo-46170
            supercls_name = self.__name__

            class Dummy:
                def __init_subclass__(cls):
                    subcls_name = cls.__name__
                    raise TypeError(
                        f"Cannot subclass an instance of NewType. "
                        f"Perhaps you were looking for: "
                        f"`{subcls_name} = NewType({subcls_name!r}, {supercls_name})`"
                    )

            return (Dummy,)

        def __repr__(self):
            return f'{self.__module__}.{self.__qualname__}'

        def __reduce__(self):
            return self.__qualname__

        if sys.version_info >= (3, 10):
            # PEP 604 methods
            # It doesn't make sense to have these methods on Python <3.10

            def __or__(self, other):
                return typing.Union[self, other]

            def __ror__(self, other):
                return typing.Union[other, self]


if hasattr(typing, "TypeAliasType"):
    TypeAliasType = typing.TypeAliasType
else:
    def _is_unionable(obj):
        """Corresponds to is_unionable() in unionobject.c in CPython."""
        return obj is None or isinstance(obj, (
            type,
            _types.GenericAlias,
            _types.UnionType,
            TypeAliasType,
        ))

    class TypeAliasType:
        """Create named, parameterized type aliases.

        This provides a backport of the new `type` statement in Python 3.12:

            type ListOrSet[T] = list[T] | set[T]

        is equivalent to:

            T = TypeVar("T")
            ListOrSet = TypeAliasType("ListOrSet", list[T] | set[T], type_params=(T,))

        The name ListOrSet can then be used as an alias for the type it refers to.

        The type_params argument should contain all the type parameters used
        in the value of the type alias. If the alias is not generic, this
        argument is omitted.

        Static type checkers should only support type aliases declared using
        TypeAliasType that follow these rules:

        - The first argument (the name) must be a string literal.
        - The TypeAliasType instance must be immediately assigned to a variable
          of the same name. (For example, 'X = TypeAliasType("Y", int)' is invalid,
          as is 'X, Y = TypeAliasType("X", int), TypeAliasType("Y", int)').

        """

        def __init__(self, name: str, value, *, type_params=()):
            if not isinstance(name, str):
                raise TypeError("TypeAliasType name must be a string")
            self.__value__ = value
            self.__type_params__ = type_params

            parameters = []
            for type_param in type_params:
                if isinstance(type_param, TypeVarTuple):
                    parameters.extend(type_param)
                else:
                    parameters.append(type_param)
            self.__parameters__ = tuple(parameters)
            def_mod = _caller()
            if def_mod != 'typing_extensions':
                self.__module__ = def_mod
            # Setting this attribute closes the TypeAliasType from further modification
            self.__name__ = name

        def __setattr__(self, name: str, value: object, /) -> None:
            if hasattr(self, "__name__"):
                self._raise_attribute_error(name)
            super().__setattr__(name, value)

        def __delattr__(self, name: str, /) -> Never:
            self._raise_attribute_error(name)

        def _raise_attribute_error(self, name: str) -> Never:
            # Match the Python 3.12 error messages exactly
            if name == "__name__":
                raise AttributeError("readonly attribute")
            elif name in {"__value__", "__type_params__", "__parameters__", "__module__"}:
                raise AttributeError(
                    f"attribute '{name}' of 'typing.TypeAliasType' objects "
                    "is not writable"
                )
            else:
                raise AttributeError(
                    f"'typing.TypeAliasType' object has no attribute '{name}'"
                )

        def __repr__(self) -> str:
            return self.__name__

        def __getitem__(self, parameters):
            if not isinstance(parameters, tuple):
                parameters = (parameters,)
            parameters = [
                typing._type_check(
                    item, f'Subscripting {self.__name__} requires a type.'
                )
                for item in parameters
            ]
            return typing._GenericAlias(self, tuple(parameters))

        def __reduce__(self):
            return self.__name__

        def __init_subclass__(cls, *args, **kwargs):
            raise TypeError(
                "type 'typing_extensions.TypeAliasType' is not an acceptable base type"
            )

        # The presence of this method convinces typing._type_check
        # that TypeAliasTypes are types.
        def __call__(self):
            raise TypeError("Type alias is not callable")

        if sys.version_info >= (3, 10):
            def __or__(self, right):
                # For forward compatibility with 3.12, reject Unions
                # that are not accepted by the built-in Union.
                if not _is_unionable(right):
                    return NotImplemented
                return typing.Union[self, right]

            def __ror__(self, left):
                if not _is_unionable(left):
                    return NotImplemented
                return typing.Union[left, self]


if hasattr(typing, "is_protocol"):
    is_protocol = typing.is_protocol
    get_protocol_members = typing.get_protocol_members
else:
    def is_protocol(tp: type, /) -> bool:
        """Return True if the given type is a Protocol.

        Example::

            >>> from typing_extensions import Protocol, is_protocol
            >>> class P(Protocol):
            ...     def a(self) -> str: ...
            ...     b: int
            >>> is_protocol(P)
            True
            >>> is_protocol(int)
            False
        """
        return (
            isinstance(tp, type)
            and getattr(tp, '_is_protocol', False)
            and tp is not Protocol
            and tp is not typing.Protocol
        )

    def get_protocol_members(tp: type, /) -> typing.FrozenSet[str]:
        """Return the set of members defined in a Protocol.

        Example::

            >>> from typing_extensions import Protocol, get_protocol_members
            >>> class P(Protocol):
            ...     def a(self) -> str: ...
            ...     b: int
            >>> get_protocol_members(P)
            frozenset({'a', 'b'})

        Raise a TypeError for arguments that are not Protocols.
        """
        if not is_protocol(tp):
            raise TypeError(f'{tp!r} is not a Protocol')
        if hasattr(tp, '__protocol_attrs__'):
            return frozenset(tp.__protocol_attrs__)
        return frozenset(_get_protocol_attrs(tp))


if hasattr(typing, "Doc"):
    Doc = typing.Doc
else:
    class Doc:
        """Define the documentation of a type annotation using ``Annotated``, to be
         used in class attributes, function and method parameters, return values,
         and variables.

        The value should be a positional-only string literal to allow static tools
        like editors and documentation generators to use it.

        This complements docstrings.

        The string value passed is available in the attribute ``documentation``.

        Example::

            >>> from typing_extensions import Annotated, Doc
            >>> def hi(to: Annotated[str, Doc("Who to say hi to")]) -> None: ...
        """
        def __init__(self, documentation: str, /) -> None:
            self.documentation = documentation

        def __repr__(self) -> str:
            return f"Doc({self.documentation!r})"

        def __hash__(self) -> int:
            return hash(self.documentation)

        def __eq__(self, other: object) -> bool:
            if not isinstance(other, Doc):
                return NotImplemented
            return self.documentation == other.documentation


# Aliases for items that have always been in typing.
# Explicitly assign these (rather than using `from typing import *` at the top),
# so that we get a CI error if one of these is deleted from typing.py
# in a future version of Python
AbstractSet = typing.AbstractSet
AnyStr = typing.AnyStr
BinaryIO = typing.BinaryIO
Callable = typing.Callable
Collection = typing.Collection
Container = typing.Container
Dict = typing.Dict
ForwardRef = typing.ForwardRef
FrozenSet = typing.FrozenSet
Generator = typing.Generator
Generic = typing.Generic
Hashable = typing.Hashable
IO = typing.IO
ItemsView = typing.ItemsView
Iterable = typing.Iterable
Iterator = typing.Iterator
KeysView = typing.KeysView
List = typing.List
Mapping = typing.Mapping
MappingView = typing.MappingView
Match = typing.Match
MutableMapping = typing.MutableMapping
MutableSequence = typing.MutableSequence
MutableSet = typing.MutableSet
Optional = typing.Optional
Pattern = typing.Pattern
Reversible = typing.Reversible
Sequence = typing.Sequence
Set = typing.Set
Sized = typing.Sized
TextIO = typing.TextIO
Tuple = typing.Tuple
Union = typing.Union
ValuesView = typing.ValuesView
cast = typing.cast
no_type_check = typing.no_type_check
no_type_check_decorator = typing.no_type_check_decorator

virt/lib/python3.9/site-packages/typing_extensions 2.py
PrinceDisant/LostInNL
import abc
import collections
import collections.abc
import functools
import inspect
import operator
import sys
import types as _types
import typing
import warnings

__all__ = [
    # Super-special typing primitives.
    'Any',
    'ClassVar',
    'Concatenate',
    'Final',
    'LiteralString',
    'ParamSpec',
    'ParamSpecArgs',
    'ParamSpecKwargs',
    'Self',
    'Type',
    'TypeVar',
    'TypeVarTuple',
    'Unpack',

    # ABCs (from collections.abc).
    'Awaitable',
    'AsyncIterator',
    'AsyncIterable',
    'Coroutine',
    'AsyncGenerator',
    'AsyncContextManager',
    'Buffer',
    'ChainMap',

    # Concrete collection types.
    'ContextManager',
    'Counter',
    'Deque',
    'DefaultDict',
    'NamedTuple',
    'OrderedDict',
    'TypedDict',

    # Structural checks, a.k.a. protocols.
    'SupportsAbs',
    'SupportsBytes',
    'SupportsComplex',
    'SupportsFloat',
    'SupportsIndex',
    'SupportsInt',
    'SupportsRound',

    # One-off things.
    'Annotated',
    'assert_never',
    'assert_type',
    'clear_overloads',
    'dataclass_transform',
    'deprecated',
    'Doc',
    'get_overloads',
    'final',
    'get_args',
    'get_origin',
    'get_original_bases',
    'get_protocol_members',
    'get_type_hints',
    'IntVar',
    'is_protocol',
    'is_typeddict',
    'Literal',
    'NewType',
    'overload',
    'override',
    'Protocol',
    'reveal_type',
    'runtime',
    'runtime_checkable',
    'Text',
    'TypeAlias',
    'TypeAliasType',
    'TypeGuard',
    'TYPE_CHECKING',
    'Never',
    'NoReturn',
    'Required',
    'NotRequired',

    # Pure aliases, have always been in typing
    'AbstractSet',
    'AnyStr',
    'BinaryIO',
    'Callable',
    'Collection',
    'Container',
    'Dict',
    'ForwardRef',
    'FrozenSet',
    'Generator',
    'Generic',
    'Hashable',
    'IO',
    'ItemsView',
    'Iterable',
    'Iterator',
    'KeysView',
    'List',
    'Mapping',
    'MappingView',
    'Match',
    'MutableMapping',
    'MutableSequence',
    'MutableSet',
    'Optional',
    'Pattern',
    'Reversible',
    'Sequence',
    'Set',
    'Sized',
    'TextIO',
    'Tuple',
    'Union',
    'ValuesView',
    'cast',
    'no_type_check',
    'no_type_check_decorator',
]

# for backward compatibility
PEP_560 = True
GenericMeta = type

# The functions below are modified copies of typing internal helpers.
# They are needed by _ProtocolMeta and they provide support for PEP 646.


class _Sentinel:
    def __repr__(self):
        return "<sentinel>"


_marker = _Sentinel()


def _check_generic(cls, parameters, elen=_marker):
    """Check correct count for parameters of a generic cls (internal helper).
    This gives a nice error message in case of count mismatch.
    """
    if not elen:
        raise TypeError(f"{cls} is not a generic class")
    if elen is _marker:
        if not hasattr(cls, "__parameters__") or not cls.__parameters__:
            raise TypeError(f"{cls} is not a generic class")
        elen = len(cls.__parameters__)
    alen = len(parameters)
    if alen != elen:
        if hasattr(cls, "__parameters__"):
            parameters = [p for p in cls.__parameters__ if not _is_unpack(p)]
            num_tv_tuples = sum(isinstance(p, TypeVarTuple) for p in parameters)
            if (num_tv_tuples > 0) and (alen >= elen - num_tv_tuples):
                return
        raise TypeError(f"Too {'many' if alen > elen else 'few'} parameters for {cls};"
                        f" actual {alen}, expected {elen}")


if sys.version_info >= (3, 10):
    def _should_collect_from_parameters(t):
        return isinstance(
            t, (typing._GenericAlias, _types.GenericAlias, _types.UnionType)
        )
elif sys.version_info >= (3, 9):
    def _should_collect_from_parameters(t):
        return isinstance(t, (typing._GenericAlias, _types.GenericAlias))
else:
    def _should_collect_from_parameters(t):
        return isinstance(t, typing._GenericAlias) and not t._special


def _collect_type_vars(types, typevar_types=None):
    """Collect all type variable contained in types in order of
    first appearance (lexicographic order). For example::

        _collect_type_vars((T, List[S, T])) == (T, S)
    """
    if typevar_types is None:
        typevar_types = typing.TypeVar
    tvars = []
    for t in types:
        if (
            isinstance(t, typevar_types) and
            t not in tvars and
            not _is_unpack(t)
        ):
            tvars.append(t)
        if _should_collect_from_parameters(t):
            tvars.extend([t for t in t.__parameters__ if t not in tvars])
    return tuple(tvars)


NoReturn = typing.NoReturn

# Some unconstrained type variables.  These are used by the container types.
# (These are not for export.)
T = typing.TypeVar('T')  # Any type.
KT = typing.TypeVar('KT')  # Key type.
VT = typing.TypeVar('VT')  # Value type.
T_co = typing.TypeVar('T_co', covariant=True)  # Any type covariant containers.
T_contra = typing.TypeVar('T_contra', contravariant=True)  # Ditto contravariant.


if sys.version_info >= (3, 11):
    from typing import Any
else:

    class _AnyMeta(type):
        def __instancecheck__(self, obj):
            if self is Any:
                raise TypeError("typing_extensions.Any cannot be used with isinstance()")
            return super().__instancecheck__(obj)

        def __repr__(self):
            if self is Any:
                return "typing_extensions.Any"
            return super().__repr__()

    class Any(metaclass=_AnyMeta):
        """Special type indicating an unconstrained type.
        - Any is compatible with every type.
        - Any assumed to have all methods.
        - All values assumed to be instances of Any.
        Note that all the above statements are true from the point of view of
        static type checkers. At runtime, Any should not be used with instance
        checks.
        """
        def __new__(cls, *args, **kwargs):
            if cls is Any:
                raise TypeError("Any cannot be instantiated")
            return super().__new__(cls, *args, **kwargs)


ClassVar = typing.ClassVar


class _ExtensionsSpecialForm(typing._SpecialForm, _root=True):
    def __repr__(self):
        return 'typing_extensions.' + self._name


Final = typing.Final

if sys.version_info >= (3, 11):
    final = typing.final
else:
    # @final exists in 3.8+, but we backport it for all versions
    # before 3.11 to keep support for the __final__ attribute.
    # See https://bugs.python.org/issue46342
    def final(f):
        """This decorator can be used to indicate to type checkers that
        the decorated method cannot be overridden, and decorated class
        cannot be subclassed. For example:

            class Base:
                @final
                def done(self) -> None:
                    ...
            class Sub(Base):
                def done(self) -> None:  # Error reported by type checker
                    ...
            @final
            class Leaf:
                ...
            class Other(Leaf):  # Error reported by type checker
                ...

        There is no runtime checking of these properties. The decorator
        sets the ``__final__`` attribute to ``True`` on the decorated object
        to allow runtime introspection.
        """
        try:
            f.__final__ = True
        except (AttributeError, TypeError):
            # Skip the attribute silently if it is not writable.
            # AttributeError happens if the object has __slots__ or a
            # read-only property, TypeError if it's a builtin class.
            pass
        return f


def IntVar(name):
    return typing.TypeVar(name)


# A Literal bug was fixed in 3.11.0, 3.10.1 and 3.9.8
if sys.version_info >= (3, 10, 1):
    Literal = typing.Literal
else:
    def _flatten_literal_params(parameters):
        """An internal helper for Literal creation: flatten Literals among parameters"""
        params = []
        for p in parameters:
            if isinstance(p, _LiteralGenericAlias):
                params.extend(p.__args__)
            else:
                params.append(p)
        return tuple(params)

    def _value_and_type_iter(params):
        for p in params:
            yield p, type(p)

    class _LiteralGenericAlias(typing._GenericAlias, _root=True):
        def __eq__(self, other):
            if not isinstance(other, _LiteralGenericAlias):
                return NotImplemented
            these_args_deduped = set(_value_and_type_iter(self.__args__))
            other_args_deduped = set(_value_and_type_iter(other.__args__))
            return these_args_deduped == other_args_deduped

        def __hash__(self):
            return hash(frozenset(_value_and_type_iter(self.__args__)))

    class _LiteralForm(_ExtensionsSpecialForm, _root=True):
        def __init__(self, doc: str):
            self._name = 'Literal'
            self._doc = self.__doc__ = doc

        def __getitem__(self, parameters):
            if not isinstance(parameters, tuple):
                parameters = (parameters,)

            parameters = _flatten_literal_params(parameters)

            val_type_pairs = list(_value_and_type_iter(parameters))
            try:
                deduped_pairs = set(val_type_pairs)
            except TypeError:
                # unhashable parameters
                pass
            else:
                # similar logic to typing._deduplicate on Python 3.9+
                if len(deduped_pairs) < len(val_type_pairs):
                    new_parameters = []
                    for pair in val_type_pairs:
                        if pair in deduped_pairs:
                            new_parameters.append(pair[0])
                            deduped_pairs.remove(pair)
                    assert not deduped_pairs, deduped_pairs
                    parameters = tuple(new_parameters)

            return _LiteralGenericAlias(self, parameters)

    Literal = _LiteralForm(doc="""\
                           A type that can be used to indicate to type checkers
                           that the corresponding value has a value literally equivalent
                           to the provided parameter. For example:

                               var: Literal[4] = 4

                           The type checker understands that 'var' is literally equal to
                           the value 4 and no other value.

                           Literal[...] cannot be subclassed. There is no runtime
                           checking verifying that the parameter is actually a value
                           instead of a type.""")


_overload_dummy = typing._overload_dummy


if hasattr(typing, "get_overloads"):  # 3.11+
    overload = typing.overload
    get_overloads = typing.get_overloads
    clear_overloads = typing.clear_overloads
else:
    # {module: {qualname: {firstlineno: func}}}
    _overload_registry = collections.defaultdict(
        functools.partial(collections.defaultdict, dict)
    )

    def overload(func):
        """Decorator for overloaded functions/methods.

        In a stub file, place two or more stub definitions for the same
        function in a row, each decorated with @overload.  For example:

        @overload
        def utf8(value: None) -> None: ...
        @overload
        def utf8(value: bytes) -> bytes: ...
        @overload
        def utf8(value: str) -> bytes: ...

        In a non-stub file (i.e. a regular .py file), do the same but
        follow it with an implementation.  The implementation should *not*
        be decorated with @overload.  For example:

        @overload
        def utf8(value: None) -> None: ...
        @overload
        def utf8(value: bytes) -> bytes: ...
        @overload
        def utf8(value: str) -> bytes: ...
        def utf8(value):
            # implementation goes here

        The overloads for a function can be retrieved at runtime using the
        get_overloads() function.
        """
        # classmethod and staticmethod
        f = getattr(func, "__func__", func)
        try:
            _overload_registry[f.__module__][f.__qualname__][
                f.__code__.co_firstlineno
            ] = func
        except AttributeError:
            # Not a normal function; ignore.
            pass
        return _overload_dummy

    def get_overloads(func):
        """Return all defined overloads for *func* as a sequence."""
        # classmethod and staticmethod
        f = getattr(func, "__func__", func)
        if f.__module__ not in _overload_registry:
            return []
        mod_dict = _overload_registry[f.__module__]
        if f.__qualname__ not in mod_dict:
            return []
        return list(mod_dict[f.__qualname__].values())

    def clear_overloads():
        """Clear all overloads in the registry."""
        _overload_registry.clear()


# This is not a real generic class.  Don't use outside annotations.
Type = typing.Type

# Various ABCs mimicking those in collections.abc.
# A few are simply re-exported for completeness.
Awaitable = typing.Awaitable
Coroutine = typing.Coroutine
AsyncIterable = typing.AsyncIterable
AsyncIterator = typing.AsyncIterator
Deque = typing.Deque
ContextManager = typing.ContextManager
AsyncContextManager = typing.AsyncContextManager
DefaultDict = typing.DefaultDict
OrderedDict = typing.OrderedDict
Counter = typing.Counter
ChainMap = typing.ChainMap
AsyncGenerator = typing.AsyncGenerator
Text = typing.Text
TYPE_CHECKING = typing.TYPE_CHECKING


_PROTO_ALLOWLIST = {
    'collections.abc': [
        'Callable', 'Awaitable', 'Iterable', 'Iterator', 'AsyncIterable',
        'Hashable', 'Sized', 'Container', 'Collection', 'Reversible', 'Buffer',
    ],
    'contextlib': ['AbstractContextManager', 'AbstractAsyncContextManager'],
    'typing_extensions': ['Buffer'],
}


_EXCLUDED_ATTRS = {
    "__abstractmethods__", "__annotations__", "__weakref__", "_is_protocol",
    "_is_runtime_protocol", "__dict__", "__slots__", "__parameters__",
    "__orig_bases__", "__module__", "_MutableMapping__marker", "__doc__",
    "__subclasshook__", "__orig_class__", "__init__", "__new__",
    "__protocol_attrs__", "__callable_proto_members_only__",
}

if sys.version_info >= (3, 9):
    _EXCLUDED_ATTRS.add("__class_getitem__")

if sys.version_info >= (3, 12):
    _EXCLUDED_ATTRS.add("__type_params__")

_EXCLUDED_ATTRS = frozenset(_EXCLUDED_ATTRS)


def _get_protocol_attrs(cls):
    attrs = set()
    for base in cls.__mro__[:-1]:  # without object
        if base.__name__ in {'Protocol', 'Generic'}:
            continue
        annotations = getattr(base, '__annotations__', {})
        for attr in (*base.__dict__, *annotations):
            if (not attr.startswith('_abc_') and attr not in _EXCLUDED_ATTRS):
                attrs.add(attr)
    return attrs


def _caller(depth=2):
    try:
        return sys._getframe(depth).f_globals.get('__name__', '__main__')
    except (AttributeError, ValueError):  # For platforms without _getframe()
        return None


# The performance of runtime-checkable protocols is significantly improved on Python 3.12,
# so we backport the 3.12 version of Protocol to Python <=3.11
if sys.version_info >= (3, 12):
    Protocol = typing.Protocol
else:
    def _allow_reckless_class_checks(depth=3):
        """Allow instance and class checks for special stdlib modules.
        The abc and functools modules indiscriminately call isinstance() and
        issubclass() on the whole MRO of a user class, which may contain protocols.
        """
        return _caller(depth) in {'abc', 'functools', None}

    def _no_init(self, *args, **kwargs):
        if type(self)._is_protocol:
            raise TypeError('Protocols cannot be instantiated')

    # Inheriting from typing._ProtocolMeta isn't actually desirable,
    # but is necessary to allow typing.Protocol and typing_extensions.Protocol
    # to mix without getting TypeErrors about "metaclass conflict"
    class _ProtocolMeta(type(typing.Protocol)):
        # This metaclass is somewhat unfortunate,
        # but is necessary for several reasons...
        #
        # NOTE: DO NOT call super() in any methods in this class
        # That would call the methods on typing._ProtocolMeta on Python 3.8-3.11
        # and those are slow
        def __new__(mcls, name, bases, namespace, **kwargs):
            if name == "Protocol" and len(bases) < 2:
                pass
            elif {Protocol, typing.Protocol} & set(bases):
                for base in bases:
                    if not (
                        base in {object, typing.Generic, Protocol, typing.Protocol}
                        or base.__name__ in _PROTO_ALLOWLIST.get(base.__module__, [])
                        or is_protocol(base)
                    ):
                        raise TypeError(
                            f"Protocols can only inherit from other protocols, "
                            f"got {base!r}"
                        )
            return abc.ABCMeta.__new__(mcls, name, bases, namespace, **kwargs)

        def __init__(cls, *args, **kwargs):
            abc.ABCMeta.__init__(cls, *args, **kwargs)
            if getattr(cls, "_is_protocol", False):
                cls.__protocol_attrs__ = _get_protocol_attrs(cls)
                # PEP 544 prohibits using issubclass()
                # with protocols that have non-method members.
                cls.__callable_proto_members_only__ = all(
                    callable(getattr(cls, attr, None)) for attr in cls.__protocol_attrs__
                )

        def __subclasscheck__(cls, other):
            if cls is Protocol:
                return type.__subclasscheck__(cls, other)
            if (
                getattr(cls, '_is_protocol', False)
                and not _allow_reckless_class_checks()
            ):
                if not isinstance(other, type):
                    # Same error message as for issubclass(1, int).
                    raise TypeError('issubclass() arg 1 must be a class')
                if (
                    not cls.__callable_proto_members_only__
                    and cls.__dict__.get("__subclasshook__") is _proto_hook
                ):
                    raise TypeError(
                        "Protocols with non-method members don't support issubclass()"
                    )
                if not getattr(cls, '_is_runtime_protocol', False):
                    raise TypeError(
                        "Instance and class checks can only be used with "
                        "@runtime_checkable protocols"
                    )
            return abc.ABCMeta.__subclasscheck__(cls, other)

        def __instancecheck__(cls, instance):
            # We need this method for situations where attributes are
            # assigned in __init__.
            if cls is Protocol:
                return type.__instancecheck__(cls, instance)
            if not getattr(cls, "_is_protocol", False):
                # i.e., it's a concrete subclass of a protocol
                return abc.ABCMeta.__instancecheck__(cls, instance)

            if (
                not getattr(cls, '_is_runtime_protocol', False) and
                not _allow_reckless_class_checks()
            ):
                raise TypeError("Instance and class checks can only be used with"
                                " @runtime_checkable protocols")

            if abc.ABCMeta.__instancecheck__(cls, instance):
                return True

            for attr in cls.__protocol_attrs__:
                try:
                    val = inspect.getattr_static(instance, attr)
                except AttributeError:
                    break
                if val is None and callable(getattr(cls, attr, None)):
                    break
            else:
                return True

            return False

        def __eq__(cls, other):
            # Hack so that typing.Generic.__class_getitem__
            # treats typing_extensions.Protocol
            # as equivalent to typing.Protocol
            if abc.ABCMeta.__eq__(cls, other) is True:
                return True
            return cls is Protocol and other is typing.Protocol

        # This has to be defined, or the abc-module cache
        # complains about classes with this metaclass being unhashable,
        # if we define only __eq__!
        def __hash__(cls) -> int:
            return type.__hash__(cls)

    @classmethod
    def _proto_hook(cls, other):
        if not cls.__dict__.get('_is_protocol', False):
            return NotImplemented

        for attr in cls.__protocol_attrs__:
            for base in other.__mro__:
                # Check if the members appears in the class dictionary...
                if attr in base.__dict__:
                    if base.__dict__[attr] is None:
                        return NotImplemented
                    break

                # ...or in annotations, if it is a sub-protocol.
                annotations = getattr(base, '__annotations__', {})
                if (
                    isinstance(annotations, collections.abc.Mapping)
                    and attr in annotations
                    and is_protocol(other)
                ):
                    break
            else:
                return NotImplemented
        return True

    class Protocol(typing.Generic, metaclass=_ProtocolMeta):
        __doc__ = typing.Protocol.__doc__
        __slots__ = ()
        _is_protocol = True
        _is_runtime_protocol = False

        def __init_subclass__(cls, *args, **kwargs):
            super().__init_subclass__(*args, **kwargs)

            # Determine if this is a protocol or a concrete subclass.
            if not cls.__dict__.get('_is_protocol', False):
                cls._is_protocol = any(b is Protocol for b in cls.__bases__)

            # Set (or override) the protocol subclass hook.
            if '__subclasshook__' not in cls.__dict__:
                cls.__subclasshook__ = _proto_hook

            # Prohibit instantiation for protocol classes
            if cls._is_protocol and cls.__init__ is Protocol.__init__:
                cls.__init__ = _no_init


# The "runtime" alias exists for backwards compatibility.
runtime = runtime_checkable = typing.runtime_checkable


# Our version of runtime-checkable protocols is faster on Python 3.8-3.11
if sys.version_info >= (3, 12):
    SupportsInt = typing.SupportsInt
    SupportsFloat = typing.SupportsFloat
    SupportsComplex = typing.SupportsComplex
    SupportsBytes = typing.SupportsBytes
    SupportsIndex = typing.SupportsIndex
    SupportsAbs = typing.SupportsAbs
    SupportsRound = typing.SupportsRound
else:
    @runtime_checkable
    class SupportsInt(Protocol):
        """An ABC with one abstract method __int__."""
        __slots__ = ()

        @abc.abstractmethod
        def __int__(self) -> int:
            pass

    @runtime_checkable
    class SupportsFloat(Protocol):
        """An ABC with one abstract method __float__."""
        __slots__ = ()

        @abc.abstractmethod
        def __float__(self) -> float:
            pass

    @runtime_checkable
    class SupportsComplex(Protocol):
        """An ABC with one abstract method __complex__."""
        __slots__ = ()

        @abc.abstractmethod
        def __complex__(self) -> complex:
            pass

    @runtime_checkable
    class SupportsBytes(Protocol):
        """An ABC with one abstract method __bytes__."""
        __slots__ = ()

        @abc.abstractmethod
        def __bytes__(self) -> bytes:
            pass

    @runtime_checkable
    class SupportsIndex(Protocol):
        __slots__ = ()

        @abc.abstractmethod
        def __index__(self) -> int:
            pass

    @runtime_checkable
    class SupportsAbs(Protocol[T_co]):
        """
        An ABC with one abstract method __abs__ that is covariant in its return type.
        """
        __slots__ = ()

        @abc.abstractmethod
        def __abs__(self) -> T_co:
            pass

    @runtime_checkable
    class SupportsRound(Protocol[T_co]):
        """
        An ABC with one abstract method __round__ that is covariant in its return type.
        """
        __slots__ = ()

        @abc.abstractmethod
        def __round__(self, ndigits: int = 0) -> T_co:
            pass


def _ensure_subclassable(mro_entries):
    def inner(func):
        if sys.implementation.name == "pypy" and sys.version_info < (3, 9):
            cls_dict = {
                "__call__": staticmethod(func),
                "__mro_entries__": staticmethod(mro_entries)
            }
            t = type(func.__name__, (), cls_dict)
            return functools.update_wrapper(t(), func)
        else:
            func.__mro_entries__ = mro_entries
            return func
    return inner


if sys.version_info >= (3, 13):
    # The standard library TypedDict in Python 3.8 does not store runtime information
    # about which (if any) keys are optional.  See https://bugs.python.org/issue38834
    # The standard library TypedDict in Python 3.9.0/1 does not honour the "total"
    # keyword with old-style TypedDict().  See https://bugs.python.org/issue42059
    # The standard library TypedDict below Python 3.11 does not store runtime
    # information about optional and required keys when using Required or NotRequired.
    # Generic TypedDicts are also impossible using typing.TypedDict on Python <3.11.
    # Aaaand on 3.12 we add __orig_bases__ to TypedDict
    # to enable better runtime introspection.
    # On 3.13 we deprecate some odd ways of creating TypedDicts.
    TypedDict = typing.TypedDict
    _TypedDictMeta = typing._TypedDictMeta
    is_typeddict = typing.is_typeddict
else:
    # 3.10.0 and later
    _TAKES_MODULE = "module" in inspect.signature(typing._type_check).parameters

    class _TypedDictMeta(type):
        def __new__(cls, name, bases, ns, total=True):
            """Create new typed dict class object.

            This method is called when TypedDict is subclassed,
            or when TypedDict is instantiated. This way
            TypedDict supports all three syntax forms described in its docstring.
            Subclasses and instances of TypedDict return actual dictionaries.
            """
            for base in bases:
                if type(base) is not _TypedDictMeta and base is not typing.Generic:
                    raise TypeError('cannot inherit from both a TypedDict type '
                                    'and a non-TypedDict base class')

            if any(issubclass(b, typing.Generic) for b in bases):
                generic_base = (typing.Generic,)
            else:
                generic_base = ()

            # typing.py generally doesn't let you inherit from plain Generic, unless
            # the name of the class happens to be "Protocol"
            tp_dict = type.__new__(_TypedDictMeta, "Protocol", (*generic_base, dict), ns)
            tp_dict.__name__ = name
            if tp_dict.__qualname__ == "Protocol":
                tp_dict.__qualname__ = name

            if not hasattr(tp_dict, '__orig_bases__'):
                tp_dict.__orig_bases__ = bases

            annotations = {}
            own_annotations = ns.get('__annotations__', {})
            msg = "TypedDict('Name', {f0: t0, f1: t1, ...}); each t must be a type"
            if _TAKES_MODULE:
                own_annotations = {
                    n: typing._type_check(tp, msg, module=tp_dict.__module__)
                    for n, tp in own_annotations.items()
                }
            else:
                own_annotations = {
                    n: typing._type_check(tp, msg)
                    for n, tp in own_annotations.items()
                }
            required_keys = set()
            optional_keys = set()

            for base in bases:
                annotations.update(base.__dict__.get('__annotations__', {}))
                required_keys.update(base.__dict__.get('__required_keys__', ()))
                optional_keys.update(base.__dict__.get('__optional_keys__', ()))

            annotations.update(own_annotations)
            for annotation_key, annotation_type in own_annotations.items():
                annotation_origin = get_origin(annotation_type)
                if annotation_origin is Annotated:
                    annotation_args = get_args(annotation_type)
                    if annotation_args:
                        annotation_type = annotation_args[0]
                        annotation_origin = get_origin(annotation_type)

                if annotation_origin is Required:
                    required_keys.add(annotation_key)
                elif annotation_origin is NotRequired:
                    optional_keys.add(annotation_key)
                elif total:
                    required_keys.add(annotation_key)
                else:
                    optional_keys.add(annotation_key)

            tp_dict.__annotations__ = annotations
            tp_dict.__required_keys__ = frozenset(required_keys)
            tp_dict.__optional_keys__ = frozenset(optional_keys)
            if not hasattr(tp_dict, '__total__'):
                tp_dict.__total__ = total
            return tp_dict

        __call__ = dict  # static method

        def __subclasscheck__(cls, other):
            # Typed dicts are only for static structural subtyping.
            raise TypeError('TypedDict does not support instance and class checks')

        __instancecheck__ = __subclasscheck__

    _TypedDict = type.__new__(_TypedDictMeta, 'TypedDict', (), {})

    @_ensure_subclassable(lambda bases: (_TypedDict,))
    def TypedDict(typename, fields=_marker, /, *, total=True, **kwargs):
        """A simple typed namespace. At runtime it is equivalent to a plain dict.

        TypedDict creates a dictionary type such that a type checker will expect all
        instances to have a certain set of keys, where each key is
        associated with a value of a consistent type. This expectation
        is not checked at runtime.

        Usage::

            class Point2D(TypedDict):
                x: int
                y: int
                label: str

            a: Point2D = {'x': 1, 'y': 2, 'label': 'good'}  # OK
            b: Point2D = {'z': 3, 'label': 'bad'}           # Fails type check

            assert Point2D(x=1, y=2, label='first') == dict(x=1, y=2, label='first')

        The type info can be accessed via the Point2D.__annotations__ dict, and
        the Point2D.__required_keys__ and Point2D.__optional_keys__ frozensets.
        TypedDict supports an additional equivalent form::

            Point2D = TypedDict('Point2D', {'x': int, 'y': int, 'label': str})

        By default, all keys must be present in a TypedDict. It is possible
        to override this by specifying totality::

            class Point2D(TypedDict, total=False):
                x: int
                y: int

        This means that a Point2D TypedDict can have any of the keys omitted. A type
        checker is only expected to support a literal False or True as the value of
        the total argument. True is the default, and makes all items defined in the
        class body be required.

        The Required and NotRequired special forms can also be used to mark
        individual keys as being required or not required::

            class Point2D(TypedDict):
                x: int  # the "x" key must always be present (Required is the default)
                y: NotRequired[int]  # the "y" key can be omitted

        See PEP 655 for more details on Required and NotRequired.
        """
        if fields is _marker or fields is None:
            if fields is _marker:
                deprecated_thing = "Failing to pass a value for the 'fields' parameter"
            else:
                deprecated_thing = "Passing `None` as the 'fields' parameter"

            example = f"`{typename} = TypedDict({typename!r}, {{}})`"
            deprecation_msg = (
                f"{deprecated_thing} is deprecated and will be disallowed in "
                "Python 3.15. To create a TypedDict class with 0 fields "
                "using the functional syntax, pass an empty dictionary, e.g. "
            ) + example + "."
            warnings.warn(deprecation_msg, DeprecationWarning, stacklevel=2)
            fields = kwargs
        elif kwargs:
            raise TypeError("TypedDict takes either a dict or keyword arguments,"
                            " but not both")
        if kwargs:
            warnings.warn(
                "The kwargs-based syntax for TypedDict definitions is deprecated "
                "in Python 3.11, will be removed in Python 3.13, and may not be "
                "understood by third-party type checkers.",
                DeprecationWarning,
                stacklevel=2,
            )

        ns = {'__annotations__': dict(fields)}
        module = _caller()
        if module is not None:
            # Setting correct module is necessary to make typed dict classes pickleable.
            ns['__module__'] = module

        td = _TypedDictMeta(typename, (), ns, total=total)
        td.__orig_bases__ = (TypedDict,)
        return td

    if hasattr(typing, "_TypedDictMeta"):
        _TYPEDDICT_TYPES = (typing._TypedDictMeta, _TypedDictMeta)
    else:
        _TYPEDDICT_TYPES = (_TypedDictMeta,)

    def is_typeddict(tp):
        """Check if an annotation is a TypedDict class

        For example::
            class Film(TypedDict):
                title: str
                year: int

            is_typeddict(Film)  # => True
            is_typeddict(Union[list, str])  # => False
        """
        # On 3.8, this would otherwise return True
        if hasattr(typing, "TypedDict") and tp is typing.TypedDict:
            return False
        return isinstance(tp, _TYPEDDICT_TYPES)


if hasattr(typing, "assert_type"):
    assert_type = typing.assert_type

else:
    def assert_type(val, typ, /):
        """Assert (to the type checker) that the value is of the given type.

        When the type checker encounters a call to assert_type(), it
        emits an error if the value is not of the specified type::

            def greet(name: str) -> None:
                assert_type(name, str)  # ok
                assert_type(name, int)  # type checker error

        At runtime this returns the first argument unchanged and otherwise
        does nothing.
        """
        return val


if hasattr(typing, "Required"):  # 3.11+
    get_type_hints = typing.get_type_hints
else:  # <=3.10
    # replaces _strip_annotations()
    def _strip_extras(t):
        """Strips Annotated, Required and NotRequired from a given type."""
        if isinstance(t, _AnnotatedAlias):
            return _strip_extras(t.__origin__)
        if hasattr(t, "__origin__") and t.__origin__ in (Required, NotRequired):
            return _strip_extras(t.__args__[0])
        if isinstance(t, typing._GenericAlias):
            stripped_args = tuple(_strip_extras(a) for a in t.__args__)
            if stripped_args == t.__args__:
                return t
            return t.copy_with(stripped_args)
        if hasattr(_types, "GenericAlias") and isinstance(t, _types.GenericAlias):
            stripped_args = tuple(_strip_extras(a) for a in t.__args__)
            if stripped_args == t.__args__:
                return t
            return _types.GenericAlias(t.__origin__, stripped_args)
        if hasattr(_types, "UnionType") and isinstance(t, _types.UnionType):
            stripped_args = tuple(_strip_extras(a) for a in t.__args__)
            if stripped_args == t.__args__:
                return t
            return functools.reduce(operator.or_, stripped_args)

        return t

    def get_type_hints(obj, globalns=None, localns=None, include_extras=False):
        """Return type hints for an object.

        This is often the same as obj.__annotations__, but it handles
        forward references encoded as string literals, adds Optional[t] if a
        default value equal to None is set and recursively replaces all
        'Annotated[T, ...]', 'Required[T]' or 'NotRequired[T]' with 'T'
        (unless 'include_extras=True').

        The argument may be a module, class, method, or function. The annotations
        are returned as a dictionary. For classes, annotations include also
        inherited members.

        TypeError is raised if the argument is not of a type that can contain
        annotations, and an empty dictionary is returned if no annotations are
        present.

        BEWARE -- the behavior of globalns and localns is counterintuitive
        (unless you are familiar with how eval() and exec() work).  The
        search order is locals first, then globals.

        - If no dict arguments are passed, an attempt is made to use the
          globals from obj (or the respective module's globals for classes),
          and these are also used as the locals.  If the object does not appear
          to have globals, an empty dictionary is used.

        - If one dict argument is passed, it is used for both globals and
          locals.

        - If two dict arguments are passed, they specify globals and
          locals, respectively.
        """
        if hasattr(typing, "Annotated"):  # 3.9+
            hint = typing.get_type_hints(
                obj, globalns=globalns, localns=localns, include_extras=True
            )
        else:  # 3.8
            hint = typing.get_type_hints(obj, globalns=globalns, localns=localns)
        if include_extras:
            return hint
        return {k: _strip_extras(t) for k, t in hint.items()}


# Python 3.9+ has PEP 593 (Annotated)
if hasattr(typing, 'Annotated'):
    Annotated = typing.Annotated
    # Not exported and not a public API, but needed for get_origin() and get_args()
    # to work.
    _AnnotatedAlias = typing._AnnotatedAlias
# 3.8
else:
    class _AnnotatedAlias(typing._GenericAlias, _root=True):
        """Runtime representation of an annotated type.

        At its core 'Annotated[t, dec1, dec2, ...]' is an alias for the type 't'
        with extra annotations. The alias behaves like a normal typing alias,
        instantiating is the same as instantiating the underlying type, binding
        it to types is also the same.
        """
        def __init__(self, origin, metadata):
            if isinstance(origin, _AnnotatedAlias):
                metadata = origin.__metadata__ + metadata
                origin = origin.__origin__
            super().__init__(origin, origin)
            self.__metadata__ = metadata

        def copy_with(self, params):
            assert len(params) == 1
            new_type = params[0]
            return _AnnotatedAlias(new_type, self.__metadata__)

        def __repr__(self):
            return (f"typing_extensions.Annotated[{typing._type_repr(self.__origin__)}, "
                    f"{', '.join(repr(a) for a in self.__metadata__)}]")

        def __reduce__(self):
            return operator.getitem, (
                Annotated, (self.__origin__,) + self.__metadata__
            )

        def __eq__(self, other):
            if not isinstance(other, _AnnotatedAlias):
                return NotImplemented
            if self.__origin__ != other.__origin__:
                return False
            return self.__metadata__ == other.__metadata__

        def __hash__(self):
            return hash((self.__origin__, self.__metadata__))

    class Annotated:
        """Add context specific metadata to a type.

        Example: Annotated[int, runtime_check.Unsigned] indicates to the
        hypothetical runtime_check module that this type is an unsigned int.
        Every other consumer of this type can ignore this metadata and treat
        this type as int.

        The first argument to Annotated must be a valid type (and will be in
        the __origin__ field), the remaining arguments are kept as a tuple in
        the __extra__ field.

        Details:

        - It's an error to call `Annotated` with less than two arguments.
        - Nested Annotated are flattened::

            Annotated[Annotated[T, Ann1, Ann2], Ann3] == Annotated[T, Ann1, Ann2, Ann3]

        - Instantiating an annotated type is equivalent to instantiating the
        underlying type::

            Annotated[C, Ann1](5) == C(5)

        - Annotated can be used as a generic type alias::

            Optimized = Annotated[T, runtime.Optimize()]
            Optimized[int] == Annotated[int, runtime.Optimize()]

            OptimizedList = Annotated[List[T], runtime.Optimize()]
            OptimizedList[int] == Annotated[List[int], runtime.Optimize()]
        """

        __slots__ = ()

        def __new__(cls, *args, **kwargs):
            raise TypeError("Type Annotated cannot be instantiated.")

        @typing._tp_cache
        def __class_getitem__(cls, params):
            if not isinstance(params, tuple) or len(params) < 2:
                raise TypeError("Annotated[...] should be used "
                                "with at least two arguments (a type and an "
                                "annotation).")
            allowed_special_forms = (ClassVar, Final)
            if get_origin(params[0]) in allowed_special_forms:
                origin = params[0]
            else:
                msg = "Annotated[t, ...]: t must be a type."
                origin = typing._type_check(params[0], msg)
            metadata = tuple(params[1:])
            return _AnnotatedAlias(origin, metadata)

        def __init_subclass__(cls, *args, **kwargs):
            raise TypeError(
                f"Cannot subclass {cls.__module__}.Annotated"
            )

# Python 3.8 has get_origin() and get_args() but those implementations aren't
# Annotated-aware, so we can't use those. Python 3.9's versions don't support
# ParamSpecArgs and ParamSpecKwargs, so only Python 3.10's versions will do.
if sys.version_info[:2] >= (3, 10):
    get_origin = typing.get_origin
    get_args = typing.get_args
# 3.8-3.9
else:
    try:
        # 3.9+
        from typing import _BaseGenericAlias
    except ImportError:
        _BaseGenericAlias = typing._GenericAlias
    try:
        # 3.9+
        from typing import GenericAlias as _typing_GenericAlias
    except ImportError:
        _typing_GenericAlias = typing._GenericAlias

    def get_origin(tp):
        """Get the unsubscripted version of a type.

        This supports generic types, Callable, Tuple, Union, Literal, Final, ClassVar
        and Annotated. Return None for unsupported types. Examples::

            get_origin(Literal[42]) is Literal
            get_origin(int) is None
            get_origin(ClassVar[int]) is ClassVar
            get_origin(Generic) is Generic
            get_origin(Generic[T]) is Generic
            get_origin(Union[T, int]) is Union
            get_origin(List[Tuple[T, T]][int]) == list
            get_origin(P.args) is P
        """
        if isinstance(tp, _AnnotatedAlias):
            return Annotated
        if isinstance(tp, (typing._GenericAlias, _typing_GenericAlias, _BaseGenericAlias,
                           ParamSpecArgs, ParamSpecKwargs)):
            return tp.__origin__
        if tp is typing.Generic:
            return typing.Generic
        return None

    def get_args(tp):
        """Get type arguments with all substitutions performed.

        For unions, basic simplifications used by Union constructor are performed.
        Examples::
            get_args(Dict[str, int]) == (str, int)
            get_args(int) == ()
            get_args(Union[int, Union[T, int], str][int]) == (int, str)
            get_args(Union[int, Tuple[T, int]][str]) == (int, Tuple[str, int])
            get_args(Callable[[], T][int]) == ([], int)
        """
        if isinstance(tp, _AnnotatedAlias):
            return (tp.__origin__,) + tp.__metadata__
        if isinstance(tp, (typing._GenericAlias, _typing_GenericAlias)):
            if getattr(tp, "_special", False):
                return ()
            res = tp.__args__
            if get_origin(tp) is collections.abc.Callable and res[0] is not Ellipsis:
                res = (list(res[:-1]), res[-1])
            return res
        return ()


# 3.10+
if hasattr(typing, 'TypeAlias'):
    TypeAlias = typing.TypeAlias
# 3.9
elif sys.version_info[:2] >= (3, 9):
    @_ExtensionsSpecialForm
    def TypeAlias(self, parameters):
        """Special marker indicating that an assignment should
        be recognized as a proper type alias definition by type
        checkers.

        For example::

            Predicate: TypeAlias = Callable[..., bool]

        It's invalid when used anywhere except as in the example above.
        """
        raise TypeError(f"{self} is not subscriptable")
# 3.8
else:
    TypeAlias = _ExtensionsSpecialForm(
        'TypeAlias',
        doc="""Special marker indicating that an assignment should
        be recognized as a proper type alias definition by type
        checkers.

        For example::

            Predicate: TypeAlias = Callable[..., bool]

        It's invalid when used anywhere except as in the example
        above."""
    )


def _set_default(type_param, default):
    if isinstance(default, (tuple, list)):
        type_param.__default__ = tuple((typing._type_check(d, "Default must be a type")
                                        for d in default))
    elif default != _marker:
        if isinstance(type_param, ParamSpec) and default is ...:  # ... not valid <3.11
            type_param.__default__ = default
        else:
            type_param.__default__ = typing._type_check(default, "Default must be a type")
    else:
        type_param.__default__ = None


def _set_module(typevarlike):
    # for pickling:
    def_mod = _caller(depth=3)
    if def_mod != 'typing_extensions':
        typevarlike.__module__ = def_mod


class _DefaultMixin:
    """Mixin for TypeVarLike defaults."""

    __slots__ = ()
    __init__ = _set_default


# Classes using this metaclass must provide a _backported_typevarlike ClassVar
class _TypeVarLikeMeta(type):
    def __instancecheck__(cls, __instance: Any) -> bool:
        return isinstance(__instance, cls._backported_typevarlike)


# Add default and infer_variance parameters from PEP 696 and 695
class TypeVar(metaclass=_TypeVarLikeMeta):
    """Type variable."""

    _backported_typevarlike = typing.TypeVar

    def __new__(cls, name, *constraints, bound=None,
                covariant=False, contravariant=False,
                default=_marker, infer_variance=False):
        if hasattr(typing, "TypeAliasType"):
            # PEP 695 implemented (3.12+), can pass infer_variance to typing.TypeVar
            typevar = typing.TypeVar(name, *constraints, bound=bound,
                                     covariant=covariant, contravariant=contravariant,
                                     infer_variance=infer_variance)
        else:
            typevar = typing.TypeVar(name, *constraints, bound=bound,
                                     covariant=covariant, contravariant=contravariant)
            if infer_variance and (covariant or contravariant):
                raise ValueError("Variance cannot be specified with infer_variance.")
            typevar.__infer_variance__ = infer_variance
        _set_default(typevar, default)
        _set_module(typevar)
        return typevar

    def __init_subclass__(cls) -> None:
        raise TypeError(f"type '{__name__}.TypeVar' is not an acceptable base type")


# Python 3.10+ has PEP 612
if hasattr(typing, 'ParamSpecArgs'):
    ParamSpecArgs = typing.ParamSpecArgs
    ParamSpecKwargs = typing.ParamSpecKwargs
# 3.8-3.9
else:
    class _Immutable:
        """Mixin to indicate that object should not be copied."""
        __slots__ = ()

        def __copy__(self):
            return self

        def __deepcopy__(self, memo):
            return self

    class ParamSpecArgs(_Immutable):
        """The args for a ParamSpec object.

        Given a ParamSpec object P, P.args is an instance of ParamSpecArgs.

        ParamSpecArgs objects have a reference back to their ParamSpec:

        P.args.__origin__ is P

        This type is meant for runtime introspection and has no special meaning to
        static type checkers.
        """
        def __init__(self, origin):
            self.__origin__ = origin

        def __repr__(self):
            return f"{self.__origin__.__name__}.args"

        def __eq__(self, other):
            if not isinstance(other, ParamSpecArgs):
                return NotImplemented
            return self.__origin__ == other.__origin__

    class ParamSpecKwargs(_Immutable):
        """The kwargs for a ParamSpec object.

        Given a ParamSpec object P, P.kwargs is an instance of ParamSpecKwargs.

        ParamSpecKwargs objects have a reference back to their ParamSpec:

        P.kwargs.__origin__ is P

        This type is meant for runtime introspection and has no special meaning to
        static type checkers.
        """
        def __init__(self, origin):
            self.__origin__ = origin

        def __repr__(self):
            return f"{self.__origin__.__name__}.kwargs"

        def __eq__(self, other):
            if not isinstance(other, ParamSpecKwargs):
                return NotImplemented
            return self.__origin__ == other.__origin__

# 3.10+
if hasattr(typing, 'ParamSpec'):

    # Add default parameter - PEP 696
    class ParamSpec(metaclass=_TypeVarLikeMeta):
        """Parameter specification."""

        _backported_typevarlike = typing.ParamSpec

        def __new__(cls, name, *, bound=None,
                    covariant=False, contravariant=False,
                    infer_variance=False, default=_marker):
            if hasattr(typing, "TypeAliasType"):
                # PEP 695 implemented, can pass infer_variance to typing.TypeVar
                paramspec = typing.ParamSpec(name, bound=bound,
                                             covariant=covariant,
                                             contravariant=contravariant,
                                             infer_variance=infer_variance)
            else:
                paramspec = typing.ParamSpec(name, bound=bound,
                                             covariant=covariant,
                                             contravariant=contravariant)
                paramspec.__infer_variance__ = infer_variance

            _set_default(paramspec, default)
            _set_module(paramspec)
            return paramspec

        def __init_subclass__(cls) -> None:
            raise TypeError(f"type '{__name__}.ParamSpec' is not an acceptable base type")

# 3.8-3.9
else:

    # Inherits from list as a workaround for Callable checks in Python < 3.9.2.
    class ParamSpec(list, _DefaultMixin):
        """Parameter specification variable.

        Usage::

           P = ParamSpec('P')

        Parameter specification variables exist primarily for the benefit of static
        type checkers.  They are used to forward the parameter types of one
        callable to another callable, a pattern commonly found in higher order
        functions and decorators.  They are only valid when used in ``Concatenate``,
        or s the first argument to ``Callable``. In Python 3.10 and higher,
        they are also supported in user-defined Generics at runtime.
        See class Generic for more information on generic types.  An
        example for annotating a decorator::

           T = TypeVar('T')
           P = ParamSpec('P')

           def add_logging(f: Callable[P, T]) -> Callable[P, T]:
               '''A type-safe decorator to add logging to a function.'''
               def inner(*args: P.args, **kwargs: P.kwargs) -> T:
                   logging.info(f'{f.__name__} was called')
                   return f(*args, **kwargs)
               return inner

           @add_logging
           def add_two(x: float, y: float) -> float:
               '''Add two numbers together.'''
               return x + y

        Parameter specification variables defined with covariant=True or
        contravariant=True can be used to declare covariant or contravariant
        generic types.  These keyword arguments are valid, but their actual semantics
        are yet to be decided.  See PEP 612 for details.

        Parameter specification variables can be introspected. e.g.:

           P.__name__ == 'T'
           P.__bound__ == None
           P.__covariant__ == False
           P.__contravariant__ == False

        Note that only parameter specification variables defined in global scope can
        be pickled.
        """

        # Trick Generic __parameters__.
        __class__ = typing.TypeVar

        @property
        def args(self):
            return ParamSpecArgs(self)

        @property
        def kwargs(self):
            return ParamSpecKwargs(self)

        def __init__(self, name, *, bound=None, covariant=False, contravariant=False,
                     infer_variance=False, default=_marker):
            super().__init__([self])
            self.__name__ = name
            self.__covariant__ = bool(covariant)
            self.__contravariant__ = bool(contravariant)
            self.__infer_variance__ = bool(infer_variance)
            if bound:
                self.__bound__ = typing._type_check(bound, 'Bound must be a type.')
            else:
                self.__bound__ = None
            _DefaultMixin.__init__(self, default)

            # for pickling:
            def_mod = _caller()
            if def_mod != 'typing_extensions':
                self.__module__ = def_mod

        def __repr__(self):
            if self.__infer_variance__:
                prefix = ''
            elif self.__covariant__:
                prefix = '+'
            elif self.__contravariant__:
                prefix = '-'
            else:
                prefix = '~'
            return prefix + self.__name__

        def __hash__(self):
            return object.__hash__(self)

        def __eq__(self, other):
            return self is other

        def __reduce__(self):
            return self.__name__

        # Hack to get typing._type_check to pass.
        def __call__(self, *args, **kwargs):
            pass


# 3.8-3.9
if not hasattr(typing, 'Concatenate'):
    # Inherits from list as a workaround for Callable checks in Python < 3.9.2.
    class _ConcatenateGenericAlias(list):

        # Trick Generic into looking into this for __parameters__.
        __class__ = typing._GenericAlias

        # Flag in 3.8.
        _special = False

        def __init__(self, origin, args):
            super().__init__(args)
            self.__origin__ = origin
            self.__args__ = args

        def __repr__(self):
            _type_repr = typing._type_repr
            return (f'{_type_repr(self.__origin__)}'
                    f'[{", ".join(_type_repr(arg) for arg in self.__args__)}]')

        def __hash__(self):
            return hash((self.__origin__, self.__args__))

        # Hack to get typing._type_check to pass in Generic.
        def __call__(self, *args, **kwargs):
            pass

        @property
        def __parameters__(self):
            return tuple(
                tp for tp in self.__args__ if isinstance(tp, (typing.TypeVar, ParamSpec))
            )


# 3.8-3.9
@typing._tp_cache
def _concatenate_getitem(self, parameters):
    if parameters == ():
        raise TypeError("Cannot take a Concatenate of no types.")
    if not isinstance(parameters, tuple):
        parameters = (parameters,)
    if not isinstance(parameters[-1], ParamSpec):
        raise TypeError("The last parameter to Concatenate should be a "
                        "ParamSpec variable.")
    msg = "Concatenate[arg, ...]: each arg must be a type."
    parameters = tuple(typing._type_check(p, msg) for p in parameters)
    return _ConcatenateGenericAlias(self, parameters)


# 3.10+
if hasattr(typing, 'Concatenate'):
    Concatenate = typing.Concatenate
    _ConcatenateGenericAlias = typing._ConcatenateGenericAlias  # noqa: F811
# 3.9
elif sys.version_info[:2] >= (3, 9):
    @_ExtensionsSpecialForm
    def Concatenate(self, parameters):
        """Used in conjunction with ``ParamSpec`` and ``Callable`` to represent a
        higher order function which adds, removes or transforms parameters of a
        callable.

        For example::

           Callable[Concatenate[int, P], int]

        See PEP 612 for detailed information.
        """
        return _concatenate_getitem(self, parameters)
# 3.8
else:
    class _ConcatenateForm(_ExtensionsSpecialForm, _root=True):
        def __getitem__(self, parameters):
            return _concatenate_getitem(self, parameters)

    Concatenate = _ConcatenateForm(
        'Concatenate',
        doc="""Used in conjunction with ``ParamSpec`` and ``Callable`` to represent a
        higher order function which adds, removes or transforms parameters of a
        callable.

        For example::

           Callable[Concatenate[int, P], int]

        See PEP 612 for detailed information.
        """)

# 3.10+
if hasattr(typing, 'TypeGuard'):
    TypeGuard = typing.TypeGuard
# 3.9
elif sys.version_info[:2] >= (3, 9):
    @_ExtensionsSpecialForm
    def TypeGuard(self, parameters):
        """Special typing form used to annotate the return type of a user-defined
        type guard function.  ``TypeGuard`` only accepts a single type argument.
        At runtime, functions marked this way should return a boolean.

        ``TypeGuard`` aims to benefit *type narrowing* -- a technique used by static
        type checkers to determine a more precise type of an expression within a
        program's code flow.  Usually type narrowing is done by analyzing
        conditional code flow and applying the narrowing to a block of code.  The
        conditional expression here is sometimes referred to as a "type guard".

        Sometimes it would be convenient to use a user-defined boolean function
        as a type guard.  Such a function should use ``TypeGuard[...]`` as its
        return type to alert static type checkers to this intention.

        Using  ``-> TypeGuard`` tells the static type checker that for a given
        function:

        1. The return value is a boolean.
        2. If the return value is ``True``, the type of its argument
        is the type inside ``TypeGuard``.

        For example::

            def is_str(val: Union[str, float]):
                # "isinstance" type guard
                if isinstance(val, str):
                    # Type of ``val`` is narrowed to ``str``
                    ...
                else:
                    # Else, type of ``val`` is narrowed to ``float``.
                    ...

        Strict type narrowing is not enforced -- ``TypeB`` need not be a narrower
        form of ``TypeA`` (it can even be a wider form) and this may lead to
        type-unsafe results.  The main reason is to allow for things like
        narrowing ``List[object]`` to ``List[str]`` even though the latter is not
        a subtype of the former, since ``List`` is invariant.  The responsibility of
        writing type-safe type guards is left to the user.

        ``TypeGuard`` also works with type variables.  For more information, see
        PEP 647 (User-Defined Type Guards).
        """
        item = typing._type_check(parameters, f'{self} accepts only a single type.')
        return typing._GenericAlias(self, (item,))
# 3.8
else:
    class _TypeGuardForm(_ExtensionsSpecialForm, _root=True):
        def __getitem__(self, parameters):
            item = typing._type_check(parameters,
                                      f'{self._name} accepts only a single type')
            return typing._GenericAlias(self, (item,))

    TypeGuard = _TypeGuardForm(
        'TypeGuard',
        doc="""Special typing form used to annotate the return type of a user-defined
        type guard function.  ``TypeGuard`` only accepts a single type argument.
        At runtime, functions marked this way should return a boolean.

        ``TypeGuard`` aims to benefit *type narrowing* -- a technique used by static
        type checkers to determine a more precise type of an expression within a
        program's code flow.  Usually type narrowing is done by analyzing
        conditional code flow and applying the narrowing to a block of code.  The
        conditional expression here is sometimes referred to as a "type guard".

        Sometimes it would be convenient to use a user-defined boolean function
        as a type guard.  Such a function should use ``TypeGuard[...]`` as its
        return type to alert static type checkers to this intention.

        Using  ``-> TypeGuard`` tells the static type checker that for a given
        function:

        1. The return value is a boolean.
        2. If the return value is ``True``, the type of its argument
        is the type inside ``TypeGuard``.

        For example::

            def is_str(val: Union[str, float]):
                # "isinstance" type guard
                if isinstance(val, str):
                    # Type of ``val`` is narrowed to ``str``
                    ...
                else:
                    # Else, type of ``val`` is narrowed to ``float``.
                    ...

        Strict type narrowing is not enforced -- ``TypeB`` need not be a narrower
        form of ``TypeA`` (it can even be a wider form) and this may lead to
        type-unsafe results.  The main reason is to allow for things like
        narrowing ``List[object]`` to ``List[str]`` even though the latter is not
        a subtype of the former, since ``List`` is invariant.  The responsibility of
        writing type-safe type guards is left to the user.

        ``TypeGuard`` also works with type variables.  For more information, see
        PEP 647 (User-Defined Type Guards).
        """)


# Vendored from cpython typing._SpecialFrom
class _SpecialForm(typing._Final, _root=True):
    __slots__ = ('_name', '__doc__', '_getitem')

    def __init__(self, getitem):
        self._getitem = getitem
        self._name = getitem.__name__
        self.__doc__ = getitem.__doc__

    def __getattr__(self, item):
        if item in {'__name__', '__qualname__'}:
            return self._name

        raise AttributeError(item)

    def __mro_entries__(self, bases):
        raise TypeError(f"Cannot subclass {self!r}")

    def __repr__(self):
        return f'typing_extensions.{self._name}'

    def __reduce__(self):
        return self._name

    def __call__(self, *args, **kwds):
        raise TypeError(f"Cannot instantiate {self!r}")

    def __or__(self, other):
        return typing.Union[self, other]

    def __ror__(self, other):
        return typing.Union[other, self]

    def __instancecheck__(self, obj):
        raise TypeError(f"{self} cannot be used with isinstance()")

    def __subclasscheck__(self, cls):
        raise TypeError(f"{self} cannot be used with issubclass()")

    @typing._tp_cache
    def __getitem__(self, parameters):
        return self._getitem(self, parameters)


if hasattr(typing, "LiteralString"):  # 3.11+
    LiteralString = typing.LiteralString
else:
    @_SpecialForm
    def LiteralString(self, params):
        """Represents an arbitrary literal string.

        Example::

          from typing_extensions import LiteralString

          def query(sql: LiteralString) -> ...:
              ...

          query("SELECT * FROM table")  # ok
          query(f"SELECT * FROM {input()}")  # not ok

        See PEP 675 for details.

        """
        raise TypeError(f"{self} is not subscriptable")


if hasattr(typing, "Self"):  # 3.11+
    Self = typing.Self
else:
    @_SpecialForm
    def Self(self, params):
        """Used to spell the type of "self" in classes.

        Example::

          from typing import Self

          class ReturnsSelf:
              def parse(self, data: bytes) -> Self:
                  ...
                  return self

        """

        raise TypeError(f"{self} is not subscriptable")


if hasattr(typing, "Never"):  # 3.11+
    Never = typing.Never
else:
    @_SpecialForm
    def Never(self, params):
        """The bottom type, a type that has no members.

        This can be used to define a function that should never be
        called, or a function that never returns::

            from typing_extensions import Never

            def never_call_me(arg: Never) -> None:
                pass

            def int_or_str(arg: int | str) -> None:
                never_call_me(arg)  # type checker error
                match arg:
                    case int():
                        print("It's an int")
                    case str():
                        print("It's a str")
                    case _:
                        never_call_me(arg)  # ok, arg is of type Never

        """

        raise TypeError(f"{self} is not subscriptable")


if hasattr(typing, 'Required'):  # 3.11+
    Required = typing.Required
    NotRequired = typing.NotRequired
elif sys.version_info[:2] >= (3, 9):  # 3.9-3.10
    @_ExtensionsSpecialForm
    def Required(self, parameters):
        """A special typing construct to mark a key of a total=False TypedDict
        as required. For example:

            class Movie(TypedDict, total=False):
                title: Required[str]
                year: int

            m = Movie(
                title='The Matrix',  # typechecker error if key is omitted
                year=1999,
            )

        There is no runtime checking that a required key is actually provided
        when instantiating a related TypedDict.
        """
        item = typing._type_check(parameters, f'{self._name} accepts only a single type.')
        return typing._GenericAlias(self, (item,))

    @_ExtensionsSpecialForm
    def NotRequired(self, parameters):
        """A special typing construct to mark a key of a TypedDict as
        potentially missing. For example:

            class Movie(TypedDict):
                title: str
                year: NotRequired[int]

            m = Movie(
                title='The Matrix',  # typechecker error if key is omitted
                year=1999,
            )
        """
        item = typing._type_check(parameters, f'{self._name} accepts only a single type.')
        return typing._GenericAlias(self, (item,))

else:  # 3.8
    class _RequiredForm(_ExtensionsSpecialForm, _root=True):
        def __getitem__(self, parameters):
            item = typing._type_check(parameters,
                                      f'{self._name} accepts only a single type.')
            return typing._GenericAlias(self, (item,))

    Required = _RequiredForm(
        'Required',
        doc="""A special typing construct to mark a key of a total=False TypedDict
        as required. For example:

            class Movie(TypedDict, total=False):
                title: Required[str]
                year: int

            m = Movie(
                title='The Matrix',  # typechecker error if key is omitted
                year=1999,
            )

        There is no runtime checking that a required key is actually provided
        when instantiating a related TypedDict.
        """)
    NotRequired = _RequiredForm(
        'NotRequired',
        doc="""A special typing construct to mark a key of a TypedDict as
        potentially missing. For example:

            class Movie(TypedDict):
                title: str
                year: NotRequired[int]

            m = Movie(
                title='The Matrix',  # typechecker error if key is omitted
                year=1999,
            )
        """)


_UNPACK_DOC = """\
Type unpack operator.

The type unpack operator takes the child types from some container type,
such as `tuple[int, str]` or a `TypeVarTuple`, and 'pulls them out'. For
example:

  # For some generic class `Foo`:
  Foo[Unpack[tuple[int, str]]]  # Equivalent to Foo[int, str]

  Ts = TypeVarTuple('Ts')
  # Specifies that `Bar` is generic in an arbitrary number of types.
  # (Think of `Ts` as a tuple of an arbitrary number of individual
  #  `TypeVar`s, which the `Unpack` is 'pulling out' directly into the
  #  `Generic[]`.)
  class Bar(Generic[Unpack[Ts]]): ...
  Bar[int]  # Valid
  Bar[int, str]  # Also valid

From Python 3.11, this can also be done using the `*` operator:

    Foo[*tuple[int, str]]
    class Bar(Generic[*Ts]): ...

The operator can also be used along with a `TypedDict` to annotate
`**kwargs` in a function signature. For instance:

  class Movie(TypedDict):
    name: str
    year: int

  # This function expects two keyword arguments - *name* of type `str` and
  # *year* of type `int`.
  def foo(**kwargs: Unpack[Movie]): ...

Note that there is only some runtime checking of this operator. Not
everything the runtime allows may be accepted by static type checkers.

For more information, see PEP 646 and PEP 692.
"""


if sys.version_info >= (3, 12):  # PEP 692 changed the repr of Unpack[]
    Unpack = typing.Unpack

    def _is_unpack(obj):
        return get_origin(obj) is Unpack

elif sys.version_info[:2] >= (3, 9):  # 3.9+
    class _UnpackSpecialForm(_ExtensionsSpecialForm, _root=True):
        def __init__(self, getitem):
            super().__init__(getitem)
            self.__doc__ = _UNPACK_DOC

    class _UnpackAlias(typing._GenericAlias, _root=True):
        __class__ = typing.TypeVar

    @_UnpackSpecialForm
    def Unpack(self, parameters):
        item = typing._type_check(parameters, f'{self._name} accepts only a single type.')
        return _UnpackAlias(self, (item,))

    def _is_unpack(obj):
        return isinstance(obj, _UnpackAlias)

else:  # 3.8
    class _UnpackAlias(typing._GenericAlias, _root=True):
        __class__ = typing.TypeVar

    class _UnpackForm(_ExtensionsSpecialForm, _root=True):
        def __getitem__(self, parameters):
            item = typing._type_check(parameters,
                                      f'{self._name} accepts only a single type.')
            return _UnpackAlias(self, (item,))

    Unpack = _UnpackForm('Unpack', doc=_UNPACK_DOC)

    def _is_unpack(obj):
        return isinstance(obj, _UnpackAlias)


if hasattr(typing, "TypeVarTuple"):  # 3.11+

    # Add default parameter - PEP 696
    class TypeVarTuple(metaclass=_TypeVarLikeMeta):
        """Type variable tuple."""

        _backported_typevarlike = typing.TypeVarTuple

        def __new__(cls, name, *, default=_marker):
            tvt = typing.TypeVarTuple(name)
            _set_default(tvt, default)
            _set_module(tvt)
            return tvt

        def __init_subclass__(self, *args, **kwds):
            raise TypeError("Cannot subclass special typing classes")

else:  # <=3.10
    class TypeVarTuple(_DefaultMixin):
        """Type variable tuple.

        Usage::

            Ts = TypeVarTuple('Ts')

        In the same way that a normal type variable is a stand-in for a single
        type such as ``int``, a type variable *tuple* is a stand-in for a *tuple*
        type such as ``Tuple[int, str]``.

        Type variable tuples can be used in ``Generic`` declarations.
        Consider the following example::

            class Array(Generic[*Ts]): ...

        The ``Ts`` type variable tuple here behaves like ``tuple[T1, T2]``,
        where ``T1`` and ``T2`` are type variables. To use these type variables
        as type parameters of ``Array``, we must *unpack* the type variable tuple using
        the star operator: ``*Ts``. The signature of ``Array`` then behaves
        as if we had simply written ``class Array(Generic[T1, T2]): ...``.
        In contrast to ``Generic[T1, T2]``, however, ``Generic[*Shape]`` allows
        us to parameterise the class with an *arbitrary* number of type parameters.

        Type variable tuples can be used anywhere a normal ``TypeVar`` can.
        This includes class definitions, as shown above, as well as function
        signatures and variable annotations::

            class Array(Generic[*Ts]):

                def __init__(self, shape: Tuple[*Ts]):
                    self._shape: Tuple[*Ts] = shape

                def get_shape(self) -> Tuple[*Ts]:
                    return self._shape

            shape = (Height(480), Width(640))
            x: Array[Height, Width] = Array(shape)
            y = abs(x)  # Inferred type is Array[Height, Width]
            z = x + x   #        ...    is Array[Height, Width]
            x.get_shape()  #     ...    is tuple[Height, Width]

        """

        # Trick Generic __parameters__.
        __class__ = typing.TypeVar

        def __iter__(self):
            yield self.__unpacked__

        def __init__(self, name, *, default=_marker):
            self.__name__ = name
            _DefaultMixin.__init__(self, default)

            # for pickling:
            def_mod = _caller()
            if def_mod != 'typing_extensions':
                self.__module__ = def_mod

            self.__unpacked__ = Unpack[self]

        def __repr__(self):
            return self.__name__

        def __hash__(self):
            return object.__hash__(self)

        def __eq__(self, other):
            return self is other

        def __reduce__(self):
            return self.__name__

        def __init_subclass__(self, *args, **kwds):
            if '_root' not in kwds:
                raise TypeError("Cannot subclass special typing classes")


if hasattr(typing, "reveal_type"):  # 3.11+
    reveal_type = typing.reveal_type
else:  # <=3.10
    def reveal_type(obj: T, /) -> T:
        """Reveal the inferred type of a variable.

        When a static type checker encounters a call to ``reveal_type()``,
        it will emit the inferred type of the argument::

            x: int = 1
            reveal_type(x)

        Running a static type checker (e.g., ``mypy``) on this example
        will produce output similar to 'Revealed type is "builtins.int"'.

        At runtime, the function prints the runtime type of the
        argument and returns it unchanged.

        """
        print(f"Runtime type is {type(obj).__name__!r}", file=sys.stderr)
        return obj


if hasattr(typing, "assert_never"):  # 3.11+
    assert_never = typing.assert_never
else:  # <=3.10
    def assert_never(arg: Never, /) -> Never:
        """Assert to the type checker that a line of code is unreachable.

        Example::

            def int_or_str(arg: int | str) -> None:
                match arg:
                    case int():
                        print("It's an int")
                    case str():
                        print("It's a str")
                    case _:
                        assert_never(arg)

        If a type checker finds that a call to assert_never() is
        reachable, it will emit an error.

        At runtime, this throws an exception when called.

        """
        raise AssertionError("Expected code to be unreachable")


if sys.version_info >= (3, 12):  # 3.12+
    # dataclass_transform exists in 3.11 but lacks the frozen_default parameter
    dataclass_transform = typing.dataclass_transform
else:  # <=3.11
    def dataclass_transform(
        *,
        eq_default: bool = True,
        order_default: bool = False,
        kw_only_default: bool = False,
        frozen_default: bool = False,
        field_specifiers: typing.Tuple[
            typing.Union[typing.Type[typing.Any], typing.Callable[..., typing.Any]],
            ...
        ] = (),
        **kwargs: typing.Any,
    ) -> typing.Callable[[T], T]:
        """Decorator that marks a function, class, or metaclass as providing
        dataclass-like behavior.

        Example:

            from typing_extensions import dataclass_transform

            _T = TypeVar("_T")

            # Used on a decorator function
            @dataclass_transform()
            def create_model(cls: type[_T]) -> type[_T]:
                ...
                return cls

            @create_model
            class CustomerModel:
                id: int
                name: str

            # Used on a base class
            @dataclass_transform()
            class ModelBase: ...

            class CustomerModel(ModelBase):
                id: int
                name: str

            # Used on a metaclass
            @dataclass_transform()
            class ModelMeta(type): ...

            class ModelBase(metaclass=ModelMeta): ...

            class CustomerModel(ModelBase):
                id: int
                name: str

        Each of the ``CustomerModel`` classes defined in this example will now
        behave similarly to a dataclass created with the ``@dataclasses.dataclass``
        decorator. For example, the type checker will synthesize an ``__init__``
        method.

        The arguments to this decorator can be used to customize this behavior:
        - ``eq_default`` indicates whether the ``eq`` parameter is assumed to be
          True or False if it is omitted by the caller.
        - ``order_default`` indicates whether the ``order`` parameter is
          assumed to be True or False if it is omitted by the caller.
        - ``kw_only_default`` indicates whether the ``kw_only`` parameter is
          assumed to be True or False if it is omitted by the caller.
        - ``frozen_default`` indicates whether the ``frozen`` parameter is
          assumed to be True or False if it is omitted by the caller.
        - ``field_specifiers`` specifies a static list of supported classes
          or functions that describe fields, similar to ``dataclasses.field()``.

        At runtime, this decorator records its arguments in the
        ``__dataclass_transform__`` attribute on the decorated object.

        See PEP 681 for details.

        """
        def decorator(cls_or_fn):
            cls_or_fn.__dataclass_transform__ = {
                "eq_default": eq_default,
                "order_default": order_default,
                "kw_only_default": kw_only_default,
                "frozen_default": frozen_default,
                "field_specifiers": field_specifiers,
                "kwargs": kwargs,
            }
            return cls_or_fn
        return decorator


if hasattr(typing, "override"):  # 3.12+
    override = typing.override
else:  # <=3.11
    _F = typing.TypeVar("_F", bound=typing.Callable[..., typing.Any])

    def override(arg: _F, /) -> _F:
        """Indicate that a method is intended to override a method in a base class.

        Usage:

            class Base:
                def method(self) -> None: ...
                    pass

            class Child(Base):
                @override
                def method(self) -> None:
                    super().method()

        When this decorator is applied to a method, the type checker will
        validate that it overrides a method with the same name on a base class.
        This helps prevent bugs that may occur when a base class is changed
        without an equivalent change to a child class.

        There is no runtime checking of these properties. The decorator
        sets the ``__override__`` attribute to ``True`` on the decorated object
        to allow runtime introspection.

        See PEP 698 for details.

        """
        try:
            arg.__override__ = True
        except (AttributeError, TypeError):
            # Skip the attribute silently if it is not writable.
            # AttributeError happens if the object has __slots__ or a
            # read-only property, TypeError if it's a builtin class.
            pass
        return arg


if hasattr(typing, "deprecated"):
    deprecated = typing.deprecated
else:
    _T = typing.TypeVar("_T")

    def deprecated(
        msg: str,
        /,
        *,
        category: typing.Optional[typing.Type[Warning]] = DeprecationWarning,
        stacklevel: int = 1,
    ) -> typing.Callable[[_T], _T]:
        """Indicate that a class, function or overload is deprecated.

        Usage:

            @deprecated("Use B instead")
            class A:
                pass

            @deprecated("Use g instead")
            def f():
                pass

            @overload
            @deprecated("int support is deprecated")
            def g(x: int) -> int: ...
            @overload
            def g(x: str) -> int: ...

        When this decorator is applied to an object, the type checker
        will generate a diagnostic on usage of the deprecated object.

        The warning specified by ``category`` will be emitted on use
        of deprecated objects. For functions, that happens on calls;
        for classes, on instantiation. If the ``category`` is ``None``,
        no warning is emitted. The ``stacklevel`` determines where the
        warning is emitted. If it is ``1`` (the default), the warning
        is emitted at the direct caller of the deprecated object; if it
        is higher, it is emitted further up the stack.

        The decorator sets the ``__deprecated__``
        attribute on the decorated object to the deprecation message
        passed to the decorator. If applied to an overload, the decorator
        must be after the ``@overload`` decorator for the attribute to
        exist on the overload as returned by ``get_overloads()``.

        See PEP 702 for details.

        """
        def decorator(arg: _T, /) -> _T:
            if category is None:
                arg.__deprecated__ = msg
                return arg
            elif isinstance(arg, type):
                original_new = arg.__new__
                has_init = arg.__init__ is not object.__init__

                @functools.wraps(original_new)
                def __new__(cls, *args, **kwargs):
                    warnings.warn(msg, category=category, stacklevel=stacklevel + 1)
                    if original_new is not object.__new__:
                        return original_new(cls, *args, **kwargs)
                    # Mirrors a similar check in object.__new__.
                    elif not has_init and (args or kwargs):
                        raise TypeError(f"{cls.__name__}() takes no arguments")
                    else:
                        return original_new(cls)

                arg.__new__ = staticmethod(__new__)
                arg.__deprecated__ = __new__.__deprecated__ = msg
                return arg
            elif callable(arg):
                @functools.wraps(arg)
                def wrapper(*args, **kwargs):
                    warnings.warn(msg, category=category, stacklevel=stacklevel + 1)
                    return arg(*args, **kwargs)

                arg.__deprecated__ = wrapper.__deprecated__ = msg
                return wrapper
            else:
                raise TypeError(
                    "@deprecated decorator with non-None category must be applied to "
                    f"a class or callable, not {arg!r}"
                )

        return decorator


# We have to do some monkey patching to deal with the dual nature of
# Unpack/TypeVarTuple:
# - We want Unpack to be a kind of TypeVar so it gets accepted in
#   Generic[Unpack[Ts]]
# - We want it to *not* be treated as a TypeVar for the purposes of
#   counting generic parameters, so that when we subscript a generic,
#   the runtime doesn't try to substitute the Unpack with the subscripted type.
if not hasattr(typing, "TypeVarTuple"):
    typing._collect_type_vars = _collect_type_vars
    typing._check_generic = _check_generic


# Backport typing.NamedTuple as it exists in Python 3.13.
# In 3.11, the ability to define generic `NamedTuple`s was supported.
# This was explicitly disallowed in 3.9-3.10, and only half-worked in <=3.8.
# On 3.12, we added __orig_bases__ to call-based NamedTuples
# On 3.13, we deprecated kwargs-based NamedTuples
if sys.version_info >= (3, 13):
    NamedTuple = typing.NamedTuple
else:
    def _make_nmtuple(name, types, module, defaults=()):
        fields = [n for n, t in types]
        annotations = {n: typing._type_check(t, f"field {n} annotation must be a type")
                       for n, t in types}
        nm_tpl = collections.namedtuple(name, fields,
                                        defaults=defaults, module=module)
        nm_tpl.__annotations__ = nm_tpl.__new__.__annotations__ = annotations
        # The `_field_types` attribute was removed in 3.9;
        # in earlier versions, it is the same as the `__annotations__` attribute
        if sys.version_info < (3, 9):
            nm_tpl._field_types = annotations
        return nm_tpl

    _prohibited_namedtuple_fields = typing._prohibited
    _special_namedtuple_fields = frozenset({'__module__', '__name__', '__annotations__'})

    class _NamedTupleMeta(type):
        def __new__(cls, typename, bases, ns):
            assert _NamedTuple in bases
            for base in bases:
                if base is not _NamedTuple and base is not typing.Generic:
                    raise TypeError(
                        'can only inherit from a NamedTuple type and Generic')
            bases = tuple(tuple if base is _NamedTuple else base for base in bases)
            types = ns.get('__annotations__', {})
            default_names = []
            for field_name in types:
                if field_name in ns:
                    default_names.append(field_name)
                elif default_names:
                    raise TypeError(f"Non-default namedtuple field {field_name} "
                                    f"cannot follow default field"
                                    f"{'s' if len(default_names) > 1 else ''} "
                                    f"{', '.join(default_names)}")
            nm_tpl = _make_nmtuple(
                typename, types.items(),
                defaults=[ns[n] for n in default_names],
                module=ns['__module__']
            )
            nm_tpl.__bases__ = bases
            if typing.Generic in bases:
                if hasattr(typing, '_generic_class_getitem'):  # 3.12+
                    nm_tpl.__class_getitem__ = classmethod(typing._generic_class_getitem)
                else:
                    class_getitem = typing.Generic.__class_getitem__.__func__
                    nm_tpl.__class_getitem__ = classmethod(class_getitem)
            # update from user namespace without overriding special namedtuple attributes
            for key in ns:
                if key in _prohibited_namedtuple_fields:
                    raise AttributeError("Cannot overwrite NamedTuple attribute " + key)
                elif key not in _special_namedtuple_fields and key not in nm_tpl._fields:
                    setattr(nm_tpl, key, ns[key])
            if typing.Generic in bases:
                nm_tpl.__init_subclass__()
            return nm_tpl

    _NamedTuple = type.__new__(_NamedTupleMeta, 'NamedTuple', (), {})

    def _namedtuple_mro_entries(bases):
        assert NamedTuple in bases
        return (_NamedTuple,)

    @_ensure_subclassable(_namedtuple_mro_entries)
    def NamedTuple(typename, fields=_marker, /, **kwargs):
        """Typed version of namedtuple.

        Usage::

            class Employee(NamedTuple):
                name: str
                id: int

        This is equivalent to::

            Employee = collections.namedtuple('Employee', ['name', 'id'])

        The resulting class has an extra __annotations__ attribute, giving a
        dict that maps field names to types.  (The field names are also in
        the _fields attribute, which is part of the namedtuple API.)
        An alternative equivalent functional syntax is also accepted::

            Employee = NamedTuple('Employee', [('name', str), ('id', int)])
        """
        if fields is _marker:
            if kwargs:
                deprecated_thing = "Creating NamedTuple classes using keyword arguments"
                deprecation_msg = (
                    "{name} is deprecated and will be disallowed in Python {remove}. "
                    "Use the class-based or functional syntax instead."
                )
            else:
                deprecated_thing = "Failing to pass a value for the 'fields' parameter"
                example = f"`{typename} = NamedTuple({typename!r}, [])`"
                deprecation_msg = (
                    "{name} is deprecated and will be disallowed in Python {remove}. "
                    "To create a NamedTuple class with 0 fields "
                    "using the functional syntax, "
                    "pass an empty list, e.g. "
                ) + example + "."
        elif fields is None:
            if kwargs:
                raise TypeError(
                    "Cannot pass `None` as the 'fields' parameter "
                    "and also specify fields using keyword arguments"
                )
            else:
                deprecated_thing = "Passing `None` as the 'fields' parameter"
                example = f"`{typename} = NamedTuple({typename!r}, [])`"
                deprecation_msg = (
                    "{name} is deprecated and will be disallowed in Python {remove}. "
                    "To create a NamedTuple class with 0 fields "
                    "using the functional syntax, "
                    "pass an empty list, e.g. "
                ) + example + "."
        elif kwargs:
            raise TypeError("Either list of fields or keywords"
                            " can be provided to NamedTuple, not both")
        if fields is _marker or fields is None:
            warnings.warn(
                deprecation_msg.format(name=deprecated_thing, remove="3.15"),
                DeprecationWarning,
                stacklevel=2,
            )
            fields = kwargs.items()
        nt = _make_nmtuple(typename, fields, module=_caller())
        nt.__orig_bases__ = (NamedTuple,)
        return nt


if hasattr(collections.abc, "Buffer"):
    Buffer = collections.abc.Buffer
else:
    class Buffer(abc.ABC):
        """Base class for classes that implement the buffer protocol.

        The buffer protocol allows Python objects to expose a low-level
        memory buffer interface. Before Python 3.12, it is not possible
        to implement the buffer protocol in pure Python code, or even
        to check whether a class implements the buffer protocol. In
        Python 3.12 and higher, the ``__buffer__`` method allows access
        to the buffer protocol from Python code, and the
        ``collections.abc.Buffer`` ABC allows checking whether a class
        implements the buffer protocol.

        To indicate support for the buffer protocol in earlier versions,
        inherit from this ABC, either in a stub file or at runtime,
        or use ABC registration. This ABC provides no methods, because
        there is no Python-accessible methods shared by pre-3.12 buffer
        classes. It is useful primarily for static checks.

        """

    # As a courtesy, register the most common stdlib buffer classes.
    Buffer.register(memoryview)
    Buffer.register(bytearray)
    Buffer.register(bytes)


# Backport of types.get_original_bases, available on 3.12+ in CPython
if hasattr(_types, "get_original_bases"):
    get_original_bases = _types.get_original_bases
else:
    def get_original_bases(cls, /):
        """Return the class's "original" bases prior to modification by `__mro_entries__`.

        Examples::

            from typing import TypeVar, Generic
            from typing_extensions import NamedTuple, TypedDict

            T = TypeVar("T")
            class Foo(Generic[T]): ...
            class Bar(Foo[int], float): ...
            class Baz(list[str]): ...
            Eggs = NamedTuple("Eggs", [("a", int), ("b", str)])
            Spam = TypedDict("Spam", {"a": int, "b": str})

            assert get_original_bases(Bar) == (Foo[int], float)
            assert get_original_bases(Baz) == (list[str],)
            assert get_original_bases(Eggs) == (NamedTuple,)
            assert get_original_bases(Spam) == (TypedDict,)
            assert get_original_bases(int) == (object,)
        """
        try:
            return cls.__dict__.get("__orig_bases__", cls.__bases__)
        except AttributeError:
            raise TypeError(
                f'Expected an instance of type, not {type(cls).__name__!r}'
            ) from None


# NewType is a class on Python 3.10+, making it pickleable
# The error message for subclassing instances of NewType was improved on 3.11+
if sys.version_info >= (3, 11):
    NewType = typing.NewType
else:
    class NewType:
        """NewType creates simple unique types with almost zero
        runtime overhead. NewType(name, tp) is considered a subtype of tp
        by static type checkers. At runtime, NewType(name, tp) returns
        a dummy callable that simply returns its argument. Usage::
            UserId = NewType('UserId', int)
            def name_by_id(user_id: UserId) -> str:
                ...
            UserId('user')          # Fails type check
            name_by_id(42)          # Fails type check
            name_by_id(UserId(42))  # OK
            num = UserId(5) + 1     # type: int
        """

        def __call__(self, obj):
            return obj

        def __init__(self, name, tp):
            self.__qualname__ = name
            if '.' in name:
                name = name.rpartition('.')[-1]
            self.__name__ = name
            self.__supertype__ = tp
            def_mod = _caller()
            if def_mod != 'typing_extensions':
                self.__module__ = def_mod

        def __mro_entries__(self, bases):
            # We defined __mro_entries__ to get a better error message
            # if a user attempts to subclass a NewType instance. bpo-46170
            supercls_name = self.__name__

            class Dummy:
                def __init_subclass__(cls):
                    subcls_name = cls.__name__
                    raise TypeError(
                        f"Cannot subclass an instance of NewType. "
                        f"Perhaps you were looking for: "
                        f"`{subcls_name} = NewType({subcls_name!r}, {supercls_name})`"
                    )

            return (Dummy,)

        def __repr__(self):
            return f'{self.__module__}.{self.__qualname__}'

        def __reduce__(self):
            return self.__qualname__

        if sys.version_info >= (3, 10):
            # PEP 604 methods
            # It doesn't make sense to have these methods on Python <3.10

            def __or__(self, other):
                return typing.Union[self, other]

            def __ror__(self, other):
                return typing.Union[other, self]


if hasattr(typing, "TypeAliasType"):
    TypeAliasType = typing.TypeAliasType
else:
    def _is_unionable(obj):
        """Corresponds to is_unionable() in unionobject.c in CPython."""
        return obj is None or isinstance(obj, (
            type,
            _types.GenericAlias,
            _types.UnionType,
            TypeAliasType,
        ))

    class TypeAliasType:
        """Create named, parameterized type aliases.

        This provides a backport of the new `type` statement in Python 3.12:

            type ListOrSet[T] = list[T] | set[T]

        is equivalent to:

            T = TypeVar("T")
            ListOrSet = TypeAliasType("ListOrSet", list[T] | set[T], type_params=(T,))

        The name ListOrSet can then be used as an alias for the type it refers to.

        The type_params argument should contain all the type parameters used
        in the value of the type alias. If the alias is not generic, this
        argument is omitted.

        Static type checkers should only support type aliases declared using
        TypeAliasType that follow these rules:

        - The first argument (the name) must be a string literal.
        - The TypeAliasType instance must be immediately assigned to a variable
          of the same name. (For example, 'X = TypeAliasType("Y", int)' is invalid,
          as is 'X, Y = TypeAliasType("X", int), TypeAliasType("Y", int)').

        """

        def __init__(self, name: str, value, *, type_params=()):
            if not isinstance(name, str):
                raise TypeError("TypeAliasType name must be a string")
            self.__value__ = value
            self.__type_params__ = type_params

            parameters = []
            for type_param in type_params:
                if isinstance(type_param, TypeVarTuple):
                    parameters.extend(type_param)
                else:
                    parameters.append(type_param)
            self.__parameters__ = tuple(parameters)
            def_mod = _caller()
            if def_mod != 'typing_extensions':
                self.__module__ = def_mod
            # Setting this attribute closes the TypeAliasType from further modification
            self.__name__ = name

        def __setattr__(self, name: str, value: object, /) -> None:
            if hasattr(self, "__name__"):
                self._raise_attribute_error(name)
            super().__setattr__(name, value)

        def __delattr__(self, name: str, /) -> Never:
            self._raise_attribute_error(name)

        def _raise_attribute_error(self, name: str) -> Never:
            # Match the Python 3.12 error messages exactly
            if name == "__name__":
                raise AttributeError("readonly attribute")
            elif name in {"__value__", "__type_params__", "__parameters__", "__module__"}:
                raise AttributeError(
                    f"attribute '{name}' of 'typing.TypeAliasType' objects "
                    "is not writable"
                )
            else:
                raise AttributeError(
                    f"'typing.TypeAliasType' object has no attribute '{name}'"
                )

        def __repr__(self) -> str:
            return self.__name__

        def __getitem__(self, parameters):
            if not isinstance(parameters, tuple):
                parameters = (parameters,)
            parameters = [
                typing._type_check(
                    item, f'Subscripting {self.__name__} requires a type.'
                )
                for item in parameters
            ]
            return typing._GenericAlias(self, tuple(parameters))

        def __reduce__(self):
            return self.__name__

        def __init_subclass__(cls, *args, **kwargs):
            raise TypeError(
                "type 'typing_extensions.TypeAliasType' is not an acceptable base type"
            )

        # The presence of this method convinces typing._type_check
        # that TypeAliasTypes are types.
        def __call__(self):
            raise TypeError("Type alias is not callable")

        if sys.version_info >= (3, 10):
            def __or__(self, right):
                # For forward compatibility with 3.12, reject Unions
                # that are not accepted by the built-in Union.
                if not _is_unionable(right):
                    return NotImplemented
                return typing.Union[self, right]

            def __ror__(self, left):
                if not _is_unionable(left):
                    return NotImplemented
                return typing.Union[left, self]


if hasattr(typing, "is_protocol"):
    is_protocol = typing.is_protocol
    get_protocol_members = typing.get_protocol_members
else:
    def is_protocol(tp: type, /) -> bool:
        """Return True if the given type is a Protocol.

        Example::

            >>> from typing_extensions import Protocol, is_protocol
            >>> class P(Protocol):
            ...     def a(self) -> str: ...
            ...     b: int
            >>> is_protocol(P)
            True
            >>> is_protocol(int)
            False
        """
        return (
            isinstance(tp, type)
            and getattr(tp, '_is_protocol', False)
            and tp is not Protocol
            and tp is not typing.Protocol
        )

    def get_protocol_members(tp: type, /) -> typing.FrozenSet[str]:
        """Return the set of members defined in a Protocol.

        Example::

            >>> from typing_extensions import Protocol, get_protocol_members
            >>> class P(Protocol):
            ...     def a(self) -> str: ...
            ...     b: int
            >>> get_protocol_members(P)
            frozenset({'a', 'b'})

        Raise a TypeError for arguments that are not Protocols.
        """
        if not is_protocol(tp):
            raise TypeError(f'{tp!r} is not a Protocol')
        if hasattr(tp, '__protocol_attrs__'):
            return frozenset(tp.__protocol_attrs__)
        return frozenset(_get_protocol_attrs(tp))


if hasattr(typing, "Doc"):
    Doc = typing.Doc
else:
    class Doc:
        """Define the documentation of a type annotation using ``Annotated``, to be
         used in class attributes, function and method parameters, return values,
         and variables.

        The value should be a positional-only string literal to allow static tools
        like editors and documentation generators to use it.

        This complements docstrings.

        The string value passed is available in the attribute ``documentation``.

        Example::

            >>> from typing_extensions import Annotated, Doc
            >>> def hi(to: Annotated[str, Doc("Who to say hi to")]) -> None: ...
        """
        def __init__(self, documentation: str, /) -> None:
            self.documentation = documentation

        def __repr__(self) -> str:
            return f"Doc({self.documentation!r})"

        def __hash__(self) -> int:
            return hash(self.documentation)

        def __eq__(self, other: object) -> bool:
            if not isinstance(other, Doc):
                return NotImplemented
            return self.documentation == other.documentation


# Aliases for items that have always been in typing.
# Explicitly assign these (rather than using `from typing import *` at the top),
# so that we get a CI error if one of these is deleted from typing.py
# in a future version of Python
AbstractSet = typing.AbstractSet
AnyStr = typing.AnyStr
BinaryIO = typing.BinaryIO
Callable = typing.Callable
Collection = typing.Collection
Container = typing.Container
Dict = typing.Dict
ForwardRef = typing.ForwardRef
FrozenSet = typing.FrozenSet
Generator = typing.Generator
Generic = typing.Generic
Hashable = typing.Hashable
IO = typing.IO
ItemsView = typing.ItemsView
Iterable = typing.Iterable
Iterator = typing.Iterator
KeysView = typing.KeysView
List = typing.List
Mapping = typing.Mapping
MappingView = typing.MappingView
Match = typing.Match
MutableMapping = typing.MutableMapping
MutableSequence = typing.MutableSequence
MutableSet = typing.MutableSet
Optional = typing.Optional
Pattern = typing.Pattern
Reversible = typing.Reversible
Sequence = typing.Sequence
Set = typing.Set
Sized = typing.Sized
TextIO = typing.TextIO
Tuple = typing.Tuple
Union = typing.Union
ValuesView = typing.ValuesView
cast = typing.cast
no_type_check = typing.no_type_check
no_type_check_decorator = typing.no_type_check_decorator

virt/lib/python3.9/site-packages/typing_extensions 3.py
PrinceDisant/LostInNL
import abc
import collections
import collections.abc
import functools
import inspect
import operator
import sys
import types as _types
import typing
import warnings

__all__ = [
    # Super-special typing primitives.
    'Any',
    'ClassVar',
    'Concatenate',
    'Final',
    'LiteralString',
    'ParamSpec',
    'ParamSpecArgs',
    'ParamSpecKwargs',
    'Self',
    'Type',
    'TypeVar',
    'TypeVarTuple',
    'Unpack',

    # ABCs (from collections.abc).
    'Awaitable',
    'AsyncIterator',
    'AsyncIterable',
    'Coroutine',
    'AsyncGenerator',
    'AsyncContextManager',
    'Buffer',
    'ChainMap',

    # Concrete collection types.
    'ContextManager',
    'Counter',
    'Deque',
    'DefaultDict',
    'NamedTuple',
    'OrderedDict',
    'TypedDict',

    # Structural checks, a.k.a. protocols.
    'SupportsAbs',
    'SupportsBytes',
    'SupportsComplex',
    'SupportsFloat',
    'SupportsIndex',
    'SupportsInt',
    'SupportsRound',

    # One-off things.
    'Annotated',
    'assert_never',
    'assert_type',
    'clear_overloads',
    'dataclass_transform',
    'deprecated',
    'Doc',
    'get_overloads',
    'final',
    'get_args',
    'get_origin',
    'get_original_bases',
    'get_protocol_members',
    'get_type_hints',
    'IntVar',
    'is_protocol',
    'is_typeddict',
    'Literal',
    'NewType',
    'overload',
    'override',
    'Protocol',
    'reveal_type',
    'runtime',
    'runtime_checkable',
    'Text',
    'TypeAlias',
    'TypeAliasType',
    'TypeGuard',
    'TYPE_CHECKING',
    'Never',
    'NoReturn',
    'Required',
    'NotRequired',

    # Pure aliases, have always been in typing
    'AbstractSet',
    'AnyStr',
    'BinaryIO',
    'Callable',
    'Collection',
    'Container',
    'Dict',
    'ForwardRef',
    'FrozenSet',
    'Generator',
    'Generic',
    'Hashable',
    'IO',
    'ItemsView',
    'Iterable',
    'Iterator',
    'KeysView',
    'List',
    'Mapping',
    'MappingView',
    'Match',
    'MutableMapping',
    'MutableSequence',
    'MutableSet',
    'Optional',
    'Pattern',
    'Reversible',
    'Sequence',
    'Set',
    'Sized',
    'TextIO',
    'Tuple',
    'Union',
    'ValuesView',
    'cast',
    'no_type_check',
    'no_type_check_decorator',
]

# for backward compatibility
PEP_560 = True
GenericMeta = type

# The functions below are modified copies of typing internal helpers.
# They are needed by _ProtocolMeta and they provide support for PEP 646.


class _Sentinel:
    def __repr__(self):
        return "<sentinel>"


_marker = _Sentinel()


def _check_generic(cls, parameters, elen=_marker):
    """Check correct count for parameters of a generic cls (internal helper).
    This gives a nice error message in case of count mismatch.
    """
    if not elen:
        raise TypeError(f"{cls} is not a generic class")
    if elen is _marker:
        if not hasattr(cls, "__parameters__") or not cls.__parameters__:
            raise TypeError(f"{cls} is not a generic class")
        elen = len(cls.__parameters__)
    alen = len(parameters)
    if alen != elen:
        if hasattr(cls, "__parameters__"):
            parameters = [p for p in cls.__parameters__ if not _is_unpack(p)]
            num_tv_tuples = sum(isinstance(p, TypeVarTuple) for p in parameters)
            if (num_tv_tuples > 0) and (alen >= elen - num_tv_tuples):
                return
        raise TypeError(f"Too {'many' if alen > elen else 'few'} parameters for {cls};"
                        f" actual {alen}, expected {elen}")


if sys.version_info >= (3, 10):
    def _should_collect_from_parameters(t):
        return isinstance(
            t, (typing._GenericAlias, _types.GenericAlias, _types.UnionType)
        )
elif sys.version_info >= (3, 9):
    def _should_collect_from_parameters(t):
        return isinstance(t, (typing._GenericAlias, _types.GenericAlias))
else:
    def _should_collect_from_parameters(t):
        return isinstance(t, typing._GenericAlias) and not t._special


def _collect_type_vars(types, typevar_types=None):
    """Collect all type variable contained in types in order of
    first appearance (lexicographic order). For example::

        _collect_type_vars((T, List[S, T])) == (T, S)
    """
    if typevar_types is None:
        typevar_types = typing.TypeVar
    tvars = []
    for t in types:
        if (
            isinstance(t, typevar_types) and
            t not in tvars and
            not _is_unpack(t)
        ):
            tvars.append(t)
        if _should_collect_from_parameters(t):
            tvars.extend([t for t in t.__parameters__ if t not in tvars])
    return tuple(tvars)


NoReturn = typing.NoReturn

# Some unconstrained type variables.  These are used by the container types.
# (These are not for export.)
T = typing.TypeVar('T')  # Any type.
KT = typing.TypeVar('KT')  # Key type.
VT = typing.TypeVar('VT')  # Value type.
T_co = typing.TypeVar('T_co', covariant=True)  # Any type covariant containers.
T_contra = typing.TypeVar('T_contra', contravariant=True)  # Ditto contravariant.


if sys.version_info >= (3, 11):
    from typing import Any
else:

    class _AnyMeta(type):
        def __instancecheck__(self, obj):
            if self is Any:
                raise TypeError("typing_extensions.Any cannot be used with isinstance()")
            return super().__instancecheck__(obj)

        def __repr__(self):
            if self is Any:
                return "typing_extensions.Any"
            return super().__repr__()

    class Any(metaclass=_AnyMeta):
        """Special type indicating an unconstrained type.
        - Any is compatible with every type.
        - Any assumed to have all methods.
        - All values assumed to be instances of Any.
        Note that all the above statements are true from the point of view of
        static type checkers. At runtime, Any should not be used with instance
        checks.
        """
        def __new__(cls, *args, **kwargs):
            if cls is Any:
                raise TypeError("Any cannot be instantiated")
            return super().__new__(cls, *args, **kwargs)


ClassVar = typing.ClassVar


class _ExtensionsSpecialForm(typing._SpecialForm, _root=True):
    def __repr__(self):
        return 'typing_extensions.' + self._name


Final = typing.Final

if sys.version_info >= (3, 11):
    final = typing.final
else:
    # @final exists in 3.8+, but we backport it for all versions
    # before 3.11 to keep support for the __final__ attribute.
    # See https://bugs.python.org/issue46342
    def final(f):
        """This decorator can be used to indicate to type checkers that
        the decorated method cannot be overridden, and decorated class
        cannot be subclassed. For example:

            class Base:
                @final
                def done(self) -> None:
                    ...
            class Sub(Base):
                def done(self) -> None:  # Error reported by type checker
                    ...
            @final
            class Leaf:
                ...
            class Other(Leaf):  # Error reported by type checker
                ...

        There is no runtime checking of these properties. The decorator
        sets the ``__final__`` attribute to ``True`` on the decorated object
        to allow runtime introspection.
        """
        try:
            f.__final__ = True
        except (AttributeError, TypeError):
            # Skip the attribute silently if it is not writable.
            # AttributeError happens if the object has __slots__ or a
            # read-only property, TypeError if it's a builtin class.
            pass
        return f


def IntVar(name):
    return typing.TypeVar(name)


# A Literal bug was fixed in 3.11.0, 3.10.1 and 3.9.8
if sys.version_info >= (3, 10, 1):
    Literal = typing.Literal
else:
    def _flatten_literal_params(parameters):
        """An internal helper for Literal creation: flatten Literals among parameters"""
        params = []
        for p in parameters:
            if isinstance(p, _LiteralGenericAlias):
                params.extend(p.__args__)
            else:
                params.append(p)
        return tuple(params)

    def _value_and_type_iter(params):
        for p in params:
            yield p, type(p)

    class _LiteralGenericAlias(typing._GenericAlias, _root=True):
        def __eq__(self, other):
            if not isinstance(other, _LiteralGenericAlias):
                return NotImplemented
            these_args_deduped = set(_value_and_type_iter(self.__args__))
            other_args_deduped = set(_value_and_type_iter(other.__args__))
            return these_args_deduped == other_args_deduped

        def __hash__(self):
            return hash(frozenset(_value_and_type_iter(self.__args__)))

    class _LiteralForm(_ExtensionsSpecialForm, _root=True):
        def __init__(self, doc: str):
            self._name = 'Literal'
            self._doc = self.__doc__ = doc

        def __getitem__(self, parameters):
            if not isinstance(parameters, tuple):
                parameters = (parameters,)

            parameters = _flatten_literal_params(parameters)

            val_type_pairs = list(_value_and_type_iter(parameters))
            try:
                deduped_pairs = set(val_type_pairs)
            except TypeError:
                # unhashable parameters
                pass
            else:
                # similar logic to typing._deduplicate on Python 3.9+
                if len(deduped_pairs) < len(val_type_pairs):
                    new_parameters = []
                    for pair in val_type_pairs:
                        if pair in deduped_pairs:
                            new_parameters.append(pair[0])
                            deduped_pairs.remove(pair)
                    assert not deduped_pairs, deduped_pairs
                    parameters = tuple(new_parameters)

            return _LiteralGenericAlias(self, parameters)

    Literal = _LiteralForm(doc="""\
                           A type that can be used to indicate to type checkers
                           that the corresponding value has a value literally equivalent
                           to the provided parameter. For example:

                               var: Literal[4] = 4

                           The type checker understands that 'var' is literally equal to
                           the value 4 and no other value.

                           Literal[...] cannot be subclassed. There is no runtime
                           checking verifying that the parameter is actually a value
                           instead of a type.""")


_overload_dummy = typing._overload_dummy


if hasattr(typing, "get_overloads"):  # 3.11+
    overload = typing.overload
    get_overloads = typing.get_overloads
    clear_overloads = typing.clear_overloads
else:
    # {module: {qualname: {firstlineno: func}}}
    _overload_registry = collections.defaultdict(
        functools.partial(collections.defaultdict, dict)
    )

    def overload(func):
        """Decorator for overloaded functions/methods.

        In a stub file, place two or more stub definitions for the same
        function in a row, each decorated with @overload.  For example:

        @overload
        def utf8(value: None) -> None: ...
        @overload
        def utf8(value: bytes) -> bytes: ...
        @overload
        def utf8(value: str) -> bytes: ...

        In a non-stub file (i.e. a regular .py file), do the same but
        follow it with an implementation.  The implementation should *not*
        be decorated with @overload.  For example:

        @overload
        def utf8(value: None) -> None: ...
        @overload
        def utf8(value: bytes) -> bytes: ...
        @overload
        def utf8(value: str) -> bytes: ...
        def utf8(value):
            # implementation goes here

        The overloads for a function can be retrieved at runtime using the
        get_overloads() function.
        """
        # classmethod and staticmethod
        f = getattr(func, "__func__", func)
        try:
            _overload_registry[f.__module__][f.__qualname__][
                f.__code__.co_firstlineno
            ] = func
        except AttributeError:
            # Not a normal function; ignore.
            pass
        return _overload_dummy

    def get_overloads(func):
        """Return all defined overloads for *func* as a sequence."""
        # classmethod and staticmethod
        f = getattr(func, "__func__", func)
        if f.__module__ not in _overload_registry:
            return []
        mod_dict = _overload_registry[f.__module__]
        if f.__qualname__ not in mod_dict:
            return []
        return list(mod_dict[f.__qualname__].values())

    def clear_overloads():
        """Clear all overloads in the registry."""
        _overload_registry.clear()


# This is not a real generic class.  Don't use outside annotations.
Type = typing.Type

# Various ABCs mimicking those in collections.abc.
# A few are simply re-exported for completeness.
Awaitable = typing.Awaitable
Coroutine = typing.Coroutine
AsyncIterable = typing.AsyncIterable
AsyncIterator = typing.AsyncIterator
Deque = typing.Deque
ContextManager = typing.ContextManager
AsyncContextManager = typing.AsyncContextManager
DefaultDict = typing.DefaultDict
OrderedDict = typing.OrderedDict
Counter = typing.Counter
ChainMap = typing.ChainMap
AsyncGenerator = typing.AsyncGenerator
Text = typing.Text
TYPE_CHECKING = typing.TYPE_CHECKING


_PROTO_ALLOWLIST = {
    'collections.abc': [
        'Callable', 'Awaitable', 'Iterable', 'Iterator', 'AsyncIterable',
        'Hashable', 'Sized', 'Container', 'Collection', 'Reversible', 'Buffer',
    ],
    'contextlib': ['AbstractContextManager', 'AbstractAsyncContextManager'],
    'typing_extensions': ['Buffer'],
}


_EXCLUDED_ATTRS = {
    "__abstractmethods__", "__annotations__", "__weakref__", "_is_protocol",
    "_is_runtime_protocol", "__dict__", "__slots__", "__parameters__",
    "__orig_bases__", "__module__", "_MutableMapping__marker", "__doc__",
    "__subclasshook__", "__orig_class__", "__init__", "__new__",
    "__protocol_attrs__", "__callable_proto_members_only__",
}

if sys.version_info >= (3, 9):
    _EXCLUDED_ATTRS.add("__class_getitem__")

if sys.version_info >= (3, 12):
    _EXCLUDED_ATTRS.add("__type_params__")

_EXCLUDED_ATTRS = frozenset(_EXCLUDED_ATTRS)


def _get_protocol_attrs(cls):
    attrs = set()
    for base in cls.__mro__[:-1]:  # without object
        if base.__name__ in {'Protocol', 'Generic'}:
            continue
        annotations = getattr(base, '__annotations__', {})
        for attr in (*base.__dict__, *annotations):
            if (not attr.startswith('_abc_') and attr not in _EXCLUDED_ATTRS):
                attrs.add(attr)
    return attrs


def _caller(depth=2):
    try:
        return sys._getframe(depth).f_globals.get('__name__', '__main__')
    except (AttributeError, ValueError):  # For platforms without _getframe()
        return None


# The performance of runtime-checkable protocols is significantly improved on Python 3.12,
# so we backport the 3.12 version of Protocol to Python <=3.11
if sys.version_info >= (3, 12):
    Protocol = typing.Protocol
else:
    def _allow_reckless_class_checks(depth=3):
        """Allow instance and class checks for special stdlib modules.
        The abc and functools modules indiscriminately call isinstance() and
        issubclass() on the whole MRO of a user class, which may contain protocols.
        """
        return _caller(depth) in {'abc', 'functools', None}

    def _no_init(self, *args, **kwargs):
        if type(self)._is_protocol:
            raise TypeError('Protocols cannot be instantiated')

    # Inheriting from typing._ProtocolMeta isn't actually desirable,
    # but is necessary to allow typing.Protocol and typing_extensions.Protocol
    # to mix without getting TypeErrors about "metaclass conflict"
    class _ProtocolMeta(type(typing.Protocol)):
        # This metaclass is somewhat unfortunate,
        # but is necessary for several reasons...
        #
        # NOTE: DO NOT call super() in any methods in this class
        # That would call the methods on typing._ProtocolMeta on Python 3.8-3.11
        # and those are slow
        def __new__(mcls, name, bases, namespace, **kwargs):
            if name == "Protocol" and len(bases) < 2:
                pass
            elif {Protocol, typing.Protocol} & set(bases):
                for base in bases:
                    if not (
                        base in {object, typing.Generic, Protocol, typing.Protocol}
                        or base.__name__ in _PROTO_ALLOWLIST.get(base.__module__, [])
                        or is_protocol(base)
                    ):
                        raise TypeError(
                            f"Protocols can only inherit from other protocols, "
                            f"got {base!r}"
                        )
            return abc.ABCMeta.__new__(mcls, name, bases, namespace, **kwargs)

        def __init__(cls, *args, **kwargs):
            abc.ABCMeta.__init__(cls, *args, **kwargs)
            if getattr(cls, "_is_protocol", False):
                cls.__protocol_attrs__ = _get_protocol_attrs(cls)
                # PEP 544 prohibits using issubclass()
                # with protocols that have non-method members.
                cls.__callable_proto_members_only__ = all(
                    callable(getattr(cls, attr, None)) for attr in cls.__protocol_attrs__
                )

        def __subclasscheck__(cls, other):
            if cls is Protocol:
                return type.__subclasscheck__(cls, other)
            if (
                getattr(cls, '_is_protocol', False)
                and not _allow_reckless_class_checks()
            ):
                if not isinstance(other, type):
                    # Same error message as for issubclass(1, int).
                    raise TypeError('issubclass() arg 1 must be a class')
                if (
                    not cls.__callable_proto_members_only__
                    and cls.__dict__.get("__subclasshook__") is _proto_hook
                ):
                    raise TypeError(
                        "Protocols with non-method members don't support issubclass()"
                    )
                if not getattr(cls, '_is_runtime_protocol', False):
                    raise TypeError(
                        "Instance and class checks can only be used with "
                        "@runtime_checkable protocols"
                    )
            return abc.ABCMeta.__subclasscheck__(cls, other)

        def __instancecheck__(cls, instance):
            # We need this method for situations where attributes are
            # assigned in __init__.
            if cls is Protocol:
                return type.__instancecheck__(cls, instance)
            if not getattr(cls, "_is_protocol", False):
                # i.e., it's a concrete subclass of a protocol
                return abc.ABCMeta.__instancecheck__(cls, instance)

            if (
                not getattr(cls, '_is_runtime_protocol', False) and
                not _allow_reckless_class_checks()
            ):
                raise TypeError("Instance and class checks can only be used with"
                                " @runtime_checkable protocols")

            if abc.ABCMeta.__instancecheck__(cls, instance):
                return True

            for attr in cls.__protocol_attrs__:
                try:
                    val = inspect.getattr_static(instance, attr)
                except AttributeError:
                    break
                if val is None and callable(getattr(cls, attr, None)):
                    break
            else:
                return True

            return False

        def __eq__(cls, other):
            # Hack so that typing.Generic.__class_getitem__
            # treats typing_extensions.Protocol
            # as equivalent to typing.Protocol
            if abc.ABCMeta.__eq__(cls, other) is True:
                return True
            return cls is Protocol and other is typing.Protocol

        # This has to be defined, or the abc-module cache
        # complains about classes with this metaclass being unhashable,
        # if we define only __eq__!
        def __hash__(cls) -> int:
            return type.__hash__(cls)

    @classmethod
    def _proto_hook(cls, other):
        if not cls.__dict__.get('_is_protocol', False):
            return NotImplemented

        for attr in cls.__protocol_attrs__:
            for base in other.__mro__:
                # Check if the members appears in the class dictionary...
                if attr in base.__dict__:
                    if base.__dict__[attr] is None:
                        return NotImplemented
                    break

                # ...or in annotations, if it is a sub-protocol.
                annotations = getattr(base, '__annotations__', {})
                if (
                    isinstance(annotations, collections.abc.Mapping)
                    and attr in annotations
                    and is_protocol(other)
                ):
                    break
            else:
                return NotImplemented
        return True

    class Protocol(typing.Generic, metaclass=_ProtocolMeta):
        __doc__ = typing.Protocol.__doc__
        __slots__ = ()
        _is_protocol = True
        _is_runtime_protocol = False

        def __init_subclass__(cls, *args, **kwargs):
            super().__init_subclass__(*args, **kwargs)

            # Determine if this is a protocol or a concrete subclass.
            if not cls.__dict__.get('_is_protocol', False):
                cls._is_protocol = any(b is Protocol for b in cls.__bases__)

            # Set (or override) the protocol subclass hook.
            if '__subclasshook__' not in cls.__dict__:
                cls.__subclasshook__ = _proto_hook

            # Prohibit instantiation for protocol classes
            if cls._is_protocol and cls.__init__ is Protocol.__init__:
                cls.__init__ = _no_init


# The "runtime" alias exists for backwards compatibility.
runtime = runtime_checkable = typing.runtime_checkable


# Our version of runtime-checkable protocols is faster on Python 3.8-3.11
if sys.version_info >= (3, 12):
    SupportsInt = typing.SupportsInt
    SupportsFloat = typing.SupportsFloat
    SupportsComplex = typing.SupportsComplex
    SupportsBytes = typing.SupportsBytes
    SupportsIndex = typing.SupportsIndex
    SupportsAbs = typing.SupportsAbs
    SupportsRound = typing.SupportsRound
else:
    @runtime_checkable
    class SupportsInt(Protocol):
        """An ABC with one abstract method __int__."""
        __slots__ = ()

        @abc.abstractmethod
        def __int__(self) -> int:
            pass

    @runtime_checkable
    class SupportsFloat(Protocol):
        """An ABC with one abstract method __float__."""
        __slots__ = ()

        @abc.abstractmethod
        def __float__(self) -> float:
            pass

    @runtime_checkable
    class SupportsComplex(Protocol):
        """An ABC with one abstract method __complex__."""
        __slots__ = ()

        @abc.abstractmethod
        def __complex__(self) -> complex:
            pass

    @runtime_checkable
    class SupportsBytes(Protocol):
        """An ABC with one abstract method __bytes__."""
        __slots__ = ()

        @abc.abstractmethod
        def __bytes__(self) -> bytes:
            pass

    @runtime_checkable
    class SupportsIndex(Protocol):
        __slots__ = ()

        @abc.abstractmethod
        def __index__(self) -> int:
            pass

    @runtime_checkable
    class SupportsAbs(Protocol[T_co]):
        """
        An ABC with one abstract method __abs__ that is covariant in its return type.
        """
        __slots__ = ()

        @abc.abstractmethod
        def __abs__(self) -> T_co:
            pass

    @runtime_checkable
    class SupportsRound(Protocol[T_co]):
        """
        An ABC with one abstract method __round__ that is covariant in its return type.
        """
        __slots__ = ()

        @abc.abstractmethod
        def __round__(self, ndigits: int = 0) -> T_co:
            pass


def _ensure_subclassable(mro_entries):
    def inner(func):
        if sys.implementation.name == "pypy" and sys.version_info < (3, 9):
            cls_dict = {
                "__call__": staticmethod(func),
                "__mro_entries__": staticmethod(mro_entries)
            }
            t = type(func.__name__, (), cls_dict)
            return functools.update_wrapper(t(), func)
        else:
            func.__mro_entries__ = mro_entries
            return func
    return inner


if sys.version_info >= (3, 13):
    # The standard library TypedDict in Python 3.8 does not store runtime information
    # about which (if any) keys are optional.  See https://bugs.python.org/issue38834
    # The standard library TypedDict in Python 3.9.0/1 does not honour the "total"
    # keyword with old-style TypedDict().  See https://bugs.python.org/issue42059
    # The standard library TypedDict below Python 3.11 does not store runtime
    # information about optional and required keys when using Required or NotRequired.
    # Generic TypedDicts are also impossible using typing.TypedDict on Python <3.11.
    # Aaaand on 3.12 we add __orig_bases__ to TypedDict
    # to enable better runtime introspection.
    # On 3.13 we deprecate some odd ways of creating TypedDicts.
    TypedDict = typing.TypedDict
    _TypedDictMeta = typing._TypedDictMeta
    is_typeddict = typing.is_typeddict
else:
    # 3.10.0 and later
    _TAKES_MODULE = "module" in inspect.signature(typing._type_check).parameters

    class _TypedDictMeta(type):
        def __new__(cls, name, bases, ns, total=True):
            """Create new typed dict class object.

            This method is called when TypedDict is subclassed,
            or when TypedDict is instantiated. This way
            TypedDict supports all three syntax forms described in its docstring.
            Subclasses and instances of TypedDict return actual dictionaries.
            """
            for base in bases:
                if type(base) is not _TypedDictMeta and base is not typing.Generic:
                    raise TypeError('cannot inherit from both a TypedDict type '
                                    'and a non-TypedDict base class')

            if any(issubclass(b, typing.Generic) for b in bases):
                generic_base = (typing.Generic,)
            else:
                generic_base = ()

            # typing.py generally doesn't let you inherit from plain Generic, unless
            # the name of the class happens to be "Protocol"
            tp_dict = type.__new__(_TypedDictMeta, "Protocol", (*generic_base, dict), ns)
            tp_dict.__name__ = name
            if tp_dict.__qualname__ == "Protocol":
                tp_dict.__qualname__ = name

            if not hasattr(tp_dict, '__orig_bases__'):
                tp_dict.__orig_bases__ = bases

            annotations = {}
            own_annotations = ns.get('__annotations__', {})
            msg = "TypedDict('Name', {f0: t0, f1: t1, ...}); each t must be a type"
            if _TAKES_MODULE:
                own_annotations = {
                    n: typing._type_check(tp, msg, module=tp_dict.__module__)
                    for n, tp in own_annotations.items()
                }
            else:
                own_annotations = {
                    n: typing._type_check(tp, msg)
                    for n, tp in own_annotations.items()
                }
            required_keys = set()
            optional_keys = set()

            for base in bases:
                annotations.update(base.__dict__.get('__annotations__', {}))
                required_keys.update(base.__dict__.get('__required_keys__', ()))
                optional_keys.update(base.__dict__.get('__optional_keys__', ()))

            annotations.update(own_annotations)
            for annotation_key, annotation_type in own_annotations.items():
                annotation_origin = get_origin(annotation_type)
                if annotation_origin is Annotated:
                    annotation_args = get_args(annotation_type)
                    if annotation_args:
                        annotation_type = annotation_args[0]
                        annotation_origin = get_origin(annotation_type)

                if annotation_origin is Required:
                    required_keys.add(annotation_key)
                elif annotation_origin is NotRequired:
                    optional_keys.add(annotation_key)
                elif total:
                    required_keys.add(annotation_key)
                else:
                    optional_keys.add(annotation_key)

            tp_dict.__annotations__ = annotations
            tp_dict.__required_keys__ = frozenset(required_keys)
            tp_dict.__optional_keys__ = frozenset(optional_keys)
            if not hasattr(tp_dict, '__total__'):
                tp_dict.__total__ = total
            return tp_dict

        __call__ = dict  # static method

        def __subclasscheck__(cls, other):
            # Typed dicts are only for static structural subtyping.
            raise TypeError('TypedDict does not support instance and class checks')

        __instancecheck__ = __subclasscheck__

    _TypedDict = type.__new__(_TypedDictMeta, 'TypedDict', (), {})

    @_ensure_subclassable(lambda bases: (_TypedDict,))
    def TypedDict(typename, fields=_marker, /, *, total=True, **kwargs):
        """A simple typed namespace. At runtime it is equivalent to a plain dict.

        TypedDict creates a dictionary type such that a type checker will expect all
        instances to have a certain set of keys, where each key is
        associated with a value of a consistent type. This expectation
        is not checked at runtime.

        Usage::

            class Point2D(TypedDict):
                x: int
                y: int
                label: str

            a: Point2D = {'x': 1, 'y': 2, 'label': 'good'}  # OK
            b: Point2D = {'z': 3, 'label': 'bad'}           # Fails type check

            assert Point2D(x=1, y=2, label='first') == dict(x=1, y=2, label='first')

        The type info can be accessed via the Point2D.__annotations__ dict, and
        the Point2D.__required_keys__ and Point2D.__optional_keys__ frozensets.
        TypedDict supports an additional equivalent form::

            Point2D = TypedDict('Point2D', {'x': int, 'y': int, 'label': str})

        By default, all keys must be present in a TypedDict. It is possible
        to override this by specifying totality::

            class Point2D(TypedDict, total=False):
                x: int
                y: int

        This means that a Point2D TypedDict can have any of the keys omitted. A type
        checker is only expected to support a literal False or True as the value of
        the total argument. True is the default, and makes all items defined in the
        class body be required.

        The Required and NotRequired special forms can also be used to mark
        individual keys as being required or not required::

            class Point2D(TypedDict):
                x: int  # the "x" key must always be present (Required is the default)
                y: NotRequired[int]  # the "y" key can be omitted

        See PEP 655 for more details on Required and NotRequired.
        """
        if fields is _marker or fields is None:
            if fields is _marker:
                deprecated_thing = "Failing to pass a value for the 'fields' parameter"
            else:
                deprecated_thing = "Passing `None` as the 'fields' parameter"

            example = f"`{typename} = TypedDict({typename!r}, {{}})`"
            deprecation_msg = (
                f"{deprecated_thing} is deprecated and will be disallowed in "
                "Python 3.15. To create a TypedDict class with 0 fields "
                "using the functional syntax, pass an empty dictionary, e.g. "
            ) + example + "."
            warnings.warn(deprecation_msg, DeprecationWarning, stacklevel=2)
            fields = kwargs
        elif kwargs:
            raise TypeError("TypedDict takes either a dict or keyword arguments,"
                            " but not both")
        if kwargs:
            warnings.warn(
                "The kwargs-based syntax for TypedDict definitions is deprecated "
                "in Python 3.11, will be removed in Python 3.13, and may not be "
                "understood by third-party type checkers.",
                DeprecationWarning,
                stacklevel=2,
            )

        ns = {'__annotations__': dict(fields)}
        module = _caller()
        if module is not None:
            # Setting correct module is necessary to make typed dict classes pickleable.
            ns['__module__'] = module

        td = _TypedDictMeta(typename, (), ns, total=total)
        td.__orig_bases__ = (TypedDict,)
        return td

    if hasattr(typing, "_TypedDictMeta"):
        _TYPEDDICT_TYPES = (typing._TypedDictMeta, _TypedDictMeta)
    else:
        _TYPEDDICT_TYPES = (_TypedDictMeta,)

    def is_typeddict(tp):
        """Check if an annotation is a TypedDict class

        For example::
            class Film(TypedDict):
                title: str
                year: int

            is_typeddict(Film)  # => True
            is_typeddict(Union[list, str])  # => False
        """
        # On 3.8, this would otherwise return True
        if hasattr(typing, "TypedDict") and tp is typing.TypedDict:
            return False
        return isinstance(tp, _TYPEDDICT_TYPES)


if hasattr(typing, "assert_type"):
    assert_type = typing.assert_type

else:
    def assert_type(val, typ, /):
        """Assert (to the type checker) that the value is of the given type.

        When the type checker encounters a call to assert_type(), it
        emits an error if the value is not of the specified type::

            def greet(name: str) -> None:
                assert_type(name, str)  # ok
                assert_type(name, int)  # type checker error

        At runtime this returns the first argument unchanged and otherwise
        does nothing.
        """
        return val


if hasattr(typing, "Required"):  # 3.11+
    get_type_hints = typing.get_type_hints
else:  # <=3.10
    # replaces _strip_annotations()
    def _strip_extras(t):
        """Strips Annotated, Required and NotRequired from a given type."""
        if isinstance(t, _AnnotatedAlias):
            return _strip_extras(t.__origin__)
        if hasattr(t, "__origin__") and t.__origin__ in (Required, NotRequired):
            return _strip_extras(t.__args__[0])
        if isinstance(t, typing._GenericAlias):
            stripped_args = tuple(_strip_extras(a) for a in t.__args__)
            if stripped_args == t.__args__:
                return t
            return t.copy_with(stripped_args)
        if hasattr(_types, "GenericAlias") and isinstance(t, _types.GenericAlias):
            stripped_args = tuple(_strip_extras(a) for a in t.__args__)
            if stripped_args == t.__args__:
                return t
            return _types.GenericAlias(t.__origin__, stripped_args)
        if hasattr(_types, "UnionType") and isinstance(t, _types.UnionType):
            stripped_args = tuple(_strip_extras(a) for a in t.__args__)
            if stripped_args == t.__args__:
                return t
            return functools.reduce(operator.or_, stripped_args)

        return t

    def get_type_hints(obj, globalns=None, localns=None, include_extras=False):
        """Return type hints for an object.

        This is often the same as obj.__annotations__, but it handles
        forward references encoded as string literals, adds Optional[t] if a
        default value equal to None is set and recursively replaces all
        'Annotated[T, ...]', 'Required[T]' or 'NotRequired[T]' with 'T'
        (unless 'include_extras=True').

        The argument may be a module, class, method, or function. The annotations
        are returned as a dictionary. For classes, annotations include also
        inherited members.

        TypeError is raised if the argument is not of a type that can contain
        annotations, and an empty dictionary is returned if no annotations are
        present.

        BEWARE -- the behavior of globalns and localns is counterintuitive
        (unless you are familiar with how eval() and exec() work).  The
        search order is locals first, then globals.

        - If no dict arguments are passed, an attempt is made to use the
          globals from obj (or the respective module's globals for classes),
          and these are also used as the locals.  If the object does not appear
          to have globals, an empty dictionary is used.

        - If one dict argument is passed, it is used for both globals and
          locals.

        - If two dict arguments are passed, they specify globals and
          locals, respectively.
        """
        if hasattr(typing, "Annotated"):  # 3.9+
            hint = typing.get_type_hints(
                obj, globalns=globalns, localns=localns, include_extras=True
            )
        else:  # 3.8
            hint = typing.get_type_hints(obj, globalns=globalns, localns=localns)
        if include_extras:
            return hint
        return {k: _strip_extras(t) for k, t in hint.items()}


# Python 3.9+ has PEP 593 (Annotated)
if hasattr(typing, 'Annotated'):
    Annotated = typing.Annotated
    # Not exported and not a public API, but needed for get_origin() and get_args()
    # to work.
    _AnnotatedAlias = typing._AnnotatedAlias
# 3.8
else:
    class _AnnotatedAlias(typing._GenericAlias, _root=True):
        """Runtime representation of an annotated type.

        At its core 'Annotated[t, dec1, dec2, ...]' is an alias for the type 't'
        with extra annotations. The alias behaves like a normal typing alias,
        instantiating is the same as instantiating the underlying type, binding
        it to types is also the same.
        """
        def __init__(self, origin, metadata):
            if isinstance(origin, _AnnotatedAlias):
                metadata = origin.__metadata__ + metadata
                origin = origin.__origin__
            super().__init__(origin, origin)
            self.__metadata__ = metadata

        def copy_with(self, params):
            assert len(params) == 1
            new_type = params[0]
            return _AnnotatedAlias(new_type, self.__metadata__)

        def __repr__(self):
            return (f"typing_extensions.Annotated[{typing._type_repr(self.__origin__)}, "
                    f"{', '.join(repr(a) for a in self.__metadata__)}]")

        def __reduce__(self):
            return operator.getitem, (
                Annotated, (self.__origin__,) + self.__metadata__
            )

        def __eq__(self, other):
            if not isinstance(other, _AnnotatedAlias):
                return NotImplemented
            if self.__origin__ != other.__origin__:
                return False
            return self.__metadata__ == other.__metadata__

        def __hash__(self):
            return hash((self.__origin__, self.__metadata__))

    class Annotated:
        """Add context specific metadata to a type.

        Example: Annotated[int, runtime_check.Unsigned] indicates to the
        hypothetical runtime_check module that this type is an unsigned int.
        Every other consumer of this type can ignore this metadata and treat
        this type as int.

        The first argument to Annotated must be a valid type (and will be in
        the __origin__ field), the remaining arguments are kept as a tuple in
        the __extra__ field.

        Details:

        - It's an error to call `Annotated` with less than two arguments.
        - Nested Annotated are flattened::

            Annotated[Annotated[T, Ann1, Ann2], Ann3] == Annotated[T, Ann1, Ann2, Ann3]

        - Instantiating an annotated type is equivalent to instantiating the
        underlying type::

            Annotated[C, Ann1](5) == C(5)

        - Annotated can be used as a generic type alias::

            Optimized = Annotated[T, runtime.Optimize()]
            Optimized[int] == Annotated[int, runtime.Optimize()]

            OptimizedList = Annotated[List[T], runtime.Optimize()]
            OptimizedList[int] == Annotated[List[int], runtime.Optimize()]
        """

        __slots__ = ()

        def __new__(cls, *args, **kwargs):
            raise TypeError("Type Annotated cannot be instantiated.")

        @typing._tp_cache
        def __class_getitem__(cls, params):
            if not isinstance(params, tuple) or len(params) < 2:
                raise TypeError("Annotated[...] should be used "
                                "with at least two arguments (a type and an "
                                "annotation).")
            allowed_special_forms = (ClassVar, Final)
            if get_origin(params[0]) in allowed_special_forms:
                origin = params[0]
            else:
                msg = "Annotated[t, ...]: t must be a type."
                origin = typing._type_check(params[0], msg)
            metadata = tuple(params[1:])
            return _AnnotatedAlias(origin, metadata)

        def __init_subclass__(cls, *args, **kwargs):
            raise TypeError(
                f"Cannot subclass {cls.__module__}.Annotated"
            )

# Python 3.8 has get_origin() and get_args() but those implementations aren't
# Annotated-aware, so we can't use those. Python 3.9's versions don't support
# ParamSpecArgs and ParamSpecKwargs, so only Python 3.10's versions will do.
if sys.version_info[:2] >= (3, 10):
    get_origin = typing.get_origin
    get_args = typing.get_args
# 3.8-3.9
else:
    try:
        # 3.9+
        from typing import _BaseGenericAlias
    except ImportError:
        _BaseGenericAlias = typing._GenericAlias
    try:
        # 3.9+
        from typing import GenericAlias as _typing_GenericAlias
    except ImportError:
        _typing_GenericAlias = typing._GenericAlias

    def get_origin(tp):
        """Get the unsubscripted version of a type.

        This supports generic types, Callable, Tuple, Union, Literal, Final, ClassVar
        and Annotated. Return None for unsupported types. Examples::

            get_origin(Literal[42]) is Literal
            get_origin(int) is None
            get_origin(ClassVar[int]) is ClassVar
            get_origin(Generic) is Generic
            get_origin(Generic[T]) is Generic
            get_origin(Union[T, int]) is Union
            get_origin(List[Tuple[T, T]][int]) == list
            get_origin(P.args) is P
        """
        if isinstance(tp, _AnnotatedAlias):
            return Annotated
        if isinstance(tp, (typing._GenericAlias, _typing_GenericAlias, _BaseGenericAlias,
                           ParamSpecArgs, ParamSpecKwargs)):
            return tp.__origin__
        if tp is typing.Generic:
            return typing.Generic
        return None

    def get_args(tp):
        """Get type arguments with all substitutions performed.

        For unions, basic simplifications used by Union constructor are performed.
        Examples::
            get_args(Dict[str, int]) == (str, int)
            get_args(int) == ()
            get_args(Union[int, Union[T, int], str][int]) == (int, str)
            get_args(Union[int, Tuple[T, int]][str]) == (int, Tuple[str, int])
            get_args(Callable[[], T][int]) == ([], int)
        """
        if isinstance(tp, _AnnotatedAlias):
            return (tp.__origin__,) + tp.__metadata__
        if isinstance(tp, (typing._GenericAlias, _typing_GenericAlias)):
            if getattr(tp, "_special", False):
                return ()
            res = tp.__args__
            if get_origin(tp) is collections.abc.Callable and res[0] is not Ellipsis:
                res = (list(res[:-1]), res[-1])
            return res
        return ()


# 3.10+
if hasattr(typing, 'TypeAlias'):
    TypeAlias = typing.TypeAlias
# 3.9
elif sys.version_info[:2] >= (3, 9):
    @_ExtensionsSpecialForm
    def TypeAlias(self, parameters):
        """Special marker indicating that an assignment should
        be recognized as a proper type alias definition by type
        checkers.

        For example::

            Predicate: TypeAlias = Callable[..., bool]

        It's invalid when used anywhere except as in the example above.
        """
        raise TypeError(f"{self} is not subscriptable")
# 3.8
else:
    TypeAlias = _ExtensionsSpecialForm(
        'TypeAlias',
        doc="""Special marker indicating that an assignment should
        be recognized as a proper type alias definition by type
        checkers.

        For example::

            Predicate: TypeAlias = Callable[..., bool]

        It's invalid when used anywhere except as in the example
        above."""
    )


def _set_default(type_param, default):
    if isinstance(default, (tuple, list)):
        type_param.__default__ = tuple((typing._type_check(d, "Default must be a type")
                                        for d in default))
    elif default != _marker:
        if isinstance(type_param, ParamSpec) and default is ...:  # ... not valid <3.11
            type_param.__default__ = default
        else:
            type_param.__default__ = typing._type_check(default, "Default must be a type")
    else:
        type_param.__default__ = None


def _set_module(typevarlike):
    # for pickling:
    def_mod = _caller(depth=3)
    if def_mod != 'typing_extensions':
        typevarlike.__module__ = def_mod


class _DefaultMixin:
    """Mixin for TypeVarLike defaults."""

    __slots__ = ()
    __init__ = _set_default


# Classes using this metaclass must provide a _backported_typevarlike ClassVar
class _TypeVarLikeMeta(type):
    def __instancecheck__(cls, __instance: Any) -> bool:
        return isinstance(__instance, cls._backported_typevarlike)


# Add default and infer_variance parameters from PEP 696 and 695
class TypeVar(metaclass=_TypeVarLikeMeta):
    """Type variable."""

    _backported_typevarlike = typing.TypeVar

    def __new__(cls, name, *constraints, bound=None,
                covariant=False, contravariant=False,
                default=_marker, infer_variance=False):
        if hasattr(typing, "TypeAliasType"):
            # PEP 695 implemented (3.12+), can pass infer_variance to typing.TypeVar
            typevar = typing.TypeVar(name, *constraints, bound=bound,
                                     covariant=covariant, contravariant=contravariant,
                                     infer_variance=infer_variance)
        else:
            typevar = typing.TypeVar(name, *constraints, bound=bound,
                                     covariant=covariant, contravariant=contravariant)
            if infer_variance and (covariant or contravariant):
                raise ValueError("Variance cannot be specified with infer_variance.")
            typevar.__infer_variance__ = infer_variance
        _set_default(typevar, default)
        _set_module(typevar)
        return typevar

    def __init_subclass__(cls) -> None:
        raise TypeError(f"type '{__name__}.TypeVar' is not an acceptable base type")


# Python 3.10+ has PEP 612
if hasattr(typing, 'ParamSpecArgs'):
    ParamSpecArgs = typing.ParamSpecArgs
    ParamSpecKwargs = typing.ParamSpecKwargs
# 3.8-3.9
else:
    class _Immutable:
        """Mixin to indicate that object should not be copied."""
        __slots__ = ()

        def __copy__(self):
            return self

        def __deepcopy__(self, memo):
            return self

    class ParamSpecArgs(_Immutable):
        """The args for a ParamSpec object.

        Given a ParamSpec object P, P.args is an instance of ParamSpecArgs.

        ParamSpecArgs objects have a reference back to their ParamSpec:

        P.args.__origin__ is P

        This type is meant for runtime introspection and has no special meaning to
        static type checkers.
        """
        def __init__(self, origin):
            self.__origin__ = origin

        def __repr__(self):
            return f"{self.__origin__.__name__}.args"

        def __eq__(self, other):
            if not isinstance(other, ParamSpecArgs):
                return NotImplemented
            return self.__origin__ == other.__origin__

    class ParamSpecKwargs(_Immutable):
        """The kwargs for a ParamSpec object.

        Given a ParamSpec object P, P.kwargs is an instance of ParamSpecKwargs.

        ParamSpecKwargs objects have a reference back to their ParamSpec:

        P.kwargs.__origin__ is P

        This type is meant for runtime introspection and has no special meaning to
        static type checkers.
        """
        def __init__(self, origin):
            self.__origin__ = origin

        def __repr__(self):
            return f"{self.__origin__.__name__}.kwargs"

        def __eq__(self, other):
            if not isinstance(other, ParamSpecKwargs):
                return NotImplemented
            return self.__origin__ == other.__origin__

# 3.10+
if hasattr(typing, 'ParamSpec'):

    # Add default parameter - PEP 696
    class ParamSpec(metaclass=_TypeVarLikeMeta):
        """Parameter specification."""

        _backported_typevarlike = typing.ParamSpec

        def __new__(cls, name, *, bound=None,
                    covariant=False, contravariant=False,
                    infer_variance=False, default=_marker):
            if hasattr(typing, "TypeAliasType"):
                # PEP 695 implemented, can pass infer_variance to typing.TypeVar
                paramspec = typing.ParamSpec(name, bound=bound,
                                             covariant=covariant,
                                             contravariant=contravariant,
                                             infer_variance=infer_variance)
            else:
                paramspec = typing.ParamSpec(name, bound=bound,
                                             covariant=covariant,
                                             contravariant=contravariant)
                paramspec.__infer_variance__ = infer_variance

            _set_default(paramspec, default)
            _set_module(paramspec)
            return paramspec

        def __init_subclass__(cls) -> None:
            raise TypeError(f"type '{__name__}.ParamSpec' is not an acceptable base type")

# 3.8-3.9
else:

    # Inherits from list as a workaround for Callable checks in Python < 3.9.2.
    class ParamSpec(list, _DefaultMixin):
        """Parameter specification variable.

        Usage::

           P = ParamSpec('P')

        Parameter specification variables exist primarily for the benefit of static
        type checkers.  They are used to forward the parameter types of one
        callable to another callable, a pattern commonly found in higher order
        functions and decorators.  They are only valid when used in ``Concatenate``,
        or s the first argument to ``Callable``. In Python 3.10 and higher,
        they are also supported in user-defined Generics at runtime.
        See class Generic for more information on generic types.  An
        example for annotating a decorator::

           T = TypeVar('T')
           P = ParamSpec('P')

           def add_logging(f: Callable[P, T]) -> Callable[P, T]:
               '''A type-safe decorator to add logging to a function.'''
               def inner(*args: P.args, **kwargs: P.kwargs) -> T:
                   logging.info(f'{f.__name__} was called')
                   return f(*args, **kwargs)
               return inner

           @add_logging
           def add_two(x: float, y: float) -> float:
               '''Add two numbers together.'''
               return x + y

        Parameter specification variables defined with covariant=True or
        contravariant=True can be used to declare covariant or contravariant
        generic types.  These keyword arguments are valid, but their actual semantics
        are yet to be decided.  See PEP 612 for details.

        Parameter specification variables can be introspected. e.g.:

           P.__name__ == 'T'
           P.__bound__ == None
           P.__covariant__ == False
           P.__contravariant__ == False

        Note that only parameter specification variables defined in global scope can
        be pickled.
        """

        # Trick Generic __parameters__.
        __class__ = typing.TypeVar

        @property
        def args(self):
            return ParamSpecArgs(self)

        @property
        def kwargs(self):
            return ParamSpecKwargs(self)

        def __init__(self, name, *, bound=None, covariant=False, contravariant=False,
                     infer_variance=False, default=_marker):
            super().__init__([self])
            self.__name__ = name
            self.__covariant__ = bool(covariant)
            self.__contravariant__ = bool(contravariant)
            self.__infer_variance__ = bool(infer_variance)
            if bound:
                self.__bound__ = typing._type_check(bound, 'Bound must be a type.')
            else:
                self.__bound__ = None
            _DefaultMixin.__init__(self, default)

            # for pickling:
            def_mod = _caller()
            if def_mod != 'typing_extensions':
                self.__module__ = def_mod

        def __repr__(self):
            if self.__infer_variance__:
                prefix = ''
            elif self.__covariant__:
                prefix = '+'
            elif self.__contravariant__:
                prefix = '-'
            else:
                prefix = '~'
            return prefix + self.__name__

        def __hash__(self):
            return object.__hash__(self)

        def __eq__(self, other):
            return self is other

        def __reduce__(self):
            return self.__name__

        # Hack to get typing._type_check to pass.
        def __call__(self, *args, **kwargs):
            pass


# 3.8-3.9
if not hasattr(typing, 'Concatenate'):
    # Inherits from list as a workaround for Callable checks in Python < 3.9.2.
    class _ConcatenateGenericAlias(list):

        # Trick Generic into looking into this for __parameters__.
        __class__ = typing._GenericAlias

        # Flag in 3.8.
        _special = False

        def __init__(self, origin, args):
            super().__init__(args)
            self.__origin__ = origin
            self.__args__ = args

        def __repr__(self):
            _type_repr = typing._type_repr
            return (f'{_type_repr(self.__origin__)}'
                    f'[{", ".join(_type_repr(arg) for arg in self.__args__)}]')

        def __hash__(self):
            return hash((self.__origin__, self.__args__))

        # Hack to get typing._type_check to pass in Generic.
        def __call__(self, *args, **kwargs):
            pass

        @property
        def __parameters__(self):
            return tuple(
                tp for tp in self.__args__ if isinstance(tp, (typing.TypeVar, ParamSpec))
            )


# 3.8-3.9
@typing._tp_cache
def _concatenate_getitem(self, parameters):
    if parameters == ():
        raise TypeError("Cannot take a Concatenate of no types.")
    if not isinstance(parameters, tuple):
        parameters = (parameters,)
    if not isinstance(parameters[-1], ParamSpec):
        raise TypeError("The last parameter to Concatenate should be a "
                        "ParamSpec variable.")
    msg = "Concatenate[arg, ...]: each arg must be a type."
    parameters = tuple(typing._type_check(p, msg) for p in parameters)
    return _ConcatenateGenericAlias(self, parameters)


# 3.10+
if hasattr(typing, 'Concatenate'):
    Concatenate = typing.Concatenate
    _ConcatenateGenericAlias = typing._ConcatenateGenericAlias  # noqa: F811
# 3.9
elif sys.version_info[:2] >= (3, 9):
    @_ExtensionsSpecialForm
    def Concatenate(self, parameters):
        """Used in conjunction with ``ParamSpec`` and ``Callable`` to represent a
        higher order function which adds, removes or transforms parameters of a
        callable.

        For example::

           Callable[Concatenate[int, P], int]

        See PEP 612 for detailed information.
        """
        return _concatenate_getitem(self, parameters)
# 3.8
else:
    class _ConcatenateForm(_ExtensionsSpecialForm, _root=True):
        def __getitem__(self, parameters):
            return _concatenate_getitem(self, parameters)

    Concatenate = _ConcatenateForm(
        'Concatenate',
        doc="""Used in conjunction with ``ParamSpec`` and ``Callable`` to represent a
        higher order function which adds, removes or transforms parameters of a
        callable.

        For example::

           Callable[Concatenate[int, P], int]

        See PEP 612 for detailed information.
        """)

# 3.10+
if hasattr(typing, 'TypeGuard'):
    TypeGuard = typing.TypeGuard
# 3.9
elif sys.version_info[:2] >= (3, 9):
    @_ExtensionsSpecialForm
    def TypeGuard(self, parameters):
        """Special typing form used to annotate the return type of a user-defined
        type guard function.  ``TypeGuard`` only accepts a single type argument.
        At runtime, functions marked this way should return a boolean.

        ``TypeGuard`` aims to benefit *type narrowing* -- a technique used by static
        type checkers to determine a more precise type of an expression within a
        program's code flow.  Usually type narrowing is done by analyzing
        conditional code flow and applying the narrowing to a block of code.  The
        conditional expression here is sometimes referred to as a "type guard".

        Sometimes it would be convenient to use a user-defined boolean function
        as a type guard.  Such a function should use ``TypeGuard[...]`` as its
        return type to alert static type checkers to this intention.

        Using  ``-> TypeGuard`` tells the static type checker that for a given
        function:

        1. The return value is a boolean.
        2. If the return value is ``True``, the type of its argument
        is the type inside ``TypeGuard``.

        For example::

            def is_str(val: Union[str, float]):
                # "isinstance" type guard
                if isinstance(val, str):
                    # Type of ``val`` is narrowed to ``str``
                    ...
                else:
                    # Else, type of ``val`` is narrowed to ``float``.
                    ...

        Strict type narrowing is not enforced -- ``TypeB`` need not be a narrower
        form of ``TypeA`` (it can even be a wider form) and this may lead to
        type-unsafe results.  The main reason is to allow for things like
        narrowing ``List[object]`` to ``List[str]`` even though the latter is not
        a subtype of the former, since ``List`` is invariant.  The responsibility of
        writing type-safe type guards is left to the user.

        ``TypeGuard`` also works with type variables.  For more information, see
        PEP 647 (User-Defined Type Guards).
        """
        item = typing._type_check(parameters, f'{self} accepts only a single type.')
        return typing._GenericAlias(self, (item,))
# 3.8
else:
    class _TypeGuardForm(_ExtensionsSpecialForm, _root=True):
        def __getitem__(self, parameters):
            item = typing._type_check(parameters,
                                      f'{self._name} accepts only a single type')
            return typing._GenericAlias(self, (item,))

    TypeGuard = _TypeGuardForm(
        'TypeGuard',
        doc="""Special typing form used to annotate the return type of a user-defined
        type guard function.  ``TypeGuard`` only accepts a single type argument.
        At runtime, functions marked this way should return a boolean.

        ``TypeGuard`` aims to benefit *type narrowing* -- a technique used by static
        type checkers to determine a more precise type of an expression within a
        program's code flow.  Usually type narrowing is done by analyzing
        conditional code flow and applying the narrowing to a block of code.  The
        conditional expression here is sometimes referred to as a "type guard".

        Sometimes it would be convenient to use a user-defined boolean function
        as a type guard.  Such a function should use ``TypeGuard[...]`` as its
        return type to alert static type checkers to this intention.

        Using  ``-> TypeGuard`` tells the static type checker that for a given
        function:

        1. The return value is a boolean.
        2. If the return value is ``True``, the type of its argument
        is the type inside ``TypeGuard``.

        For example::

            def is_str(val: Union[str, float]):
                # "isinstance" type guard
                if isinstance(val, str):
                    # Type of ``val`` is narrowed to ``str``
                    ...
                else:
                    # Else, type of ``val`` is narrowed to ``float``.
                    ...

        Strict type narrowing is not enforced -- ``TypeB`` need not be a narrower
        form of ``TypeA`` (it can even be a wider form) and this may lead to
        type-unsafe results.  The main reason is to allow for things like
        narrowing ``List[object]`` to ``List[str]`` even though the latter is not
        a subtype of the former, since ``List`` is invariant.  The responsibility of
        writing type-safe type guards is left to the user.

        ``TypeGuard`` also works with type variables.  For more information, see
        PEP 647 (User-Defined Type Guards).
        """)


# Vendored from cpython typing._SpecialFrom
class _SpecialForm(typing._Final, _root=True):
    __slots__ = ('_name', '__doc__', '_getitem')

    def __init__(self, getitem):
        self._getitem = getitem
        self._name = getitem.__name__
        self.__doc__ = getitem.__doc__

    def __getattr__(self, item):
        if item in {'__name__', '__qualname__'}:
            return self._name

        raise AttributeError(item)

    def __mro_entries__(self, bases):
        raise TypeError(f"Cannot subclass {self!r}")

    def __repr__(self):
        return f'typing_extensions.{self._name}'

    def __reduce__(self):
        return self._name

    def __call__(self, *args, **kwds):
        raise TypeError(f"Cannot instantiate {self!r}")

    def __or__(self, other):
        return typing.Union[self, other]

    def __ror__(self, other):
        return typing.Union[other, self]

    def __instancecheck__(self, obj):
        raise TypeError(f"{self} cannot be used with isinstance()")

    def __subclasscheck__(self, cls):
        raise TypeError(f"{self} cannot be used with issubclass()")

    @typing._tp_cache
    def __getitem__(self, parameters):
        return self._getitem(self, parameters)


if hasattr(typing, "LiteralString"):  # 3.11+
    LiteralString = typing.LiteralString
else:
    @_SpecialForm
    def LiteralString(self, params):
        """Represents an arbitrary literal string.

        Example::

          from typing_extensions import LiteralString

          def query(sql: LiteralString) -> ...:
              ...

          query("SELECT * FROM table")  # ok
          query(f"SELECT * FROM {input()}")  # not ok

        See PEP 675 for details.

        """
        raise TypeError(f"{self} is not subscriptable")


if hasattr(typing, "Self"):  # 3.11+
    Self = typing.Self
else:
    @_SpecialForm
    def Self(self, params):
        """Used to spell the type of "self" in classes.

        Example::

          from typing import Self

          class ReturnsSelf:
              def parse(self, data: bytes) -> Self:
                  ...
                  return self

        """

        raise TypeError(f"{self} is not subscriptable")


if hasattr(typing, "Never"):  # 3.11+
    Never = typing.Never
else:
    @_SpecialForm
    def Never(self, params):
        """The bottom type, a type that has no members.

        This can be used to define a function that should never be
        called, or a function that never returns::

            from typing_extensions import Never

            def never_call_me(arg: Never) -> None:
                pass

            def int_or_str(arg: int | str) -> None:
                never_call_me(arg)  # type checker error
                match arg:
                    case int():
                        print("It's an int")
                    case str():
                        print("It's a str")
                    case _:
                        never_call_me(arg)  # ok, arg is of type Never

        """

        raise TypeError(f"{self} is not subscriptable")


if hasattr(typing, 'Required'):  # 3.11+
    Required = typing.Required
    NotRequired = typing.NotRequired
elif sys.version_info[:2] >= (3, 9):  # 3.9-3.10
    @_ExtensionsSpecialForm
    def Required(self, parameters):
        """A special typing construct to mark a key of a total=False TypedDict
        as required. For example:

            class Movie(TypedDict, total=False):
                title: Required[str]
                year: int

            m = Movie(
                title='The Matrix',  # typechecker error if key is omitted
                year=1999,
            )

        There is no runtime checking that a required key is actually provided
        when instantiating a related TypedDict.
        """
        item = typing._type_check(parameters, f'{self._name} accepts only a single type.')
        return typing._GenericAlias(self, (item,))

    @_ExtensionsSpecialForm
    def NotRequired(self, parameters):
        """A special typing construct to mark a key of a TypedDict as
        potentially missing. For example:

            class Movie(TypedDict):
                title: str
                year: NotRequired[int]

            m = Movie(
                title='The Matrix',  # typechecker error if key is omitted
                year=1999,
            )
        """
        item = typing._type_check(parameters, f'{self._name} accepts only a single type.')
        return typing._GenericAlias(self, (item,))

else:  # 3.8
    class _RequiredForm(_ExtensionsSpecialForm, _root=True):
        def __getitem__(self, parameters):
            item = typing._type_check(parameters,
                                      f'{self._name} accepts only a single type.')
            return typing._GenericAlias(self, (item,))

    Required = _RequiredForm(
        'Required',
        doc="""A special typing construct to mark a key of a total=False TypedDict
        as required. For example:

            class Movie(TypedDict, total=False):
                title: Required[str]
                year: int

            m = Movie(
                title='The Matrix',  # typechecker error if key is omitted
                year=1999,
            )

        There is no runtime checking that a required key is actually provided
        when instantiating a related TypedDict.
        """)
    NotRequired = _RequiredForm(
        'NotRequired',
        doc="""A special typing construct to mark a key of a TypedDict as
        potentially missing. For example:

            class Movie(TypedDict):
                title: str
                year: NotRequired[int]

            m = Movie(
                title='The Matrix',  # typechecker error if key is omitted
                year=1999,
            )
        """)


_UNPACK_DOC = """\
Type unpack operator.

The type unpack operator takes the child types from some container type,
such as `tuple[int, str]` or a `TypeVarTuple`, and 'pulls them out'. For
example:

  # For some generic class `Foo`:
  Foo[Unpack[tuple[int, str]]]  # Equivalent to Foo[int, str]

  Ts = TypeVarTuple('Ts')
  # Specifies that `Bar` is generic in an arbitrary number of types.
  # (Think of `Ts` as a tuple of an arbitrary number of individual
  #  `TypeVar`s, which the `Unpack` is 'pulling out' directly into the
  #  `Generic[]`.)
  class Bar(Generic[Unpack[Ts]]): ...
  Bar[int]  # Valid
  Bar[int, str]  # Also valid

From Python 3.11, this can also be done using the `*` operator:

    Foo[*tuple[int, str]]
    class Bar(Generic[*Ts]): ...

The operator can also be used along with a `TypedDict` to annotate
`**kwargs` in a function signature. For instance:

  class Movie(TypedDict):
    name: str
    year: int

  # This function expects two keyword arguments - *name* of type `str` and
  # *year* of type `int`.
  def foo(**kwargs: Unpack[Movie]): ...

Note that there is only some runtime checking of this operator. Not
everything the runtime allows may be accepted by static type checkers.

For more information, see PEP 646 and PEP 692.
"""


if sys.version_info >= (3, 12):  # PEP 692 changed the repr of Unpack[]
    Unpack = typing.Unpack

    def _is_unpack(obj):
        return get_origin(obj) is Unpack

elif sys.version_info[:2] >= (3, 9):  # 3.9+
    class _UnpackSpecialForm(_ExtensionsSpecialForm, _root=True):
        def __init__(self, getitem):
            super().__init__(getitem)
            self.__doc__ = _UNPACK_DOC

    class _UnpackAlias(typing._GenericAlias, _root=True):
        __class__ = typing.TypeVar

    @_UnpackSpecialForm
    def Unpack(self, parameters):
        item = typing._type_check(parameters, f'{self._name} accepts only a single type.')
        return _UnpackAlias(self, (item,))

    def _is_unpack(obj):
        return isinstance(obj, _UnpackAlias)

else:  # 3.8
    class _UnpackAlias(typing._GenericAlias, _root=True):
        __class__ = typing.TypeVar

    class _UnpackForm(_ExtensionsSpecialForm, _root=True):
        def __getitem__(self, parameters):
            item = typing._type_check(parameters,
                                      f'{self._name} accepts only a single type.')
            return _UnpackAlias(self, (item,))

    Unpack = _UnpackForm('Unpack', doc=_UNPACK_DOC)

    def _is_unpack(obj):
        return isinstance(obj, _UnpackAlias)


if hasattr(typing, "TypeVarTuple"):  # 3.11+

    # Add default parameter - PEP 696
    class TypeVarTuple(metaclass=_TypeVarLikeMeta):
        """Type variable tuple."""

        _backported_typevarlike = typing.TypeVarTuple

        def __new__(cls, name, *, default=_marker):
            tvt = typing.TypeVarTuple(name)
            _set_default(tvt, default)
            _set_module(tvt)
            return tvt

        def __init_subclass__(self, *args, **kwds):
            raise TypeError("Cannot subclass special typing classes")

else:  # <=3.10
    class TypeVarTuple(_DefaultMixin):
        """Type variable tuple.

        Usage::

            Ts = TypeVarTuple('Ts')

        In the same way that a normal type variable is a stand-in for a single
        type such as ``int``, a type variable *tuple* is a stand-in for a *tuple*
        type such as ``Tuple[int, str]``.

        Type variable tuples can be used in ``Generic`` declarations.
        Consider the following example::

            class Array(Generic[*Ts]): ...

        The ``Ts`` type variable tuple here behaves like ``tuple[T1, T2]``,
        where ``T1`` and ``T2`` are type variables. To use these type variables
        as type parameters of ``Array``, we must *unpack* the type variable tuple using
        the star operator: ``*Ts``. The signature of ``Array`` then behaves
        as if we had simply written ``class Array(Generic[T1, T2]): ...``.
        In contrast to ``Generic[T1, T2]``, however, ``Generic[*Shape]`` allows
        us to parameterise the class with an *arbitrary* number of type parameters.

        Type variable tuples can be used anywhere a normal ``TypeVar`` can.
        This includes class definitions, as shown above, as well as function
        signatures and variable annotations::

            class Array(Generic[*Ts]):

                def __init__(self, shape: Tuple[*Ts]):
                    self._shape: Tuple[*Ts] = shape

                def get_shape(self) -> Tuple[*Ts]:
                    return self._shape

            shape = (Height(480), Width(640))
            x: Array[Height, Width] = Array(shape)
            y = abs(x)  # Inferred type is Array[Height, Width]
            z = x + x   #        ...    is Array[Height, Width]
            x.get_shape()  #     ...    is tuple[Height, Width]

        """

        # Trick Generic __parameters__.
        __class__ = typing.TypeVar

        def __iter__(self):
            yield self.__unpacked__

        def __init__(self, name, *, default=_marker):
            self.__name__ = name
            _DefaultMixin.__init__(self, default)

            # for pickling:
            def_mod = _caller()
            if def_mod != 'typing_extensions':
                self.__module__ = def_mod

            self.__unpacked__ = Unpack[self]

        def __repr__(self):
            return self.__name__

        def __hash__(self):
            return object.__hash__(self)

        def __eq__(self, other):
            return self is other

        def __reduce__(self):
            return self.__name__

        def __init_subclass__(self, *args, **kwds):
            if '_root' not in kwds:
                raise TypeError("Cannot subclass special typing classes")


if hasattr(typing, "reveal_type"):  # 3.11+
    reveal_type = typing.reveal_type
else:  # <=3.10
    def reveal_type(obj: T, /) -> T:
        """Reveal the inferred type of a variable.

        When a static type checker encounters a call to ``reveal_type()``,
        it will emit the inferred type of the argument::

            x: int = 1
            reveal_type(x)

        Running a static type checker (e.g., ``mypy``) on this example
        will produce output similar to 'Revealed type is "builtins.int"'.

        At runtime, the function prints the runtime type of the
        argument and returns it unchanged.

        """
        print(f"Runtime type is {type(obj).__name__!r}", file=sys.stderr)
        return obj


if hasattr(typing, "assert_never"):  # 3.11+
    assert_never = typing.assert_never
else:  # <=3.10
    def assert_never(arg: Never, /) -> Never:
        """Assert to the type checker that a line of code is unreachable.

        Example::

            def int_or_str(arg: int | str) -> None:
                match arg:
                    case int():
                        print("It's an int")
                    case str():
                        print("It's a str")
                    case _:
                        assert_never(arg)

        If a type checker finds that a call to assert_never() is
        reachable, it will emit an error.

        At runtime, this throws an exception when called.

        """
        raise AssertionError("Expected code to be unreachable")


if sys.version_info >= (3, 12):  # 3.12+
    # dataclass_transform exists in 3.11 but lacks the frozen_default parameter
    dataclass_transform = typing.dataclass_transform
else:  # <=3.11
    def dataclass_transform(
        *,
        eq_default: bool = True,
        order_default: bool = False,
        kw_only_default: bool = False,
        frozen_default: bool = False,
        field_specifiers: typing.Tuple[
            typing.Union[typing.Type[typing.Any], typing.Callable[..., typing.Any]],
            ...
        ] = (),
        **kwargs: typing.Any,
    ) -> typing.Callable[[T], T]:
        """Decorator that marks a function, class, or metaclass as providing
        dataclass-like behavior.

        Example:

            from typing_extensions import dataclass_transform

            _T = TypeVar("_T")

            # Used on a decorator function
            @dataclass_transform()
            def create_model(cls: type[_T]) -> type[_T]:
                ...
                return cls

            @create_model
            class CustomerModel:
                id: int
                name: str

            # Used on a base class
            @dataclass_transform()
            class ModelBase: ...

            class CustomerModel(ModelBase):
                id: int
                name: str

            # Used on a metaclass
            @dataclass_transform()
            class ModelMeta(type): ...

            class ModelBase(metaclass=ModelMeta): ...

            class CustomerModel(ModelBase):
                id: int
                name: str

        Each of the ``CustomerModel`` classes defined in this example will now
        behave similarly to a dataclass created with the ``@dataclasses.dataclass``
        decorator. For example, the type checker will synthesize an ``__init__``
        method.

        The arguments to this decorator can be used to customize this behavior:
        - ``eq_default`` indicates whether the ``eq`` parameter is assumed to be
          True or False if it is omitted by the caller.
        - ``order_default`` indicates whether the ``order`` parameter is
          assumed to be True or False if it is omitted by the caller.
        - ``kw_only_default`` indicates whether the ``kw_only`` parameter is
          assumed to be True or False if it is omitted by the caller.
        - ``frozen_default`` indicates whether the ``frozen`` parameter is
          assumed to be True or False if it is omitted by the caller.
        - ``field_specifiers`` specifies a static list of supported classes
          or functions that describe fields, similar to ``dataclasses.field()``.

        At runtime, this decorator records its arguments in the
        ``__dataclass_transform__`` attribute on the decorated object.

        See PEP 681 for details.

        """
        def decorator(cls_or_fn):
            cls_or_fn.__dataclass_transform__ = {
                "eq_default": eq_default,
                "order_default": order_default,
                "kw_only_default": kw_only_default,
                "frozen_default": frozen_default,
                "field_specifiers": field_specifiers,
                "kwargs": kwargs,
            }
            return cls_or_fn
        return decorator


if hasattr(typing, "override"):  # 3.12+
    override = typing.override
else:  # <=3.11
    _F = typing.TypeVar("_F", bound=typing.Callable[..., typing.Any])

    def override(arg: _F, /) -> _F:
        """Indicate that a method is intended to override a method in a base class.

        Usage:

            class Base:
                def method(self) -> None: ...
                    pass

            class Child(Base):
                @override
                def method(self) -> None:
                    super().method()

        When this decorator is applied to a method, the type checker will
        validate that it overrides a method with the same name on a base class.
        This helps prevent bugs that may occur when a base class is changed
        without an equivalent change to a child class.

        There is no runtime checking of these properties. The decorator
        sets the ``__override__`` attribute to ``True`` on the decorated object
        to allow runtime introspection.

        See PEP 698 for details.

        """
        try:
            arg.__override__ = True
        except (AttributeError, TypeError):
            # Skip the attribute silently if it is not writable.
            # AttributeError happens if the object has __slots__ or a
            # read-only property, TypeError if it's a builtin class.
            pass
        return arg


if hasattr(typing, "deprecated"):
    deprecated = typing.deprecated
else:
    _T = typing.TypeVar("_T")

    def deprecated(
        msg: str,
        /,
        *,
        category: typing.Optional[typing.Type[Warning]] = DeprecationWarning,
        stacklevel: int = 1,
    ) -> typing.Callable[[_T], _T]:
        """Indicate that a class, function or overload is deprecated.

        Usage:

            @deprecated("Use B instead")
            class A:
                pass

            @deprecated("Use g instead")
            def f():
                pass

            @overload
            @deprecated("int support is deprecated")
            def g(x: int) -> int: ...
            @overload
            def g(x: str) -> int: ...

        When this decorator is applied to an object, the type checker
        will generate a diagnostic on usage of the deprecated object.

        The warning specified by ``category`` will be emitted on use
        of deprecated objects. For functions, that happens on calls;
        for classes, on instantiation. If the ``category`` is ``None``,
        no warning is emitted. The ``stacklevel`` determines where the
        warning is emitted. If it is ``1`` (the default), the warning
        is emitted at the direct caller of the deprecated object; if it
        is higher, it is emitted further up the stack.

        The decorator sets the ``__deprecated__``
        attribute on the decorated object to the deprecation message
        passed to the decorator. If applied to an overload, the decorator
        must be after the ``@overload`` decorator for the attribute to
        exist on the overload as returned by ``get_overloads()``.

        See PEP 702 for details.

        """
        def decorator(arg: _T, /) -> _T:
            if category is None:
                arg.__deprecated__ = msg
                return arg
            elif isinstance(arg, type):
                original_new = arg.__new__
                has_init = arg.__init__ is not object.__init__

                @functools.wraps(original_new)
                def __new__(cls, *args, **kwargs):
                    warnings.warn(msg, category=category, stacklevel=stacklevel + 1)
                    if original_new is not object.__new__:
                        return original_new(cls, *args, **kwargs)
                    # Mirrors a similar check in object.__new__.
                    elif not has_init and (args or kwargs):
                        raise TypeError(f"{cls.__name__}() takes no arguments")
                    else:
                        return original_new(cls)

                arg.__new__ = staticmethod(__new__)
                arg.__deprecated__ = __new__.__deprecated__ = msg
                return arg
            elif callable(arg):
                @functools.wraps(arg)
                def wrapper(*args, **kwargs):
                    warnings.warn(msg, category=category, stacklevel=stacklevel + 1)
                    return arg(*args, **kwargs)

                arg.__deprecated__ = wrapper.__deprecated__ = msg
                return wrapper
            else:
                raise TypeError(
                    "@deprecated decorator with non-None category must be applied to "
                    f"a class or callable, not {arg!r}"
                )

        return decorator


# We have to do some monkey patching to deal with the dual nature of
# Unpack/TypeVarTuple:
# - We want Unpack to be a kind of TypeVar so it gets accepted in
#   Generic[Unpack[Ts]]
# - We want it to *not* be treated as a TypeVar for the purposes of
#   counting generic parameters, so that when we subscript a generic,
#   the runtime doesn't try to substitute the Unpack with the subscripted type.
if not hasattr(typing, "TypeVarTuple"):
    typing._collect_type_vars = _collect_type_vars
    typing._check_generic = _check_generic


# Backport typing.NamedTuple as it exists in Python 3.13.
# In 3.11, the ability to define generic `NamedTuple`s was supported.
# This was explicitly disallowed in 3.9-3.10, and only half-worked in <=3.8.
# On 3.12, we added __orig_bases__ to call-based NamedTuples
# On 3.13, we deprecated kwargs-based NamedTuples
if sys.version_info >= (3, 13):
    NamedTuple = typing.NamedTuple
else:
    def _make_nmtuple(name, types, module, defaults=()):
        fields = [n for n, t in types]
        annotations = {n: typing._type_check(t, f"field {n} annotation must be a type")
                       for n, t in types}
        nm_tpl = collections.namedtuple(name, fields,
                                        defaults=defaults, module=module)
        nm_tpl.__annotations__ = nm_tpl.__new__.__annotations__ = annotations
        # The `_field_types` attribute was removed in 3.9;
        # in earlier versions, it is the same as the `__annotations__` attribute
        if sys.version_info < (3, 9):
            nm_tpl._field_types = annotations
        return nm_tpl

    _prohibited_namedtuple_fields = typing._prohibited
    _special_namedtuple_fields = frozenset({'__module__', '__name__', '__annotations__'})

    class _NamedTupleMeta(type):
        def __new__(cls, typename, bases, ns):
            assert _NamedTuple in bases
            for base in bases:
                if base is not _NamedTuple and base is not typing.Generic:
                    raise TypeError(
                        'can only inherit from a NamedTuple type and Generic')
            bases = tuple(tuple if base is _NamedTuple else base for base in bases)
            types = ns.get('__annotations__', {})
            default_names = []
            for field_name in types:
                if field_name in ns:
                    default_names.append(field_name)
                elif default_names:
                    raise TypeError(f"Non-default namedtuple field {field_name} "
                                    f"cannot follow default field"
                                    f"{'s' if len(default_names) > 1 else ''} "
                                    f"{', '.join(default_names)}")
            nm_tpl = _make_nmtuple(
                typename, types.items(),
                defaults=[ns[n] for n in default_names],
                module=ns['__module__']
            )
            nm_tpl.__bases__ = bases
            if typing.Generic in bases:
                if hasattr(typing, '_generic_class_getitem'):  # 3.12+
                    nm_tpl.__class_getitem__ = classmethod(typing._generic_class_getitem)
                else:
                    class_getitem = typing.Generic.__class_getitem__.__func__
                    nm_tpl.__class_getitem__ = classmethod(class_getitem)
            # update from user namespace without overriding special namedtuple attributes
            for key in ns:
                if key in _prohibited_namedtuple_fields:
                    raise AttributeError("Cannot overwrite NamedTuple attribute " + key)
                elif key not in _special_namedtuple_fields and key not in nm_tpl._fields:
                    setattr(nm_tpl, key, ns[key])
            if typing.Generic in bases:
                nm_tpl.__init_subclass__()
            return nm_tpl

    _NamedTuple = type.__new__(_NamedTupleMeta, 'NamedTuple', (), {})

    def _namedtuple_mro_entries(bases):
        assert NamedTuple in bases
        return (_NamedTuple,)

    @_ensure_subclassable(_namedtuple_mro_entries)
    def NamedTuple(typename, fields=_marker, /, **kwargs):
        """Typed version of namedtuple.

        Usage::

            class Employee(NamedTuple):
                name: str
                id: int

        This is equivalent to::

            Employee = collections.namedtuple('Employee', ['name', 'id'])

        The resulting class has an extra __annotations__ attribute, giving a
        dict that maps field names to types.  (The field names are also in
        the _fields attribute, which is part of the namedtuple API.)
        An alternative equivalent functional syntax is also accepted::

            Employee = NamedTuple('Employee', [('name', str), ('id', int)])
        """
        if fields is _marker:
            if kwargs:
                deprecated_thing = "Creating NamedTuple classes using keyword arguments"
                deprecation_msg = (
                    "{name} is deprecated and will be disallowed in Python {remove}. "
                    "Use the class-based or functional syntax instead."
                )
            else:
                deprecated_thing = "Failing to pass a value for the 'fields' parameter"
                example = f"`{typename} = NamedTuple({typename!r}, [])`"
                deprecation_msg = (
                    "{name} is deprecated and will be disallowed in Python {remove}. "
                    "To create a NamedTuple class with 0 fields "
                    "using the functional syntax, "
                    "pass an empty list, e.g. "
                ) + example + "."
        elif fields is None:
            if kwargs:
                raise TypeError(
                    "Cannot pass `None` as the 'fields' parameter "
                    "and also specify fields using keyword arguments"
                )
            else:
                deprecated_thing = "Passing `None` as the 'fields' parameter"
                example = f"`{typename} = NamedTuple({typename!r}, [])`"
                deprecation_msg = (
                    "{name} is deprecated and will be disallowed in Python {remove}. "
                    "To create a NamedTuple class with 0 fields "
                    "using the functional syntax, "
                    "pass an empty list, e.g. "
                ) + example + "."
        elif kwargs:
            raise TypeError("Either list of fields or keywords"
                            " can be provided to NamedTuple, not both")
        if fields is _marker or fields is None:
            warnings.warn(
                deprecation_msg.format(name=deprecated_thing, remove="3.15"),
                DeprecationWarning,
                stacklevel=2,
            )
            fields = kwargs.items()
        nt = _make_nmtuple(typename, fields, module=_caller())
        nt.__orig_bases__ = (NamedTuple,)
        return nt


if hasattr(collections.abc, "Buffer"):
    Buffer = collections.abc.Buffer
else:
    class Buffer(abc.ABC):
        """Base class for classes that implement the buffer protocol.

        The buffer protocol allows Python objects to expose a low-level
        memory buffer interface. Before Python 3.12, it is not possible
        to implement the buffer protocol in pure Python code, or even
        to check whether a class implements the buffer protocol. In
        Python 3.12 and higher, the ``__buffer__`` method allows access
        to the buffer protocol from Python code, and the
        ``collections.abc.Buffer`` ABC allows checking whether a class
        implements the buffer protocol.

        To indicate support for the buffer protocol in earlier versions,
        inherit from this ABC, either in a stub file or at runtime,
        or use ABC registration. This ABC provides no methods, because
        there is no Python-accessible methods shared by pre-3.12 buffer
        classes. It is useful primarily for static checks.

        """

    # As a courtesy, register the most common stdlib buffer classes.
    Buffer.register(memoryview)
    Buffer.register(bytearray)
    Buffer.register(bytes)


# Backport of types.get_original_bases, available on 3.12+ in CPython
if hasattr(_types, "get_original_bases"):
    get_original_bases = _types.get_original_bases
else:
    def get_original_bases(cls, /):
        """Return the class's "original" bases prior to modification by `__mro_entries__`.

        Examples::

            from typing import TypeVar, Generic
            from typing_extensions import NamedTuple, TypedDict

            T = TypeVar("T")
            class Foo(Generic[T]): ...
            class Bar(Foo[int], float): ...
            class Baz(list[str]): ...
            Eggs = NamedTuple("Eggs", [("a", int), ("b", str)])
            Spam = TypedDict("Spam", {"a": int, "b": str})

            assert get_original_bases(Bar) == (Foo[int], float)
            assert get_original_bases(Baz) == (list[str],)
            assert get_original_bases(Eggs) == (NamedTuple,)
            assert get_original_bases(Spam) == (TypedDict,)
            assert get_original_bases(int) == (object,)
        """
        try:
            return cls.__dict__.get("__orig_bases__", cls.__bases__)
        except AttributeError:
            raise TypeError(
                f'Expected an instance of type, not {type(cls).__name__!r}'
            ) from None


# NewType is a class on Python 3.10+, making it pickleable
# The error message for subclassing instances of NewType was improved on 3.11+
if sys.version_info >= (3, 11):
    NewType = typing.NewType
else:
    class NewType:
        """NewType creates simple unique types with almost zero
        runtime overhead. NewType(name, tp) is considered a subtype of tp
        by static type checkers. At runtime, NewType(name, tp) returns
        a dummy callable that simply returns its argument. Usage::
            UserId = NewType('UserId', int)
            def name_by_id(user_id: UserId) -> str:
                ...
            UserId('user')          # Fails type check
            name_by_id(42)          # Fails type check
            name_by_id(UserId(42))  # OK
            num = UserId(5) + 1     # type: int
        """

        def __call__(self, obj):
            return obj

        def __init__(self, name, tp):
            self.__qualname__ = name
            if '.' in name:
                name = name.rpartition('.')[-1]
            self.__name__ = name
            self.__supertype__ = tp
            def_mod = _caller()
            if def_mod != 'typing_extensions':
                self.__module__ = def_mod

        def __mro_entries__(self, bases):
            # We defined __mro_entries__ to get a better error message
            # if a user attempts to subclass a NewType instance. bpo-46170
            supercls_name = self.__name__

            class Dummy:
                def __init_subclass__(cls):
                    subcls_name = cls.__name__
                    raise TypeError(
                        f"Cannot subclass an instance of NewType. "
                        f"Perhaps you were looking for: "
                        f"`{subcls_name} = NewType({subcls_name!r}, {supercls_name})`"
                    )

            return (Dummy,)

        def __repr__(self):
            return f'{self.__module__}.{self.__qualname__}'

        def __reduce__(self):
            return self.__qualname__

        if sys.version_info >= (3, 10):
            # PEP 604 methods
            # It doesn't make sense to have these methods on Python <3.10

            def __or__(self, other):
                return typing.Union[self, other]

            def __ror__(self, other):
                return typing.Union[other, self]


if hasattr(typing, "TypeAliasType"):
    TypeAliasType = typing.TypeAliasType
else:
    def _is_unionable(obj):
        """Corresponds to is_unionable() in unionobject.c in CPython."""
        return obj is None or isinstance(obj, (
            type,
            _types.GenericAlias,
            _types.UnionType,
            TypeAliasType,
        ))

    class TypeAliasType:
        """Create named, parameterized type aliases.

        This provides a backport of the new `type` statement in Python 3.12:

            type ListOrSet[T] = list[T] | set[T]

        is equivalent to:

            T = TypeVar("T")
            ListOrSet = TypeAliasType("ListOrSet", list[T] | set[T], type_params=(T,))

        The name ListOrSet can then be used as an alias for the type it refers to.

        The type_params argument should contain all the type parameters used
        in the value of the type alias. If the alias is not generic, this
        argument is omitted.

        Static type checkers should only support type aliases declared using
        TypeAliasType that follow these rules:

        - The first argument (the name) must be a string literal.
        - The TypeAliasType instance must be immediately assigned to a variable
          of the same name. (For example, 'X = TypeAliasType("Y", int)' is invalid,
          as is 'X, Y = TypeAliasType("X", int), TypeAliasType("Y", int)').

        """

        def __init__(self, name: str, value, *, type_params=()):
            if not isinstance(name, str):
                raise TypeError("TypeAliasType name must be a string")
            self.__value__ = value
            self.__type_params__ = type_params

            parameters = []
            for type_param in type_params:
                if isinstance(type_param, TypeVarTuple):
                    parameters.extend(type_param)
                else:
                    parameters.append(type_param)
            self.__parameters__ = tuple(parameters)
            def_mod = _caller()
            if def_mod != 'typing_extensions':
                self.__module__ = def_mod
            # Setting this attribute closes the TypeAliasType from further modification
            self.__name__ = name

        def __setattr__(self, name: str, value: object, /) -> None:
            if hasattr(self, "__name__"):
                self._raise_attribute_error(name)
            super().__setattr__(name, value)

        def __delattr__(self, name: str, /) -> Never:
            self._raise_attribute_error(name)

        def _raise_attribute_error(self, name: str) -> Never:
            # Match the Python 3.12 error messages exactly
            if name == "__name__":
                raise AttributeError("readonly attribute")
            elif name in {"__value__", "__type_params__", "__parameters__", "__module__"}:
                raise AttributeError(
                    f"attribute '{name}' of 'typing.TypeAliasType' objects "
                    "is not writable"
                )
            else:
                raise AttributeError(
                    f"'typing.TypeAliasType' object has no attribute '{name}'"
                )

        def __repr__(self) -> str:
            return self.__name__

        def __getitem__(self, parameters):
            if not isinstance(parameters, tuple):
                parameters = (parameters,)
            parameters = [
                typing._type_check(
                    item, f'Subscripting {self.__name__} requires a type.'
                )
                for item in parameters
            ]
            return typing._GenericAlias(self, tuple(parameters))

        def __reduce__(self):
            return self.__name__

        def __init_subclass__(cls, *args, **kwargs):
            raise TypeError(
                "type 'typing_extensions.TypeAliasType' is not an acceptable base type"
            )

        # The presence of this method convinces typing._type_check
        # that TypeAliasTypes are types.
        def __call__(self):
            raise TypeError("Type alias is not callable")

        if sys.version_info >= (3, 10):
            def __or__(self, right):
                # For forward compatibility with 3.12, reject Unions
                # that are not accepted by the built-in Union.
                if not _is_unionable(right):
                    return NotImplemented
                return typing.Union[self, right]

            def __ror__(self, left):
                if not _is_unionable(left):
                    return NotImplemented
                return typing.Union[left, self]


if hasattr(typing, "is_protocol"):
    is_protocol = typing.is_protocol
    get_protocol_members = typing.get_protocol_members
else:
    def is_protocol(tp: type, /) -> bool:
        """Return True if the given type is a Protocol.

        Example::

            >>> from typing_extensions import Protocol, is_protocol
            >>> class P(Protocol):
            ...     def a(self) -> str: ...
            ...     b: int
            >>> is_protocol(P)
            True
            >>> is_protocol(int)
            False
        """
        return (
            isinstance(tp, type)
            and getattr(tp, '_is_protocol', False)
            and tp is not Protocol
            and tp is not typing.Protocol
        )

    def get_protocol_members(tp: type, /) -> typing.FrozenSet[str]:
        """Return the set of members defined in a Protocol.

        Example::

            >>> from typing_extensions import Protocol, get_protocol_members
            >>> class P(Protocol):
            ...     def a(self) -> str: ...
            ...     b: int
            >>> get_protocol_members(P)
            frozenset({'a', 'b'})

        Raise a TypeError for arguments that are not Protocols.
        """
        if not is_protocol(tp):
            raise TypeError(f'{tp!r} is not a Protocol')
        if hasattr(tp, '__protocol_attrs__'):
            return frozenset(tp.__protocol_attrs__)
        return frozenset(_get_protocol_attrs(tp))


if hasattr(typing, "Doc"):
    Doc = typing.Doc
else:
    class Doc:
        """Define the documentation of a type annotation using ``Annotated``, to be
         used in class attributes, function and method parameters, return values,
         and variables.

        The value should be a positional-only string literal to allow static tools
        like editors and documentation generators to use it.

        This complements docstrings.

        The string value passed is available in the attribute ``documentation``.

        Example::

            >>> from typing_extensions import Annotated, Doc
            >>> def hi(to: Annotated[str, Doc("Who to say hi to")]) -> None: ...
        """
        def __init__(self, documentation: str, /) -> None:
            self.documentation = documentation

        def __repr__(self) -> str:
            return f"Doc({self.documentation!r})"

        def __hash__(self) -> int:
            return hash(self.documentation)

        def __eq__(self, other: object) -> bool:
            if not isinstance(other, Doc):
                return NotImplemented
            return self.documentation == other.documentation


# Aliases for items that have always been in typing.
# Explicitly assign these (rather than using `from typing import *` at the top),
# so that we get a CI error if one of these is deleted from typing.py
# in a future version of Python
AbstractSet = typing.AbstractSet
AnyStr = typing.AnyStr
BinaryIO = typing.BinaryIO
Callable = typing.Callable
Collection = typing.Collection
Container = typing.Container
Dict = typing.Dict
ForwardRef = typing.ForwardRef
FrozenSet = typing.FrozenSet
Generator = typing.Generator
Generic = typing.Generic
Hashable = typing.Hashable
IO = typing.IO
ItemsView = typing.ItemsView
Iterable = typing.Iterable
Iterator = typing.Iterator
KeysView = typing.KeysView
List = typing.List
Mapping = typing.Mapping
MappingView = typing.MappingView
Match = typing.Match
MutableMapping = typing.MutableMapping
MutableSequence = typing.MutableSequence
MutableSet = typing.MutableSet
Optional = typing.Optional
Pattern = typing.Pattern
Reversible = typing.Reversible
Sequence = typing.Sequence
Set = typing.Set
Sized = typing.Sized
TextIO = typing.TextIO
Tuple = typing.Tuple
Union = typing.Union
ValuesView = typing.ValuesView
cast = typing.cast
no_type_check = typing.no_type_check
no_type_check_decorator = typing.no_type_check_decorator

virt/lib/python3.9/site-packages/typing_extensions.py
PrinceDisant/LostInNL