<commit_before># -*- coding: utf-8 -*-
"""
    pyrseas.dbobject
    ~~~~~~~~~~~~~~~~

    This defines two low level classes and an intermediate class.
    Most Pyrseas classes are derived from either DbObject or
    DbObjectDict.
"""
import os
import re
import string
from functools import wraps

from pyrseas.lib.pycompat import PY2, strtypes
from pyrseas.yamlutil import MultiLineStr, yamldump
from pyrseas.dbobject.privileges import privileges_to_map
from pyrseas.dbobject.privileges import add_grant, diff_privs


VALID_FIRST_CHARS = string.ascii_lowercase + '_'
VALID_CHARS = string.ascii_lowercase + string.digits + '_$'
RESERVED_WORDS = []
NON_FILENAME_CHARS = re.compile(r'\W', re.U)
MAX_IDENT_LEN = int(os.environ.get("PYRSEAS_MAX_IDENT_LEN", 16))


def fetch_reserved_words(db):
    """Fetch PostgreSQL reserved words

    :param db: DbConnection object
    """
    global RESERVED_WORDS

    if len(RESERVED_WORDS) == 0:
        RESERVED_WORDS = [word[0] for word in
                          db.fetchall("""SELECT word FROM pg_get_keywords()
                                         WHERE catcode = 'R'""")]


def quote_id(name):
    """Quotes an identifier if necessary.

    :param name: string to be quoted

    :return: possibly quoted string
    """
    regular_id = True
    if not name[0] in VALID_FIRST_CHARS or name in RESERVED_WORDS:
        regular_id = False
    else:
        for ltr in name[1:]:
            if ltr not in VALID_CHARS:
                regular_id = False
                break

    return regular_id and name or '"%s"' % name


def split_schema_obj(obj, sch=None):
    """Return a (schema, object) tuple given a possibly schema-qualified name

    :param obj: object name or schema.object
    :param sch: schema name (defaults to 'public')
    :return: tuple
    """
    qualsch = sch
    if sch is None:
        qualsch = 'public'
    if '.' in obj:
        (qualsch, obj) = obj.split('.')
    if obj[:1] == '"' and obj[-1:] == '"':
        obj = obj[1:-1]
    if sch != qualsch:
        sch = qualsch
    return (sch, obj)


def commentable(func):
    """Decorator to add comments to various objects"""
    @wraps(func)
    def add_comment(obj, *args, **kwargs):
        stmts = func(obj, *args, **kwargs)
        if hasattr(obj, 'description'):
            stmts.append(obj.comment())
        return stmts
    return add_comment


def grantable(func):
    """Decorator to add GRANT to various objects"""
    @wraps(func)
    def grant(obj, *args, **kwargs):
        stmts = func(obj, *args, **kwargs)
        if hasattr(obj, 'privileges'):
            for priv in obj.privileges:
                stmts.append(add_grant(obj, priv))
        return stmts
    return grant


def ownable(func):
    """Decorator to add ALTER OWNER to various objects"""
    @wraps(func)
    def add_alter(obj, *args, **kwargs):
        stmts = func(obj, *args, **kwargs)
        if hasattr(obj, 'owner'):
            stmts.append(obj.alter_owner())
        return stmts
    return add_alter


class DbObject(object):
    "A single object in a database catalog, e.g., a schema, a table, a column"

    keylist = ['name']
    """List of attributes that uniquely identify the object in the catalogs

    See description of :meth:`key` for further details.
    """

    objtype = ''
    """Type of object as an uppercase string, for SQL syntax generation

    This is used in most CREATE, ALTER and DROP statements.  It is
    also used by :meth:`extern_key` in lowercase form.
    """

    allprivs = ''

    def __init__(self, **attrs):
        """Initialize the catalog object from a dictionary of attributes

        :param attrs: the dictionary of attributes

        Non-key attributes without a value are discarded. Values that
        are multi-line strings are treated specially so that YAML will
        output them in block style.
        """
        for key, val in list(attrs.items()):
            if val or key in self.keylist:
                if key in ['definition', 'description', 'source'] and \
                        isinstance(val, strtypes) and '\n' in val:
                    newval = []
                    for line in val.split('\n'):
                        if line and line[-1] in (' ', '\t'):
                            line = line.rstrip()
                        newval.append(line)
                    strval = '\n'.join(newval)
                    if PY2:
                        strval = strval.decode('utf_8')
                    val = MultiLineStr(strval)
                setattr(self, key, val)

    def extern_key(self):
        """Return the key to be used in external maps for this object

        :return: string

        This is used for the first two levels of external maps.  The
        first level is the one that includes schemas, as well as
        extensions, languages, casts and FDWs.  The second level
        includes all schema-owned objects, i.e., tables, functions,
        operators, etc.  All subsequent levels, e.g., primary keys,
        indexes, etc., currently use the object name as the external
        identifier, appearing in the map after an object grouping
        header, such as ``primary_key``.

        The common format for an external key is `object-type
        non-schema-qualified-name`, where `object-type` is the
        lowercase version of :attr:`objtype`, e.g., ``table
        tablename``.  Some object types require more, e.g., functions<commit_msg>On Python 2.x, encode/decode strings properly but don't use MultiLineStr.<commit_after># -*- coding: utf-8 -*-
"""
    pyrseas.dbobject
    ~~~~~~~~~~~~~~~~

    This defines two low level classes and an intermediate class.
    Most Pyrseas classes are derived from either DbObject or
    DbObjectDict.
