o
    ia                     @   s  d Z ddlZddlZddlZddlZddlZddlZddlZddlZddl	Z	ddl
Z
ddlmZ ddlmZ dd Zdeefdeefdd	d
 dd
 fddd
 dd
 fdeefdZG dd dejZG dd dejZdd Zdddejdededeje dedeje fdd Zdejded!ejeje ejf fd"d#Ze	jG d$d% d%Z e	jG d&d' d'e!Z"	(d/d)e
jd*eje d!eje" fd+d,Z#G d-d. d.e!Z$dS )0a(  
SQLite as alternative storage backend for a TableGroup's data.

For the most part, translation of a TableGroup's tableSchema to SQL works as expected:

- each table is converted to a `CREATE TABLE` statement
- each column specifies a column in the corresponding `CREATE TABLE` statement
- `foreignKey` constraints are added according to the corresponding `tableSchema` property.

List-valued foreignKeys are supported as follows: For each pair of tables related through a
list-valued foreign key, an association table is created. To make it possible to distinguish
multiple list-valued foreign keys between the same two tables, the association table has
a column `context`, which stores the name of the foreign key column from which a row in the
assocation table was created.

Other list-valued columns work in two different ways: If the atomic datatype is `string`, the
specified separator is used to create a concatenated string representation in the database field.
Otherwise, the list of values is serialized as JSON.

SQL table and column names can be customized by passing a translator callable when instantiating
a :class:`Database`.

SQLite support has the following limitations:

- regex constraints on strings (as specified via a :class:`csvw.Datatype`'s format attribute) are
  not enforced by the database.
    N)	DATATYPES)
TableGroupc                 C   s   | S N sr   r   ;/home/ubuntu/.local/lib/python3.10/site-packages/csvw/db.pyidentity,      r	   TEXTINTEGERc                 C      | d u r| S t | S r   )intr   r   r   r   <lambda>;       r   c                 C   r   r   )boolr   r   r   r   r   <   r   REALc                 C   r   r   )floatr   r   r   r   r   ?   r   c                 C   s   | d u r| S t | S r   )decimalDecimalr   r   r   r   r   @       BLOB)stringintegerbooleanr   	hexBinaryc                   @   s*   e Zd Zddedeje defddZdS )SchemaTranslatorNtablecolumnreturnc                 C      d S r   r   )selfr   r   r   r   r   __call__I   r
   zSchemaTranslator.__call__r   )__name__
__module____qualname__strtypingOptionalr"   r   r   r   r   r   H   s    "r   c                   @   s   e Zd ZdedefddZdS )ColumnTranslatorr   r   c                 C   r    r   r   )r!   r   r   r   r   r"   N   r
   zColumnTranslator.__call__N)r#   r$   r%   r&   r"   r   r   r   r   r)   M   s    r)   c                  G   s   d dd | D S )N,c                 s   s    | ]}d  |V  qdS )z`{0}`N)format).0namer   r   r   	<genexpr>S   s    zquoted.<locals>.<genexpr>)join)namesr   r   r   quotedR   s   r1   Fsingledb	translater   keysrowsr3   c                   s   |rId t t fdd|D  ddd |D }z	| || W dS    |s@|D ]}t|  ||dd q0Y dS t| t|  dS )	a  
    Insert a sequence of rows into a table.

    :param db: Database connection.
    :param translate: Callable translating table and column names to proper schema object names.
    :param table: Untranslated table name.
    :param keys: Untranslated column names.
    :param rows: Sequence of rows to insert.
    :param single: Flag signaling whether to insert all rows at once using `executemany` or one at     a time, allowing for more focused debugging output in case of errors.
    z"INSERT INTO {0} ({1}) VALUES ({2})c                       g | ]} |qS r   r   r,   kr   r5   r   r   
