o
    mia                     @   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   A/home/kim/smarthome/.venv/lib/python3.10/site-packages/csvw/db.pyidentity,      r	   TEXTZ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   Z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   @       ZBLOB)stringintegerbooleanr   Z	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   r-   Fsingledb	translater   keysrowsr/   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   r1   r   r   
<listcomp>j   r   zinsert.<locals>.<listcomp>r&   c                 S   s   g | ]}d qS )?r   )r(   _r   r   r   r8   k   s    Tr.   N)r'   r-   r+   Zexecutemanyinsertprint)r0   r1   r   r2   r/   r3   sqlrowr   r7   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   r8   z   r   zselect.<locals>.<listcomp>)executer'   r-   descriptionlistfetchall)r0   r   cucolsr   r   r   selectx   s   rF   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rH   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   Z	to_string	to_python	separatorr   r   r   r   __attrs_post_init__   s   

zColSpec.__attrs_post_init__r1   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)rT   rU   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getrK   appendr'   anylengthZ	minLengthZ	maxLengthr+   )r   r1   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}))rb   r'   r)   rM   required)r   r1   _checkr   r   r   r=      s   
zColSpec.sql)r   r    r!   __doc__attribr)   rK   rQ   rM   rN   rO   rd   rW   rS   r%   r#   r$   r"   rb   r=   r   r   r   r   rG   ~   s    
#rG   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>`_
    rJ   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   )rQ   headerr(   r^   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datatyperQ   rd   )r)   rK   rQ   rd   rW   )Z
local_nameZtableSchemaZ
primaryKeycolumnsZforeignKeys	referenceZschemaReferencelenZcolumnReferencer'   resourcerk   r)   ri   association_tabler   many_to_manyforeign_keysr[   sortedrl   inheritrG   base)clsr   rj   specZlist_valuedfkr^   rp   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}Z_1Z_2context)r)   rq   rw   )rG   r'   r)   )r{   ZatableZapkZbtableZbpkZafkZbfkr   r   r   ru   	  s   	
zTableSpec.association_tabler1   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   )r=   )r(   colcol_translater   r   r8   &  r   z!TableSpec.sql.<locals>.<listcomp>zPRIMARY KEY({0})c                       g | ]} |qS r   r   rm   r   r   r   r8   )  r   z6FOREIGN KEY({0}) REFERENCES {1}({2}) ON DELETE CASCADEc                    r   r   r   rm   r   r   r   r8   ,  r   c                    r4   r   r   rm   )refr1   r   r   r8   .  r   z,CREATE TABLE IF NOT EXISTS `{0}` (
    {1}
)z,
    )
	functoolspartialr)   rq   rk   r[   r'   r-   rw   r+   )r   r1   Zclausesr}   Zrefcolsr   )r   r   r1   r   r=      s   

zTableSpec.sqlT)r   ri   )r   r    r!   rf   rg   rh   r)   FactoryrB   rq   rw   collectionsOrderedDictrv   rk   classmethodrW   Tabler#   r$   r   r~   ru   r   r"   r=   r   r   r   r   ri      s&    2ri   Ttgrj   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).
    rj   r   d   ro   c                 3   s(    | ]}|d   v p|d  kV  qdS )ro   Nr   )r(   r   orderedr   r   r   r*   P  s   & zschema.<locals>.<genexpr>z7there seem to be cyclic dependencies between the tables)	tabledictitemsri   r~   r)   rv   valuesr   r   rB   r2   allrw   pop
ValueError)r   rj   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   fnamer1   rj   c                 C   s8   |pt j| _|rt|nd | _| j||d d | _d S Nr   )r   name_translatorr1   pathlibPathr   init_schema_connection)r   r   r   r1   rj   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   rj   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   rR   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   rR   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}'rc   zgSELECT {0}, group_concat({1}, ' '), group_concat(COALESCE(context, ''), '||')
FROM {2} {3} GROUP BY {0}r   ro   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(   r6   vr   r   r   r8     s    z;Database.select_many_to_many.<locals>.<dictcomp>.<listcomp>ro      z||)zipsplit)r(   rr   r   r   r     s    z0Database.select_many_to_many.<locals>.<dictcomp>)r'   r-   r1   r)   rq   r@   rC   )r   r0   r   r   Zcontext_sqlr=   rD   r   r   r   select_many_to_many  s   

zDatabase.select_many_to_manyr   r_   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   r1   rq   r)   rQ   )r   r   r_   r)   r   r   r   r   rQ     s   
zDatabase.separatorc                 C   s"   |  ||}|r|pd|S |S )Nrc   )rQ   r   )r   r   r_   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   ro   r   jsonNc                    s   g | ]
}  d  |qS )ro   r   )r(   Zv_rN   r6   r   r   r8     s    z!Database.read.<locals>.<listcomp>rc   r   c                 S   s   i | ]}|g qS r   r   r5   r   r   r   r         z!Database.read.<locals>.<dictcomp>)r   defaultdictrB   r   r   r   dictr   rq   r)   r	   r1   rK   rL   r   rP   rQ   rv   r   r   rF   r   r   r   loadsr   rk   rs   updaterZ   r[   )r   resconnr   sepsrefsr   r   r   pkr   rE   r3   r>   r?   r   r   r   rO     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   rO   )r   Z_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)r1   zPRAGMA foreign_keys = ON;c                 S   r   r   r   rm   r   r   r   r     r   z"Database.write.<locals>.<dictcomp>ro   r   Nc                 S   s   g | ]}|j qS r   r   rm   r   r   r   r8   %  r   z"Database.write.<locals>.<listcomp>z$unspecified column {0} found in datar   ;c                 3   s    | ]
}  |p
d V  qdS )rc   N)rN   )r(   vvr   r   r   r*   6  s    
z!Database.write.<locals>.<genexpr>) r   existsr   unlinkr   r   r@   r=   r1   commitr   r   rB   r)   rq   	enumeraterk   rs   r   rv   tupler   r[   r'   
isinstancerK   rQ   r+   r   dumpsrN   r;   )r   r   r   r   r   r0   r   r   r   r3   r2   rE   r   r>   r   r   r6   r   r   Zatkeyr   r   r   r   r   r   r     sn   











(
$zDatabase.write)NNTr   r   )FFF)$r   r    r!   rf   r   r#   r$   Unionr   r   r"   r   r   r   r   propertyDictri   r   staticmethodr   r   
Connectionr   r   r   r   r   rQ   Listr   r   rO   r   r   r   r   r   r   r   r   Z  s6    

  
 8
r   r   )%rf   r   r#   r   r   r   r   r   r   rg   rW   Zcsvw.datatypesr   Zcsvw.metadatar   r	   rL   Protocolr   r%   r-   r   r"   SequencerB   r$   r   r;   Tupler   rF   r   rG   objectri   r   r   r   r   r   r   <module>   s|    	
*"Cq

'