U SW‹@sddZddlZddlZddlZddlZddlZddlZddlZz ddlZ Wne k rdddl Z YnXzddl m Z Wne k rYnXddl Tddl mZddl mZddl mZddl mZdd l mZdd l mZdd l mZdd l mZdd l mZddl mZddl mZddl mZzddlmZWne k rNdZYnXejddkrdeZdZ dZ!ej"dddkrdpdZ#dZ$GdddeZ%GdddeZ&Gddde'Z(Gdd d e)Z*Gd!d"d"e+Z,Gd#d$d$e-Z.Gd%d&d&e.e+Z/Gd'd(d(e.e0Z1Gd)d*d*e.e2Z3Gd+d,d,e.e4Z5Gd-d.d.e6Z7Gd/d0d0e7Z8Gd1d2d2e8Z9d3Z:e;d4d5d6d7ed?d@g e;dABe;e:Be;e:=BZ>e;dBdCe?dDDZ@eAdEZBGdFdGdGe8ZCd]dHdIZDGdJdKdKeZEejFdLdMdNdOZGGdPdQdQeHZIGdRdSdSeZJdTe_KeILejKdUidVdTZMdWdXZNdYdZZOd[d\ZPdS)^a Sqlite3 extensions ================== * Define custom aggregates, collations and functions * Basic support for virtual tables * Basic support for FTS3/4 * Specify isolation level in transactions Example usage of the Full-text search: class Document(FTSModel): title = TextField() # type affinities are ignored in FTS content = TextField() Document.create_table(tokenize='porter') # use the porter stemmer # populate the documents using normal operations. for doc in documents: Document.create(title=doc['title'], content=doc['content']) # use the "match" operation for FTS queries. matching_docs = Document.select().where(match(Document.title, 'some query')) # to sort by best match, use the custom "rank" function. best_docs = (Document .select(Document, Document.rank('score')) .where(match(Document.title, 'some query')) .order_by(SQL('score'))) # or use the shortcut method. best_docs = Document.match('some phrase') N) TableFunction)*)EnclosedClause)Entity) Expression)Node)OP)SqliteQueryCompiler)_AutoPrimaryKeyField)sqlite3) transaction)_sqlite_date_part)_sqlite_date_trunc)_sqlite_regexp) _sqlite_extZpcnalxZpcx)rZFTS4ZFTS3)r rc@seZdZdZdZdS) RowIDFieldz Field used to access hidden primary key on FTS5 or any other SQLite table that does not have a separately-defined primary key. rowidN__name__ __module__ __qualname____doc__Z _column_namerr/sqlite_ext.pyrOsrc@seZdZdZdZdS) DocIDFieldz9Field used to access hidden primary key on FTS3/4 tables.docidNrrrrrrWsrcs eZdZdZfddZZS)PrimaryKeyAutoIncrementFielda  SQLite by default uses MAX(primary key) + 1 to set the ID on a new row. Using the `AUTOINCREMENT` field, the IDs will increase monotonically even if rows are deleted. Use this if you need to guarantee IDs are not re-used in the event of deletion. cstt||}|tdgS)NZ AUTOINCREMENT)superr __ddl__SQL)selfZ column_typeZddl __class__rrr"csz$PrimaryKeyAutoIncrementField.__ddl__)rrrrr" __classcell__rrr%rr \sr c@seZdZddZddZddZddd Zd d Zd d ZddZ ddZ ddZ ddZ ddZ dddZd ddZd!ddZdS)" JSONFieldc Cs6|dk r2z t|WSttfk r0|YSXdSN)jsonloads TypeError ValueErrorr$valuerrr python_valueis  zJSONField.python_valuecCs|dk rt|SdSr))r*dumpsr.rrrdb_valuepszJSONField.db_valuecCs|ds|sd|Sd|S)N[z$%sz$.%s) startswithr$pathrrr clean_pathtszJSONField.clean_pathNcCs |rt|||St|Sr))fnZjson_array_lengthr7r5rrrlengthyszJSONField.lengthcCst|||Sr))r8Z json_extractr7r5rrrextract~szJSONField.extractcCs$t|tttfr tt|S|Sr)) isinstancelisttupledictr8r*r1r.rrr_value_for_insertionszJSONField._value_for_insertioncCslt|}|ddkrtdg}td|dD]0}||||||||dq,||f|S)Nrz%Mismatched path and value parameters.)lenr-rangeappendr7r?)r$r8pairsZnpairsaccumirrr _insert_likes zJSONField._insert_likecGs|tj|Sr))rHr8Z json_insertr$rErrrinsertszJSONField.insertcGs|tj|Sr))rHr8Z json_replacerIrrrreplaceszJSONField.replacecGs|tj|Sr))rHr8Zjson_setrIrrrsetsz JSONField.setcstjffdd|DS)Ncsg|]}|qSr)r7).0r6r$rr sz$JSONField.remove..)r8Z json_remove)r$pathsrrNrremoveszJSONField.removecCs |rt|||St|Sr))r8 json_typer7r5rrrrRszJSONField.json_typecCs |rt|||St|S)a? Schema of `json_each` and `json_tree`: key, value, type TEXT (object, array, string, etc), atom (value for primitive/scalar types, NULL for array and object) id INTEGER (unique identifier for element) parent INTEGER (unique identifier of parent element or NULL) fullkey TEXT (full path describing element) path TEXT (path to the container of the current element) json JSON hidden (1st input parameter to function) root TEXT hidden (2nd input parameter, path at which to start) )r8Z json_eachr7r5rrrchildrenszJSONField.childrencCs |rt|||St|Sr))r8Z json_treer7r5rrrtreeszJSONField.tree)N)N)N)N)rrrr0r2r7r9r:r?rHrJrKrLrQrRrSrTrrrrr(hs    r(cs.eZdZdZdfdd ZfddZZS) SearchFielda- Field class to be used with full-text search extension. Since the FTS extensions do not support any field types besides `TEXT`, and furthermore do not support secondary indexes, using this field will prevent you from mistakenly creating the wrong kind of field on your FTS table. FNc s:d||d}||_|r$tdg|d<tt|jf|dS)NT)Znull db_columncoerceZ UNINDEXEDZ constraints) _unindexedr#r!rU__init__)r$Z unindexedrVrW_kwargsr%rrrYs  zSearchField.__init__c stt|jf|}|j|_|Sr))r!rU clone_baserX)r$r[cloner%rrr\szSearchField.clone_base)FNN)rrrrrYr\r'rrr%rrUsrUcs eZdZdZfddZZS)_VirtualFieldMixinzx Field mixin to support virtual table attributes that may not correspond to actual columns in the database. cs"tt||||j|dSr))r!r^ add_to_class_metaZ remove_field)r$ model_classnamer%rrr_sz_VirtualFieldMixin.add_to_class)rrrrr_r'rrr%rr^sr^c@s eZdZdS) VirtualFieldNrrrrrrrrcsrcc@s eZdZdS)VirtualIntegerFieldNrdrrrrresrec@s eZdZdS)VirtualCharFieldNrdrrrrrfsrfc@s eZdZdS)VirtualFloatFieldNrdrrrrrgsrgc@s4eZdZGdddZeddZed ddZdS) VirtualModelc@seZdZdZdZiZdS)zVirtualModel.MetaTN)rrrZ virtual_tableextension_moduleextension_optionsrrrrMetasrkcKs|Sr)r)clsoptionsrrr clean_optionsszVirtualModel.clean_optionsFcKs.|r|rdS|jjj||d|dS)N)rm)Z table_existsr`database create_tableZ_create_indexes)rlZ fail_silentlyrmrrrrps zVirtualModel.create_tableN)F)rrrrk classmethodrnrprrrrrhs  rhc@seZdZeddZdS) BaseFTSModelcKsB|d}|d}|r$d||d<t|tr>|dkr>d|d<|S)Ntokenizecontentz"%s"z'')getr; basestring)rlrmrsrtrrrrns   zBaseFTSModel.clean_optionsN)rrrrqrnrrrrrrsrrc@seZdZdZeZGdddZeddZeddZ edd Z ed d Z ed d Z ed(ddZ ed)ddZeddZeddZeddZeddZeddZed*d"d#Zed+d$d%Zed,d&d'ZdS)-FTSModela_ VirtualModel class for creating tables that use either the FTS3 or FTS4 search extensions. Peewee automatically determines which version of the FTS extension is supported and will use FTS4 if possible. Note: because FTS5 is significantly different from FTS3 and FTS4, there is a separate model class for FTS5 virtual tables. c@seZdZeZdS)z FTSModel.MetaN)rrrFTS_VERrirrrrrksrkcCs|jjjdkrtddS)Nrz:FTSModel classes must use the default `docid` primary key.)r` primary_keyrbImproperlyConfiguredrlrrrvalidate_modelszFTSModel.validate_modelcCs(|jj}|jjd|||f}|S)Nz INSERT INTO %s(%s) VALUES('%s');)r`db_tablero execute_sqlZfetchone)rlcmdtblresrrr_fts_cmds  zFTSModel._fts_cmdcCs |dS)Noptimizerr|rrrr$szFTSModel.optimizecCs |dS)Nrebuildrr|rrrr(szFTSModel.rebuildcCs |dS)Nzintegrity-checkrr|rrrintegrity_check,szFTSModel.integrity_checkcCs|d||fS)Nz merge=%s,%sr)rlZblocksZsegmentsrrrmerge0szFTSModel.mergeTcCs|d|rdpdS)Nz automerge=%s10r)rlstaterrr automerge4szFTSModel.automergecCst||SzU Generate a `MATCH` expression appropriate for searching this table. match as_entityrltermrrrr8szFTSModel.matchcGstjt|tf|Sr))r8fts_rank matchinforFTS_MATCHINFO_FORMAT_SIMPLErlweightsrrrrank?s z FTSModel.rankcGs t|t}tj|f|Sr))r8rrFTS_MATCHINFO_FORMATfts_bm25rlr match_inforrrbm25Dsz FTSModel.bm25cGs t|t}tj|f|Sr))r8rrr fts_lucenerrrrluceneIszFTSModel.lucenec Cs|s |}nJt|trNg}|jjD] } ||| || jdq"||}n||}d} |} |rp|||f} |r|st|} |j | | | | S)N?r) r;r>r`declared_fieldsrDrvrbaliasr#selectwhererorder_by) rlrr with_score score_aliasZscore_fnexplicit_orderingr weight_argsfield selectionrrrr_searchNs.   zFTSModel._searchNFscorecCs||||||j|S'Full-text search using selected `term`.)rrrlrrrrrrrrsearchhszFTSModel.searchcCs||||||j|Sz:Full-text search for selected `term` using BM25 algorithm.)rrrrrr search_bm25tszFTSModel.search_bm25cCs||||||j|Sr)rrrrrr search_luceneszFTSModel.search_lucene)rr)T)NFrF)NFrF)NFrF)rrrrrrrkrqr}rrrrrrrrrrrrrrrrrrrxsR               rxZabcdefghijklmnopqrstuvwxyz  ,"(){}r:rZ+ 0123456789cCs g|]}t|tkrt|qSr)chr _alphanum)rMprrrrOs rOz(?:[^\s"]|"(?:\.|[^"])*")+c@seZdZdZeZGdddZddddZedd Z ed d Z e d d Z e e dfddZeddZeddZeddZed-ddZed.ddZeddZed d!Zed"d#Zed$d%Zed&d'Zed(d)Zed/d+d,ZdS)0 FTS5Modelan Requires SQLite >= 3.9.0. Table options: content: table name of external content, or empty string for "contentless" content_rowid: column name of external content primary key prefix: integer(s). Ex: '2' or '2 3 4' tokenize: porter, unicode61, ascii. Ex: 'porter unicode61' The unicode tokenizer supports the following parameters: * remove_diacritics (1 or 0, default is 1) * tokenchars (string of characters, e.g. '-_' * separators (string of characters) Parameters are passed as alternating parameter name and value, so: {'tokenize': "unicode61 remove_diacritics 0 tokenchars '-_'"} Content-less tables: If you don't need the full-text content in it's original form, you can specify a content-less table. Searches and auxiliary functions will work as usual, but the only values returned when SELECT-ing can be rowid. Also content-less tables do not support UPDATE or DELETE. External content tables: You can set up triggers to sync these, e.g. -- Create a table. And an external content fts5 table to index it. CREATE TABLE tbl(a INTEGER PRIMARY KEY, b); CREATE VIRTUAL TABLE ft USING fts5(b, content='tbl', content_rowid='a'); -- Triggers to keep the FTS index up to date. CREATE TRIGGER tbl_ai AFTER INSERT ON tbl BEGIN INSERT INTO ft(rowid, b) VALUES (new.a, new.b); END; CREATE TRIGGER tbl_ad AFTER DELETE ON tbl BEGIN INSERT INTO ft(fts_idx, rowid, b) VALUES('delete', old.a, old.b); END; CREATE TRIGGER tbl_au AFTER UPDATE ON tbl BEGIN INSERT INTO ft(fts_idx, rowid, b) VALUES('delete', old.a, old.b); INSERT INTO ft(rowid, b) VALUES (new.a, new.b); END; Built-in auxiliary functions: * bm25(tbl[, weight_0, ... weight_n]) * highlight(tbl, col_idx, prefix, suffix) * snippet(tbl, col_idx, prefix, suffix, ?, max_tokens) c@seZdZdZdS)zFTS5Model.Metafts5NrrrrirrrrrksrkzQBesides the implicit `rowid` column, all columns must be instances of SearchFieldz3Secondary indexes are not supported for FTS5 modelsz4FTS5 models must use the default `rowid` primary key) field_typeindexpkcCsd|jjjdkrt|jd|jjD] }t|tt fs(t|jdq(|jj r`t|jddS)Nrrrr) r`rzrbr{_error_messagesZfieldsvaluesr;rUrZindexes)rlrrrrr}szFTS5Model.validate_modelc CstjddtkrdStd}z\z|dWnHztdtdWnYYWdSX|jj dYnXW5|XdS)NrFz:memory:z0CREATE VIRTUAL TABLE fts5test USING fts5 (data);Tr) r sqlite_version_infoFTS5_MIN_VERSIONZconnectcloseexecuteenable_load_extensionload_extensionr`ro)rlZtmp_dbrrrfts5_installeds   zFTS5Model.fts5_installedcCs@t|}|D],}|dr(|dr(qt|t@rdSqdS)z Simple helper function to indicate whether a search query is a valid FTS5 query. Note: this simply looks at the characters being used, and is not guaranteed to catch all problematic queries. rFT) _quote_refindallr4endswithrL_invalid_ascii)querytokenstokenrrrvalidate_querys  zFTS5Model.validate_queryrc Csg}d}t|}|D]\}|dr:|dr:||qt|}|t@}|rhd}|D]}|||}qV||q|rd|S|S)z2 Clean a query of invalid tokens. FrTr) rrr4rrDrLrrKjoin) rrKrFZ any_invalidrrZ token_setZinvalid_for_tokencrrr clean_query s"    zFTS5Model.clean_querycCst||Srrrrrrr$szFTS5Model.matchcGs|r|j|StdSdS)Nr)rr#)rlargsrrrr+s zFTS5Model.rankcGstj|f|Sr))r8rrrrrrr2szFTS5Model.bm25NFrcCs|t|||||Sr)rrrrrrrr6szFTS5Model.searchc Cs|std}nbt|tr\g}|jjD] }|||||jdq$tj | f|}ntj | f|}d} |} |r|| |f} |r|st|} |j | |t|| S)rrrr)r#r;r>r`rrDrvrbr8rrrrrrrrr) rlrrrrrrrrrrrrrrAs.   zFTS5Model.search_bm25c Ksx|}|g}|g}|D] \}}|t|||qt|}ttd|t|tdt|} |jj | S)Nz INSERT INTOZVALUES) ritemsrDrrClauser#r`ror) rlrZ extra_paramsrcolumnsrkeyr/Z inner_clauseclauserrrr\s zFTS5Model._fts_cmdcCs,d|krdksntd|jd|dS)Nrzlevel must be between 0 and 16rr)r-r)rllevelrrrrnszFTS5Model.automergecCs|jd|dS)Nrrr)rlZnpagesrrrrtszFTS5Model.mergecCs|jd|dS)Npgszrr)rlrrrrset_pgszxszFTS5Model.set_pgszcCs|jd|dS)Nrrr)rlZrank_expressionrrrset_rank|szFTS5Model.set_rankcCs |dS)Nz delete-allrr|rrr delete_allszFTS5Model.delete_allrowcsdkrtdd}t|sGfddd}tttt|d}dkrbt|d<dj}t|t|tf|t |S) N)rcolz)table_type must be either "row" or "col".z_vocab_model_%scs8eZdZjjZpjjdZee Z dS)z"FTS5Model.VocabModel..MetaZ_vN) rrrr`ror~r8Z fts5vocabrr#rirrl table_name table_typerrrks rk)rdocZcntrrkrz%sVocab) r-hasattr BareField IntegerFieldrrsetattrtyperhgetattr)rlrrattrrkZattrs class_namerrr VocabModels    zFTS5Model.VocabModel)NFrF)NFrF)rN)rrrrrrrkrrqr}r staticmethodrrrrrrrrrrrrrrrrrrrrsX7              rcsdkr4jjD]}|jkr|q4qtdjjGfdddt}Gfddd}dj}t||fd|iS)z3Model factory for the transitive closure extension.Nz,Unable to find self-referential foreign key.cs|eZdZeZeZeZeZeZe Z GdddZ e d fdd Z e d fdd Ze d fd d ZdS)z&ClosureTable..BaseClosureTablec@seZdZdZdS)z+ClosureTable..BaseClosureTable.MetaZtransitive_closureNrrrrrrksrkNFcsb|jdj||jkd|j|k}|dk rJ||j|k}n|s^||jdk}|SNdepth)Zonr)rrrridrrootrlnoder include_noderrarzrr descendantss z2ClosureTable..BaseClosureTable.descendantscs^|jdj||jkd|j|k}|rF||j|k}n|sZ||jdk}|Sr)rrrrrrrrrrr ancestorss z0ClosureTable..BaseClosureTable.ancestorscs6|jj}|k}|s2||k}|Sr))_datarvrbrr)rlrrZfk_valuer foreign_keyrarzrrsiblingss z/ClosureTable..BaseClosureTable.siblings)NF)NF)F)rrrrerridcolumn parentcolumnrrf tablenamerkrqrrr rr rrBaseClosureTables  rcs0eZdZjjZjjjjjjdZdZdS)zClosureTable..Meta)rr r FN) rrrr`ror~rzrVrjr)r rarrrks rkz %sClosure) r`ZrelrZ rel_modelr-rzrhrr)rar Z field_objrrkrbrr r ClosureTables + rcs4eZdZdZd fdd ZddZd dd ZZS) SqliteExtQueryCompilerzQ Subclass of QueryCompiler that can be used to construct virtual tables. FNc s|tt|j||d}t|trpd}|jj}t|trX|j ddt d|g}t |}qt|j dt d|nd}|r|d7}t ||j d<| |||}|rX|j d } t|D]\} } t| tr| |d k} n\t| rt| tr| } n>t| ttfr"t d d tt| } nt| ts6t | } t t | | } d | _| j | qt|jddrx|j t d|S)N)safezCREATE VIRTUAL TABLEr@ZUSINGzUSING %sz CREATE TABLEz IF NOT EXISTSrrz'%s'r=Z without_rowidz WITHOUT ROWID)r!r _create_table issubclassrhr`rir;rZnodesr#rrJrnsortedrZFieldrinspectZisclassModelr<r=rmapstrgluerDr) r$rarrmrZ statement extensionpartsZ table_optionsZcolumns_constraintskvZoptionr%rrrsB        z$SqliteExtQueryCompiler._create_tablecCs>t|jdd}|r |jf|}ni}|r:||jf||S)Nrj)rr`rnupdate)r$rarZ extra_optionsZ model_optionsrmrrrrn sz$SqliteExtQueryCompiler.clean_optionscCs|||||Sr))Z parse_noder)r$rarrmrrrrp*sz#SqliteExtQueryCompiler.create_table)FN)FN)rrrrrrnrpr'rrr%rrs6 rF)r]cCstd|ddS)Nrru)r)rrNrrr disqualify.sr"cseZdZdZeZd3fdd ZeddZddZ d d Z d d Z d dZ ddZ d4ddZd5ddZd6ddZd7ddZd8ddZd9ddZdd Zd!d"Zd#d$Zd%d&Zd'd(Zd)d*Zd:d,d-Zd;fd.d/ Zd.decoratorrr$rbr5r<rr;r aggregate{szSqliteExtDatabase.aggregatecs@p|jfdd}||_||j<|s<||dS)Ncs|tdf}t|S)Nz collate %s)r#r)rZ expressionsrbrr _collationsz8SqliteExtDatabase.register_collation.._collation)r collationr%r8r.r9)r$r8rbr@rr?rregister_collations    z$SqliteExtDatabase.register_collationcsfdd}|S)Ncs||Sr))rBr8rbr$rrr<s z.SqliteExtDatabase.collation..decoratorr)r$rbr<rrDrrAszSqliteExtDatabase.collationcCs.||f|j|p|j<|s*||dSr))r&rr8r/r9)r$r8rbr5rrrr+sz#SqliteExtDatabase.register_functioncsfdd}|S)Ncs||Sr))r+rCr;rrr<sz)SqliteExtDatabase.func..decoratorrr=rr;rfuncszSqliteExtDatabase.funccCs4|j||s0|}|d||dSr6)r'addr8r9rr)r$rr2rrrrs   z SqliteExtDatabase.load_extensioncCs |j|=dSr))r$r$rbrrrunregister_aggregatesz&SqliteExtDatabase.unregister_aggregatecCs |j|=dSr))r%rGrrrunregister_collationsz&SqliteExtDatabase.unregister_collationcCs |j|=dSr))r&rGrrrunregister_functionsz%SqliteExtDatabase.unregister_functioncCs|j|dSr))r'rQ)r$rrrrunload_extensionsz"SqliteExtDatabase.unload_extensioncCs ||_dSr))r()r$r8rrrr0szSqliteExtDatabase.row_factoryFcCs"||||\}}|||Sr))Zcompilerrpr)r$rarrmZsqlZparamsrrrrpszSqliteExtDatabase.create_tablecs"t|trdStt||||Sr))rrxr!r# create_index)r$raZ field_nameuniquer%rrrLs  zSqliteExtDatabase.create_indexdeferredcCs t||Sr))granular_transaction)r$ lock_typerrrrOsz&SqliteExtDatabase.granular_transaction)T)Nr)Nr)N)N)Nr)Nr)FN)F)rN)rrrrrZcompiler_classrYpropertyr,r3r-r.r/r1r:r>rBrAr+rErrHrIrJrKr0rprLrOr'rrr%rr#5s2        r#c@seZdZdddZddZdS)rOrNcCs||_|j|_||_dSr))dbr9r2rP)r$rRrPrrrrYs zgranular_transaction.__init__cCs|j|jdSr))rRZbeginrPrNrrr_beginszgranular_transaction._beginN)rN)rrrrYrSrrrrrOs rOrMATCHcCst|tj|Sr))rrrT)Zlhsrhsrrrrscs"t}fddtd|dDS)Nc s(g|] }td||ddqS)z@Irr)structunpack)rMrGbufrrrOsz%_parse_match_info..rr)rBrC)rYbufsizerrXr_parse_match_infosr[cGst|}d}|dd\}}|s,dg|}n$dg|}t|D]\}}|||<q>t|D]l}d||d} t|D]N} || }|sqt| | d} || | d\} } | dkrt||t| | 7}qtqX| S)Nr@rArr)r[ enumeraterCfloat)raw_match_inforrrrrrGweightZ phrase_numZphrase_info_idxZcol_numZcol_idxZx1Zx2rrrrs&      rcGst|}d}d}d}td\}}}} ||} ||} ||} | | } | | }|s\dg| }n(dg| }t|D]\}}||||<qnt| D]}t| D]}||}|dkrqt|| |}t|| |}|dkrd}nd||||}|d||d}t||}t||d}tt| |d |d d}|||}|dkr\d}n||d|}||||7}qq| S) z Usage: # Format string *must* be pcnalx # Second parameter to bm25 specifies the index of the column, on # the table being queries. bm25(matchinfo(document_tbl, 'pcnalx'), 1) AS rank g333333?g?r\rrArrr@g?)r[rCr]r^maxmathlog)r_rrKBrZP_OZC_OZN_OZA_OZ term_countZ col_countZ total_docsZL_OZX_OrrGr`jZ avg_lengthZ doc_lengthDxZterm_frequencyZdocs_with_termZidfZdenomrUrrrrsT         r)N)QrZglobrrbosrerVsysZ simplejsonr* ImportErrorZvtfuncrZpeeweerrrrrr r r r r rrZ playhouserr) version_inforrwrrrryrrrZPrimaryKeyFieldr Z TextFieldr(rrUobjectr^rcrreZ CharFieldrfZ FloatFieldrgrrhrrrxZ _alphabetrLrupperrrCrcompilerrrrextendr"ZSqliteDatabaser#rOrTZ register_opsrr[rrrrrrs!               R   CH