<listcomp>j   r   zinsert.<locals>.<listcomp>r*   c                 S   s   g | ]}d qS )?r   )r,   _r   r   r   r<   k   s    Tr2   N)r+   r1   r/   executemanyinsertprint)r4   r5   r   r6   r3   r7   sqlrowr   r;   r   r@   V   s"   
r@   r   c                 C   s4   |  dt|}dd |jD }|t| fS )NzSELECT * FROM {0}c                 S   s   g | ]}|d  qS )r   r   )r,   dr   r   r   r<   z   r   zselect.<locals>.<listcomp>)executer+   r1   descriptionlistfetchall)r4   r   cucolsr   r   r   selectx   s   rK   c                   @   s   e Zd ZdZe Zejddd dZejddZejddZ	ejddZ
ejddZejddZejddZd	d
 Zdedeje fddZdedefddZdS )ColSpecze
    A `ColSpec` captures sufficient information about a :class:`csvw.Column` for the DB schema.
    r   c                 C   s   | r| S dS )Nr   r   r   r   r   r   r      s    zColSpec.<lambda>)default	converterNrM   Fc                 C   sf   | j tv rt| j  \| _| _| _nd| _t| j  j| _t| j  j| _| jr/| jdkr1d| _d S d S d S )Nr   )		csvw_typeTYPE_MAPdb_typeconvertreadr   	to_string	to_python	separatorr!   r   r   r   __attrs_post_init__   s   

zColSpec.__attrs_post_init__r5   r   c                 C   s2  | j sdS | j || j}}g }|jdus|jdur^ddd| j}|jdur@|r6|d||j| n
|d||j |jdur]|rS|d||j| nA|d||j n6td	d
 |j	|j
|jfD r|j	rz|d||j	 |j
r|d||j
 |jr|d||j d|S )a  
        We try to convert as many data constraints as possible into SQLite CHECK constraints.

        :param translate: Callable to translate column names between CSVW metadata and DB schema.
        :return: A string suitable as argument of an SQL CHECK constraint.
        Ndatedatetime)rZ   r[   z{2}(`{0}`) >= {2}('{1}')z`{0}` >= {1}z{2}(`{0}`) <= {2}('{1}')z`{0}` <= {1}c                 s   s    | ]}|d uV  qd S r   r   )r,   ccr   r   r   r.      s    z ColSpec.check.<locals>.<genexpr>zlength(`{0}`) = {1}zlength(`{0}`) >= {1}zlength(`{0}`) <= {1}z AND )csvwr-   minimummaximumgetrP   appendr+   anylength	minLength	maxLengthr/   )r!   r5   ccnameconstraintsfuncr   r   r   check   s6   


zColSpec.checkc                 C   s<   |  |}d|| j| j| jrdnd|rd|S dS )Nz`{0}` {1}{2}{3}z	 NOT NULL z CHECK ({0}))rj   r+   r-   rR   required)r!   r5   _checkr   r   r   rB      s   
zColSpec.sql)r#   r$   r%   __doc__attribr-   rP   rW   rR   rS   rT   rl   r]   rY   r)   r'   r(   r&   rj   rB   r   r   r   r   rL   ~   s    
#rL   c                   @   s   e Zd ZdZe ZejeedZ	ejeedZ
ejeejdZejddZe	ddejdeje dd fdd	Zedd
dZdedefddZdS )	TableSpeca7  
    A `TableSpec` captures sufficient information about a :class:`csvw.Table` for the DB schema.

    .. note::

        We support "light-weight" many-to-many relationships by allowing list-valued foreign key
        columns in CSVW. In the database these columns are turned into an associative table, adding
        the name of the column as value a `context` column. Thus, multiple columns in a table my be
        specified as targets of many-to-many relations with the same table.

        .. seealso:: `<https://en.wikipedia.org/wiki/Associative_entity>`_
    rO   NTr   drop_self_referential_fksr   c                 C   sp  | |j |jjd}dd |jjD }|jjD ]q}|jjst|jdkrj|jd |v rjt|jjdks>J d	|jj|jj