"""
import os
import re
import string
from functools import wraps

from pyrseas.lib.pycompat import PY2, strtypes
from pyrseas.yamlutil import MultiLineStr, yamldump
from pyrseas.dbobject.privileges import privileges_to_map
from pyrseas.dbobject.privileges import add_grant, diff_privs


VALID_FIRST_CHARS = string.ascii_lowercase + '_'
VALID_CHARS = string.ascii_lowercase + string.digits + '_$'
RESERVED_WORDS = []
NON_FILENAME_CHARS = re.compile(r'\W', re.U)
MAX_IDENT_LEN = int(os.environ.get("PYRSEAS_MAX_IDENT_LEN", 16))


def fetch_reserved_words(db):
    """Fetch PostgreSQL reserved words

    :param db: DbConnection object
    """
    global RESERVED_WORDS

    if len(RESERVED_WORDS) == 0:
        RESERVED_WORDS = [word[0] for word in
                          db.fetchall("""SELECT word FROM pg_get_keywords()
                                         WHERE catcode = 'R'""")]


def quote_id(name):
    """Quotes an identifier if necessary.

    :param name: string to be quoted

    :return: possibly quoted string
    """
    regular_id = True
    if not name[0] in VALID_FIRST_CHARS or name in RESERVED_WORDS:
        regular_id = False
    else:
        for ltr in name[1:]:
            if ltr not in VALID_CHARS:
                regular_id = False
                break

    return regular_id and name or '"%s"' % name


def split_schema_obj(obj, sch=None):
    """Return a (schema, object) tuple given a possibly schema-qualified name

    :param obj: object name or schema.object
    :param sch: schema name (defaults to 'public')
    :return: tuple
    """
    qualsch = sch
    if sch is None:
        qualsch = 'public'
    if '.' in obj:
        (qualsch, obj) = obj.split('.')
    if obj[:1] == '"' and obj[-1:] == '"':
        obj = obj[1:-1]
    if sch != qualsch:
        sch = qualsch
    return (sch, obj)


def commentable(func):
    """Decorator to add comments to various objects"""
    @wraps(func)
    def add_comment(obj, *args, **kwargs):
        stmts = func(obj, *args, **kwargs)
        if hasattr(obj, 'description'):
            stmts.append(obj.comment())
        return stmts
    return add_comment


def grantable(func):
    """Decorator to add GRANT to various objects"""
    @wraps(func)
    def grant(obj, *args, **kwargs):
        stmts = func(obj, *args, **kwargs)
        if hasattr(obj, 'privileges'):
            for priv in obj.privileges:
                stmts.append(add_grant(obj, priv))
        return stmts
    return grant


def ownable(func):
    """Decorator to add ALTER OWNER to various objects"""
    @wraps(func)
    def add_alter(obj, *args, **kwargs):
        stmts = func(obj, *args, **kwargs)
        if hasattr(obj, 'owner'):
            stmts.append(obj.alter_owner())
        return stmts
    return add_alter


class DbObject(object):
    "A single object in a database catalog, e.g., a schema, a table, a column"

    keylist = ['name']
    """List of attributes that uniquely identify the object in the catalogs

    See description of :meth:`key` for further details.
    """

    objtype = ''
    """Type of object as an uppercase string, for SQL syntax generation

    This is used in most CREATE, ALTER and DROP statements.  It is
    also used by :meth:`extern_key` in lowercase form.
    """

    allprivs = ''

    def __init__(self, **attrs):
        """Initialize the catalog object from a dictionary of attributes

        :param attrs: the dictionary of attributes

        Non-key attributes without a value are discarded. Values that
        are multi-line strings are treated specially so that YAML will
        output them in block style.
        """
        for key, val in list(attrs.items()):
            if val or key in self.keylist:
                if key in ['definition', 'description', 'source'] and \
                        isinstance(val, strtypes) and '\n' in val:
                    newval = []
                    for line in val.split('\n'):
                        if line and line[-1] in (' ', '\t'):
                            line = line.rstrip()
                        newval.append(line)
                    strval = '\n'.join(newval)
                    if PY2:
                        val = strval.encode('utf_8').decode('utf_8')
                    else:
                        val = MultiLineStr(strval)
                setattr(self, key, val)

    def extern_key(self):
        """Return the key to be used in external maps for this object

        :return: string

        This is used for the first two levels of external maps.  The
        first level is the one that includes schemas, as well as
        extensions, languages, casts and FDWs.  The second level
        includes all schema-owned objects, i.e., tables, functions,
        operators, etc.  All subsequent levels, e.g., primary keys,
        indexes, etc., currently use the object name as the external
        identifier, appearing in the map after an object grouping
        header, such as ``primary_key``.

        The common format for an external key is `object-type
        non-schema-qualified-name`, where `object-type` is the
        lowercase version of :attr:`objtype`, e.g., ``table
        tablename``.  Some object types require more, e.g., functions
{'fraction_non_alphanumeric': 0.10126237398964671, 'fraction_numerical': 0.0029061847243665425, 'mean_word_length': 2.2512547977561264, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1436246', 'n_tokens_mistral': 3306, 'n_tokens_neox': 3160, 'n_words': 1117}