comparison sat/memory/sqla_mapping.py @ 3593:cb8d0e8b917f

core (memory/sqla_mapping): mapping for PubsubNode and PubsubItem (will be used for caching)
author Goffi <goffi@goffi.org>
date Thu, 29 Jul 2021 21:38:37 +0200
parents 16ade4ad63f3
children 05db744f194f
comparison
equal deleted inserted replaced
3592:ef144aaea2bb 3593:cb8d0e8b917f
16 # You should have received a copy of the GNU Affero General Public License 16 # You should have received a copy of the GNU Affero General Public License
17 # along with this program. If not, see <http://www.gnu.org/licenses/>. 17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
18 18
19 import pickle 19 import pickle
20 import json 20 import json
21 from datetime import datetime
22 import time
23 import enum
21 from sqlalchemy import ( 24 from sqlalchemy import (
22 MetaData, Column, Integer, Text, Float, Enum, ForeignKey, UniqueConstraint, Index, 25 MetaData, Column, Integer, Text, Float, Boolean, DateTime, Enum, JSON, ForeignKey,
26 UniqueConstraint, Index
23 ) 27 )
24 28
25 from sqlalchemy.orm import declarative_base, relationship 29 from sqlalchemy.orm import declarative_base, relationship
26 from sqlalchemy.types import TypeDecorator 30 from sqlalchemy.types import TypeDecorator
31 from sqlalchemy.sql.functions import now
27 from twisted.words.protocols.jabber import jid 32 from twisted.words.protocols.jabber import jid
28 from datetime import datetime 33 from wokkel import generic
29 34
30 35
31 Base = declarative_base( 36 Base = declarative_base(
32 metadata=MetaData( 37 metadata=MetaData(
33 naming_convention={ 38 naming_convention={
42 # keys which are in message data extra but not stored in extra field this is 47 # keys which are in message data extra but not stored in extra field this is
43 # because those values are stored in separate fields 48 # because those values are stored in separate fields
44 NOT_IN_EXTRA = ('stanza_id', 'received_timestamp', 'update_uid') 49 NOT_IN_EXTRA = ('stanza_id', 'received_timestamp', 'update_uid')
45 50
46 51
52 class SyncState(enum.Enum):
53 #: synchronisation is currently in progress
54 IN_PROGRESS = 1
55 #: synchronisation is done
56 COMPLETED = 2
57 #: something wrong happened during synchronisation, won't sync
58 ERROR = 3
59 #: synchronisation won't be done even if a syncing analyser match
60 NO_SYNC = 4
61
62
47 class LegacyPickle(TypeDecorator): 63 class LegacyPickle(TypeDecorator):
48 """Handle troubles with data pickled by former version of SàT 64 """Handle troubles with data pickled by former version of SàT
49 65
50 This type is temporary until we do migration to a proper data type 66 This type is temporary until we do migration to a proper data type
51 """ 67 """
93 109
94 def process_result_value(self, value, dialect): 110 def process_result_value(self, value, dialect):
95 if value is None: 111 if value is None:
96 return {} 112 return {}
97 return json.loads(value) 113 return json.loads(value)
114
115
116 class Xml(TypeDecorator):
117 impl = Text
118 cache_ok = True
119
120 def process_bind_param(self, value, dialect):
121 if value is None:
122 return None
123 return value.toXml()
124
125 def process_result_value(self, value, dialect):
126 if value is None:
127 return None
128 return generic.parseXml(value.encode())
98 129
99 130
100 class JID(TypeDecorator): 131 class JID(TypeDecorator):
101 """Store twisted JID in text fields""" 132 """Store twisted JID in text fields"""
102 impl = Text 133 impl = Text
446 access = Column(JsonDefaultDict) 477 access = Column(JsonDefaultDict)
447 extra = Column(JsonDefaultDict) 478 extra = Column(JsonDefaultDict)
448 profile_id = Column(ForeignKey("profiles.id", ondelete="CASCADE")) 479 profile_id = Column(ForeignKey("profiles.id", ondelete="CASCADE"))
449 480
450 profile = relationship("Profile") 481 profile = relationship("Profile")
482
483
484 class PubsubNode(Base):
485 __tablename__ = "pubsub_nodes"
486 __table_args__ = (
487 UniqueConstraint("profile_id", "service", "name"),
488 )
489
490 id = Column(Integer, primary_key=True)
491 profile_id = Column(
492 ForeignKey("profiles.id", ondelete="CASCADE")
493 )
494 service = Column(JID)
495 name = Column(Text, nullable=False)
496 subscribed = Column(
497 Boolean(create_constraint=True, name="subscribed_bool"), nullable=False
498 )
499 analyser = Column(Text)
500 sync_state = Column(
501 Enum(
502 SyncState,
503 name="sync_state",
504 create_constraint=True,
505 ),
506 nullable=True
507 )
508 sync_state_updated = Column(
509 Float,
510 nullable=False,
511 default=time.time()
512 )
513 type_ = Column(
514 Text, name="type", nullable=True
515 )
516 subtype = Column(
517 Text, nullable=True
518 )
519 extra = Column(JSON)
520
521 items = relationship("PubsubItem", back_populates="node", passive_deletes=True)
522
523 def __str__(self):
524 return f"Pubsub node {self.name!r} at {self.service}"
525
526
527 class PubsubItem(Base):
528 __tablename__ = "pubsub_items"
529 __table_args__ = (
530 UniqueConstraint("node_id", "name"),
531 )
532 id = Column(Integer, primary_key=True)
533 node_id = Column(ForeignKey("pubsub_nodes.id", ondelete="CASCADE"), nullable=False)
534 name = Column(Text, nullable=False)
535 data = Column(Xml, nullable=False)
536 created = Column(DateTime, nullable=False, server_default=now())
537 updated = Column(DateTime, nullable=False, server_default=now(), onupdate=now())
538 parsed = Column(JSON)
539
540 node = relationship("PubsubNode", back_populates="items")