|jrHt|jdksPJ d	|jt|j|jd |jj
j|jjd |j|jd < q|rt|jj
j|jks|jt|j|jj
jt|jjf q|jjD ])}|j|jvr|d}|jt|j|r|jn||d	|d
|dd q|S )a  
        Create a `TableSpec` from the schema description of a `csvw.metadata.Table`.

        :param table: `csvw.metadata.Table` instance.
        :param drop_self_referential_fks: Flag signaling whether to drop self-referential foreign         keys. This may be necessary, if the order of rows in a CSVW table does not guarantee         referential integrity when inserted in order (e.g. an eralier row refering to a later one).
        :return: `TableSpec` instance.
        )r-   primary_keyc                 S   s   h | ]}|j r|jqS r   )rW   headerr,   rf   r   r   r   	<setcomp>   s    z0TableSpec.from_table_metadata.<locals>.<setcomp>   r   z)Composite key {0} in table {1} referencedzSTable {0} referenced by list-valued foreign key must have non-composite primary keydatatyperW   rl   )r-   rP   rW   rl   r]   )
local_nametableSchema
primaryKeycolumnsforeignKeys	referenceschemaReferencelencolumnReferencer+   resourcers   r-   rq   association_tabler   many_to_manyforeign_keysra   sortedrt   inheritrL   base)clsr   rr   speclist_valuedfkrf   rx   r   r   r   from_table_metadata   sP   


zTableSpec.from_table_metadatac                 C   s   t d||}t d||}|j|jkr$| jd7  _| jd7  _| d||||t dg|jg||gf|jg||gfgdS )a  
        List-valued foreignKeys are supported as follows: For each pair of tables related through a
        list-valued foreign key, an association table is created. To make it possible to distinguish
        multiple list-valued foreign keys between the same two tables, the association table has
        a column `context`, which stores the name of the foreign key column from which a row in the
        assocation table was created.
        z{0}_{1}_1_2context)r-   r|   r   )rL   r+   r-   )r   atableapkbtablebpkafkbfkr   r   r   r   	  s   	
zTableSpec.association_tabler5   c                    s   t | j  fdd| jD }| jr&|dt fdd| jD   | jD ]&\}}|dt fdd|D  ttfdd|D   q)d| jd		|S )
z[
        :param translate:
        :return: The SQL statement to create the table.
        c                    s   g | ]}|  qS r   )rB   )r,   colcol_translater   r   r<   &  r   z!TableSpec.sql.<locals>.<listcomp>zPRIMARY KEY({0})c                       g | ]} |qS r   r   ru   r   r   r   r<   )  r   z6FOREIGN KEY({0}) REFERENCES {1}({2}) ON DELETE CASCADEc                    r   r   r   ru   r   r   r   r<   ,  r   c                    r8   r   r   ru   )refr5   r   r   r<   .  r   z,CREATE TABLE IF NOT EXISTS `{0}` (
    {1}
)z,
    )
	functoolspartialr-   r|   rs   ra   r+   r1   r   r/   )r!   r5   clausesr   refcolsr   )r   r   r5   r   rB      s   

zTableSpec.sqlT)r   rq   )r#   r$   r%   rn   ro   rp   r-   FactoryrG   r|   r   collectionsOrderedDictr   rs   classmethodr]   Tabler'   r(   r   r   r   r   r&   rB   r   r   r   r   rq      s&    2rq   Ttgrr   c                    s   i }| j  D ]\}tj|d}|||j< |j D ]}|||j< qqt  d}|r\|dk r\|d7 }t	|
 D ]t fdd| jD rU| <  nq;|r\|dk s1|rbtdt	  S )a  
    Convert the table and column descriptions of a `TableGroup` into specifications for the
    DB schema.

    :param tg: CSVW TableGroup.
    :param drop_self_referential_fks: Flag signaling whether to drop self-referential foreign     keys. This may be necessary, if the order of rows in a CSVW table does not guarantee     referential integrity when inserted in order (e.g. an eralier row refering to a later one).
    :return: A pair (tables, reference_tables).
    rr   r   d   rw   c                 3   s(    | ]}|d   v p|d  kV  qdS )rw   Nr   )r,   r   orderedr   r   r   r.   P  s   & zschema.<locals>.<genexpr>z7there seem to be cyclic dependencies between the tables)	tabledictitemsrq   r   r-   r   valuesr   r   rG   r6   allr   pop
ValueError)r   rr   tablestnametatir   r   r   schema3  s,   
r   c                
   @   sB  e Zd ZdZ			d'dedejejej	e
f  deje deje fdd	Zd(d
dZedeje
ef fddZed)de
deje
 de
fddZdejejejf fddZdefddZde
de
deje
 fddZdejeje
 e
df fddZdeje
ejej f fddZ dd  Z!d*d"d#Z"d!d!d!d$d%d&Z#dS )+Databasea  
    Represents a SQLite database associated with a :class:`csvw.TableGroup` instance.

    :param tg: `TableGroup` instance defining the schema of the database.
    :param fname: Path to which to write the database file.
    :param translate: Schema object name translator.
    :param drop_self_referential_fks: Flag signaling whether to drop or enforce self-referential     foreign-key constraints.

    .. warning::

        We write rows of a table to the database sequentially. Since CSVW does not require ordering
        rows in tables such that self-referential foreign-key constraints are satisfied at each row,
        we don't enforce self-referential foreign-keys by default in order to not trigger "false"
        integrity errors. If data in a CSVW Table is known to be ordered appropriately, `False`
        should be passed as `drop_self_referential_fks` keyword parameter to enforce
        self-referential foreign-keys.
    NTr   fnamer5   rr   c                 C   s8   |pt j| _|rt|nd | _| j||d d | _d S Nr   )r   name_translatorr5   pathlibPathr   init_schema_connection)r!   r   r   r5   rr   r   r   r   __init__m  s   
zDatabase.__init__c                 C   s*   || _ | j rt| j |d| _d S g | _d S r   )r   r   r   )r!   r   rr   r   r   r   r   y  s   zDatabase.init_schemar   c                 C   s   dd | j D S )Nc                 S      i | ]}|j |qS r   r-   )r,   r   r   r   r   
<dictcomp>  r   z"Database.tdict.<locals>.<dictcomp>)r   rX   r   r   r   tdict~  s   zDatabase.tdictr   r   c                 C   s   |p| S )af  
        A callable with this signature can be passed into DB creation to control the names
        of the schema objects.

        :param table: CSVW name of the table before translation
        :param column: CSVW name of a column of `table` before translation
        :return: Translated table name if `column is None` else translated column name
        r   )r   r   r   r   r   r     s   zDatabase.name_translatorc                 C   s4   | j rttt| j S | jstd| _| jS )Nz:memory:)r   
contextlibclosingsqlite3connectr&   r   rX   r   r   r   
connection  s
   zDatabase.connectionc              	      s    d ur
d  }nd}d t| |j|jd jt| |j|jd jt| |j|}||} fdd| D S )NzWHERE context = '{0}'rk   zgSELECT {0}, group_concat({1}, ' '), group_concat(COALESCE(context, ''), '||')
FROM {2} {3} GROUP BY {0}r   rw   c              	      s<   i | ]}|d   fddt |d  |d dD qS )r   c                    s$   g | ]\}} d u r||fn|qS r   r   )r,   r:   vr   r   r   r<     s    z;Database.select_many_to_many.<locals>.<dictcomp>.<listcomp>rw      z||)zipsplit)r,   rr   r   r   r     s    z0Database.select_many_to_many.<locals>.<dictcomp>)r+   r1   r5   r-   r|   rE   rH   )r!   r4   r   r   context_sqlrB   rI   r   r   r   select_many_to_many  s   

zDatabase.select_many_to_manyr   rg   c                 C   sP   | j D ]"}| ||kr%| j | jD ]}| ||j|kr$|j    S qqdS )ze
        :return: separator for the column specified by db schema names `tname` and `cname`.
        N)r   r5   r|   r-   rW   )r!   r   rg   r-   r   r   r   r   rW     s   
zDatabase.separatorc                 C   s"   |  ||}|r|pd|S |S )Nrk   )rW   r   )r!   r   rg   valuesepr   r   r   split_value  s   zDatabase.split_valuec              
      s  t t}|  0}| jjD ]!}i i t t }}| j| }|jD ]O}|j	t
g | ||j	< |jtv rIt|j d  | ||j	 d< nt|j j | ||j	 d< |jrt|jdkrk|j|| ||j	< q%d|| ||j	< q%|j D ]\}}| ||| D ]\}	}
|
||	 | ||< qqzt|| |\}}|D ]}t  }t||D ]J\}
|v r|
du rd|< q|
sg |< q| dkrt|
|< q fdd|
pd| D |< q|
dur  d |
nd|< q|jrt|jdkr|| ||jd	  nd}	|d
d |jD  |||	i  || | | qqW d   |S 1 s>w   Y  |S )z
        :return: A `dict` where keys are SQL table names corresponding to CSVW tables and values         are lists of rows, represented as dicts where keys are the SQL column names.
        r   rw   r   jsonNc                    s   g | ]
}  d  |qS )rw   r   )r,   v_rS   r:   r   r   r<     s    z!Database.read.<locals>.<listcomp>rk   r   c                 S   s   i | ]}|g qS r   r   r9   r   r   r   r         z!Database.read.<locals>.<dictcomp>)r   defaultdictrG   r   r   r   dictr   r|   r-   r	   r5   rP   rQ   r   rV   rW   r   r   r   rK   r   r   r   loadsr   rs   r   updater`   ra   )r!   resconnr   sepsrefsr   r   r   pkr   rJ   r7   rC   rD   r   r   r   rT     s`   



"



("
00zDatabase.readc                 C   s   ||fS )a  
        Context for association tables is created calling this method.

        Note: If a custom value for the `context` column is created by overwriting this method,
        `select_many_to_many` must be adapted accordingly, to make sure the custom
        context is retrieved when reading the data from the db.

        :param table:
        :param column:
        :param fkey:
        :return: a pair (foreign key, context)
        r   )r!   r   r   fkeyr   r   r   association_table_context  s   z"Database.association_table_contextFc                 C   s   | j d|||d| j S )Nforce
_exists_ok_skip_extrar   )writer   rT   )r!   _forcer   r   r   r   r   write_from_tg  s   zDatabase.write_from_tgr   c             
      s  | j r| j  r|std| j   |  !}| jD ]}||j| jd q|d |	  t
t}| jD ]}|j|vrBq:g g }	}
dd |jD }t||j D ]\}}|jrkt|jdkrk||jd  nd}g }| D ]\}}||jv r|sJ |j| }t|jgd	d
 |jD  }|pg D ]}| |||\}}|| |||f qqs||vr|rqstd|||  t|tr jdkrڈ jpd fdd|D }nt|}n|dur |nd}|dkr|
 j || qs|	t| qVt|| j|j|
g|	R   q:| D ]\}}	t|| j|d |dd g|	R   q|	  W d   dS 1 s=w   Y  dS )z
        Creates a db file with the core schema.

        :param force: If `True` an existing db file will be overwritten.
        z3db file already exists, use force=True to overwrite)r5   zPRAGMA foreign_keys = ON;c                 S   r   r   r   ru   r   r   r   r     r   z"Database.write.<locals>.<dictcomp>rw   r   Nc                 S   s   g | ]}|j qS r   r   ru   r   r   r   r<   %  r   z"Database.write.<locals>.<listcomp>z$unspecified column {0} found in datar   ;c                 3   s    | ]
}  |p
d V  qdS )rk   N)rS   )r,   vvr   r   r   r.   6  s    
z!Database.write.<locals>.<genexpr>) r   existsr   unlinkr   r   rE   rB   r5   commitr   r   rG   r-   r|   	enumeraters   r   r   r   tupler   ra   r+   
isinstancerP   rW   r/   r   dumpsrS   r@   )r!   r   r   r   r   r4   r   r   r   r7   r6   rJ   r   rC   r   r   r:   r   r   atkeyr   r   r   r   r   r   r     sn   











(
$zDatabase.write)NNTr   r   )FFF)$r#   r$   r%   rn   r   r'   r(   Unionr   r   r&   r   r   r   r   propertyDictrq   r   staticmethodr   r   
Connectionr   r   r   r   r   rW   Listr   r   rT   r   r   r   r   r   r   r   r   Z  s6    

  
 8
r   r   )%rn   r   r'   r   r   r   r   r   r   ro   r]   csvw.datatypesr   csvw.metadatar   r	   rQ   Protocolr   r)   r1   r   r&   SequencerG   r(   r   r@   Tupler   rK   r   rL   objectrq   r   r   r   r   r   r   <module>   s|    	
*"Cq

'